From b805a2925984144a31575b8aaa622f0c30d41b56 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sat, 23 Nov 2024 23:58:31 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E7=BE=A4=E8=81=8A=E6=9C=BA=E5=99=A8=E4=BA=BA?= =?UTF-8?q?=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/pipeline/src/core/executor.ts | 59 +++++++++++++++---- .../core/pipeline/src/core/run-history.ts | 2 +- packages/core/pipeline/src/dt/pipeline.ts | 5 +- .../core/pipeline/src/notification/api.ts | 28 ++++++--- .../pipeline/src/notification/decorator.ts | 9 +-- .../core/pipeline/src/registry/registry.ts | 4 +- packages/core/pipeline/src/service/index.ts | 1 + packages/core/pipeline/src/service/url.ts | 3 + .../src/plugin/cert-plugin/base.ts | 1 - .../src/views/certd/notification/common.tsx | 10 ++-- .../src/views/certd/notification/crud.tsx | 1 + .../notification-selector/modal/crud.tsx | 2 - .../src/views/certd/pipeline/detail.vue | 2 +- .../modules/pipeline/entity/notification.ts | 2 +- .../pipeline/service/notification-getter.ts | 14 +++++ .../pipeline/service/notification-service.ts | 27 ++++++++- .../pipeline/service/pipeline-service.ts | 12 ++++ .../modules/pipeline/service/url-service.ts | 19 ++++++ .../plugins/plugin-notification/qywx/index.ts | 3 +- 19 files changed, 163 insertions(+), 41 deletions(-) create mode 100644 packages/core/pipeline/src/service/url.ts create mode 100644 packages/ui/certd-server/src/modules/pipeline/service/notification-getter.ts create mode 100644 packages/ui/certd-server/src/modules/pipeline/service/url-service.ts diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 7a64ad85..a18980e6 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -3,13 +3,14 @@ import { RunHistory, RunnableCollection } from "./run-history.js"; import { AbstractTaskPlugin, PluginDefine, pluginRegistry, TaskInstanceContext, UserInfo } from "../plugin/index.js"; import { ContextFactory, IContext } from "./context.js"; import { IStorage } from "./storage.js"; -import { createAxiosService, hashUtils, HttpRequestConfig, ILogger, logger, utils } from "@certd/basic"; +import { createAxiosService, hashUtils, HttpRequestConfig, ILogger, logger, utils } from "@Certd/basic"; import { IAccessService } from "../access/index.js"; import { RegistryItem } from "../registry/index.js"; import { Decorator } from "../decorator/index.js"; -import { ICnameProxyService, IEmailService, IPluginConfigService } from "../service/index.js"; +import { ICnameProxyService, IEmailService, IPluginConfigService, IUrlService } from "../service/index.js"; import { FileStore } from "./file-store.js"; import { cloneDeep, forEach, merge } from "lodash-es"; +import { INotificationService, NotificationBody, NotificationContext, notificationRegistry } from "../notification/index.js"; export type ExecutorOptions = { pipeline: Pipeline; @@ -17,10 +18,13 @@ export type ExecutorOptions = { onChanged: (history: RunHistory) => Promise; accessService: IAccessService; emailService: IEmailService; + notificationService: INotificationService; cnameProxyService: ICnameProxyService; pluginConfigService: IPluginConfigService; + urlService: IUrlService; fileRootDir?: string; user: UserInfo; + baseURL?: string; }; export class Executor { @@ -357,20 +361,22 @@ export class Executor { if (!this.pipeline.notifications) { return; } + const url = await this.options.urlService.getPipelineDetailUrl(this.pipeline.id, this.runtime.id); let subject = ""; let content = ""; + const errorMessage = error?.message; if (when === "start") { - subject = `【CertD】开始执行,【${this.pipeline.id}】${this.pipeline.title}`; - content = `buildId:${this.runtime.id}`; + subject = `【Certd】开始执行,【${this.pipeline.id}】${this.pipeline.title}`; + content = `buildId:${this.runtime.id}\n查看详情:${url}`; } else if (when === "success") { - subject = `【CertD】执行成功,【${this.pipeline.id}】${this.pipeline.title}`; - content = `buildId:${this.runtime.id}`; + subject = `【Certd】执行成功,【${this.pipeline.id}】${this.pipeline.title}`; + content = `buildId:${this.runtime.id}\n查看详情:${url}`; } else if (when === "turnToSuccess") { - subject = `【CertD】执行成功(错误转成功),【${this.pipeline.id}】${this.pipeline.title}`; - content = `buildId:${this.runtime.id}`; + subject = `【Certd】执行成功(失败转成功),【${this.pipeline.id}】${this.pipeline.title}`; + content = `buildId:${this.runtime.id}\n查看详情:${url}`; } else if (when === "error") { - subject = `【CertD】执行失败,【${this.pipeline.id}】${this.pipeline.title}`; - content = `buildId:${this.runtime.id}\nerror:${error.message}`; + subject = `【Certd】执行失败,【${this.pipeline.id}】${this.pipeline.title}`; + content = `buildId:${this.runtime.id}\n查看详情:${url}\nerror:${error.message}`; } else { return; } @@ -390,6 +396,39 @@ export class Executor { } catch (e) { logger.error("send email error", e); } + } else { + try { + //构建notification插件,发送通知 + const notifyConfig = await this.options.notificationService.getById(notification.notificationId); + const notificationPlugin = notificationRegistry.get(notifyConfig.type); + const notificationCls: any = notificationPlugin.target; + const notificationSender = new notificationCls(); + const notificationCtx: NotificationContext = { + http: utils.http, + logger, + utils, + emailService: this.options.emailService, + }; + //设置参数 + merge(notificationSender, notifyConfig.setting); + notificationSender.setCtx(notificationCtx); + await notificationSender.onInstance(); + const body: NotificationBody = { + title: subject, + content, + userId: this.pipeline.userId, + pipeline: this.pipeline, + result: this.lastRuntime.pipeline.status, + pipelineId: this.pipeline.id, + historyId: this.runtime.id, + errorMessage, + url, + }; + //发送通知 + await notificationSender.send(body); + } catch (e) { + logger.error("send notification error", e); + } } } } diff --git a/packages/core/pipeline/src/core/run-history.ts b/packages/core/pipeline/src/core/run-history.ts index 977aa2c3..1c271186 100644 --- a/packages/core/pipeline/src/core/run-history.ts +++ b/packages/core/pipeline/src/core/run-history.ts @@ -18,7 +18,7 @@ export function NewRunHistory(obj: any) { return history; } export class RunHistory { - id!: string; + id!: any; pipeline!: Pipeline; logs: { [runnableId: string]: string[]; diff --git a/packages/core/pipeline/src/dt/pipeline.ts b/packages/core/pipeline/src/dt/pipeline.ts index 691c14b6..fd785ddb 100644 --- a/packages/core/pipeline/src/dt/pipeline.ts +++ b/packages/core/pipeline/src/dt/pipeline.ts @@ -62,7 +62,7 @@ export type FileItem = { path: string; }; export type Runnable = { - id: string; + id: any; title: string; strategy?: RunnableStrategy; runnableType?: string; // pipeline, stage, task , step @@ -83,6 +83,9 @@ export type Notification = { type: NotificationType; when: NotificationWhen[]; options: EmailOptions; + notificationId: number; + title: string; + subType: string; }; export type Pipeline = Runnable & { diff --git a/packages/core/pipeline/src/notification/api.ts b/packages/core/pipeline/src/notification/api.ts index edef364a..8488a690 100644 --- a/packages/core/pipeline/src/notification/api.ts +++ b/packages/core/pipeline/src/notification/api.ts @@ -1,19 +1,20 @@ import { PluginRequestHandleReq } from "../plugin"; import { Registrable } from "../registry/index.js"; -import { FormItemProps } from "../dt/index.js"; +import { FormItemProps, HistoryResult, Pipeline } from "../dt/index.js"; import { HttpClient, ILogger, utils } from "@certd/basic"; import * as _ from "lodash-es"; -import { IEmailService } from "../service"; +import { IEmailService } from "../service/index.js"; export type NotificationBody = { userId: number; title: string; content: string; + pipeline: Pipeline; pipelineId: number; + result?: HistoryResult; historyId: number; - url: string; - extra?: any; - options?: any; + errorMessage?: string; + url?: string; }; export type NotificationRequestHandleReqInput = { @@ -34,11 +35,21 @@ export type NotificationDefine = Registrable & { [key: string]: NotificationInputDefine; }; }; + +export type NotificationInstanceConfig = { + id: number; + type: string; + userId: number; + setting: { + [key: string]: any; + }; +}; + export interface INotificationService { - send(body: NotificationBody): Promise; + getById(id: number): Promise; } -export interface INotification extends INotificationService { +export interface INotification { ctx: NotificationContext; [key: string]: any; } @@ -55,6 +66,9 @@ export abstract class BaseNotification implements INotification { http!: HttpClient; logger!: ILogger; abstract send(body: NotificationBody): Promise; + + // eslint-disable-next-line @typescript-eslint/no-empty-function + async onInstance() {} setCtx(ctx: NotificationContext) { this.ctx = ctx; this.http = ctx.http; diff --git a/packages/core/pipeline/src/notification/decorator.ts b/packages/core/pipeline/src/notification/decorator.ts index 42cbaf39..ac80b14c 100644 --- a/packages/core/pipeline/src/notification/decorator.ts +++ b/packages/core/pipeline/src/notification/decorator.ts @@ -2,7 +2,6 @@ import { Decorator } from "../decorator/index.js"; import * as _ from "lodash-es"; import { notificationRegistry } from "./registry.js"; -import { http, logger, utils } from "@certd/basic"; import { NotificationContext, NotificationDefine, NotificationInputDefine } from "./api.js"; // 提供一个唯一 key @@ -39,7 +38,7 @@ export function NotificationInput(input?: NotificationInputDefine): PropertyDeco }; } -export function newNotification(type: string, input: any, ctx?: NotificationContext) { +export function newNotification(type: string, input: any, ctx: NotificationContext) { const register = notificationRegistry.get(type); if (register == null) { throw new Error(`notification ${type} not found`); @@ -50,11 +49,7 @@ export function newNotification(type: string, input: any, ctx?: NotificationCont access[key] = input[key]; } if (!ctx) { - ctx = { - http, - logger, - utils, - }; + throw new Error("ctx is required"); } access.ctx = ctx; return access; diff --git a/packages/core/pipeline/src/registry/registry.ts b/packages/core/pipeline/src/registry/registry.ts index 2be4d839..199e9415 100644 --- a/packages/core/pipeline/src/registry/registry.ts +++ b/packages/core/pipeline/src/registry/registry.ts @@ -19,7 +19,7 @@ export type OnRegisterContext = { value: RegistryItem; }; export type OnRegister = (ctx: OnRegisterContext) => void; -export class Registry { +export class Registry { type = ""; storage: { [key: string]: RegistryItem; @@ -89,7 +89,7 @@ export class Registry { } } -export function createRegistry(type: string, onRegister?: OnRegister) { +export function createRegistry(type: string, onRegister?: OnRegister): Registry { const pipelineregistrycacheKey = "PIPELINE_REGISTRY_CACHE"; // @ts-ignore let cached: any = global[pipelineregistrycacheKey]; diff --git a/packages/core/pipeline/src/service/index.ts b/packages/core/pipeline/src/service/index.ts index ae0b4281..71a48726 100644 --- a/packages/core/pipeline/src/service/index.ts +++ b/packages/core/pipeline/src/service/index.ts @@ -1,3 +1,4 @@ export * from "./email.js"; export * from "./cname.js"; export * from "./config.js"; +export * from "./url.js"; diff --git a/packages/core/pipeline/src/service/url.ts b/packages/core/pipeline/src/service/url.ts new file mode 100644 index 00000000..75765beb --- /dev/null +++ b/packages/core/pipeline/src/service/url.ts @@ -0,0 +1,3 @@ +export interface IUrlService { + getPipelineDetailUrl(pipelineId: number, historyId: number): Promise; +} diff --git a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts index 8383cd9f..f2290726 100644 --- a/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts +++ b/packages/plugins/plugin-cert/src/plugin/cert-plugin/base.ts @@ -6,7 +6,6 @@ import JSZip from "jszip"; import { CertConverter } from "./convert.js"; import fs from "fs"; import { pick } from "lodash-es"; -import { HttpClient } from "@certd/basic"; export { CertReader }; export type { CertInfo }; diff --git a/packages/ui/certd-client/src/views/certd/notification/common.tsx b/packages/ui/certd-client/src/views/certd/notification/common.tsx index 296bf791..5ba29e8f 100644 --- a/packages/ui/certd-client/src/views/certd/notification/common.tsx +++ b/packages/ui/certd-client/src/views/certd/notification/common.tsx @@ -41,7 +41,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { } //字段配置赋值 columnsRef.value[key] = column; - console.log("form", columnsRef.value); + console.log("form", columnsRef.value, form); }); } @@ -74,7 +74,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { } }, type: { - title: "类型", + title: "通知类型", type: "dict-select", dict: notificationTypeDictRef, search: { @@ -100,7 +100,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { return option.value.toLowerCase().indexOf(input) >= 0 || option.label.toLowerCase().indexOf(input) >= 0; } }, - rules: [{ required: true, message: "请选择类型" }], + rules: [{ required: true, message: "请选择通知类型" }], valueChange: { immediate: true, async handle({ value, mode, form, immediate }) { @@ -110,6 +110,7 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { const define = await api.GetProviderDefine(value); currentDefine.value = define; console.log("define", define); + debugger; if (!immediate) { form.body = {}; } @@ -123,9 +124,6 @@ export function getCommonColumnDefine(crudExpose: any, typeRef: any, api: any) { } return define.desc; }) - }, - addForm: { - value: typeRef } } as ColumnCompositionProps, setting: { diff --git a/packages/ui/certd-client/src/views/certd/notification/crud.tsx b/packages/ui/certd-client/src/views/certd/notification/crud.tsx index f3ca01a1..a252a769 100644 --- a/packages/ui/certd-client/src/views/certd/notification/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/notification/crud.tsx @@ -23,6 +23,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const addRequest = async (req: AddReq) => { const { form } = req; + debugger; const res = await api.AddObj(form); return res; }; diff --git a/packages/ui/certd-client/src/views/certd/notification/notification-selector/modal/crud.tsx b/packages/ui/certd-client/src/views/certd/notification/notification-selector/modal/crud.tsx index 3c62890c..fa874d66 100644 --- a/packages/ui/certd-client/src/views/certd/notification/notification-selector/modal/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/notification/notification-selector/modal/crud.tsx @@ -13,7 +13,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const editRequest = async (req: EditReq) => { const { form, row } = req; form.id = row.id; - form.type = props.type; const res = await context.api.UpdateObj(form); lastResRef.value = res; return res; @@ -25,7 +24,6 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const addRequest = async (req: AddReq) => { const { form } = req; - form.type = props.type; const res = await context.api.AddObj(form); lastResRef.value = res; return res; diff --git a/packages/ui/certd-client/src/views/certd/pipeline/detail.vue b/packages/ui/certd-client/src/views/certd/pipeline/detail.vue index e9b47c17..273f683b 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/detail.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/detail.vue @@ -68,7 +68,7 @@ const pipelineOptions: PipelineOptions = { const pipelineOptionsRef: Ref = ref(pipelineOptions); const editMode = ref(false); -if (route.query.editMode !== "false") { +if (route.query.editMode === "true") { editMode.value = true; } diff --git a/packages/ui/certd-server/src/modules/pipeline/entity/notification.ts b/packages/ui/certd-server/src/modules/pipeline/entity/notification.ts index c9bc5f91..03f00d84 100644 --- a/packages/ui/certd-server/src/modules/pipeline/entity/notification.ts +++ b/packages/ui/certd-server/src/modules/pipeline/entity/notification.ts @@ -6,7 +6,7 @@ export class NotificationEntity { id: number; @Column({ name: 'user_id', comment: 'UserId' }) - userId: string; + userId: number; @Column({ name: 'type', comment: '通知类型' }) type: string; diff --git a/packages/ui/certd-server/src/modules/pipeline/service/notification-getter.ts b/packages/ui/certd-server/src/modules/pipeline/service/notification-getter.ts new file mode 100644 index 00000000..2fbb03b3 --- /dev/null +++ b/packages/ui/certd-server/src/modules/pipeline/service/notification-getter.ts @@ -0,0 +1,14 @@ +import { INotificationService } from '@certd/pipeline'; + +export class NotificationGetter implements INotificationService { + userId: number; + getter: (id: any, userId?: number) => Promise; + constructor(userId: number, getter: (id: any, userId: number) => Promise) { + this.userId = userId; + this.getter = getter; + } + + async getById(id: any) { + return await this.getter(id, this.userId); + } +} diff --git a/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts index 8d294249..da17735c 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/notification-service.ts @@ -3,7 +3,7 @@ import { BaseService, ValidateException } from '@certd/lib-server'; import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { NotificationEntity } from '../entity/notification.js'; -import { notificationRegistry } from '@certd/pipeline'; +import { NotificationInstanceConfig, notificationRegistry } from '@certd/pipeline'; @Provide() @Scope(ScopeEnum.Singleton) @@ -35,4 +35,29 @@ export class NotificationService extends BaseService { getDefineByType(type: string) { return notificationRegistry.getDefine(type); } + + async getById(id: number, userId: number): Promise { + if (!id) { + throw new ValidateException('id不能为空'); + } + if (!userId) { + throw new ValidateException('userId不能为空'); + } + const res = await this.repository.findOne({ + where: { + id, + userId, + }, + }); + if (!res) { + throw new ValidateException('通知配置不存在'); + } + const setting = JSON.parse(res.setting); + return { + id: res.id, + type: res.type, + userId: res.userId, + setting, + }; + } } 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 7d73a8c0..587744a6 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 @@ -23,6 +23,9 @@ import dayjs from 'dayjs'; import { DbAdapter } from '../../db/index.js'; import { isPlus } from '@certd/plus-core'; import { logger } from '@certd/basic'; +import { UrlService } from './url-service.js'; +import { NotificationService } from './notification-service.js'; +import { NotificationGetter } from './notification-getter.js'; const runningTasks: Map = new Map(); const freeCount = 10; @@ -63,6 +66,12 @@ export class PipelineService extends BaseService { @Config('certd') private certdConfig: any; + @Inject() + urlService: UrlService; + + @Inject() + notificationService: NotificationService; + @Inject() dbAdapter: DbAdapter; @@ -384,6 +393,7 @@ export class PipelineService extends BaseService { }; const accessGetter = new AccessGetter(userId, this.accessService.getById.bind(this.accessService)); const cnameProxyService = new CnameProxyService(userId, this.cnameRecordService.getWithAccessByDomain.bind(this.cnameRecordService)); + const notificationGetter = new NotificationGetter(userId, this.notificationService.getById.bind(this.notificationService)); const executor = new Executor({ user, pipeline, @@ -393,6 +403,8 @@ export class PipelineService extends BaseService { pluginConfigService: this.pluginConfigGetter, storage: new DbStorage(userId, this.storageService), emailService: this.emailService, + urlService: this.urlService, + notificationService: notificationGetter, fileRootDir: this.certdConfig.fileRootDir, }); try { diff --git a/packages/ui/certd-server/src/modules/pipeline/service/url-service.ts b/packages/ui/certd-server/src/modules/pipeline/service/url-service.ts new file mode 100644 index 00000000..0d9f9795 --- /dev/null +++ b/packages/ui/certd-server/src/modules/pipeline/service/url-service.ts @@ -0,0 +1,19 @@ +import { IUrlService } from '@certd/pipeline'; +import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; +import { SysInstallInfo, SysSettingsService } from '@certd/lib-server'; + +@Provide() +@Scope(ScopeEnum.Singleton) +export class UrlService implements IUrlService { + @Inject() + sysSettingsService: SysSettingsService; + + async getPipelineDetailUrl(pipelineId: number, historyId: number): Promise { + const installInfo = await this.sysSettingsService.getSetting(SysInstallInfo); + let baseUrl = 'http://127.0.0.1:7001'; + if (installInfo.bindUrl) { + baseUrl = installInfo.bindUrl; + } + return `${baseUrl}#/certd/pipeline/detail?id=${pipelineId}&historyId=${historyId}`; + } +} diff --git a/packages/ui/certd-server/src/plugins/plugin-notification/qywx/index.ts b/packages/ui/certd-server/src/plugins/plugin-notification/qywx/index.ts index 1c8ef780..6ee26d9e 100644 --- a/packages/ui/certd-server/src/plugins/plugin-notification/qywx/index.ts +++ b/packages/ui/certd-server/src/plugins/plugin-notification/qywx/index.ts @@ -42,9 +42,10 @@ export class QywxNotification extends BaseNotification { await this.http.request({ url: this.webhook, + method: 'POST', data: { msgtype: 'markdown', - text: { + markdown: { content: `# ${body.title}\n\n${body.content}\n[查看详情](${body.url})`, mentioned_list: this.mentionedList, },