From 63be1c1cbd9b09a3b48f26130c296b1cedcca1ac Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Thu, 7 May 2026 22:54:29 +0800 Subject: [PATCH] =?UTF-8?q?perf(=E8=AF=81=E4=B9=A6=E6=B5=81=E6=B0=B4?= =?UTF-8?q?=E7=BA=BF):=20=E6=B7=BB=E5=8A=A0=E6=89=B9=E9=87=8F=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E8=AF=81=E4=B9=A6=E7=94=B3=E8=AF=B7=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现批量更新证书申请参数功能,包括前端界面和后端处理逻辑 - 添加批量修改证书申请参数的按钮和对话框 - 实现后端批量更新证书申请参数的接口和服务 - 添加相关测试用例验证功能正确性 --- .../src/views/certd/pipeline/api.ts | 8 + .../components/change-cert-apply-options.vue | 106 +++++++++++ .../src/views/certd/pipeline/index.vue | 2 + .../user/pipeline/pipeline-controller.ts | 8 + .../service/pipeline-batch-update.test.ts | 166 ++++++++++++++++++ .../pipeline/service/pipeline-batch-update.ts | 53 ++++++ .../pipeline/service/pipeline-service.ts | 32 ++++ 7 files changed, 375 insertions(+) create mode 100644 packages/ui/certd-client/src/views/certd/pipeline/components/change-cert-apply-options.vue create mode 100644 packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.test.ts create mode 100644 packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.ts diff --git a/packages/ui/certd-client/src/views/certd/pipeline/api.ts b/packages/ui/certd-client/src/views/certd/pipeline/api.ts index 6e07abf6c..81a50d42e 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/api.ts +++ b/packages/ui/certd-client/src/views/certd/pipeline/api.ts @@ -110,6 +110,14 @@ export async function BatchUpdateNotificaiton(pipelineIds: number[], notificatio }); } +export async function BatchUpdateCertApplyOptions(pipelineIds: number[], options: any): Promise { + return await request({ + url: apiPrefix + "/batchUpdateCertApplyOptions", + method: "post", + data: { ids: pipelineIds, options }, + }); +} + export async function BatchUpdateProject(pipelineIds: number[], toProjectId: number): Promise { return await request({ url: apiPrefix + "/batchTransfer", diff --git a/packages/ui/certd-client/src/views/certd/pipeline/components/change-cert-apply-options.vue b/packages/ui/certd-client/src/views/certd/pipeline/components/change-cert-apply-options.vue new file mode 100644 index 000000000..e840f6c98 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/components/change-cert-apply-options.vue @@ -0,0 +1,106 @@ + + + diff --git a/packages/ui/certd-client/src/views/certd/pipeline/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/index.vue index 4346cb3c4..2164204d6 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/index.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/index.vue @@ -40,6 +40,7 @@ + @@ -57,6 +58,7 @@ import { computed, onActivated, onMounted, provide, ref } from "vue"; import { dict, useFs } from "@fast-crud/fast-crud"; import createCrudOptions from "./crud"; import ChangeGroup from "./components/change-group.vue"; +import ChangeCertApplyOptions from "./components/change-cert-apply-options.vue"; import ChangeTrigger from "./components/change-trigger.vue"; import ChangeProject from "./components/change-project.vue"; diff --git a/packages/ui/certd-server/src/controller/user/pipeline/pipeline-controller.ts b/packages/ui/certd-server/src/controller/user/pipeline/pipeline-controller.ts index facb5aa7b..732a8996c 100644 --- a/packages/ui/certd-server/src/controller/user/pipeline/pipeline-controller.ts +++ b/packages/ui/certd-server/src/controller/user/pipeline/pipeline-controller.ts @@ -349,6 +349,14 @@ export class PipelineController extends CrudController { return this.ok({}); } + @Post('/batchUpdateCertApplyOptions', { description: Constants.per.authOnly, summary: "批量更新证书申请任务配置" }) + async batchUpdateCertApplyOptions(@Body('ids') ids: number[], @Body('options') options: any) { + await this.checkPermissionCall(async ({userId,projectId})=>{ + await this.service.batchUpdateCertApplyOptions(ids, options, userId,projectId); + }) + return this.ok({}); + } + @Post('/batchRerun', { description: Constants.per.authOnly, summary: "批量重新运行流水线" }) async batchRerun(@Body('ids') ids: number[], @Body('force') force: boolean) { await this.checkPermissionCall(async ({userId,projectId})=>{ diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.test.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.test.ts new file mode 100644 index 000000000..06c6bb857 --- /dev/null +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.test.ts @@ -0,0 +1,166 @@ +import assert from "node:assert/strict"; +import { updateCertApplyStepInputs } from "./pipeline-batch-update.js"; + +describe("pipeline batch update", () => { + it("updates only cert apply step inputs", () => { + const pipeline: any = { + stages: [ + { + tasks: [ + { + steps: [ + { + type: "CertApply", + input: { + renewDays: 20, + privateKeyType: "rsa_2048", + domains: ["example.com"], + }, + }, + { + type: "DeployToHost", + input: { + renewDays: 1, + privateKeyType: "rsa_1024", + }, + }, + ], + }, + ], + }, + ], + }; + + const count = updateCertApplyStepInputs(pipeline, { + renewDays: 10, + privateKeyType: "ec_256", + }); + + assert.equal(count, 1); + assert.deepEqual(pipeline.stages[0].tasks[0].steps[0].input, { + renewDays: 10, + privateKeyType: "ec_256", + domains: ["example.com"], + }); + assert.deepEqual(pipeline.stages[0].tasks[0].steps[1].input, { + renewDays: 1, + privateKeyType: "rsa_1024", + }); + }); + + it("does not overwrite fields omitted from the patch", () => { + const pipeline: any = { + stages: [ + { + tasks: [ + { + steps: [ + { + type: "CertApply", + input: { + renewDays: 20, + privateKeyType: "ec_256", + }, + }, + ], + }, + ], + }, + ], + }; + + updateCertApplyStepInputs(pipeline, { + renewDays: 15, + }); + + assert.deepEqual(pipeline.stages[0].tasks[0].steps[0].input, { + renewDays: 15, + privateKeyType: "ec_256", + }); + }); + + it("updates uploaded cert pipelines only for fields defined by the plugin", () => { + const pipeline: any = { + stages: [ + { + tasks: [ + { + steps: [ + { + type: "CertApplyUpload", + input: { + renewDays: 20, + }, + }, + { + type: "CertApply", + input: { + renewDays: 20, + }, + }, + ], + }, + ], + }, + ], + }; + + const inputDefines: Record> = { + CertApplyUpload: { + renewDays: {}, + }, + CertApply: { + renewDays: {}, + privateKeyType: {}, + }, + }; + + assert.equal(updateCertApplyStepInputs(pipeline, {}, stepType => inputDefines[stepType]), 0); + assert.equal( + updateCertApplyStepInputs( + pipeline, + { + renewDays: 12, + privateKeyType: "ec_256", + }, + stepType => inputDefines[stepType] + ), + 2 + ); + assert.deepEqual(pipeline.stages[0].tasks[0].steps[0].input, { + renewDays: 12, + }); + assert.deepEqual(pipeline.stages[0].tasks[0].steps[1].input, { + renewDays: 12, + privateKeyType: "ec_256", + }); + }); + + it("skips lego cert apply steps", () => { + const pipeline: any = { + stages: [ + { + tasks: [ + { + steps: [ + { + type: "CertApplyLego", + input: { + renewDays: 20, + privateKeyType: "ec256", + }, + }, + ], + }, + ], + }, + ], + }; + + assert.equal(updateCertApplyStepInputs(pipeline, { renewDays: 12, privateKeyType: "ec_256" }), 0); + assert.deepEqual(pipeline.stages[0].tasks[0].steps[0].input, { + renewDays: 20, + privateKeyType: "ec256", + }); + }); +}); diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.ts new file mode 100644 index 000000000..850a59428 --- /dev/null +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-batch-update.ts @@ -0,0 +1,53 @@ +export type CertApplyStepInputPatch = { + renewDays?: number; + privateKeyType?: string; +}; + +export type GetStepInputDefine = (stepType: string) => Record | undefined; + +function isCertApplyStep(step: any) { + return typeof step?.type === "string" && step.type !== "CertApplyLego" && step.type.startsWith("CertApply"); +} + +function hasPatchValue(patch: CertApplyStepInputPatch, key: keyof CertApplyStepInputPatch) { + return Object.prototype.hasOwnProperty.call(patch, key) && patch[key] !== undefined; +} + +function hasInputDefine(inputDefine: Record | undefined, key: keyof CertApplyStepInputPatch) { + return inputDefine == null || Object.prototype.hasOwnProperty.call(inputDefine, key); +} + +function applyPatchFields(target: Record, patch: CertApplyStepInputPatch, inputDefine: Record | undefined, fields: (keyof CertApplyStepInputPatch)[]) { + let changed = false; + for (const field of fields) { + if (!hasPatchValue(patch, field) || !hasInputDefine(inputDefine, field)) { + continue; + } + target[field] = patch[field]; + changed = true; + } + return changed; +} + +export function updateCertApplyStepInputs(pipeline: any, patch: CertApplyStepInputPatch, getStepInputDefine?: GetStepInputDefine) { + const fields: (keyof CertApplyStepInputPatch)[] = ["renewDays", "privateKeyType"]; + let count = 0; + for (const stage of pipeline?.stages || []) { + for (const task of stage?.tasks || []) { + for (const step of task?.steps || []) { + if (!isCertApplyStep(step)) { + continue; + } + const inputDefine = getStepInputDefine?.(step.type); + if (step.input == null) { + step.input = {}; + } + if (!applyPatchFields(step.input, patch, inputDefine, fields)) { + continue; + } + count++; + } + } + } + return count; +} diff --git a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts index a495a180e..c29a7c898 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/pipeline-service.ts @@ -52,6 +52,7 @@ import { set } from "lodash-es"; import { executorQueue } from "@certd/lib-server"; import parser from "cron-parser"; import { ProjectService } from "../../sys/enterprise/service/project-service.js"; +import { CertApplyStepInputPatch, updateCertApplyStepInputs } from "./pipeline-batch-update.js"; const runningTasks: Map = new Map(); @@ -1211,6 +1212,37 @@ export class PipelineService extends BaseService { } } + async batchUpdateCertApplyOptions(ids: number[], options: CertApplyStepInputPatch, userId: any, projectId?: number) { + if (!isPlus()) { + throw new NeedVIPException("此功能需要升级Certd专业版"); + } + const query: any = {} + if (userId && userId > 0) { + query.userId = userId; + } + if (projectId) { + query.projectId = projectId; + } + const list = await this.find({ + where: { + id: In(ids), + ...query + } + }); + + for (const item of list) { + const pipeline = JSON.parse(item.content); + const updatedCount = updateCertApplyStepInputs(pipeline, options, stepType => { + const pluginDefine: any = pluginRegistry.getDefine(stepType); + return pluginDefine?.input; + }); + if (updatedCount === 0) { + continue; + } + await this.doUpdatePipelineJson(item, pipeline); + } + } + async batchRerun(ids: number[], force: boolean, userId: any, projectId?: number) { if (!isPlus()) { throw new NeedVIPException("此功能需要升级Certd专业版");