From a9fffa5180c83da27b35886aa2e858a92a2c5f94 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 17 Mar 2025 00:19:01 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E6=89=8B=E5=8A=A8?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=AF=81=E4=B9=A6=E5=B9=B6=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/pipeline/src/core/executor.ts | 1 + packages/core/pipeline/src/plugin/api.ts | 3 + .../src/plugin/cert-plugin/cert-reader.ts | 11 +- .../src/plugin/cert-plugin/custom/index.ts | 61 ++++++ .../src/views/certd/access/api.ts | 4 + .../src/views/certd/access/common.tsx | 2 +- .../src/views/certd/monitor/cert/crud.tsx | 193 +++++++++++------- .../src/views/certd/monitor/site/crud.tsx | 8 +- .../src/views/certd/monitor/site/index.vue | 2 +- .../src/views/certd/pipeline/crud.tsx | 14 ++ .../user/monitor/cert-info-controller.ts | 12 +- .../monitor/service/cert-info-service.ts | 12 +- .../monitor/service/site-info-service.ts | 5 +- .../pipeline/service/pipeline-service.ts | 7 + 14 files changed, 245 insertions(+), 90 deletions(-) create mode 100644 packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 40837d7d..79193527 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -33,6 +33,7 @@ export type ExecutorOptions = { user: UserInfo; baseURL?: string; sysInfo?: SysInfo; + serviceGetter: (name: string) => any; }; export class Executor { diff --git a/packages/core/pipeline/src/plugin/api.ts b/packages/core/pipeline/src/plugin/api.ts index 460e6ca6..4af68b6d 100644 --- a/packages/core/pipeline/src/plugin/api.ts +++ b/packages/core/pipeline/src/plugin/api.ts @@ -114,6 +114,9 @@ export type TaskInstanceContext = { user: UserInfo; emitter: TaskEmitter; + + //service 容器 + serviceContainer?: Record; }; export abstract class AbstractTaskPlugin implements ITaskPlugin { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts index 9cc63745..08df4d9d 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/cert-reader.ts @@ -23,6 +23,7 @@ export class CertReader { cert: CertInfo; detail: CertificateInfo; + //毫秒时间戳 expires: number; constructor(certInfo: CertInfo) { this.cert = certInfo; @@ -39,9 +40,13 @@ export class CertReader { this.cert.one = this.cert.crt + "\n" + this.cert.key; } - const { detail, expires } = this.getCrtDetail(this.cert.crt); - this.detail = detail; - this.expires = expires.getTime(); + try { + const { detail, expires } = this.getCrtDetail(this.cert.crt); + this.detail = detail; + this.expires = expires.getTime(); + } catch (e) { + throw new Error("证书解析失败:" + e.message); + } } getIc() { diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts new file mode 100644 index 00000000..e8c34094 --- /dev/null +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/custom/index.ts @@ -0,0 +1,61 @@ +import { IsTaskPlugin, pluginGroups, RunStrategy, Step, TaskInput } from "@certd/pipeline"; +import type { CertInfo } from "../acme.js"; +import { CertReader } from "../cert-reader.js"; +import { CertApplyBasePlugin } from "../base.js"; + +export { CertReader }; +export type { CertInfo }; + +@IsTaskPlugin({ + name: "CertUpload", + icon: "ph:certificate", + title: "证书手动上传", + group: pluginGroups.cert.key, + desc: "在证书仓库手动上传后触发部署证书", + default: { + input: { + renewDays: 35, + forceUpdate: false, + }, + strategy: { + runStrategy: RunStrategy.AlwaysRun, + }, + }, +}) +export class CertUploadPlugin extends CertApplyBasePlugin { + @TaskInput({ + title: "证书仓库ID", + component: { + name: "a-cert-select", + vModel: "value", + }, + required: true, + }) + certInfoId!: string; + + async onInstance() { + this.accessService = this.ctx.accessService; + this.logger = this.ctx.logger; + this.userContext = this.ctx.userContext; + this.lastStatus = this.ctx.lastStatus as Step; + } + async onInit(): Promise {} + + async doCertApply() { + const siteInfoService = this.ctx.serviceContainer["CertInfoService"]; + + const certInfo = await siteInfoService.getCertInfo({ + certId: this.certInfoId, + userid: this.pipeline.userId, + }); + + const certReader = new CertReader(certInfo); + if (!certReader.expires && certReader.expires < new Date().getTime()) { + throw new Error("证书已过期,停止部署"); + } + + return certReader; + } +} + +new CertUploadPlugin(); diff --git a/packages/ui/certd-client/src/views/certd/access/api.ts b/packages/ui/certd-client/src/views/certd/access/api.ts index 84d04048..905e8fed 100644 --- a/packages/ui/certd-client/src/views/certd/access/api.ts +++ b/packages/ui/certd-client/src/views/certd/access/api.ts @@ -4,6 +4,10 @@ export function createAccessApi(from = "user") { const apiPrefix = from === "sys" ? "/sys/access" : "/pi/access"; return { async GetList(query: any) { + if (query?.query) { + delete query.query.access; + } + return await request({ url: apiPrefix + "/page", method: "post", diff --git a/packages/ui/certd-client/src/views/certd/access/common.tsx b/packages/ui/certd-client/src/views/certd/access/common.tsx index 350f8367..a725805d 100644 --- a/packages/ui/certd-client/src/views/certd/access/common.tsx +++ b/packages/ui/certd-client/src/views/certd/access/common.tsx @@ -77,7 +77,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { type: "dict-select", dict: AccessTypeDictRef, search: { - show: false + show: true }, column: { width: 200, diff --git a/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx index b206d875..bc9ef231 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/cert/crud.tsx @@ -1,12 +1,10 @@ // @ts-ignore import { useI18n } from "vue-i18n"; -import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, useFormWrapper, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import { certInfoApi } from "./api"; import dayjs from "dayjs"; -import { useUserStore } from "/@/store/modules/user"; import { useRouter } from "vue-router"; import { useModal } from "/@/use/use-modal"; -import * as api from "/@/views/certd/pipeline/api"; import { notification } from "ant-design-vue"; import CertView from "/@/views/certd/pipeline/cert-view.vue"; @@ -54,6 +52,69 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }); }; + async function openUpload(id?: any) { + function createCrudOptions() { + return { + crudOptions: { + request: { + // addRequest: async (form: any) => { + // return await api.Upload(form); + // }, + // editRequest: async (form: any) => { + // return await api.Upload(form); + // } + }, + columns: { + id: { + title: "ID", + type: "number", + form: { + show: false + } + }, + "cert.crt": { + title: "证书", + type: "textarea", + form: { + component: { + rows: 4 + }, + rules: [{ required: true, message: "此项必填" }], + col: { span: 24 } + } + }, + "cert.key": { + title: "私钥", + type: "textarea", + form: { + component: { + rows: 4 + }, + rules: [{ required: true, message: "此项必填" }], + col: { span: 24 } + } + } + }, + form: { + wrapper: { + title: "上传自定义证书" + }, + async doSubmit({ form }: any) { + if (!id) { + delete form.id; + } else { + form.id = id; + } + return await api.Upload(form); + } + } + } + }; + } + const { crudOptions } = createCrudOptions(); + const wrapperRef = await openCrudFormDialog({ crudOptions }); + } + return { crudOptions: { request: { @@ -83,64 +144,19 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat add: { text: "上传自定义证书", type: "primary", - show: false, + show: true, async click() { - function createCrudOptions() { - return { - crudOptions: { - request: { - addRequest: async (form: any) => { - return await api.Upload(form); - }, - editRequest: async (form: any) => { - return await api.Upload(form); - } - }, - columns: { - id: { - title: "ID", - type: "number", - form: { - show: false - } - }, - "cert.crt": { - title: "证书", - type: "textarea", - form: { - component: { - rows: 4 - }, - rules: [{ required: true, message: "此项必填" }] - } - }, - "cert.key": { - title: "私钥", - type: "textarea", - form: { - component: { - rows: 4 - }, - rules: [{ required: true, message: "此项必填" }] - } - } - }, - form: { - wrapper: { - title: "上传自定义证书" - } - } - } - }; - } - const { crudOptions } = createCrudOptions(); - const wrapperRef = await openCrudFormDialog({ crudOptions }); + await openUpload(); } } } }, + tabs: { + name: "fromType", + show: true + }, rowHandle: { - width: 100, + width: 140, fixed: "right", buttons: { view: { show: false }, @@ -155,7 +171,24 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, copy: { show: false }, edit: { show: false }, - remove: { show: false } + upload: { + show: compute(({ row }) => { + return row.fromType === "upload"; + }), + order: 4, + title: "更新证书", + type: "link", + icon: "ph:upload", + async click({ row }) { + await openUpload(row.id); + } + }, + remove: { + order: 10, + show: compute(({ row }) => { + return row.fromType === "upload"; + }) + } } }, columns: { @@ -176,25 +209,37 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat show: false } }, - // domain: { - // title: "主域名", - // search: { - // show: true - // }, - // type: "text", - // form: { - // show: false - // }, - // column: { - // width: 180, - // sorter: true, - // component: { - // name: "fs-values-format" - // } - // } - // }, + fromType: { + title: "来源", + search: { + show: true + }, + type: "dict-select", + dict: dict({ + data: [ + { label: "流水线", value: "pipeline" }, + { label: "手动上传", value: "upload" } + ] + }), + form: { + show: false + }, + column: { + width: 100, + sorter: true, + component: { + color: "auto" + }, + conditionalRender: false + }, + valueBuilder({ value, row, key }) { + if (!value) { + row[key] = "pipeline"; + } + } + }, domains: { - title: "全部域名", + title: "域名", search: { show: true }, diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx index d775f69a..4138f93a 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx @@ -177,13 +177,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, column: { align: "center", - width: 100 + width: 110 } }, certDomains: { title: "证书域名", search: { - show: false + show: true }, type: "text", form: { @@ -294,7 +294,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat show: false }, column: { - width: 100, + width: 110, align: "center", sorter: true } @@ -358,7 +358,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat value: false }, column: { - width: 90, + width: 110, sorter: true } } diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/index.vue b/packages/ui/certd-client/src/views/certd/monitor/site/index.vue index d5eee411..3429bdfe 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/index.vue +++ b/packages/ui/certd-client/src/views/certd/monitor/site/index.vue @@ -4,7 +4,7 @@
站点证书监控
-
每天0点,检查网站证书的过期时间,并发出提醒;
+
每天0点,检查网站证书的过期时间,到期前10天时将发出提醒(使用默认通知渠道);
基础版限制1条,专业版以上无限制,当前
diff --git a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx index 3afb6e31..2823eec2 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/pipeline/crud.tsx @@ -504,6 +504,20 @@ export default function ({ crudExpose, context: { certdFormRef, groupDictRef, se width: 150 } }, + "lastVars.certExpiresTime": { + title: "过期时间", + search: { + show: false + }, + type: "datetime", + form: { + show: false + }, + column: { + sorter: true, + align: "center" + } + }, status: { title: "状态", type: "dict-select", diff --git a/packages/ui/certd-server/src/controller/user/monitor/cert-info-controller.ts b/packages/ui/certd-server/src/controller/user/monitor/cert-info-controller.ts index 9dc5d038..083629f1 100644 --- a/packages/ui/certd-server/src/controller/user/monitor/cert-info-controller.ts +++ b/packages/ui/certd-server/src/controller/user/monitor/cert-info-controller.ts @@ -24,11 +24,17 @@ export class CertInfoController extends CrudController { async page(@Body(ALL) body: any) { body.query = body.query ?? {}; body.query.userId = this.getUserId(); - + const domains = body.query?.domains; + delete body.query.domains; const res = await this.service.page({ query: body.query, page: body.page, sort: body.sort, + buildQuery: (bq) => { + if (domains) { + bq.andWhere('domains like :domains', { domains: `%${domains}%` }); + } + } }); const records = res.records; @@ -88,7 +94,11 @@ export class CertInfoController extends CrudController { @Post('/upload', { summary: Constants.per.authOnly }) async upload(@Body(ALL) body: any) { if (body.id) { + //修改 await this.service.checkUserId(body.id, this.getUserId()); + }else{ + //添加 + body.userId = this.getUserId(); } const res = await this.service.upload(body); diff --git a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts index b83b082a..3ebfc886 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/cert-info-service.ts @@ -10,9 +10,10 @@ export type UploadCertReq = { id?: number; certReader: CertReader; fromType?: string; + userId?: number; }; -@Provide() +@Provide("CertInfoService") @Scope(ScopeEnum.Request, { allowDowngrade: true }) export class CertInfoService extends BaseService { @InjectEntityModel(CertInfoEntity) @@ -147,7 +148,7 @@ export class CertInfoService extends BaseService { private async updateCert(req: UploadCertReq) { const bean = new CertInfoEntity(); - const { id, fromType, certReader } = req; + const { id, fromType,userId, certReader } = req; if (id) { bean.id = id; } else { @@ -162,13 +163,13 @@ export class CertInfoService extends BaseService { bean.domainCount = domains.length; bean.expiresTime = certReader.expires; bean.certProvider = certReader.detail.issuer.commonName; - + bean.userId = userId await this.addOrUpdate(bean); return bean; } - async upload(body: { id?: number; cert: CertInfo }) { - const { id, cert } = body; + async upload(body: { id?: number; userId?:number ;cert: CertInfo }) { + const { id, userId, cert } = body; if (!cert) { throw new CommonException("cert can't be empty"); } @@ -176,6 +177,7 @@ export class CertInfoService extends BaseService { id, certReader: new CertReader(cert), fromType: 'upload', + userId }); return res.id; } diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts index 37d6de4a..68d04c97 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-info-service.ts @@ -182,6 +182,9 @@ export class SiteInfoService extends BaseService { ); } async sendExpiresNotify(site: SiteInfoEntity) { + + const tipDays = 10 + const expires = site.certExpiresTime; const validDays = dayjs(expires).diff(dayjs(), 'day'); const url = await this.notificationService.getBindUrl('#/monitor/site'); @@ -190,7 +193,7 @@ export class SiteInfoService extends BaseService { 证书域名: ${site.certDomains} \n 证书颁发者: ${site.certProvider} \n 过期时间: ${dayjs(site.certExpiresTime).format('YYYY-MM-DD')} \n`; - if (validDays >= 0 && validDays < 10) { + if (validDays >= 0 && validDays < tipDays) { // 发通知 await this.notificationService.send( { 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 94a11e74..d9d86929 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 @@ -475,6 +475,12 @@ export class PipelineService extends BaseService { const siteInfo = await this.sysSettingsService.getSetting(SysSiteInfo); sysInfo.title = siteInfo.title; } + const serviceContainer = { + CertInfoService: this.certInfoService + } + const serviceGetter = (name: string) => { + return serviceContainer[name] + } const executor = new Executor({ user, pipeline, @@ -488,6 +494,7 @@ export class PipelineService extends BaseService { notificationService: notificationGetter, fileRootDir: this.certdConfig.fileRootDir, sysInfo, + serviceGetter }); try { runningTasks.set(historyId, executor);