From c408687af7669afe733b5506720ca795555acdce Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Wed, 28 Jan 2026 23:53:57 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20ucloud=E6=94=AF=E6=8C=81=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E5=88=B0ulb=EF=BC=88alb=E3=80=81clb=E7=BB=9F=E4=B8=80?= =?UTF-8?q?=E6=88=90=E4=B8=80=E4=B8=AA=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/common/remote-select.vue | 8 +- .../component/history-timeline-item.vue | 12 +- .../src/modules/auto/auto-a-init-site.ts | 10 +- .../src/plugins/plugin-ucloud/access.ts | 4 +- .../plugins/plugin-ucloud/plugins/index.ts | 2 +- .../plugins/plugin-deploy-to-alb.ts | 289 --------------- .../plugins/plugin-deploy-to-ulb.ts | 333 ++++++++++++++++++ 7 files changed, 356 insertions(+), 302 deletions(-) delete mode 100644 packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts create mode 100644 packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-ulb.ts 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 55c0bcc11..e951b4d54 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 @@ -145,12 +145,18 @@ const getOptions = async () => { showErrorNotify: false, } ); - const list = res?.list || res || []; + let list = res?.list || res || []; if (list.length > 0) { message.value = "获取数据成功,请从下拉框中选择"; } else { message.value = "获取数据成功,没有数据"; } + list = list.map((item: any) => { + return { + ...item, + title: `${item.domain || item.value}`, + }; + }); optionsRef.value = list; pagerRef.value.total = list.length; if (props.pager) { diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/history-timeline-item.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/history-timeline-item.vue index 12546c7df..b8555ccf1 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/history-timeline-item.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/history-timeline-item.vue @@ -5,12 +5,12 @@

- - + + {{ status.label }} - 当前 - 查看 + 当前 + 查看

@@ -89,5 +89,9 @@ export default defineComponent({ .ant-tag.pointer { cursor: pointer; } + + .ant-timeline .ant-timeline-item-content { + margin-inline-start: 22px !important; + } } diff --git a/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts b/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts index d7a33473b..194e8f0fc 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-a-init-site.ts @@ -1,10 +1,10 @@ -import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core'; -import { http, logger } from '@certd/basic'; -import { UserService } from '../sys/authority/service/user-service.js'; +import { logger } from '@certd/basic'; import { PlusService, SysInstallInfo, SysPrivateSettings, SysSettingsService } from '@certd/lib-server'; -import { nanoid } from 'nanoid'; +import { Autoload, Config, Init, Inject, Scope, ScopeEnum } from '@midwayjs/core'; import crypto from 'crypto'; -import {SafeService} from "../sys/settings/safe-service.js"; +import { nanoid } from 'nanoid'; +import { UserService } from '../sys/authority/service/user-service.js'; +import { SafeService } from "../sys/settings/safe-service.js"; @Autoload() @Scope(ScopeEnum.Request, { allowDowngrade: true }) diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts index 09e761ab2..6ddff3054 100644 --- a/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/access.ts @@ -99,7 +99,7 @@ export class UCloudAccess extends BaseAccess { const resp = await client.uaccount().getProjectList({ "Action": "GetProjectList" }); - this.ctx.logger.info(`获取到项目列表:${JSON.stringify(resp)}`); + // this.ctx.logger.info(`获取到项目列表:${JSON.stringify(resp)}`); return resp; } @@ -120,7 +120,7 @@ export class UCloudAccess extends BaseAccess { "PageNo": req.PageNo, "PageSize": req.PageSize, }); - this.ctx.logger.info(`获取到CDN域名列表:${JSON.stringify(resp)}`); + // this.ctx.logger.info(`获取到CDN域名列表:${JSON.stringify(resp)}`); return resp; } diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts index 6d7717aea..0dfa01f67 100644 --- a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/index.ts @@ -1,5 +1,5 @@ export * from './plugin-deploy-to-cdn.js'; export * from './plugin-upload-to-ussl.js'; export * from './plugin-deploy-to-waf.js'; -export * from './plugin-deploy-to-alb.js'; +export * from './plugin-deploy-to-ulb.js'; export * from './plugin-deploy-to-us3.js'; diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts deleted file mode 100644 index 056fb4b95..000000000 --- a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-alb.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; -import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; -import { CertReader, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; -import { UCloudAccess } from "../access.js"; -import { UCloudRegions } from "./constants.js"; - -@IsTaskPlugin({ - name: "UCloudDeployToALB", - title: "UCloud-部署到ALB", - desc: "将证书部署到UCloud ALB(应用负载均衡)", - icon: "svg:icon-ucloud", - group: pluginGroups.ucloud.key, - needPlus: false, - default: { - strategy: { - runStrategy: RunStrategy.SkipWhenSucceed - } - } -}) -export class UCloudDeployToALB extends AbstractTaskPlugin { - @TaskInput({ - title: "域名证书", - helper: "请选择前置任务输出的域名证书", - component: { - name: "output-selector", - from: [...CertApplyPluginNames] - } - }) - cert!: CertInfo; - - @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) - certDomains!: string[]; - - @TaskInput({ - title: "UCloud授权", - component: { - name: "access-selector", - type: "ucloud" - }, - required: true - }) - accessId!: string; - - @TaskInput( - createRemoteSelectInputDefine({ - title: "地域", - helper: "选择UCloud地域", - action: UCloudDeployToALB.prototype.onGetRegionList.name, - multi:false - }) - ) - region!: string; - - @TaskInput( - createRemoteSelectInputDefine({ - title: "负载均衡实例", - helper: "选择ALB负载均衡实例", - action: UCloudDeployToALB.prototype.onGetALBList.name, - multi:false - }) - ) - loadBalancerId!: string; - - @TaskInput( - createRemoteSelectInputDefine({ - title: "监听器列表", - helper: "要更新的ALB监听器列表", - action: UCloudDeployToALB.prototype.onGetListenerList.name - }) - ) - listenerList!: string[]; - - @TaskInput({ - title: "上传证书模式", - helper: "选择是更新默认证书还是添加扩展证书", - component: { - name: "a-select", - options: [ - { label: "更新默认证书", value: "update_default" }, - { label: "添加扩展证书", value: "add_extension" } - ] - }, - required: true, - default: "update_default" - }) - deployMode!: string; - - async onInstance() { - } - - async onGetRegionList(req: PageSearch = {}) { - const access = await this.getAccess(this.accessId); - - const res = await access.GetRegion(); - let list = res.Regions || []; - - if (!list || list.length === 0) { - throw new Error("没有获取到UCloud地域列表"); - } - - const haveSet = {} - list = list.filter((item: any) => { - const region = item.Region; - if (haveSet[region]) { - return false; - } - haveSet[region] = true; - return true; - }) - let options = list.map((item: any) => { - const region = item.Region; - const name = UCloudRegions.find((r) => r.value === region)?.label || item.RegionName; - return { - label: `${name}(${item.Region})`, - value: item.Region - }; - }); - - return { - list: options, - total: options.length, - pageNo: 1, - pageSize: options.length - }; - } - - async uploadCertToULB(){ - const access = await this.getAccess(this.accessId); - const certInfo = this.cert as CertInfo; - const sslName = CertReader.buildCertName(certInfo); - const sslContent = certInfo.crt + '\n' + certInfo.key; - - const createRes = await access.invoke({ - "Action": "CreateSSL", - "Region": this.region, - "ProjectId": access.projectId, - "SSLName": sslName, - "SSLContent": sslContent, - }); - - if (createRes.RetCode !== 0) { - throw new Error(`创建SSL证书失败: ${createRes.Message || '未知错误'}`); - } - - return createRes.SSLId; - } - - async execute(): Promise { - const access = await this.getAccess(this.accessId); - let certId = await this.uploadCertToULB(); - - - - for (const item of this.listenerList) { - this.logger.info(`----------- 开始处理监听器:${item}`); - if (this.deployMode === "update_default") { - await this.updateDefaultCert({ - access: access, - loadBalancerId: this.loadBalancerId, - listenerId: item, - certId: certId - }); - this.logger.info(`----------- 更新监听器默认证书${item}成功`); - } else if (this.deployMode === "add_extension") { - await this.addExtensionCert({ - access: access, - loadBalancerId: this.loadBalancerId, - listenerId: item, - certId: certId - }); - this.logger.info(`----------- 添加监听器扩展证书${item}成功`); - } - } - - this.logger.info("部署完成"); - } - - async updateDefaultCert(req: { access: any, loadBalancerId: string, listenerId: string, certId: string }) { - const { access, loadBalancerId, listenerId, certId } = req - - this.logger.info(`----------- 更新ALB监听器默认证书${listenerId}`); - const resp = await access.invoke({ - "Action": "UpdateListenerAttribute", - "Region": this.region, - "ProjectId": access.projectId, - "LoadBalancerId": loadBalancerId, - "ListenerId": listenerId, - "Certificates": [certId] - }); - this.logger.info(`----------- 更新监听器默认证书${listenerId}成功,${JSON.stringify(resp)}`); - } - - async addExtensionCert(req: { access: any, loadBalancerId: string, listenerId: string, certId: string }) { - const { access, loadBalancerId, listenerId, certId } = req - - this.logger.info(`----------- 添加ALB监听器扩展证书${listenerId}`); - const resp = await access.invoke({ - "Action": "AddSSLBinding", - "Region": this.region, - "ProjectId": access.projectId, - "LoadBalancerId": loadBalancerId, - "ListenerId": listenerId, - "SSLIds": [certId] - }); - this.logger.info(`----------- 添加监听器扩展证书${listenerId}成功,${JSON.stringify(resp)}`); - } - - async onGetALBList(req: PageSearch = {}) { - const access = await this.getAccess(this.accessId); - - const pageNo = req.pageNo ?? 1; - const pageSize = req.pageSize ?? 100; - - const res = await access.invoke({ - "Action": "DescribeLoadBalancers", - "Region": this.region, - "ProjectId": access.projectId, - "Type": "Application", - "Offset": (pageNo - 1) * pageSize, - "Limit": pageSize - }); - - const total = res.LoadBalancers?.length || 0; - const list = res.LoadBalancers || []; - - if (!list || list.length === 0) { - throw new Error("没有找到ALB实例,请先在控制台创建ALB实例"); - } - - const options = list.map((item: any) => { - return { - label: `${item.Name || item.LoadBalancerId}<${item.LoadBalancerId}>`, - value: `${item.LoadBalancerId}` - }; - }); - - return { - list: options, - total: total, - pageNo: pageNo, - pageSize: pageSize - }; - } - - - async onGetListenerList(req: PageSearch = {}) { - const access = await this.getAccess(this.accessId); - - if (!this.loadBalancerId) { - throw new Error("请先选择ALB负载均衡实例"); - } - - const pageNo = req.pageNo ?? 1; - const pageSize = req.pageSize ?? 100; - - const res = await access.invoke({ - "Action": "DescribeListeners", - "Region": this.region, - "ProjectId": access.projectId, - "LoadBalancerId": this.loadBalancerId, - "Offset": (pageNo - 1) * pageSize, - "Limit": pageSize - }); - - const total = res.TotalCount || 0; - const list = res.Listeners || []; - - if (!list || list.length === 0) { - throw new Error("没有找到ALB监听器,请先在控制台创建ALB实例和监听器"); - } - - const options = list.map((item: any) => { - return { - label: `${item.Name || item.ListenerId}<${item.ListenerId}>`, - value: `${item.ListenerId}`, - domain: item.Name || item.ListenerId - }; - }); - - return { - list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains), - total: total, - pageNo: pageNo, - pageSize: pageSize - }; - } -} - -new UCloudDeployToALB(); diff --git a/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-ulb.ts b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-ulb.ts new file mode 100644 index 000000000..faa500832 --- /dev/null +++ b/packages/ui/certd-server/src/plugins/plugin-ucloud/plugins/plugin-deploy-to-ulb.ts @@ -0,0 +1,333 @@ +import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline"; +import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert"; +import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib"; +import { UCloudAccess } from "../access.js"; +import { UCloudRegions } from "./constants.js"; + +@IsTaskPlugin({ + name: "UCloudDeployToULB", + title: "UCloud-部署到负载均衡", + desc: "将证书部署到UCloud负载均衡(ULB/ALB/CLB)", + icon: "svg:icon-ucloud", + group: pluginGroups.ucloud.key, + needPlus: false, + default: { + strategy: { + runStrategy: RunStrategy.SkipWhenSucceed + } + } +}) +export class UCloudDeployToULB extends AbstractTaskPlugin { + @TaskInput({ + title: "域名证书", + helper: "请选择前置任务输出的域名证书", + component: { + name: "output-selector", + from: [...CertApplyPluginNames] + } + }) + cert!: CertInfo; + + @TaskInput(createCertDomainGetterInputDefine({ props: { required: false } })) + certDomains!: string[]; + + @TaskInput({ + title: "UCloud授权", + component: { + name: "access-selector", + type: "ucloud" + }, + required: true + }) + accessId!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "地域", + helper: "选择UCloud地域", + action: UCloudDeployToULB.prototype.onGetRegionList.name, + multi: false + }) + ) + region!: string; + + @TaskInput( + createRemoteSelectInputDefine({ + title: "证书列表", + helper: "选择要替换的证书名称\n如果证书不存在,可以手动输入证书名称(运行一次后将会自动创建,您可以在ULB控制台进行使用)\n请确保证书名称不要重复,如果重复,只会更新创建时间最近的那一条", + action: UCloudDeployToULB.prototype.onGetCertList.name, + }) + ) + certNameList!: string[]; + + + async onInstance() { + } + + async onGetRegionList(req: PageSearch = {}) { + const access = await this.getAccess(this.accessId); + + const res = await access.GetRegion(); + let list = res.Regions || []; + + if (!list || list.length === 0) { + throw new Error("没有获取到UCloud地域列表"); + } + + const haveSet = {} + list = list.filter((item: any) => { + const region = item.Region; + if (haveSet[region]) { + return false; + } + haveSet[region] = true; + return true; + }) + let options = list.map((item: any) => { + const region = item.Region; + const name = UCloudRegions.find((r) => r.value === region)?.label || item.RegionName; + return { + label: `${name}(${item.Region})`, + value: item.Region + }; + }); + + return { + list: options, + total: options.length, + pageNo: 1, + pageSize: options.length + }; + } + + + async execute(): Promise { + const access = await this.getAccess(this.accessId); + const allCertList = await this.getAllCert(access); + const certMap = {} + allCertList.forEach((item) => { + if (!item.name) { + return; + } + certMap[item.name] = item; + }) + + for (const certName of this.certNameList) { + this.logger.info(`----------- 开始更新证书:${certName}`); + const oldCert = certMap[certName]; + if (!oldCert) { + //如果没有找到旧证书,则跳过 + this.logger.info(`没有找到ULB证书${certName},仅上传证书,不更新`); + await this.uploadCertToULB({ + access, + certName: `${certName}`, + }); + this.logger.info(`----------- 上传证书${certName}完成`); + continue; + } + + const bakCertName = `${certName}_${oldCert.id}_${Date.now()}` + let certId = await this.uploadCertToULB({ + access, + certName:bakCertName, + }); + this.logger.info(`----------- 上传证书${bakCertName}完成`); + + await this.updateSSLBinding({ + access, + oldSSL: oldCert, + newSSLId: certId, + certName, + }); + this.logger.info(`----------- 证书${certName}部署完成`); + + } + + await this.ctx.utils.sleep(2000); + this.logger.info(`----------- 开始清理过期证书`); + await this.clearExpiredCert(access, allCertList); + this.logger.info(`----------- 清理过期证书完成`); + this.logger.info("部署完成"); + } + + async clearExpiredCert(access: UCloudAccess, allCertList: any[]) { + const now = Date.now()/1000; + const expiredCertList = allCertList.filter((item) => { + if (!item.notAfter) { + return false; + } + return item.notAfter < now; + }) + if (!expiredCertList || expiredCertList.length === 0) { + this.logger.info(`----------- 没有过期证书需要清理`); + return; + } + for (const cert of expiredCertList) { + this.logger.info(`----------- 清理过期证书:${cert.name} ${cert.id}`); + try { + await access.invoke({ + "Action": "DeleteSSL", + "Region": this.region, + "ProjectId": access.projectId, + "SSLId": cert.id, + }); + this.logger.info(`----------- 清理过期证书成功:${cert.name} ${cert.id}`); + } catch (error) { + this.logger.error(`----------- 清理过期证书失败:${cert.name} ${cert.id}`, error); + } + } + } + async updateSSLBinding(req: { + access: UCloudAccess, + oldSSL: any, + newSSLId: string, + certName: string, + }) { + const access = req.access; + const oldSSLId = req.oldSSL.id; + const newSSLId = req.newSSLId; + + if (!req.oldSSL.relations || req.oldSSL.relations.length === 0) { + this.logger.info(`----------- 证书${req.oldSSL.name} ${req.oldSSL.id} 没有绑定ULB,无需更新绑定`); + } else { + this.logger.info(`----------- 更新ULB证书绑定:${oldSSLId} -> ${newSSLId}`); + await access.invoke({ + "Action": "UpdateSSLBinding", + "Region": this.region, + "ProjectId": access.projectId, + "OldSSLId": oldSSLId, + "NewSSLId": newSSLId, + }); + this.logger.info(`----------- 更新ULB证书绑定成功: ${newSSLId}`); + } + + // 更新证书名称 + this.logger.info(`----------- 更新ULB证书名称:${newSSLId} -> ${req.certName}`); + await access.invoke({ + "Action": "UpdateSSLAttribute", + "Region": this.region, + "ProjectId": access.projectId, + "SSLId": newSSLId, + "SSLName": req.certName, + }); + this.logger.info(`----------- 更新ULB证书名称成功:${req.certName}`); + await this.ctx.utils.sleep(5000); + try { + this.logger.info(`----------- 删除ULB旧证书:${oldSSLId}`); + await access.invoke({ + "Action": "DeleteSSL", + "Region": this.region, + "ProjectId": access.projectId, + "SSLId": oldSSLId, + }); + this.logger.info(`----------- 删除ULB旧证书成功:${oldSSLId}`); + } catch (error) { + this.logger.error(`----------- 删除ULB旧证书失败:${oldSSLId}`, error); + } + + } + + async uploadCertToULB(req: { + access: UCloudAccess, + certName: string, + }) { + const access = req.access; + const certName = req.certName; + const certContent = `${this.cert.crt}\n${this.cert.key}` + const res = await access.invoke({ + "Action": "CreateSSL", + "Region": this.region, + "ProjectId": access.projectId, + "SSLName": certName, + "SSLContent": certContent, + }); + + if (res.RetCode !== 0) { + throw new Error(`创建ULB证书失败: ${res.Message || '未知错误'}`); + } + + return res.SSLId; + } + + + async getAllCert(access: UCloudAccess) { + const certList = [] as any[]; + const pager = { + pageNo: 1, + pageSize: 100, + }; + while (true) { + const { list, total } = await this.getCertPage(access, pager); + certList.push(...list); + if (certList.length >= total) { + break; + } + pager.pageNo++; + } + // this.logger.info(`----------- certList----------:\n`, certList); + return certList; + } + + + async getCertPage(access: UCloudAccess, req: PageSearch = {}) { + const pageNo = req.pageNo ?? 1; + const pageSize = req.pageSize ?? 100; + + const res = await access.invoke({ + "Action": "DescribeSSLV2", + "Region": this.region, + "ProjectId": access.projectId, + "Offset": (pageNo - 1) * pageSize, + "Limit": pageSize + }); + + let list = res.DataSet || []; + const total = res.TotalCount || list.length; + + list = list.map((item: any) => { + const domains = [ + ...item.Domains.split(','), + ...item.DNSNames.split(',') + ] + return { + id: item.SSLId, + name: item.SSLName, + domain: domains, + notAfter: item.NotAfter, + notBefore: item.NotBefore, + createTime: item.CreateTime, + relations: item.Relations, + }; + }) + return { + list: list, + total: total, + }; + } + + async onGetCertList(req: PageSearch = {}) { + const access = await this.getAccess(this.accessId); + + const { list, total } = await this.getCertPage(access, req); + + if (!list || list.length === 0) { + throw new Error("没有找到ULB证书,请先在控制台创建ULB证书"); + } + + const options = list.map((item: any) => { + return { + label: `${item.name}<${item.id}>`, + value: `${item.name}`, + domain: item.domain, + }; + }); + + return { + list: options, + total: total, + }; + } + +} + +new UCloudDeployToULB();