chore: auto

v2-dev-auto
xiaojunnuo 2025-07-13 23:08:00 +08:00
parent af5e1b805f
commit 896cd950e9
10 changed files with 96 additions and 29 deletions

View File

@ -62,12 +62,14 @@ export interface IDomainParser {
export type DnsVerifier = { export type DnsVerifier = {
// dns直接校验 // dns直接校验
dnsProviderType: string; dnsProviderType?: string;
dnsProviderAccessId: number; dnsProviderAccessId?: number;
}; };
export type CnameVerifier = { export type CnameVerifier = {
cnameRecord: string; hostRecord: string;
domain: string;
recordValue: string;
}; };
export type HttpVerifier = { export type HttpVerifier = {

View File

@ -269,11 +269,11 @@ export class AcmeService {
throw new Error("不支持的校验类型", domainVerifyPlan.type); throw new Error("不支持的校验类型", domainVerifyPlan.type);
} }
} else { } else {
this.logger.info("未找到域名校验计划使用默认的dnsProvider"); this.logger.warn(`未找到域名${fullDomain}的校验计划使用默认的dnsProvider`);
} }
} }
if (!dnsProvider) { if (!dnsProvider) {
this.logger.error("dnsProvider不存在无法申请证书"); throw new Error(`域名${fullDomain}没有匹配到任何校验方式,证书申请失败`);
} }
const dnsChallenge = getChallenge("dns-01"); const dnsChallenge = getChallenge("dns-01");

View File

