From 607afe864a12d6f50993895a4e10f4c9a3dd8fee Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 22 Jan 2026 10:56:45 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20cname=E8=AE=B0=E5=BD=95=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=89=B9=E9=87=8F=E5=AF=BC=E5=85=A5=E5=92=8C=E5=AF=BC?= =?UTF-8?q?=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/views/certd/cert/domain/api.ts | 2 +- .../src/views/certd/cert/domain/crud.tsx | 3 + .../src/views/certd/cname/record/api.ts | 8 +++ .../src/views/certd/cname/record/crud.tsx | 31 ++++++++++ .../src/views/certd/cname/record/use.tsx | 56 +++++++++++++++++++ .../controller/user/cert/domain-controller.ts | 6 +- .../user/cname/cname-record-controller.ts | 11 ++++ .../modules/cert/service/domain-service.ts | 6 +- .../src/modules/cert/service/task-executor.ts | 6 +- .../cname/service/cname-record-service.ts | 46 +++++++++++++++ 10 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 packages/ui/certd-client/src/views/certd/cname/record/use.tsx diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/api.ts b/packages/ui/certd-client/src/views/certd/cert/domain/api.ts index 778281ef6..23ba9736c 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/api.ts +++ b/packages/ui/certd-client/src/views/certd/cert/domain/api.ts @@ -60,7 +60,7 @@ export async function DeleteBatch(ids: any[]) { export async function SyncSubmit(body: any) { return await request({ - url: apiPrefix + "/sync/submit", + url: apiPrefix + "/sync/import", method: "post", data: body, }); 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 0bfe78d9e..0223f4461 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 @@ -157,6 +157,9 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat expirationDate: { title: t("certd.domain.expirationDate"), type: "date", + form: { + show: false, + }, }, challengeType: { title: t("certd.domain.challengeType"), diff --git a/packages/ui/certd-client/src/views/certd/cname/record/api.ts b/packages/ui/certd-client/src/views/certd/cname/record/api.ts index 9c5c61009..7066d10ab 100644 --- a/packages/ui/certd-client/src/views/certd/cname/record/api.ts +++ b/packages/ui/certd-client/src/views/certd/cname/record/api.ts @@ -77,3 +77,11 @@ export async function ResetStatus(id: number) { }, }); } + +export async function Import(form: { domainList: string; cnameProviderId: any }) { + return await request({ + url: apiPrefix + "/import", + method: "post", + data: form, + }); +} diff --git a/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx b/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx index 69b033df9..f126cc6ba 100644 --- a/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/cname/record/crud.tsx @@ -7,7 +7,9 @@ import { useUserStore } from "/@/store/user"; import { useSettingStore } from "/@/store/settings"; import { message, Modal } from "ant-design-vue"; import CnameTip from "/@/components/plugins/cert/domains-verify-plan-editor/cname-tip.vue"; +import { useCnameImport } from "./use"; export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet { + const crudBinding = crudExpose.crudBinding; const router = useRouter(); const { t } = useI18n(); const pageRequest = async (query: UserPageQuery): Promise => { @@ -27,10 +29,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat return res; }; + const openCnameImportDialog = useCnameImport(); + const userStore = useUserStore(); const settingStore = useSettingStore(); const selectedRowKeys: Ref = ref([]); context.selectedRowKeys = selectedRowKeys; + const dictRef = dict({ data: [ { label: t("certd.pending_cname_setup"), value: "cname", color: "warning" }, @@ -64,6 +69,32 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat editRequest, delRequest, }, + actionbar: { + buttons: { + import: { + title: "导入CNAME记录", + type: "primary", + text: "批量导入", + click: () => { + openCnameImportDialog({ + afterSubmit: () => { + setTimeout(() => { + crudExpose?.doRefresh(); + }, 2000); + }, + }); + }, + }, + export: { + title: "导出CNAME记录之后,可用于批量导入cname解析到域名注册商", + type: "primary", + text: "批量导出", + click: () => { + crudBinding.value.toolbar.buttons.export.click({}); + }, + }, + }, + }, tabs: { name: "status", show: true, diff --git a/packages/ui/certd-client/src/views/certd/cname/record/use.tsx b/packages/ui/certd-client/src/views/certd/cname/record/use.tsx new file mode 100644 index 000000000..ebd79d051 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/cname/record/use.tsx @@ -0,0 +1,56 @@ +import { dict } from "@fast-crud/fast-crud"; +import { message } from "ant-design-vue"; +import * as api from "./api"; +import { useFormDialog } from "/@/use/use-dialog"; + +export const cnameProviderDict = dict({ + url: "/cname/provider/list", + value: "id", + label: "domain", +}); +export function useCnameImport() { + const { openFormDialog } = useFormDialog(); + + const columns = { + domainList: { + title: "域名列表", + type: "text", + form: { + component: { + name: "a-textarea", + rows: 5, + }, + col: { + span: 24, + }, + required: true, + helper: "每个域名一行,批量导入\n泛域名请去掉*.\n已经存在的会自动跳过", + }, + }, + cnameProviderId: { + title: "CNAME服务", + type: "dict-select", + dict: cnameProviderDict, + form: { + required: true, + }, + }, + }; + + return function openCnameImportDialog(req: { afterSubmit?: () => void }) { + openFormDialog({ + title: "导入CNAME记录", + columns: columns, + onSubmit: async (form: any) => { + await api.Import({ + domainList: form.domainList, + cnameProviderId: form.cnameProviderId, + }); + message.success("导入任务已提交"); + if (req.afterSubmit) { + req.afterSubmit(); + } + }, + }); + }; +} diff --git a/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts b/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts index 422fd126b..78d476fd1 100644 --- a/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts +++ b/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts @@ -1,6 +1,7 @@ import { ALL, Body, Controller, Inject, Post, Provide, Query } from '@midwayjs/core'; import { Constants, CrudController } from '@certd/lib-server'; import {DomainService} from "../../../modules/cert/service/domain-service.js"; +import { checkPlus } from '@certd/plus-core'; /** * 授权 @@ -79,12 +80,13 @@ export class DomainController extends CrudController { } - @Post('/sync/submit', { summary: Constants.per.authOnly }) - async syncSubmit(@Body(ALL) body: any) { + @Post('/sync/import', { summary: Constants.per.authOnly }) + async syncImport(@Body(ALL) body: any) { const { dnsProviderType, dnsProviderAccessId } = body; const req = { dnsProviderType, dnsProviderAccessId, userId: this.getUserId(), } + checkPlus() await this.service.doSyncFromProvider(req); return this.ok(); } diff --git a/packages/ui/certd-server/src/controller/user/cname/cname-record-controller.ts b/packages/ui/certd-server/src/controller/user/cname/cname-record-controller.ts index 038eb338f..21065e29a 100644 --- a/packages/ui/certd-server/src/controller/user/cname/cname-record-controller.ts +++ b/packages/ui/certd-server/src/controller/user/cname/cname-record-controller.ts @@ -99,4 +99,15 @@ export class CnameRecordController extends CrudController { const res = await this.service.resetStatus(body.id); return this.ok(res); } + @Post('/import', { summary: Constants.per.authOnly }) + async import(@Body(ALL) body: { domainList: string; cnameProviderId: any }) { + const userId = this.getUserId(); + const res = await this.service.doImport({ + userId, + domainList: body.domainList, + cnameProviderId: body.cnameProviderId, + }); + return this.ok(res); + } + } 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 b0163e69c..eb559206f 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 @@ -350,9 +350,9 @@ export class DomainService extends BaseService { const getDomainPage = async (pager: Pager) => { const pageRes = await this.page({ query: query, - buildQuery(bq) { - bq.andWhere(" (expiration_date is null or expiration_date < :now) ", { now: dayjs().add(1, 'month').valueOf() }) - }, + // buildQuery(bq) { + // bq.andWhere(" (expiration_date is null or expiration_date < :now) ", { now: dayjs().add(1, 'month').valueOf() }) + // }, page: { offset: pager.getOffset(), limit: pager.pageSize, diff --git a/packages/ui/certd-server/src/modules/cert/service/task-executor.ts b/packages/ui/certd-server/src/modules/cert/service/task-executor.ts index 1e920ea8a..73e4d9b87 100644 --- a/packages/ui/certd-server/src/modules/cert/service/task-executor.ts +++ b/packages/ui/certd-server/src/modules/cert/service/task-executor.ts @@ -9,7 +9,7 @@ export class BackTaskExecutor { } const oldTask = this.tasks[type][task.key] if (oldTask && oldTask.status === "running") { - throw new Error(`任务 ${task.key} 正在运行中`) + throw new Error(`任务 ${type}—${task.key} 正在运行中`) } this.tasks[type][task.key] = task this.run(type, task); @@ -39,7 +39,7 @@ export class BackTaskExecutor { private async run(type: string, task: any) { if (task.status === "running") { - throw new Error(`任务 ${task.key} 正在运行中`) + throw new Error(`任务 ${type}—${task.key} 正在运行中`) } task.startTime = Date.now(); task.clearTimeout(); @@ -47,7 +47,7 @@ export class BackTaskExecutor { task.status = "running"; return await task.run(task); } catch (e) { - logger.error(`任务 ${task.title}[${task.key}] 执行失败`, e.message); + logger.error(`任务 ${task.title}[${type}-${task.key}] 执行失败`, e.message); task.status = "failed"; task.error = e.message; } finally { diff --git a/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts b/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts index ee4224bd3..de7f5a3a2 100644 --- a/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts +++ b/packages/ui/certd-server/src/modules/cname/service/cname-record-service.ts @@ -22,6 +22,7 @@ import punycode from "punycode.js"; import { SubDomainService } from "../../pipeline/service/sub-domain-service.js"; import { SubDomainsGetter } from "../../pipeline/service/getter/sub-domain-getter.js"; import { TaskServiceBuilder } from "../../pipeline/service/getter/task-service-getter.js"; +import { BackTask, taskExecutor } from "../../cert/service/task-executor.js"; type CnameCheckCacheValue = { validating: boolean; @@ -487,4 +488,49 @@ export class CnameRecordService extends BaseService { } await this.getRepository().update(id, { status: "cname", mainDomain: "" }); } + + async doImport(req:{ userId: number; domainList: string; cnameProviderId: any }) { + const {userId,cnameProviderId,domainList} = req; + const domains = domainList.split("\n").map(item => item.trim()).filter(item => item.length > 0); + if (domains.length === 0) { + throw new ValidateException("域名列表不能为空"); + } + if (!req.cnameProviderId) { + throw new ValidateException("CNAME服务提供商不能为空"); + } + + taskExecutor.start("cnameImport",new BackTask({ + key: "user_"+userId, + title: "导入CNAME记录", + run: async (task) => { + await this._import({ userId, domains, cnameProviderId },task); + } + })); + } + + async _import(req :{ userId: number; domains: string[]; cnameProviderId: any },task:BackTask) { + const userId = req.userId; + for (const domain of req.domains) { + const old = await this.getRepository().findOne({ + where: { + userId: req.userId, + domain, + }, + }); + if (old) { + logger.warn(`域名${domain}已存在,跳过`); + } + //开始导入 + try{ + await this.add({ + userId, + domain: domain, + cnameProviderId: req.cnameProviderId, + }); + }catch(e){ + logger.error(`导入域名${domain}失败:${e.message}`); + } + } + + } }