From 5f852194953dc1b4e6336770f417507b8f5a33ad Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Tue, 29 Apr 2025 18:40:13 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E9=83=A8=E7=BD=B2?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E5=88=B0=E7=81=AB=E5=B1=B1dcdn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/basic/src/utils/util.options.ts | 8 +- .../plugins/plugin-lib/src/common/index.ts | 7 +- .../plugins/common/remote-select.vue | 19 +- .../plugins/plugin-deploy-to-dcdn.ts | 204 ++++++++++++++++++ .../plugins/plugin-volcengine/ve-client.ts | 25 ++- 5 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-dcdn.ts diff --git a/packages/core/basic/src/utils/util.options.ts b/packages/core/basic/src/utils/util.options.ts index 4689c9df..8367e40e 100644 --- a/packages/core/basic/src/utils/util.options.ts +++ b/packages/core/basic/src/utils/util.options.ts @@ -1,4 +1,4 @@ -import { domainUtils } from './util.domain.js'; +import { domainUtils } from "./util.domain.js"; function groupByDomain(options: any[], inDomains: string[]) { const matched = []; @@ -19,16 +19,16 @@ function groupByDomain(options: any[], inDomains: string[]) { function buildGroupOptions(options: any[], inDomains: string[]) { const grouped = groupByDomain(options, inDomains); const groupOptions = []; - groupOptions.push({ value: 'matched', disabled: true, label: '----已匹配----' }); + groupOptions.push({ value: "matched", disabled: true, label: "----已匹配----" }); if (grouped.matched.length === 0) { - options.push({ value: '', disabled: true, label: '没有可以匹配的域名' }); + options.push({ value: "", disabled: true, label: "没有可以匹配的域名" }); } else { for (const matched of grouped.matched) { groupOptions.push(matched); } } if (grouped.notMatched.length > 0) { - groupOptions.push({ value: 'unmatched', disabled: true, label: '----未匹配----' }); + groupOptions.push({ value: "unmatched", disabled: true, label: "----未匹配----" }); for (const notMatched of grouped.notMatched) { groupOptions.push(notMatched); } diff --git a/packages/plugins/plugin-lib/src/common/index.ts b/packages/plugins/plugin-lib/src/common/index.ts index c790299c..ea766648 100644 --- a/packages/plugins/plugin-lib/src/common/index.ts +++ b/packages/plugins/plugin-lib/src/common/index.ts @@ -37,6 +37,7 @@ export function createRemoteSelectInputDefine(opts?: { multi?: boolean; required?: boolean; rules?: any; + mergeScript?: string; }) { const title = opts?.title || "请选择"; const certDomainsInputKey = opts?.certDomainsInputKey || "certDomains"; @@ -66,7 +67,9 @@ export function createRemoteSelectInputDefine(opts?: { }, rules: opts?.rules, required: opts.required ?? true, - mergeScript: ` + mergeScript: + opts.mergeScript ?? + ` return { component:{ form: ctx.compute(({form})=>{ @@ -80,3 +83,5 @@ export function createRemoteSelectInputDefine(opts?: { return merge(item, opts?.formItem); } + + diff --git a/packages/ui/certd-client/src/components/plugins/common/remote-select.vue b/packages/ui/certd-client/src/components/plugins/common/remote-select.vue index aa80b4c8..e6c0c7e1 100644 --- a/packages/ui/certd-client/src/components/plugins/common/remote-select.vue +++ b/packages/ui/certd-client/src/components/plugins/common/remote-select.vue @@ -53,11 +53,15 @@ const getOptions = async () => { if (!define) { return; } + const pluginType = getPluginType(); + const { form } = getScope(); + const input = pluginType === "plugin" ? form.input : form; + for (let key in define.input) { const inWatches = props.watches.includes(key); const inputDefine = define.input[key]; if (inWatches && inputDefine.required) { - const value = props.form[key]; + const value = input[key]; if (value == null || value === "") { console.log("remote-select required", key); return; @@ -69,8 +73,6 @@ const getOptions = async () => { hasError.value = false; loading.value = true; optionsRef.value = []; - const { form } = getScope(); - const pluginType = getPluginType(); try { const res = await doRequest( @@ -78,7 +80,7 @@ const getOptions = async () => { type: pluginType, typeName: form.type, action: props.action, - input: pluginType === "plugin" ? form.input : form, + input, }, { onError(err: any) { @@ -115,11 +117,16 @@ async function refreshOptions() { watch( () => { const values = []; + + const pluginType = getPluginType(); + const { form } = getScope(); + const input = pluginType === "plugin" ? form.input : form; + for (const item of props.watches) { - values.push(props.form[item]); + values.push(input[item]); } return { - form: props.form, + form: input, watched: values, }; }, diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-dcdn.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-dcdn.ts new file mode 100644 index 00000000..c6f492cb --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/plugins/plugin-deploy-to-dcdn.ts @@ -0,0 +1,204 @@ +import {AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput} from "@certd/pipeline"; +import {createCertDomainGetterInputDefine, createRemoteSelectInputDefine} from "@certd/plugin-lib"; +import {CertApplyPluginNames, CertInfo, CertReader} from "@certd/plugin-cert"; +import {VolcengineAccess} from "../access.js"; +import {VolcengineClient} from "../ve-client.js"; + +@IsTaskPlugin({ + name: "VolcengineDeployToDCDN", + title: "火山引擎-部署证书至DCDN", + icon: "svg:icon-volcengine", + group: pluginGroups.volcengine.key, + desc: "部署至火山引擎全站加速", + // showRunStrategy: true, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed + } + } +}) +export class VolcengineDeployToDCDN extends AbstractTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames] + }, + required: true + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({props: {required: false}})) + certDomains!: string[]; + + @TaskInput({ + title: "自动匹配", + helper: "是否根据证书自动匹配合适的DCDN域名进行部署", + value: false, + component: { + name: "a-switch", + type: "checked" + }, + required: true + }) + autoMatch!: boolean; + + @TaskInput({ + title: "Access授权", + helper: "火山引擎AccessKeyId、AccessKeySecret", + component: { + name: "access-selector", + type: "volcengine" + }, + required: true + }) + accessId!: string; + + + @TaskInput( + createRemoteSelectInputDefine({ + title: "DCDN域名", + helper: "选择要部署证书的DCDN域名", + action: VolcengineDeployToDCDN.prototype.onGetDomainList.name, + watches: ["certDomains", "accessId"], + required: true, + mergeScript: ` + return { + show: ctx.compute(({form})=>{ + return !form.autoMatch + }) + } + ` + }) + ) + domainList!: string | string[]; + + + async onInstance() { + } + + async uploadCert(client: VolcengineClient) { + const certService = await client.getCertCenterService(); + let certId = this.cert; + if (typeof certId !== "string") { + const certInfo = this.cert as CertInfo; + this.logger.info(`开始上传证书`); + certId = await certService.ImportCertificate({ + certName: this.appendTimeSuffix("certd"), + cert: certInfo + }); + this.logger.info(`上传证书成功:${certId}`); + } else { + this.logger.info(`使用已有证书ID:${certId}`); + } + return certId + } + + + async execute(): Promise { + this.logger.info("开始部署证书到火山引擎DCDN"); + + const client = await this.getClient(); + let certId = await this.uploadCert(client); + + const service = await client.getDCDNService(); + + this.certDomains = new CertReader(this.cert).getAllDomains() + + + let domainList = this.domainList + if (!this.autoMatch) { + //手动根据域名部署 + if (!this.domainList || this.domainList.length === 0) { + throw new Error("域名列表不能为空"); + } + } else { + //自动匹配 + const options = await this.getDomainOptions(service); + const grouped = this.ctx.utils.options.groupByDomain(options, this.certDomains); + + const matched = grouped.matched + + domainList = matched.map(item => item.domain) + + if (domainList.length === 0) { + this.logger.warn("没有匹配到域名,跳过部署") + this.logger.info("当前证书域名:", this.certDomains) + this.logger.info("当前DCDN域名:", grouped.notMatched.map(item => item.domain)) + return + } + } + + + //域名十个十个的分割 + for (let i = 0; i < domainList.length; i += 10) { + const batch = domainList.slice(i, i + 10); + this.logger.info(`开始部署证书到域名:${batch}`); + const res = await service.request({ + action: "CreateCertBind", + method: "POST", + body: { + "DomainNames": batch, + "CertSource": "volc", + "CertId": certId + }, + version: "2021-04-01" + }); + this.logger.info(`部署证书到域名成功:`,JSON.stringify(res)); + } + + this.logger.info("部署完成"); + + } + + + async getClient() { + const access = await this.getAccess(this.accessId); + + return new VolcengineClient({ + logger: this.logger, + access, + http: this.http + }) + } + + async onGetDomainList(data: any) { + if (!this.accessId) { + throw new Error("请选择Access授权"); + } + + const client = await this.getClient(); + const service = await client.getDCDNService(); + const options = await this.getDomainOptions(service); + return this.ctx.utils.options.buildGroupOptions(options, this.certDomains); + } + + private async getDomainOptions(service: any) { + const res = await service.request({ + method: "POST", + action: "DescribeUserDomains", + body: { + "PageSize": 1000 + } + }) + + + const list = res.Result?.Domains; + if (!list || list.length === 0) { + throw new Error("找不到DCDN域名,您也可以手动输入域名"); + } + const options = list.map((item: any) => { + return { + value: item.Domain, + label: `${item.Domain}<${item.Scope}>`, + domain: item.Domain + }; + }); + return options; + } + + +} + +new VolcengineDeployToDCDN(); diff --git a/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts b/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts index adcf707a..b9d1634e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts +++ b/packages/ui/certd-server/src/plugins/plugin-volcengine/ve-client.ts @@ -100,6 +100,19 @@ export class VolcengineClient { return service; } + async getDCDNService( opts?: { }) { + const CommonService = await this.getServiceCls(); + + const service = new CommonService({ + serviceName: "dcdn", + defaultVersion: "2023-01-01" + }); + service.setAccessKeyId(this.opts.access.accessKeyId); + service.setSecretKey(this.opts.access.secretAccessKey); + service.setRegion("cn-north-1"); + return service; + } + async getServiceCls() { if (this.CommonService) { return this.CommonService; @@ -114,11 +127,11 @@ export class VolcengineClient { defaultVersion: string; }) { super(Object.assign({ host: "open.volcengineapi.com" }, options)); - this.Generic = async (req: { action: string, body?: any, method?: string, query?: any }) => { - const { action, method, body, query } = req; + this.Generic = async (req: { action: string, body?: any, method?: string, query?: any ,version?:string}) => { + const { action, method, body, query,version } = req; return await this.fetchOpenAPI({ Action: action, - Version: options.defaultVersion, + Version: version||options.defaultVersion, method: method as any, headers: { "content-type": "application/json" @@ -129,8 +142,11 @@ export class VolcengineClient { }; } - async request(req: { action: string, body?: any, method?: string, query?: any }) { + async request(req: { action: string, body?: any, method?: string, query?: any,version?:string }) { const res = await this.Generic(req); + if (res ==="Not Found"){ + throw new Error(`${res} (检查method)`); + } if (res.errorcode) { throw new Error(`${res.errorcode}:${res.message}`); } @@ -146,4 +162,5 @@ export class VolcengineClient { } + }