@ -66,15 +66,15 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
{ value: "cname", label: "CNAME代理验证" }, { value: "cname", label: "CNAME代理验证" },
{ value: "http", label: "HTTP文件验证" }, { value: "http", label: "HTTP文件验证" },
{ value: "dnses", label: "多DNS提供商" }, { value: "dnses", label: "多DNS提供商" },
{ value: "auto", label: "自动选择" }, { value: "auto", label: "自动匹配" },
], ],
}, },
required: true, required: true,
helper: `1. <b>DNS直接验证</b>域名dns解析是在阿里云/腾讯云/华为云/CF/NameSilo/西数/火山/dns.la/京东云/51dns的选它 helper: `1. <b>DNS直接验证</b>域名dns解析是在阿里云/腾讯云/华为云/CF/NameSilo/西数/火山/dns.la/京东云/51dns的选它
2. <b>CNAME</b>CNAMEDNS/使DNS 2. <b>CNAME</b>[CNAME](#/certd/cname/record)DNS/使DNS
3. <b>HTTP</b> 3. <b>HTTP</b>
4. <b>DNS</b>DNS 4. <b>DNS</b>DNS
5. <b></b>[](#/certd/cert/domain) 5. <b></b>[](#/certd/cert/domain)
`, `,
}) })
challengeType!: string; challengeType!: string;
@ -469,13 +469,13 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
for (const fullDomain of domains) { for (const fullDomain of domains) {
const domain = fullDomain.replaceAll("*.", ""); const domain = fullDomain.replaceAll("*.", "");
const mainDomain = await domainParser.parse(domain); const mainDomain = await domainParser.parse(domain);
const planSetting = verifyPlanSetting[mainDomain]; const planSetting: DomainVerifyPlanInput = verifyPlanSetting[mainDomain];
if (planSetting.type === "dns") { if (planSetting.type === "dns") {
await this.createDnsDomainVerifyPlan(planSetting[mainDomain], domain, mainDomain); plan[domain] = await this.createDnsDomainVerifyPlan(planSetting, domain, mainDomain);
} else if (planSetting.type === "cname") { } else if (planSetting.type === "cname") {
await this.createCnameDomainVerifyPlan(domain, mainDomain); plan[domain] = await this.createCnameDomainVerifyPlan(domain, mainDomain);
} else if (planSetting.type === "http") { } 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; return plan;
@ -486,7 +486,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
// domain list // domain list
const domainList = new Set<string>(); const domainList = new Set<string>();
//整理域名 //整理域名
for (let domain in this.domains) { for (let domain of domains) {
domain = domain.replaceAll("*.", ""); domain = domain.replaceAll("*.", "");
domainList.add(domain); domainList.add(domain);
} }
@ -563,7 +563,7 @@ export class CertApplyPlugin extends CertApplyBasePlugin {
domain, domain,
mainDomain, mainDomain,
cnameVerifyPlan: { cnameVerifyPlan: {
domain, domain: cnameRecord.cnameProvider.domain,
fullRecord: cnameRecord.recordValue, fullRecord: cnameRecord.recordValue,
dnsProvider, dnsProvider,
}, },

View File

@ -714,6 +714,7 @@ export default {
}, },
domain: { domain: {
domainManager: "Domain Manager", domainManager: "Domain Manager",
domainDescription: "used to auto apply for certificate", //管理域名的校验方式,用于申请证书时自动选择验证方式
domain: "Domain", domain: "Domain",
challengeType: "Challenge Type", challengeType: "Challenge Type",
dnsProviderType: "DNS Provider Type", dnsProviderType: "DNS Provider Type",
@ -722,5 +723,7 @@ export default {
httpUploaderAccess: "HTTP Uploader Access", httpUploaderAccess: "HTTP Uploader Access",
httpUploadRootDir: "HTTP Upload Root Dir", httpUploadRootDir: "HTTP Upload Root Dir",
disabled: "Disabled", disabled: "Disabled",
challengeSetting: "Challenge Setting",
gotoCnameTip: "Please go to CNAME Record Page",
}, },
}; };

View File

@ -14,6 +14,8 @@ export default {
search: "Search", search: "Search",
enabled: "Enabled", enabled: "Enabled",
disabled: "Disabled", disabled: "Disabled",
enable: "Enable",
disable: "Disable",
edit: "Edit", edit: "Edit",
delete: "Delete", delete: "Delete",
create: "Create", create: "Create",

View File

@ -717,6 +717,7 @@ export default {
}, },
domain: { domain: {
domainManager: "域名管理", domainManager: "域名管理",
domainDescription: "管理域名的校验方式,用于申请证书时自动选择验证方式",
domain: "域名", domain: "域名",
challengeType: "校验类型", challengeType: "校验类型",
dnsProviderType: "DNS提供商类型", dnsProviderType: "DNS提供商类型",
@ -725,5 +726,7 @@ export default {
httpUploaderAccess: "上传授权信息", httpUploaderAccess: "上传授权信息",
httpUploadRootDir: "网站根路径", httpUploadRootDir: "网站根路径",
disabled: "禁用/启用", disabled: "禁用/启用",
challengeSetting: "校验配置",
gotoCnameTip: "CNAME域名配置请前往CNAME记录页面添加",
}, },
}; };

View File

@ -14,6 +14,8 @@ export default {
search: "搜索", search: "搜索",
enabled: "已启用", enabled: "已启用",
disabled: "已禁用", disabled: "已禁用",
enable: "启用",
disable: "禁用",
edit: "修改", edit: "修改",
delete: "删除", delete: "删除",
create: "新增", create: "新增",

View File

@ -7,6 +7,7 @@ import { useUserStore } from "/@/store/user";
import { useSettingStore } from "/@/store/settings"; import { useSettingStore } from "/@/store/settings";
import { Dicts } from "/@/components/plugins/lib/dicts"; import { Dicts } from "/@/components/plugins/lib/dicts";
import { createAccessApi } from "/@/views/certd/access/api"; import { createAccessApi } from "/@/views/certd/access/api";
import { Modal } from "ant-design-vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter(); const router = useRouter();
@ -73,13 +74,20 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
delRequest, delRequest,
}, },
tabs: { tabs: {
name: "status", name: "challengeType",
show: true, show: true,
}, },
rowHandle: { rowHandle: {
minWidth: 200, minWidth: 200,
fixed: "right", fixed: "right",
}, },
form: {
beforeSubmit({ form }) {
if (form.challengeType === "cname") {
throw new Error("CNAME方式请前往CNAME记录页面进行管理");
}
},
},
columns: { columns: {
id: { id: {
title: "ID", title: "ID",
@ -114,11 +122,28 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
title: t("certd.domain.challengeType"), title: t("certd.domain.challengeType"),
type: "dict-select", type: "dict-select",
dict: Dicts.challengeTypeDict, dict: Dicts.challengeTypeDict,
search: {
show: true,
},
form: { form: {
required: true, 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: { column: {
sorter: true, sorter: true,
show: false,
}, },
}, },
/** /**
@ -196,6 +221,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
form: { form: {
component: { component: {
name: "AccessSelector", name: "AccessSelector",
vModel: "modelValue",
type: compute(({ form }) => {
return form.httpUploaderType;
}),
}, },
show: compute(({ form }) => { show: compute(({ form }) => {
return form.challengeType === "http"; return form.challengeType === "http";
@ -226,16 +255,17 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
challengeSetting: { challengeSetting: {
title: "校验配置", title: t("certd.domain.challengeSetting"),
type: "text", type: "text",
form: { show: false }, form: { show: false },
column: { column: {
width: 400, width: 600,
conditionalRender: false, conditionalRender: false,
cellRender({ row }) { cellRender({ row }) {
if (row.challengeType === "dns") { if (row.challengeType === "dns") {
return ( return (
<div class={"flex"}> <div class={"flex"}>
<fs-values-format modelValue={row.challengeType} dict={Dicts.challengeTypeDict} color={"auto"}></fs-values-format>
<fs-values-format modelValue={row.dnsProviderType} dict={dnsProviderTypeDict} color={"auto"}></fs-values-format> <fs-values-format modelValue={row.dnsProviderType} dict={dnsProviderTypeDict} color={"auto"}></fs-values-format>
<fs-values-format class={"ml-5"} modelValue={row.dnsProviderAccess} dict={accessDict} color={"auto"}></fs-values-format> <fs-values-format class={"ml-5"} modelValue={row.dnsProviderAccess} dict={accessDict} color={"auto"}></fs-values-format>
</div> </div>
@ -243,9 +273,10 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
} else if (row.challengeType === "http") { } else if (row.challengeType === "http") {
return ( return (
<div class={"flex"}> <div class={"flex"}>
<fs-values-format modelValue={row.challengeType} dict={Dicts.challengeTypeDict} color={"auto"}></fs-values-format>
<fs-values-format modelValue={row.httpUploaderType} dict={httpUploaderTypeDict} color={"auto"}></fs-values-format> <fs-values-format modelValue={row.httpUploaderType} dict={httpUploaderTypeDict} color={"auto"}></fs-values-format>
<fs-values-format class={"ml-5"} modelValue={row.httpUploaderAccess} dict={accessDict} color={"auto"}></fs-values-format> <fs-values-format class={"ml-5"} modelValue={row.httpUploaderAccess} dict={accessDict} color={"auto"}></fs-values-format>
<a-tag class={"ml-5"}>{row.httpUploadRootDir}</a-tag> <a-tag class={"ml-5 flex items-center"}>{row.httpUploadRootDir}</a-tag>
</div> </div>
); );
} }
@ -255,10 +286,11 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
disabled: { disabled: {
title: t("certd.domain.disabled"), title: t("certd.domain.disabled"),
type: "dict-switch", type: "dict-switch",
search: { show: true },
dict: dict({ dict: dict({
data: [ data: [
{ label: "启用", value: false, color: "green" }, { label: t("common.enabled"), value: false, color: "green" },
{ label: "禁用", value: true, color: "red" }, { label: t("common.disabled"), value: true, color: "red" },
], ],
}), }),
form: { form: {

View File

@ -3,7 +3,9 @@
<template #header> <template #header>
<div class="title"> <div class="title">
{{ t("certd.domain.domainManager") }} {{ t("certd.domain.domainManager") }}
<span class="sub"> </span> <span class="sub">
{{ t("certd.domain.domainDescription") }}
</span>
</div> </div>
</template> </template>
<fs-crud ref="crudRef" v-bind="crudBinding"> <fs-crud ref="crudRef" v-bind="crudBinding">

View File

@ -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 {DomainParser} from "@certd/plugin-cert/dist/dns-provider/domain-parser.js";
import {DomainVerifiers} from "@certd/plugin-cert"; import {DomainVerifiers} from "@certd/plugin-cert";
import { SubDomainsGetter } from '../../pipeline/service/getter/sub-domain-getter.js'; 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<DomainEntity> {
@Inject() @Inject()
subDomainService: SubDomainService; subDomainService: SubDomainService;
@Inject()
cnameRecordService: CnameRecordService;
//@ts-ignore //@ts-ignore
getRepository() { getRepository() {
return this.repository; return this.repository;
@ -96,10 +101,12 @@ export class DomainService extends BaseService<DomainEntity> {
//去重 //去重
allDomains = [...new Set(allDomains)] allDomains = [...new Set(allDomains)]
//从 domain 表中获取配置
const domainRecords = await this.find({ const domainRecords = await this.find({
where: { where: {
domain: In(allDomains), domain: In(allDomains),
userId userId,
disabled:false,
} }
}) })
@ -107,16 +114,28 @@ export class DomainService extends BaseService<DomainEntity> {
pre[item.domain] = item pre[item.domain] = item
return pre 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) => { const httpMap = domainRecords.filter(item=>item.challengeType === 'http').reduce((pre, item) => {
pre[item.domain] = item pre[item.domain] = item
return pre 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 = {} const domainVerifiers:DomainVerifiers = {}
for (const domain of domains) { for (const domain of domains) {
@ -130,19 +149,21 @@ export class DomainService extends BaseService<DomainEntity> {
type: 'dns', type: 'dns',
dns: { dns: {
dnsProviderType: dnsRecord.dnsProviderType, dnsProviderType: dnsRecord.dnsProviderType,
dnsProviderAccessId: dnsRecord.dnsProviderAccessId dnsProviderAccessId: dnsRecord.dnsProviderAccess
} }
} }
continue continue
} }
const cnameRecord = cnameMap[mainDomain] const cnameRecord:CnameRecordEntity = cnameMap[mainDomain]
if (cnameRecord) { if (cnameRecord) {
domainVerifiers[domain] = { domainVerifiers[domain] = {
domain, domain,
mainDomain, mainDomain,
type: 'cname', type: 'cname',
cname: { cname: {
cnameRecord: cnameRecord.cnameRecord domain: cnameRecord.domain,
hostRecord: cnameRecord.hostRecord,
recordValue: cnameRecord.recordValue
} }
} }
continue continue