From 937e3fac19cd03b8aa91db8ba03fda7fcfbacea2 Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sun, 25 Jun 2023 15:30:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=82=AE=E4=BB=B6=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/pipeline/package.json | 7 +- packages/core/pipeline/src/core/executor.ts | 13 +- packages/core/pipeline/src/service/email.ts | 10 + packages/core/pipeline/src/service/index.ts | 10 +- packages/ui/certd-client/.env | 1 + packages/ui/certd-client/index.html | 2 +- packages/ui/certd-client/src/api/service.ts | 4 +- packages/ui/certd-client/src/api/tools.ts | 11 +- .../src/router/source/modules/certd.ts | 22 ++ .../ui/certd-client/src/utils/util.env.ts | 17 +- .../ui/certd-client/src/utils/util.site.ts | 4 +- .../src/views/certd/pipeline/api.history.ts | 4 +- .../component/notification-form/index.vue | 200 ++++++++++++++++++ .../pi-notification-form-email.vue | 57 +++++ .../views/certd/pipeline/pipeline/index.vue | 99 ++++++++- .../certd/pipeline/pipeline/plugin/index.ts | 8 +- .../src/views/certd/settings/api.email.ts | 12 ++ .../src/views/certd/settings/api.ts | 26 +++ .../views/certd/settings/email-setting.vue | 128 +++++++++++ .../db/migration/v10002__settings.sql | 9 + packages/ui/certd-server/package.json | 4 +- .../certd-server/src/basic/base-controller.ts | 15 +- .../ui/certd-server/src/basic/base-service.ts | 11 +- .../src/middleware/global-exception.ts | 11 +- ...basic-controller.ts => code-controller.ts} | 19 +- .../basic/controller/email-controller.ts | 22 ++ .../modules/basic/service/email-service.ts | 60 ++++++ .../pipeline/auto/auto-register-cron.ts | 2 +- .../pipeline/service/pipeline-service.ts | 5 +- .../system/controller/settings-controller.ts | 29 ++- .../src/modules/system/entity/settings.ts | 4 +- .../system/service/settings-service.ts | 52 ++++- 32 files changed, 790 insertions(+), 88 deletions(-) create mode 100644 packages/core/pipeline/src/service/email.ts create mode 100644 packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/index.vue create mode 100644 packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/pi-notification-form-email.vue create mode 100644 packages/ui/certd-client/src/views/certd/settings/api.email.ts create mode 100644 packages/ui/certd-client/src/views/certd/settings/api.ts create mode 100644 packages/ui/certd-client/src/views/certd/settings/email-setting.vue create mode 100644 packages/ui/certd-server/db/migration/v10002__settings.sql rename packages/ui/certd-server/src/modules/basic/controller/{basic-controller.ts => code-controller.ts} (75%) create mode 100644 packages/ui/certd-server/src/modules/basic/controller/email-controller.ts create mode 100644 packages/ui/certd-server/src/modules/basic/service/email-service.ts diff --git a/packages/core/pipeline/package.json b/packages/core/pipeline/package.json index 356d967c4..798821007 100644 --- a/packages/core/pipeline/package.json +++ b/packages/core/pipeline/package.json @@ -2,9 +2,9 @@ "name": "@certd/pipeline", "private": false, "version": "1.0.6", - "main": "./dist/bundle.js", - "module": "./dist/pipeline.mjs", - "types": "./dist/d/index.d.ts", + "main": "./src", + "module": "./src", + "types": "./src", "publishConfig": { "main": "./dist/bundle.js", "module": "./dist/bundle.mjs", @@ -19,6 +19,7 @@ "dependencies": { "axios": "^1.4.0", "node-forge": "^1.3.1", + "nodemailer": "^6.9.3", "qs": "^6.11.2" }, "devDependencies": { diff --git a/packages/core/pipeline/src/core/executor.ts b/packages/core/pipeline/src/core/executor.ts index 8768014cc..1a629deee 100644 --- a/packages/core/pipeline/src/core/executor.ts +++ b/packages/core/pipeline/src/core/executor.ts @@ -1,4 +1,4 @@ -import { ConcurrencyStrategy, NotificationType, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts"; +import { ConcurrencyStrategy, NotificationWhen, Pipeline, ResultType, Runnable, RunStrategy, Stage, Step, Task } from "../d.ts"; import _ from "lodash"; import { RunHistory, RunnableCollection } from "./run-history"; import { AbstractTaskPlugin, PluginDefine, pluginRegistry } from "../plugin"; @@ -216,13 +216,13 @@ export class Executor { let subject = ""; let content = ""; if (when === "start") { - subject = `【CertD】${this.pipeline.title} 开始执行,buildId:${this.runtime.id}`; - content = `【CertD】${this.pipeline.title} 开始执行,buildId:${this.runtime.id}`; + subject = `【CertD】开始执行,${this.pipeline.title}, buildId:${this.runtime.id}`; + content = subject; } else if (when === "success") { - subject = `【CertD】${this.pipeline.title} 执行成功,buildId:${this.runtime.id}`; - content = `【CertD】${this.pipeline.title} 执行成功,buildId:${this.runtime.id}`; + subject = `【CertD】执行成功,${this.pipeline.title}, buildId:${this.runtime.id}`; + content = subject; } else if (when === "error") { - subject = `【CertD】${this.pipeline.title} 执行失败,buildId:${this.runtime.id}`; + subject = `【CertD】执行失败,${this.pipeline.title}, buildId:${this.runtime.id}`; content = `
${error.message}
`; } else { return; @@ -234,6 +234,7 @@ export class Executor { } if (notification.type === "email") { this.options.emailService?.send({ + userId: this.pipeline.userId, subject, content, receivers: notification.options.receivers, diff --git a/packages/core/pipeline/src/service/email.ts b/packages/core/pipeline/src/service/email.ts new file mode 100644 index 000000000..e918e2ab8 --- /dev/null +++ b/packages/core/pipeline/src/service/email.ts @@ -0,0 +1,10 @@ +export type EmailSend = { + userId: number; + subject: string; + content: string; + receivers: string[]; +}; + +export interface IEmailService { + send(email: EmailSend): Promise; +} diff --git a/packages/core/pipeline/src/service/index.ts b/packages/core/pipeline/src/service/index.ts index 871a185bc..e1722e10c 100644 --- a/packages/core/pipeline/src/service/index.ts +++ b/packages/core/pipeline/src/service/index.ts @@ -1,9 +1 @@ -export type EmailSend = { - subject: string; - content: string; - receivers: string[]; -}; - -export interface IEmailService { - send(email: EmailSend): Promise; -} +export * from "./email"; diff --git a/packages/ui/certd-client/.env b/packages/ui/certd-client/.env index 4933b20fc..c0541f2a7 100644 --- a/packages/ui/certd-client/.env +++ b/packages/ui/certd-client/.env @@ -1,3 +1,4 @@ VITE_APP_API=/api #登录与权限关闭 VITE_APP_PM_ENABLED=true +VITE_APP_TITLE=Certd diff --git a/packages/ui/certd-client/index.html b/packages/ui/certd-client/index.html index aac3b8bfe..38affcf48 100644 --- a/packages/ui/certd-client/index.html +++ b/packages/ui/certd-client/index.html @@ -4,7 +4,7 @@ - antdv-fast-crud + Certd-让你的证书永不过期 diff --git a/packages/ui/certd-client/src/api/service.ts b/packages/ui/certd-client/src/api/service.ts index 41b089038..c5601d8b1 100644 --- a/packages/ui/certd-client/src/api/service.ts +++ b/packages/ui/certd-client/src/api/service.ts @@ -106,8 +106,8 @@ function createService() { * @description 创建请求方法 * @param {Object} service axios 实例 */ -function createRequestFunction(service) { - return function (config) { +function createRequestFunction(service: any) { + return function (config: any) { const configDefault = { headers: { "Content-Type": get(config, "headers.Content-Type", "application/json") diff --git a/packages/ui/certd-client/src/api/tools.ts b/packages/ui/certd-client/src/api/tools.ts index 80fa784bf..905c1bb18 100644 --- a/packages/ui/certd-client/src/api/tools.ts +++ b/packages/ui/certd-client/src/api/tools.ts @@ -48,7 +48,10 @@ export function responseError(data = {}, msg = "请求失败", code = 500) { * @description 记录和显示错误 * @param {Error} error 错误对象 */ -export function errorLog(error) { +export function errorLog(error: any) { + if (error?.response?.data?.message) { + error.message = error?.response?.data?.message; + } // 打印到控制台 console.error(error); // 显示提示 @@ -59,8 +62,6 @@ export function errorLog(error) { * @description 创建一个错误 * @param {String} msg 错误信息 */ -export function errorCreate(msg) { - const error = new Error(msg); - errorLog(error); - throw error; +export function errorCreate(msg: string) { + throw new Error(msg); } diff --git a/packages/ui/certd-client/src/router/source/modules/certd.ts b/packages/ui/certd-client/src/router/source/modules/certd.ts index 2ece20295..8dcf62106 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -35,6 +35,28 @@ export const certdResources = [ meta: { icon: "ion:disc-outline" } + }, + { + title: "设置", + name: "certdSettings", + path: "/certd/settings", + redirect: "/certd/settings/email", + meta: { + icon: "ion:settings-outline", + auth: true + }, + children: [ + { + title: "邮箱设置", + name: "email", + path: "/certd/settings/email", + component: "/certd/settings/email-setting.vue", + meta: { + icon: "ion:mail-outline", + auth: true + } + } + ] } ] } diff --git a/packages/ui/certd-client/src/utils/util.env.ts b/packages/ui/certd-client/src/utils/util.env.ts index 002ffd526..0a33f55a8 100644 --- a/packages/ui/certd-client/src/utils/util.env.ts +++ b/packages/ui/certd-client/src/utils/util.env.ts @@ -1,15 +1,16 @@ +// @ts-ignore import _ from "lodash"; -export function getEnvValue(key) { +export function getEnvValue(key: string) { // @ts-ignore return import.meta.env["VITE_APP_" + key]; } export class EnvConfig { - API; - MODE; - STORAGE; - TITLE; - PM_ENABLED; + API: string; + MODE: string; + STORAGE: string; + TITLE: string; + PM_ENABLED: string; constructor() { this.init(); } @@ -19,6 +20,7 @@ export class EnvConfig { _.forEach(import.meta.env, (value, key) => { if (key.startsWith("VITE_APP")) { key = key.replace("VITE_APP_", ""); + //@ts-ignore this[key] = value; } }); @@ -26,7 +28,8 @@ export class EnvConfig { this.MODE = import.meta.env.MODE; } - get(key, defaultValue) { + get(key: string, defaultValue: string) { + //@ts-ignore return this[key] ?? defaultValue; } isDev() { diff --git a/packages/ui/certd-client/src/utils/util.site.ts b/packages/ui/certd-client/src/utils/util.site.ts index 6305d396c..593e8f7b0 100644 --- a/packages/ui/certd-client/src/utils/util.site.ts +++ b/packages/ui/certd-client/src/utils/util.site.ts @@ -2,9 +2,9 @@ import { env } from "./util.env"; export const site = { /** * @description 更新标题 - * @param {String} title 标题 + * @param titleText */ - title: function (titleText) { + title: function (titleText: string) { const processTitle = env.TITLE || "FsAdmin"; window.document.title = `${processTitle}${titleText ? ` | ${titleText}` : ""}`; } diff --git a/packages/ui/certd-client/src/views/certd/pipeline/api.history.ts b/packages/ui/certd-client/src/views/certd/pipeline/api.history.ts index 9cfc54d63..7e7c2086e 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/api.history.ts +++ b/packages/ui/certd-client/src/views/certd/pipeline/api.history.ts @@ -3,7 +3,7 @@ import { RunHistory } from "/@/views/certd/pipeline/pipeline/type"; const apiPrefix = "/pi/history"; -export async function GetList(query) { +export async function GetList(query: any) { const list = await request({ url: apiPrefix + "/list", method: "post", @@ -18,7 +18,7 @@ export async function GetList(query) { return list; } -export async function GetDetail(query): Promise { +export async function GetDetail(query: any): Promise { const detail = await request({ url: apiPrefix + "/detail", method: "post", diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/index.vue new file mode 100644 index 000000000..5a0146e65 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/index.vue @@ -0,0 +1,200 @@ + + + + + diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/pi-notification-form-email.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/pi-notification-form-email.vue new file mode 100644 index 000000000..6fd0584b7 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/component/notification-form/pi-notification-form-email.vue @@ -0,0 +1,57 @@ + + diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue index 832ff8a74..849bce6f6 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/index.vue @@ -2,7 +2,6 @@ @@ -148,9 +190,10 @@ import { defineComponent, ref, provide, Ref, watch } from "vue"; import { useRouter } from "vue-router"; import PiTaskForm from "./component/task-form/index.vue"; import PiTriggerForm from "./component/trigger-form/index.vue"; +import PiNotificationForm from "./component/notification-form/index.vue"; import PiTaskView from "./component/task-view/index.vue"; import PiStatusShow from "./component/status-show.vue"; -import _ from "lodash-es"; +import _ from "lodash"; import { message, Modal, notification } from "ant-design-vue"; import { pluginManager } from "/@/views/certd/pipeline/pipeline/plugin"; import { nanoid } from "nanoid"; @@ -159,7 +202,7 @@ import PiHistoryTimelineItem from "/@/views/certd/pipeline/pipeline/component/hi export default defineComponent({ name: "PipelineEdit", // eslint-disable-next-line vue/no-unused-components - components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow }, + components: { PiHistoryTimelineItem, PiTaskForm, PiTriggerForm, PiTaskView, PiStatusShow, PiNotificationForm }, props: { pipelineId: { type: [Number, String], @@ -269,7 +312,7 @@ export default defineComponent({ return; } const detail: PipelineDetail = await props.options.getPipelineDetail({ pipelineId: value }); - currentPipeline.value = _.merge({ title: "新管道流程", stages: [], triggers: [] }, detail.pipeline); + currentPipeline.value = _.merge({ title: "新管道流程", stages: [], triggers: [], notifications: [] }, detail.pipeline); pipeline.value = currentPipeline.value; await loadHistoryList(true); }, @@ -369,8 +412,13 @@ export default defineComponent({ pipeline.value.stages.splice(stageIndex, 0, stage); }); }; + + function isLastStage(index: number) { + return !props.editMode && index === pipeline.value.stages.length - 1 && pipeline.value.notifications?.length < 1; + } return { - stageAdd + stageAdd, + isLastStage }; } @@ -406,6 +454,41 @@ export default defineComponent({ }; } + function useNotification() { + const notificationFormRef = ref(); + const notificationAdd = () => { + notificationFormRef.value.notificationAdd((type: string, value: any) => { + if (type === "save") { + if (pipeline.value.notifications == null) { + pipeline.value.notifications = []; + } + pipeline.value.notifications.push(value); + } + }); + }; + const notificationEdit = (notification: any, index: any) => { + if (notificationFormRef.value == null) { + return; + } + if (props.editMode) { + notificationFormRef.value.notificationEdit(notification, (type: string, value: any) => { + if (type === "delete") { + pipeline.value.notifications.splice(index, 1); + } else if (type === "save") { + pipeline.value.notifications[index] = value; + } + }); + } else { + notificationFormRef.value.notificationView(notification, (type: string, value: any) => {}); + } + }; + return { + notificationAdd, + notificationEdit, + notificationFormRef + }; + } + function useActions() { const saveLoading = ref(); const run = async () => { @@ -484,6 +567,7 @@ export default defineComponent({ historyCancel }; } + const useTaskRet = useTask(); const useStageRet = useStage(useTaskRet); @@ -495,7 +579,8 @@ export default defineComponent({ ...useStageRet, ...useTrigger(), ...useActions(), - ...useHistory() + ...useHistory(), + ...useNotification() }; } }); diff --git a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/plugin/index.ts b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/plugin/index.ts index 07efe368c..94c4ea561 100644 --- a/packages/ui/certd-client/src/views/certd/pipeline/pipeline/plugin/index.ts +++ b/packages/ui/certd-client/src/views/certd/pipeline/pipeline/plugin/index.ts @@ -8,9 +8,9 @@ export class PluginManager { * 初始化plugins * @param plugins */ - init(plugins) { + init(plugins: any) { const list = plugins; - const map = {}; + const map: any = {}; for (const plugin of list) { map[plugin.key] = plugin; } @@ -21,7 +21,7 @@ export class PluginManager { return this.map[name]; } - getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }) { + getPreStepOutputOptions({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) { const steps = this.collectionPreStepOutputs({ pipeline, currentStageIndex, @@ -42,7 +42,7 @@ export class PluginManager { return options; } - collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }) { + collectionPreStepOutputs({ pipeline, currentStageIndex, currentStepIndex, currentTask }: any) { const steps: any[] = []; // 开始放step for (let i = 0; i < currentStageIndex; i++) { diff --git a/packages/ui/certd-client/src/views/certd/settings/api.email.ts b/packages/ui/certd-client/src/views/certd/settings/api.email.ts new file mode 100644 index 000000000..ffda61c88 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/settings/api.email.ts @@ -0,0 +1,12 @@ +import { request } from "/@/api/service"; +const apiPrefix = "/basic/email"; + +export async function TestSend(receiver: string) { + await request({ + url: apiPrefix + "/test", + method: "post", + data: { + receiver + } + }); +} diff --git a/packages/ui/certd-client/src/views/certd/settings/api.ts b/packages/ui/certd-client/src/views/certd/settings/api.ts new file mode 100644 index 000000000..f56f52535 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/settings/api.ts @@ -0,0 +1,26 @@ +import { request } from "/@/api/service"; +const apiPrefix = "/sys/settings"; + +export const SettingKeys = { + Email: "email" +}; +export async function SettingsGet(key: string) { + return await request({ + url: apiPrefix + "/get", + method: "post", + params: { + key + } + }); +} + +export async function SettingsSave(key: string, setting: any) { + await request({ + url: apiPrefix + "/save", + method: "post", + data: { + key, + setting: JSON.stringify(setting) + } + }); +} diff --git a/packages/ui/certd-client/src/views/certd/settings/email-setting.vue b/packages/ui/certd-client/src/views/certd/settings/email-setting.vue new file mode 100644 index 000000000..081463b0b --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/settings/email-setting.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/packages/ui/certd-server/db/migration/v10002__settings.sql b/packages/ui/certd-server/db/migration/v10002__settings.sql new file mode 100644 index 000000000..65790a949 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10002__settings.sql @@ -0,0 +1,9 @@ +CREATE TABLE "sys_settings" ( + "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, + "user_id" integer NOT NULL, + "key" varchar(100) NOT NULL, + "title" varchar(100) NOT NULL, + "setting" varchar(1024), + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 3081bfac6..d77565518 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -9,7 +9,7 @@ "online:preview": "NODE_ENV=preview node ./bootstrap.js", "dev": "cross-env NODE_ENV=local midway-bin dev --ts --watchFile='../../core/pipeline/src,../../plugins/'", "dev:preview": "cross-env NODE_ENV=preview midway-bin dev --ts", - "dev:syncdb": "cross-env NODE_ENV=syncdb midway-bin dev --ts --watchFile='../../core/pipeline/src'", + "db": "cross-env NODE_ENV=syncdb midway-bin dev --ts", "test": "midway-bin test --ts", "cov": "midway-bin cov --ts", "lint": "mwts check", @@ -54,6 +54,7 @@ "md5": "^2.3.0", "midway-flyway-js": "^3.0.0", "node-cron": "^3.0.2", + "nodemailer": "^6.9.3", "reflect-metadata": "^0.1.13", "sqlite3": "^5.1.4", "svg-captcha": "^1.4.0", @@ -67,6 +68,7 @@ "@types/jest": "^26.0.24", "@types/koa": "2.13.4", "@types/node": "^14.18.35", + "@types/nodemailer": "^6.4.8", "cross-env": "^6.0.3", "jest": "^26.6.3", "mwts": "^1.3.0", diff --git a/packages/ui/certd-server/src/basic/base-controller.ts b/packages/ui/certd-server/src/basic/base-controller.ts index 38a5d76b2..31c205931 100644 --- a/packages/ui/certd-server/src/basic/base-controller.ts +++ b/packages/ui/certd-server/src/basic/base-controller.ts @@ -10,7 +10,7 @@ export abstract class BaseController { * 成功返回 * @param data 返回数据 */ - ok(data) { + ok(data: any) { const res = { ...Constants.res.success, data: undefined, @@ -22,12 +22,21 @@ export abstract class BaseController { } /** * 失败返回 - * @param message + * @param msg + * @param code */ - fail(msg, code) { + fail(msg: string, code: any) { return { code: code ? code : Constants.res.error.code, msg: msg ? msg : Constants.res.error.code, }; } + + getUserId() { + const userId = this.ctx.user?.id; + if (userId == null) { + throw new Error('Token已过期'); + } + return userId; + } } diff --git a/packages/ui/certd-server/src/basic/base-service.ts b/packages/ui/certd-server/src/basic/base-service.ts index 0976b502c..889aca681 100644 --- a/packages/ui/certd-server/src/basic/base-service.ts +++ b/packages/ui/certd-server/src/basic/base-service.ts @@ -187,14 +187,19 @@ export abstract class BaseService { return await qb.getMany(); } - async checkUserId(id = 0, userId, userKey = 'userId') { + async checkUserId( + id: any = 0, + userId, + userKey = 'userId', + queryIdKey = 'id' + ) { // @ts-ignore const res = await this.getRepository().findOne({ // @ts-ignore select: { [userKey]: true }, + // @ts-ignore where: { - // @ts-ignore - id, + [queryIdKey]: id, }, }); // @ts-ignore diff --git a/packages/ui/certd-server/src/middleware/global-exception.ts b/packages/ui/certd-server/src/middleware/global-exception.ts index 5c842febc..ffb7ff7a5 100644 --- a/packages/ui/certd-server/src/middleware/global-exception.ts +++ b/packages/ui/certd-server/src/middleware/global-exception.ts @@ -1,9 +1,5 @@ import { Provide } from '@midwayjs/decorator'; -import { - IWebMiddleware, - IMidwayKoaContext, - NextFunction, -} from '@midwayjs/koa'; +import { IWebMiddleware, IMidwayKoaContext, NextFunction } from '@midwayjs/koa'; import { logger } from '../utils/logger'; import { Result } from '../basic/result'; @@ -20,7 +16,10 @@ export class GlobalExceptionMiddleware implements IWebMiddleware { } catch (err) { logger.error('请求异常:', url, Date.now() - startTime + 'ms', err); ctx.status = 200; - ctx.body = Result.error(err.code != null ? err.code : 1, err.message); + if (err.code == null || typeof err.code !== 'number') { + err.code = 1; + } + ctx.body = Result.error(err.code, err.message); } }; } diff --git a/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts b/packages/ui/certd-server/src/modules/basic/controller/code-controller.ts similarity index 75% rename from packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts rename to packages/ui/certd-server/src/modules/basic/controller/code-controller.ts index bd2eff18f..5c526ae34 100644 --- a/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts +++ b/packages/ui/certd-server/src/modules/basic/controller/code-controller.ts @@ -1,9 +1,10 @@ -import { Rule,RuleType } from '@midwayjs/validate'; +import { Rule, RuleType } from '@midwayjs/validate'; import { ALL, Inject } from '@midwayjs/decorator'; import { Body } from '@midwayjs/decorator'; import { Controller, Post, Provide } from '@midwayjs/decorator'; import { BaseController } from '../../../basic/base-controller'; import { CodeService } from '../service/code-service'; +import { EmailService } from '../service/email-service'; export class SmsCodeReq { @Rule(RuleType.number().required()) phoneCode: number; @@ -18,22 +19,17 @@ export class SmsCodeReq { imgCode: string; } -// const enumsMap = {}; -// glob('src/modules/**/enums/*.ts', {}, (err, matches) => { -// console.log('matched', matches); -// for (const filePath of matches) { -// const module = require('/' + filePath); -// console.log('modules', module); -// } -// }); - /** */ @Provide() -@Controller('/api/basic') +@Controller('/api/basic/code') export class BasicController extends BaseController { @Inject() codeService: CodeService; + + @Inject() + emailService: EmailService; + @Post('/sendSmsCode') public sendSmsCode( @Body(ALL) @@ -53,4 +49,3 @@ export class BasicController extends BaseController { return this.ok(captcha.data); } } - diff --git a/packages/ui/certd-server/src/modules/basic/controller/email-controller.ts b/packages/ui/certd-server/src/modules/basic/controller/email-controller.ts new file mode 100644 index 000000000..49171d7e8 --- /dev/null +++ b/packages/ui/certd-server/src/modules/basic/controller/email-controller.ts @@ -0,0 +1,22 @@ +import { Body, Controller, Inject, Post, Provide } from '@midwayjs/decorator'; +import { BaseController } from '../../../basic/base-controller'; +import { EmailService } from '../service/email-service'; + +/** + */ +@Provide() +@Controller('/api/basic/email') +export class EmailController extends BaseController { + @Inject() + emailService: EmailService; + + @Post('/test') + public async test( + @Body('receiver') + receiver + ) { + const userId = super.getUserId(); + await this.emailService.test(userId, receiver); + return this.ok({}); + } +} diff --git a/packages/ui/certd-server/src/modules/basic/service/email-service.ts b/packages/ui/certd-server/src/modules/basic/service/email-service.ts new file mode 100644 index 000000000..bcef8e29a --- /dev/null +++ b/packages/ui/certd-server/src/modules/basic/service/email-service.ts @@ -0,0 +1,60 @@ +import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/decorator'; +import type { EmailSend } from '@certd/pipeline'; +import { IEmailService } from '@certd/pipeline'; +import nodemailer from 'nodemailer'; +import { SettingsService } from '../../system/service/settings-service'; +import type SMTPConnection from 'nodemailer/lib/smtp-connection'; + +export type EmailConfig = { + host: string; + port: number; + auth: { + user: string; + pass: string; + }; + secure: boolean; // use TLS + tls: { + // do not fail on invalid certs + rejectUnauthorized: boolean; + }; + sender: string; +} & SMTPConnection.Options; +@Provide() +@Scope(ScopeEnum.Singleton) +export class EmailService implements IEmailService { + @Inject() + settingsService: SettingsService; + + /** + */ + async send(email: EmailSend) { + console.log('sendEmail', email); + + const emailConfigEntity = await this.settingsService.getByKey( + 'email', + email.userId + ); + if (emailConfigEntity == null || !emailConfigEntity.setting) { + throw new Error('email settings 未设置'); + } + const emailConfig = JSON.parse(emailConfigEntity.setting) as EmailConfig; + const transporter = nodemailer.createTransport(emailConfig); + const mailOptions = { + from: emailConfig.sender, + to: email.receivers.join(', '), // list of receivers + subject: email.subject, + text: email.content, + }; + await transporter.sendMail(mailOptions); + console.log('sendEmail success', email); + } + + async test(userId: number, receiver: string) { + await this.send({ + userId, + receivers: [receiver], + subject: '测试邮件,from certd', + content: '测试邮件,from certd', + }); + } +} diff --git a/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts b/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts index 263d67090..b9fdf5223 100644 --- a/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts +++ b/packages/ui/certd-server/src/modules/pipeline/auto/auto-register-cron.ts @@ -1,4 +1,4 @@ -import { Autoload, Init, Inject, Scope, ScopeEnum } from "@midwayjs/decorator"; +import { Autoload, Init, Inject, Scope, ScopeEnum } from '@midwayjs/decorator'; import { PipelineService } from '../service/pipeline-service'; import { logger } from '../../../utils/logger'; 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 eb55002b0..8538cbb1f 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 @@ -14,6 +14,7 @@ import { HistoryEntity } from '../entity/history'; import { HistoryLogEntity } from '../entity/history-log'; import { HistoryLogService } from './history-log-service'; import { logger } from '../../../utils/logger'; +import { EmailService } from '../../basic/service/email-service'; /** * 证书申请 @@ -23,7 +24,8 @@ import { logger } from '../../../utils/logger'; export class PipelineService extends BaseService { @InjectEntityModel(PipelineEntity) repository: Repository; - + @Inject() + emailService: EmailService; @Inject() accessService: AccessService; @Inject() @@ -191,6 +193,7 @@ export class PipelineService extends BaseService { onChanged, accessService: this.accessService, storage: new DbStorage(userId, this.storageService), + emailService: this.emailService, }); try { await executor.init(); diff --git a/packages/ui/certd-server/src/modules/system/controller/settings-controller.ts b/packages/ui/certd-server/src/modules/system/controller/settings-controller.ts index d15901bd1..a70d7511b 100644 --- a/packages/ui/certd-server/src/modules/system/controller/settings-controller.ts +++ b/packages/ui/certd-server/src/modules/system/controller/settings-controller.ts @@ -1,6 +1,15 @@ -import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/decorator"; -import { CrudController } from "../../../basic/crud-controller"; -import { SettingsService } from "../service/settings-service"; +import { + ALL, + Body, + Controller, + Inject, + Post, + Provide, + Query, +} from '@midwayjs/decorator'; +import { CrudController } from '../../../basic/crud-controller'; +import { SettingsService } from '../service/settings-service'; +import { SettingsEntity } from '../entity/settings'; /** */ @@ -50,4 +59,18 @@ export class SettingsController extends CrudController { return super.delete(id); } + @Post('/save') + async save(@Body(ALL) bean: SettingsEntity) { + await this.service.checkUserId(bean.key, this.ctx.user.id, 'userId', 'key'); + bean.userId = this.ctx.user.id; + await this.service.save(bean); + return this.ok({}); + } + + @Post('/get') + async get(@Query('key') key: string) { + await this.service.checkUserId(key, this.ctx.user.id, 'userId', 'key'); + const entity = await this.service.getByKey(key, this.ctx.user.id); + return this.ok(entity); + } } diff --git a/packages/ui/certd-server/src/modules/system/entity/settings.ts b/packages/ui/certd-server/src/modules/system/entity/settings.ts index ff68a002d..70829c94e 100644 --- a/packages/ui/certd-server/src/modules/system/entity/settings.ts +++ b/packages/ui/certd-server/src/modules/system/entity/settings.ts @@ -8,8 +8,10 @@ export class SettingsEntity { id: number; @Column({ name: 'user_id', comment: '用户id' }) userId: number; + @Column({ comment: 'key', length: 100 }) + key: string; @Column({ comment: '名称', length: 100 }) - name: string; + title: string; @Column({ name: 'setting', comment: '设置', length: 1024, nullable: true }) setting: string; diff --git a/packages/ui/certd-server/src/modules/system/service/settings-service.ts b/packages/ui/certd-server/src/modules/system/service/settings-service.ts index 0aedcaed6..b90c3a422 100644 --- a/packages/ui/certd-server/src/modules/system/service/settings-service.ts +++ b/packages/ui/certd-server/src/modules/system/service/settings-service.ts @@ -1,17 +1,15 @@ -import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator"; -import { InjectEntityModel } from "@midwayjs/typeorm"; -import { Repository } from "typeorm"; -import { BaseService } from "../../../basic/base-service"; -import { SettingsEntity } from "../entity/settings"; +import { Provide, Scope, ScopeEnum } from '@midwayjs/decorator'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { BaseService } from '../../../basic/base-service'; +import { SettingsEntity } from '../entity/settings'; /** * 授权 */ @Provide() @Scope(ScopeEnum.Singleton) -export class SettingsService - extends BaseService -{ +export class SettingsService extends BaseService { @InjectEntityModel(SettingsEntity) repository: Repository; @@ -19,8 +17,11 @@ export class SettingsService return this.repository; } - async getById(id: any): Promise { + async getById(id: any): Promise { const entity = await this.info(id); + if (!entity) { + return null; + } // const access = accessRegistry.get(entity.type); const setting = JSON.parse(entity.setting); return { @@ -29,5 +30,38 @@ export class SettingsService }; } + async getByKey(key: string, userId: number): Promise { + if (!key || !userId) { + return null; + } + return await this.repository.findOne({ + where: { + key, + userId, + }, + }); + } + async getSettingByKey(key: string, userId: number): Promise { + const entity = await this.getByKey(key, userId); + if (!entity) { + return null; + } + return JSON.parse(entity.setting); + } + + async save(bean: SettingsEntity) { + const entity = await this.repository.findOne({ + where: { + key: bean.key, + }, + }); + if (entity) { + entity.setting = bean.setting; + await this.repository.save(entity); + } else { + bean.title = bean.key; + await this.repository.save(bean); + } + } }