diff --git a/packages/plugins/plugin-cert/src/dns-provider/api.ts b/packages/plugins/plugin-cert/src/dns-provider/api.ts index b3d87be3..0fd36b77 100644 --- a/packages/plugins/plugin-cert/src/dns-provider/api.ts +++ b/packages/plugins/plugin-cert/src/dns-provider/api.ts @@ -62,12 +62,14 @@ export interface IDomainParser { export type DnsVerifier = { // dns直接校验 - dnsProviderType: string; - dnsProviderAccessId: number; + dnsProviderType?: string; + dnsProviderAccessId?: number; }; export type CnameVerifier = { - cnameRecord: string; + hostRecord: string; + domain: string; + recordValue: string; }; export type HttpVerifier = { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts index 9e4c095c..2d1e4362 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/acme.ts @@ -269,11 +269,11 @@ export class AcmeService { throw new Error("不支持的校验类型", domainVerifyPlan.type); } } else { - this.logger.info("未找到域名校验计划,使用默认的dnsProvider"); + this.logger.warn(`未找到域名${fullDomain}的校验计划,使用默认的dnsProvider`); } } if (!dnsProvider) { - this.logger.error("dnsProvider不存在,无法申请证书"); + throw new Error(`域名${fullDomain}没有匹配到任何校验方式,证书申请失败`); } const dnsChallenge = getChallenge("dns-01"); diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts index 3c654340..f41afe97 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/index.ts @@ -66,15 +66,15 @@ export class CertApplyPlugin extends CertApplyBasePlugin { { value: "cname", label: "CNAME代理验证" }, { value: "http", label: "HTTP文件验证" }, { value: "dnses", label: "多DNS提供商" }, - { value: "auto", label: "自动选择" }, + { value: "auto", label: "自动匹配" }, ], }, required: true, helper: `1. DNS直接验证:域名dns解析是在阿里云/腾讯云/华为云/CF/NameSilo/西数/火山/dns.la/京东云/51dns的,选它 -2. CNAME代理验证:支持任何注册商的域名,第一次需要手动添加CNAME记录(建议将DNS服务器修改为阿里云/腾讯云的,然后使用DNS直接验证) +2. CNAME代理验证:支持任何注册商的域名,第一次需要手动添加[CNAME记录](#/certd/cname/record)(建议将DNS服务器修改为阿里云/腾讯云的,然后使用DNS直接验证) 3. HTTP文件验证:不支持泛域名,需要配置网站文件上传 4. 多DNS提供商:每个域名可以选择独立的DNS提供商 -5. 自动选择:需要在[域名管理](#/certd/cert/domain)中事先配置好校验方式 +5. 自动匹配:需要在[域名管理](#/certd/cert/domain)中事先配置好校验方式 `, }) challengeType!: string; @@ -469,13 +469,13 @@ export class CertApplyPlugin extends CertApplyBasePlugin { for (const fullDomain of domains) { const domain = fullDomain.replaceAll("*.", ""); const mainDomain = await domainParser.parse(domain); - const planSetting = verifyPlanSetting[mainDomain]; + const planSetting: DomainVerifyPlanInput = verifyPlanSetting[mainDomain]; if (planSetting.type === "dns") { - await this.createDnsDomainVerifyPlan(planSetting[mainDomain], domain, mainDomain); + plan[domain] = await this.createDnsDomainVerifyPlan(planSetting, domain, mainDomain); } else if (planSetting.type === "cname") { - await this.createCnameDomainVerifyPlan(domain, mainDomain); + plan[domain] = await this.createCnameDomainVerifyPlan(domain, mainDomain); } else if (planSetting.type === "http") { - await this.createHttpDomainVerifyPlan(planSetting.httpVerifyPlan[domain], domain, mainDomain); + plan[domain] = await this.createHttpDomainVerifyPlan(planSetting.httpVerifyPlan[domain], domain, mainDomain); } } return plan; @@ -486,7 +486,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin { // domain list const domainList = new Set(); //整理域名 - for (let domain in this.domains) { + for (let domain of domains) { domain = domain.replaceAll("*.", ""); domainList.add(domain); } @@ -563,7 +563,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin { domain, mainDomain, cnameVerifyPlan: { - domain, + domain: cnameRecord.cnameProvider.domain, fullRecord: cnameRecord.recordValue, dnsProvider, }, diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index d17346cc..351bb5e6 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -714,6 +714,7 @@ export default { }, domain: { domainManager: "Domain Manager", + domainDescription: "used to auto apply for certificate", //管理域名的校验方式,用于申请证书时自动选择验证方式 domain: "Domain", challengeType: "Challenge Type", dnsProviderType: "DNS Provider Type", @@ -722,5 +723,7 @@ export default { httpUploaderAccess: "HTTP Uploader Access", httpUploadRootDir: "HTTP Upload Root Dir", disabled: "Disabled", + challengeSetting: "Challenge Setting", + gotoCnameTip: "Please go to CNAME Record Page", }, }; diff --git a/packages/ui/certd-client/src/locales/langs/en-US/common.ts b/packages/ui/certd-client/src/locales/langs/en-US/common.ts index 34777d30..d0542e97 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/common.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/common.ts @@ -14,6 +14,8 @@ export default { search: "Search", enabled: "Enabled", disabled: "Disabled", + enable: "Enable", + disable: "Disable", edit: "Edit", delete: "Delete", create: "Create", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index b5d8f37d..c32ac8d7 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -717,6 +717,7 @@ export default { }, domain: { domainManager: "域名管理", + domainDescription: "管理域名的校验方式,用于申请证书时自动选择验证方式", domain: "域名", challengeType: "校验类型", dnsProviderType: "DNS提供商类型", @@ -725,5 +726,7 @@ export default { httpUploaderAccess: "上传授权信息", httpUploadRootDir: "网站根路径", disabled: "禁用/启用", + challengeSetting: "校验配置", + gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加", }, }; diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/common.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/common.ts index 9cec01a9..1e990fb4 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/common.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/common.ts @@ -14,6 +14,8 @@ export default { search: "搜索", enabled: "已启用", disabled: "已禁用", + enable: "启用", + disable: "禁用", edit: "修改", delete: "删除", create: "新增", diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx b/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx index 57a2688e..f6e10c24 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx @@ -7,6 +7,7 @@ import { useUserStore } from "/@/store/user"; import { useSettingStore } from "/@/store/settings"; import { Dicts } from "/@/components/plugins/lib/dicts"; import { createAccessApi } from "/@/views/certd/access/api"; +import { Modal } from "ant-design-vue"; export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { const router = useRouter(); @@ -73,13 +74,20 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat delRequest, }, tabs: { - name: "status", + name: "challengeType", show: true, }, rowHandle: { minWidth: 200, fixed: "right", }, + form: { + beforeSubmit({ form }) { + if (form.challengeType === "cname") { + throw new Error("CNAME方式请前往CNAME记录页面进行管理"); + } + }, + }, columns: { id: { title: "ID", @@ -114,11 +122,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat title: t("certd.domain.challengeType"), type: "dict-select", dict: Dicts.challengeTypeDict, + search: { + show: true, + }, form: { required: true, + valueChange({ value }) { + if (value === "cname") { + Modal.confirm({ + title: t("certd.domain.gotoCnameTip"), + async onOk() { + router.push({ + path: "/certd/cname/record", + }); + crudExpose.getFormWrapperRef().close(); + }, + }); + } + }, }, column: { sorter: true, + show: false, }, }, /** @@ -196,6 +221,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat form: { component: { name: "AccessSelector", + vModel: "modelValue", + type: compute(({ form }) => { + return form.httpUploaderType; + }), }, show: compute(({ form }) => { return form.challengeType === "http"; @@ -226,16 +255,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, }, challengeSetting: { - title: "校验配置", + title: t("certd.domain.challengeSetting"), type: "text", form: { show: false }, column: { - width: 400, + width: 600, conditionalRender: false, cellRender({ row }) { if (row.challengeType === "dns") { return ( + @@ -243,9 +273,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat } else if (row.challengeType === "http") { return ( + - {row.httpUploadRootDir} + 路径:{row.httpUploadRootDir} ); } @@ -255,10 +286,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat disabled: { title: t("certd.domain.disabled"), type: "dict-switch", + search: { show: true }, dict: dict({ data: [ - { label: "启用", value: false, color: "green" }, - { label: "禁用", value: true, color: "red" }, + { label: t("common.enabled"), value: false, color: "green" }, + { label: t("common.disabled"), value: true, color: "red" }, ], }), form: { diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/index.vue b/packages/ui/certd-client/src/views/certd/cert/domain/index.vue index 8bf9ea63..b7c1f5b0 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/index.vue +++ b/packages/ui/certd-client/src/views/certd/cert/domain/index.vue @@ -3,7 +3,9 @@ {{ t("certd.domain.domainManager") }} - + + {{ t("certd.domain.domainDescription") }} + diff --git a/packages/ui/certd-server/src/modules/cert/service/domain-service.ts b/packages/ui/certd-server/src/modules/cert/service/domain-service.ts index 58c1146e..1a3426bc 100644 --- a/packages/ui/certd-server/src/modules/cert/service/domain-service.ts +++ b/packages/ui/certd-server/src/modules/cert/service/domain-service.ts @@ -7,6 +7,8 @@ import {SubDomainService} from "../../pipeline/service/sub-domain-service.js"; import {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js"; import {DomainVerifiers} from "@certd/plugin-cert"; import { SubDomainsGetter } from '../../pipeline/service/getter/sub-domain-getter.js'; +import { CnameRecordService } from '../../cname/service/cname-record-service.js'; +import { CnameRecordEntity } from "../../cname/entity/cname-record.js"; /** @@ -23,6 +25,9 @@ export class DomainService extends BaseService { @Inject() subDomainService: SubDomainService; + @Inject() + cnameRecordService: CnameRecordService; + //@ts-ignore getRepository() { return this.repository; @@ -96,10 +101,12 @@ export class DomainService extends BaseService { //去重 allDomains = [...new Set(allDomains)] + //从 domain 表中获取配置 const domainRecords = await this.find({ where: { domain: In(allDomains), - userId + userId, + disabled:false, } }) @@ -107,16 +114,28 @@ export class DomainService extends BaseService { pre[item.domain] = item return pre }, {}) - const cnameMap = domainRecords.filter(item=>item.challengeType === 'cname').reduce((pre, item) => { - pre[item.domain] = item - return pre - }, {}) + const httpMap = domainRecords.filter(item=>item.challengeType === 'http').reduce((pre, item) => { pre[item.domain] = item return pre }, {}) + //从cname record表中获取配置 + const cnameRecords = await this.cnameRecordService.find({ + where: { + domain: In(allDomains), + userId, + status: "valid", + } + }) + + const cnameMap = cnameRecords.reduce((pre, item) => { + pre[item.domain] = item + return pre + }, {}) + + //构建域名验证计划 const domainVerifiers:DomainVerifiers = {} for (const domain of domains) { @@ -130,19 +149,21 @@ export class DomainService extends BaseService { type: 'dns', dns: { dnsProviderType: dnsRecord.dnsProviderType, - dnsProviderAccessId: dnsRecord.dnsProviderAccessId + dnsProviderAccessId: dnsRecord.dnsProviderAccess } } continue } - const cnameRecord = cnameMap[mainDomain] + const cnameRecord:CnameRecordEntity = cnameMap[mainDomain] if (cnameRecord) { domainVerifiers[domain] = { domain, mainDomain, type: 'cname', cname: { - cnameRecord: cnameRecord.cnameRecord + domain: cnameRecord.domain, + hostRecord: cnameRecord.hostRecord, + recordValue: cnameRecord.recordValue } } continue