From c6628e7311d6c43c2a784581fb25ec37b29c168d Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Sun, 5 Apr 2026 23:49:25 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E6=94=AF=E6=8C=81=E5=9F=9F=E5=90=8D?= =?UTF-8?q?=E5=88=B0=E6=9C=9F=E6=97=B6=E9=97=B4=E7=9B=91=E6=8E=A7=E9=80=9A?= =?UTF-8?q?=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/pipeline/src/notification/api.ts | 2 + .../src/locales/langs/en-US/certd.ts | 1 + .../src/locales/langs/en-US/monitor.ts | 12 ++ .../src/locales/langs/zh-CN/certd.ts | 1 + .../src/locales/langs/zh-CN/monitor.ts | 11 ++ .../src/router/source/modules/certd.ts | 11 ++ .../src/views/certd/cert/domain/crud.tsx | 12 ++ .../views/certd/cert/domain/setting/api.ts | 27 +++ .../views/certd/cert/domain/setting/index.vue | 100 +++++++++++ .../db/migration/v10042__job_history.sql | 22 +++ packages/ui/certd-server/package.json | 1 + .../controller/user/cert/domain-controller.ts | 46 +++-- .../user/monitor/job-history-controller.ts | 62 +++++++ .../src/modules/auto/auto-c-register-cron.ts | 5 +- .../modules/cert/service/domain-service.ts | 166 ++++++++++++++++-- .../src/modules/cron/configuration.ts | 14 +- .../src/modules/mine/service/models.ts | 12 ++ .../src/modules/monitor/entity/job-history.ts | 52 ++++++ .../monitor/service/job-history-service.ts | 23 +++ .../monitor/service/site-info-service.ts | 26 +++ .../service/getter/notification-getter.ts | 4 + pnpm-lock.yaml | 104 ++++++----- 22 files changed, 637 insertions(+), 77 deletions(-) create mode 100644 packages/ui/certd-client/src/views/certd/cert/domain/setting/api.ts create mode 100644 packages/ui/certd-client/src/views/certd/cert/domain/setting/index.vue create mode 100644 packages/ui/certd-server/db/migration/v10042__job_history.sql create mode 100644 packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts create mode 100644 packages/ui/certd-server/src/modules/monitor/entity/job-history.ts create mode 100644 packages/ui/certd-server/src/modules/monitor/service/job-history-service.ts diff --git a/packages/core/pipeline/src/notification/api.ts b/packages/core/pipeline/src/notification/api.ts index e5e5acf57..155128ef7 100644 --- a/packages/core/pipeline/src/notification/api.ts +++ b/packages/core/pipeline/src/notification/api.ts @@ -7,6 +7,7 @@ import { IEmailService } from "../service/index.js"; export type NotificationBody = { userId?: number; + projectId?: number; title: string; content: string; pipeline?: Pipeline; @@ -20,6 +21,7 @@ export type NotificationBody = { pipelineResult?: string; pipelineTitle?: string; errors?: string; + [key: string]: any; // 其他templateData }; export type NotificationRequestHandleReqInput = { diff --git a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts index 61ea3973c..e6dbf8e83 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/certd.ts @@ -221,6 +221,7 @@ export default { projectJoin: "Join Project", currentProject: "Current Project", projectMemberManager: "Project Member", + domainMonitorSetting: "Domain Monitor Settings", }, certificateRepo: { title: "Certificate Repository", diff --git a/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts b/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts index 505d5f891..1fad8644c 100644 --- a/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts +++ b/packages/ui/certd-client/src/locales/langs/en-US/monitor.ts @@ -64,6 +64,18 @@ export default { dnsServerHelper: "Use a custom domain name resolution server, such as: 1.1.1.1 , support multiple", certValidDays: "Certificate Valid Days", certValidDaysHelper: "Number of days before expiration to send a notification", + + domain: { + monitorSettings: "Domain Monitor Settings", + enabled: "Enable Domain Monitor", + enabledHelper: "Enable to monitor all domain registration expiration time", + notificationChannel: "Notification Channel", + setNotificationChannel: "Set the notification channel", + willExpireDays: "Will Expire Days", + willExpireDaysHelper: "Number of days before expiration to send a notification", + monitorCronSetting: "Monitoring Schedule", + cronTrigger: "Scheduled trigger for monitoring", + }, }, cert: { expired: "Expired", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts index 32376652e..5364ea306 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/certd.ts @@ -226,6 +226,7 @@ export default { projectJoin: "加入项目", currentProject: "当前项目", projectMemberManager: "项目成员管理", + domainMonitorSetting: "域名监控设置", }, certificateRepo: { title: "证书仓库", diff --git a/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts b/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts index ddd609166..9d0769458 100644 --- a/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts +++ b/packages/ui/certd-client/src/locales/langs/zh-CN/monitor.ts @@ -68,6 +68,17 @@ export default { dnsServerHelper: "使用自定义的域名解析服务器,如:1.1.1.1 , 支持多个", certValidDays: "证书到期前天数", certValidDaysHelper: "证书到期前多少天发送通知", + domain: { + monitorSettings: "域名监控设置", + enabled: "启用域名监控", + enabledHelper: "启用后,将监控域名管理中的域名的过期时间", + notificationChannel: "通知渠道", + setNotificationChannel: "设置通知渠道", + willExpireDays: "到期前天数", + willExpireDaysHelper: "域名有效期到期前多少天发送通知", + monitorCronSetting: "监控定时设置", + cronTrigger: "定时触发监控", + }, }, cert: { expired: "已过期", 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 df790176a..fce00a0c7 100644 --- a/packages/ui/certd-client/src/router/source/modules/certd.ts +++ b/packages/ui/certd-client/src/router/source/modules/certd.ts @@ -241,6 +241,17 @@ export const certdResources = [ isMenu: true, }, }, + { + title: "certd.sysResources.domainMonitorSetting", + name: "DomainMonitorSetting", + path: "/certd/cert/domain/setting", + component: "/certd/cert/domain/setting/index.vue", + meta: { + icon: "ion:videocam-outline", + auth: true, + isMenu: true, + }, + }, { title: "certd.userSecurity", name: "UserSecurity", diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx b/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx index 1757c8dca..c284f2b47 100644 --- a/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/cert/domain/crud.tsx @@ -128,6 +128,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, 2000); }, }, + monitorSettingSave: { + show: hasActionPermission("write"), + title: "域名过期监控设置", + type: "primary", + icon: "ion:save-outline", + text: "域名过期监控设置", + click: async () => { + router.push({ + path: "/certd/cert/domain/setting", + }); + }, + }, }, }, columns: { diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/setting/api.ts b/packages/ui/certd-client/src/views/certd/cert/domain/setting/api.ts new file mode 100644 index 000000000..f53d924d5 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/cert/domain/setting/api.ts @@ -0,0 +1,27 @@ +// @ts-ignore +import { request } from "/src/api/service"; +const apiPrefix = "/cert/domain/setting"; +export type UserDomainMonitorSetting = { + enabled?: boolean; + notificationId?: number; + cron?: string; + willExpireDays?: number; +}; + +export async function DomainMonitorSettingsGet() { + const res = await request({ + url: apiPrefix + "/get", + method: "post", + }); + if (!res) { + return {}; + } + return res as UserDomainMonitorSetting; +} +export async function DomainMonitorSettingsSave(data: UserDomainMonitorSetting) { + await request({ + url: apiPrefix + "/save", + method: "post", + data: data, + }); +} diff --git a/packages/ui/certd-client/src/views/certd/cert/domain/setting/index.vue b/packages/ui/certd-client/src/views/certd/cert/domain/setting/index.vue new file mode 100644 index 000000000..fc798bde4 --- /dev/null +++ b/packages/ui/certd-client/src/views/certd/cert/domain/setting/index.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/packages/ui/certd-server/db/migration/v10042__job_history.sql b/packages/ui/certd-server/db/migration/v10042__job_history.sql new file mode 100644 index 000000000..df683b4c5 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v10042__job_history.sql @@ -0,0 +1,22 @@ + +CREATE TABLE "cd_job_history" +( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "user_id" integer NOT NULL, + "project_id" integer NOT NULL, + "type" varchar(100) NOT NULL, + "title" varchar(512) NOT NULL, + "related_id" varchar(100), + "result" varchar(100) NOT NULL, + "content" text , + "start_at" integer NOT NULL, + "end_at" integer , + "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), + "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP) +); + + + +CREATE INDEX "index_job_history_user_id" ON "cd_job_history" ("user_id"); +CREATE INDEX "index_job_history_project_id" ON "cd_job_history" ("project_id"); +CREATE INDEX "index_job_history_type" ON "cd_job_history" ("type"); diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index 53c96cb12..39ef7a0c5 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -141,6 +141,7 @@ "typeorm": "^0.3.20", "uuid": "^10.0.0", "wechatpay-node-v3": "^2.2.1", + "whoiser": "2.0.0-beta.10", "xml2js": "^0.6.2" }, "devDependencies": { diff --git a/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts b/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts index 10bda6fe2..d9640fc2a 100644 --- a/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts +++ b/packages/ui/certd-server/src/controller/user/cert/domain-controller.ts @@ -20,7 +20,7 @@ export class DomainController extends CrudController { @Post('/page', { description: Constants.per.authOnly, summary: "查询域名分页列表" }) async page(@Body(ALL) body: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); body.query = body.query ?? {}; body.query.projectId = projectId; body.query.userId = userId; @@ -44,7 +44,7 @@ export class DomainController extends CrudController { @Post('/list', { description: Constants.per.authOnly, summary: "查询域名列表" }) async list(@Body(ALL) body: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); body.query = body.query ?? {}; body.query.projectId = projectId; body.query.userId = userId; @@ -54,7 +54,7 @@ export class DomainController extends CrudController { @Post('/add', { description: Constants.per.authOnly, summary: "添加域名" }) async add(@Body(ALL) bean: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); bean.projectId = projectId; bean.userId = userId; return super.add(bean); @@ -82,7 +82,7 @@ export class DomainController extends CrudController { @Post('/deleteByIds', { description: Constants.per.authOnly, summary: "批量删除域名" }) async deleteByIds(@Body(ALL) body: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); await this.service.delete(body.ids, { userId: userId, projectId: projectId, @@ -94,10 +94,10 @@ export class DomainController extends CrudController { @Post('/import/start', { description: Constants.per.authOnly, summary: "开始域名导入任务" }) async importStart(@Body(ALL) body: any) { checkPlus(); - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); const { key } = body; const req = { - key, + key, userId: userId, projectId: projectId, } @@ -107,7 +107,7 @@ export class DomainController extends CrudController { @Post('/import/status', { description: Constants.per.authOnly, summary: "查询域名导入任务状态" }) async importStatus() { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); const req = { userId: userId, projectId: projectId, @@ -119,7 +119,7 @@ export class DomainController extends CrudController { @Post('/import/delete', { description: Constants.per.authOnly, summary: "删除域名导入任务" }) async importDelete(@Body(ALL) body: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); const { key } = body; const req = { userId: userId, @@ -133,12 +133,12 @@ export class DomainController extends CrudController { @Post('/import/save', { description: Constants.per.authOnly, summary: "保存域名导入任务" }) async importSave(@Body(ALL) body: any) { checkPlus(); - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); const { dnsProviderType, dnsProviderAccessId, key } = body; const req = { userId: userId, projectId: projectId, - dnsProviderType, dnsProviderAccessId, key + dnsProviderType, dnsProviderAccessId, key } const item = await this.service.saveDomainImportTask(req); return this.ok(item); @@ -147,7 +147,7 @@ export class DomainController extends CrudController { @Post('/sync/expiration/start', { description: Constants.per.authOnly, summary: "开始同步域名过期时间任务" }) async syncExpirationStart(@Body(ALL) body: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); await this.service.startSyncExpirationTask({ userId: userId, projectId: projectId, @@ -156,7 +156,7 @@ export class DomainController extends CrudController { } @Post('/sync/expiration/status', { description: Constants.per.authOnly, summary: "查询同步域名过期时间任务状态" }) async syncExpirationStatus(@Body(ALL) body: any) { - const {projectId,userId} = await this.getProjectUserIdRead(); + const { projectId, userId } = await this.getProjectUserIdRead(); const status = await this.service.getSyncExpirationTaskStatus({ userId: userId, projectId: projectId, @@ -165,4 +165,26 @@ export class DomainController extends CrudController { } + + @Post('/setting/save', { description: Constants.per.authOnly, summary: "保存域名监控设置" }) + async settingSave(@Body(ALL) body: any) { + const { projectId, userId } = await this.getProjectUserIdWrite(); + await this.service.monitorSettingSave({ + userId: userId, + projectId: projectId, + setting: {...body}, + }) + return this.ok(); + } + + @Post('/setting/get', { description: Constants.per.authOnly, summary: "查询域名监控设置" }) + async settingGet() { + const { projectId, userId } = await this.getProjectUserIdRead(); + const setting = await this.service.monitorSettingGet({ + userId: userId, + projectId: projectId, + }) + return this.ok(setting); + } + } diff --git a/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts b/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts new file mode 100644 index 000000000..6b63c4797 --- /dev/null +++ b/packages/ui/certd-server/src/controller/user/monitor/job-history-controller.ts @@ -0,0 +1,62 @@ +import { Constants, CrudController } from "@certd/lib-server"; +import { ALL, Body, Controller, Inject, Post, Provide, Query } from "@midwayjs/core"; +import { ApiTags } from "@midwayjs/swagger"; +import { SiteInfoService } from "../../../modules/monitor/index.js"; +import { JobHistoryService } from "../../../modules/monitor/service/job-history-service.js"; +import { AuthService } from "../../../modules/sys/authority/service/auth-service.js"; + +/** + */ +@Provide() +@Controller('/api/monitor/job-history') +@ApiTags(['monitor']) +export class JobHistoryController extends CrudController { + @Inject() + service: JobHistoryService; + @Inject() + authService: AuthService; + @Inject() + siteInfoService: SiteInfoService; + + getService(): JobHistoryService { + return this.service; + } + + @Post('/page', { description: Constants.per.authOnly, summary: "查询监控运行历史分页列表" }) + async page(@Body(ALL) body: any) { + const { projectId, userId } = await this.getProjectUserIdRead() + body.query = body.query ?? {}; + body.query.userId = userId; + body.query.projectId = projectId + const res = await this.service.page({ + query: body.query, + page: body.page, + sort: body.sort, + }); + return this.ok(res); + } + + @Post('/list', { description: Constants.per.authOnly, summary: "查询监控运行历史列表" }) + async list(@Body(ALL) body: any) { + body.query = body.query ?? {}; + const { projectId, userId } = await this.getProjectUserIdRead() + body.query.userId = userId; + body.query.projectId = projectId + return await super.list(body); + } + + + @Post('/info', { description: Constants.per.authOnly, summary: "查询监控运行历史详情" }) + async info(@Query('id') id: number) { + await this.checkOwner(this.service,id,"read"); + return await super.info(id); + } + + @Post('/delete', { description: Constants.per.authOnly, summary: "删除监控运行历史" }) + async delete(@Query('id') id: number) { + await this.checkOwner(this.service,id,"write"); + const res = await super.delete(id); + return res + } + +} diff --git a/packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts b/packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts index 0cf7b9cc8..6859eb2d6 100644 --- a/packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts +++ b/packages/ui/certd-server/src/modules/auto/auto-c-register-cron.ts @@ -67,6 +67,7 @@ export class AutoCRegisterCron { await this.registerUserExpireCheckCron(); await this.registerDomainExpireCheckCron(); + } async registerSiteMonitorCron() { @@ -211,11 +212,11 @@ export class AutoCRegisterCron { if (!isPlus()){ return } - // 添加域名即将到期检查任务 + // 添加域名即将到期同步任务 const randomWeek = Math.floor(Math.random() * 7) + 1 const randomHour = Math.floor(Math.random() * 24) const randomMinute = Math.floor(Math.random() * 60) - logger.info(`注册域名注册过期时间检查任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`) + logger.info(`注册域名注册过期时间同步任务,每周${randomWeek} ${randomHour}:${randomMinute}检查一次`) this.cron.register({ name: 'domain-expire-check', cron: `0 ${randomMinute} ${randomHour} ? * ${randomWeek}`, // 每周随机一天检查一次 diff --git a/packages/ui/certd-server/src/modules/cert/service/domain-service.ts b/packages/ui/certd-server/src/modules/cert/service/domain-service.ts index b8cf6e4a1..38012dc18 100644 --- a/packages/ui/certd-server/src/modules/cert/service/domain-service.ts +++ b/packages/ui/certd-server/src/modules/cert/service/domain-service.ts @@ -1,20 +1,23 @@ import { http, logger, utils } from '@certd/basic'; -import { AccessService, BaseService } from '@certd/lib-server'; +import { AccessService, BaseService, isEnterprise } from '@certd/lib-server'; import { doPageTurn, Pager, PageRes } from '@certd/pipeline'; import { DomainVerifiers } from "@certd/plugin-cert"; import { createDnsProvider, dnsProviderRegistry, DomainParser, parseDomainByPsl } from "@certd/plugin-lib"; import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; import { InjectEntityModel } from '@midwayjs/typeorm'; import dayjs from 'dayjs'; -import { In, Not, Repository } from 'typeorm'; +import { merge } from 'lodash-es'; +import { In, LessThan, Not, Repository } from 'typeorm'; import { BackTask, taskExecutor } from '../../basic/service/task-executor.js'; import { CnameRecordEntity } from "../../cname/entity/cname-record.js"; import { CnameRecordService } from '../../cname/service/cname-record-service.js'; -import { UserDomainImportSetting } from '../../mine/service/models.js'; +import { UserDomainImportSetting, UserDomainMonitorSetting } from '../../mine/service/models.js'; import { UserSettingsService } from '../../mine/service/user-settings-service.js'; +import { JobHistoryService } from '../../monitor/service/job-history-service.js'; import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js'; import { SubDomainService } from "../../pipeline/service/sub-domain-service.js"; import { DomainEntity } from '../entity/domain.js'; +import { Cron } from '../../cron/cron.js'; export interface SyncFromProviderReq { userId: number; @@ -27,6 +30,8 @@ export interface SyncFromProviderReq { const DOMAIN_IMPORT_TASK_TYPE = 'domainImportTask' const DOMAIN_EXPIRE_TASK_TYPE = 'domainExpirationSyncTask' +const DOMAIN_EXPIRE_CHECK_TYPE = 'domainExpirationCheck' + /** * @@ -51,6 +56,14 @@ export class DomainService extends BaseService { @Inject() userSettingService: UserSettingsService; + @Inject() + jobHistoryService: JobHistoryService; + + @Inject() + cron: Cron; + + + //@ts-ignore getRepository() { return this.repository; @@ -320,9 +333,9 @@ export class DomainService extends BaseService { logger.info(`从域名提供商${dnsProviderType}导入域名完成(${key}),共导入${task.total}个域名,跳过${task.getSkipCount()}个域名,成功${task.getSuccessCount()}个域名,失败${task.getErrorCount()}个域名`) } - async getDomainImportTaskStatus(req: { userId?: number ,projectId?: number}) { + async getDomainImportTaskStatus(req: { userId?: number, projectId?: number }) { const userId = req.userId || 0 - const projectId = req.projectId + const projectId = req.projectId const setting = await this.userSettingService.getSetting(userId, projectId, UserDomainImportSetting) const list = setting?.domainImportList || [] @@ -429,8 +442,6 @@ export class DomainService extends BaseService { await this.deleteDomainImportTask({ userId, projectId, key }) } - - return await this.addDomainImportTask({ userId, projectId, dnsProviderType, dnsProviderAccessId, index }) } @@ -441,7 +452,7 @@ export class DomainService extends BaseService { const userId = req.userId ?? 'all' const projectId = req.projectId let key = `user_${userId}` - if (projectId!=null) { + if (projectId != null) { key += `_${projectId}` } const task = taskExecutor.get(DOMAIN_EXPIRE_TASK_TYPE, key) @@ -452,7 +463,7 @@ export class DomainService extends BaseService { const userId = req.userId const projectId = req.projectId let key = `user_${userId ?? 'all'}` - if (projectId!=null) { + if (projectId != null) { key += `_${projectId}` } taskExecutor.start(new BackTask({ @@ -461,11 +472,15 @@ export class DomainService extends BaseService { title: `同步注册域名过期时间(${key}))`, run: async (task: BackTask) => { await this._syncDomainsExpirationDate({ userId, projectId, task }) + if (userId != null) { + await this.startCheckDomainExpiration({ userId, projectId }) + } } })) } private async _syncDomainsExpirationDate(req: { userId?: number, projectId?: number, task: BackTask }) { + //同步所有域名的过期时间 const pager = new Pager({ pageNo: 1, @@ -573,7 +588,138 @@ export class DomainService extends BaseService { await doPageTurn({ pager, getPage: getDomainPage, itemHandle: itemHandle }) const key = `user_${req.userId || 'all'}` - logger.info(`同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)`) + const log = `同步用户(${key})注册域名过期时间完成(${req.task.getSuccessCount()}个成功,${req.task.getErrorCount()}个失败)` + logger.info(log) } + + public async startCheckDomainExpiration(req: { userId?: number, projectId?: number }) { + const { userId, projectId } = req + if (userId == null) { + throw new Error('userId is required'); + } + + if (projectId && !isEnterprise()) { + logger.warn(`当前未开启企业模式,跳过检查项目(${projectId})的域名过期时间`) + return + } + + const setting = await this.monitorSettingGet({ userId, projectId }) + if (!setting || !setting.enabled) { + return + } + + const jobHistory: any = { + userId, + projectId, + type: DOMAIN_EXPIRE_CHECK_TYPE, + title: `检查注册域名过期时间`, + startAt: dayjs().valueOf(), + result: "start", + } + await this.jobHistoryService.add(jobHistory) + + const expireDays = setting.willExpireDays || 30 + const ltTime = dayjs().add(expireDays, 'day').valueOf() + + const total = await this.repository.count({ + where:{ + userId, + projectId, + disabled: false, + } + }) + //开始检查域名过期时间 + const list = await this.repository.find({ + where: { + userId, + projectId, + disabled: false, + expirationDate: LessThan(ltTime) + } + }) + + const now = dayjs().valueOf() + let willExpireDomains = [] + let hasExpireDomains = [] + + for (const item of list) { + const { expirationDate } = item + if (expirationDate < now) { + hasExpireDomains.push(item.domain) + } else { + willExpireDomains.push(item.domain) + } + } + + const title = `域名过期检查:共${total}个域名,即将过期${willExpireDomains.length}个域名,已过期${hasExpireDomains.length}个域名` + + try { + await this.jobHistoryService.update({ + id: jobHistory.id, + content: title, + result: "done", + endAt: dayjs().valueOf(), + }) + + } catch (error) { + logger.error(`更新域名过期检查任务状态失败:${error.message ?? error}`) + } + + if (list.length == 0) { + //没有过期域名 不发通知 + return + } + + //发送通知 + const content = `即将过期域名【${willExpireDomains.length}】:${willExpireDomains.join(',')} +\n已过期域名【${hasExpireDomains.length}】:${hasExpireDomains.join(',')}` + const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId }); + + const notificationService = await taskService.getNotificationService() + const url = await notificationService.getBindUrl("#/certd/cert/domain"); + await notificationService.send({ + id: setting.notificationId, + useDefault: true, + logger: logger, + body: { + title: title, + content: content, + url: url, + notificationType: DOMAIN_EXPIRE_CHECK_TYPE + } + }) + + } + + + public async monitorSettingGet(req: { userId?: number, projectId?: number }) { + const { userId, projectId } = req + const setting = await this.userSettingService.getSetting(userId, projectId, UserDomainMonitorSetting) + return setting || {} + } + + public async monitorSettingSave(req: { userId?: number, projectId?: number, setting?: any }) { + const { userId, projectId, setting } = req + const bean: UserDomainMonitorSetting = new UserDomainMonitorSetting() + merge(bean, setting) + await this.userSettingService.saveSetting(userId, projectId, bean) + await this.registerMonitorCron({ userId, projectId }) + } + + public async registerMonitorCron(req: { userId?: number, projectId?: number }) { + const { userId, projectId } = req + const setting = await this.monitorSettingGet(req) + const key = `${DOMAIN_EXPIRE_CHECK_TYPE}:${userId}_${projectId || ''}` + this.cron.remove(key) + if (setting.enabled) { + this.cron.register({ + cron: setting.cron, + name: key, + job: async () => { + await this.startCheckDomainExpiration({ userId, projectId }) + }, + }) + } + } } diff --git a/packages/ui/certd-server/src/modules/cron/configuration.ts b/packages/ui/certd-server/src/modules/cron/configuration.ts index a1043f4f7..4aae93062 100644 --- a/packages/ui/certd-server/src/modules/cron/configuration.ts +++ b/packages/ui/certd-server/src/modules/cron/configuration.ts @@ -1,6 +1,5 @@ -import { Config, Configuration, Logger } from '@midwayjs/core'; -import { ILogger } from '@midwayjs/logger'; -import { IMidwayContainer } from '@midwayjs/core'; +import { logger } from '@certd/basic'; +import { Config, Configuration, IMidwayContainer } from '@midwayjs/core'; import { Cron } from './cron.js'; // ... (see below) ... @@ -11,18 +10,15 @@ import { Cron } from './cron.js'; export class CronConfiguration { @Config() config; - @Logger() - logger: ILogger; - cron: Cron; async onReady(container: IMidwayContainer) { - this.logger.info('cron start'); + logger.info('cron start'); this.cron = new Cron({ - logger: this.logger, + logger: logger, ...this.config, }); container.registerObject('cron', this.cron); this.cron.start(); - this.logger.info('cron started'); + logger.info('cron started'); } } diff --git a/packages/ui/certd-server/src/modules/mine/service/models.ts b/packages/ui/certd-server/src/modules/mine/service/models.ts index 1c24bce28..b7e944a1b 100644 --- a/packages/ui/certd-server/src/modules/mine/service/models.ts +++ b/packages/ui/certd-server/src/modules/mine/service/models.ts @@ -31,6 +31,18 @@ export class UserSiteMonitorSetting extends BaseSettings { certValidDays?:number = 14; } + +export class UserDomainMonitorSetting extends BaseSettings { + static __title__ = "域名到期监控设置"; + static __key__ = "user.domain.monitor"; + + enabled?:boolean = false; + notificationId?:number= 0; + cron?:string = undefined; + willExpireDays?:number = 30; +} + + export class UserEmailSetting extends BaseSettings { static __title__ = "用户邮箱设置"; static __key__ = "user.email"; diff --git a/packages/ui/certd-server/src/modules/monitor/entity/job-history.ts b/packages/ui/certd-server/src/modules/monitor/entity/job-history.ts new file mode 100644 index 000000000..13b8851bb --- /dev/null +++ b/packages/ui/certd-server/src/modules/monitor/entity/job-history.ts @@ -0,0 +1,52 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; +import { PipelineEntity } from '../../pipeline/entity/pipeline.js'; + +@Entity('cd_job_history') +export class JobHistoryEntity { + @PrimaryGeneratedColumn() + id: number; + + @Column({ name: 'user_id', comment: '用户id' }) + userId: number; + + @Column({ name: 'project_id', comment: '项目id' }) + projectId: number; + + + @Column({ name: 'type', comment: '类型' }) + type: string; + + @Column({ name: 'title', comment: '标题' }) + title: string; + + @Column({ name: 'content', comment: '内容' }) + content: string; + + @Column({ name: 'related_id', comment: '关联id' }) + relatedId: string; + + @Column({ name: 'result', comment: '结果' }) + result: string; + + @Column({ name: 'start_at', comment: '开始时间' }) + startAt: number; + + @Column({ name: 'end_at', comment: '结束时间' }) + endAt: number; + + @Column({ + name: 'create_time', + comment: '创建时间', + default: () => 'CURRENT_TIMESTAMP', + }) + createTime: Date; + + @Column({ + name: 'update_time', + comment: '修改时间', + default: () => 'CURRENT_TIMESTAMP', + }) + updateTime: Date; + + pipeline?: PipelineEntity; +} diff --git a/packages/ui/certd-server/src/modules/monitor/service/job-history-service.ts b/packages/ui/certd-server/src/modules/monitor/service/job-history-service.ts new file mode 100644 index 000000000..6a17812b1 --- /dev/null +++ b/packages/ui/certd-server/src/modules/monitor/service/job-history-service.ts @@ -0,0 +1,23 @@ +import { BaseService } from "@certd/lib-server"; +import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core"; +import { InjectEntityModel } from "@midwayjs/typeorm"; +import { Repository } from "typeorm"; +import { UserSettingsService } from "../../mine/service/user-settings-service.js"; +import { JobHistoryEntity } from "../entity/job-history.js"; + + +@Provide() +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class JobHistoryService extends BaseService { + @InjectEntityModel(JobHistoryEntity) + repository: Repository; + + + @Inject() + userSettingsService: UserSettingsService; + + //@ts-ignore + getRepository() { + return this.repository; + } +} 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 548723604..b7fdc8a4c 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 @@ -17,6 +17,8 @@ import {SiteIpEntity} from "../entity/site-ip.js"; import {Cron} from "../../cron/cron.js"; import { dnsContainer } from "./dns-custom.js"; import { merge } from "lodash-es"; +import { JobHistoryService } from "./job-history-service.js"; +import { JobHistoryEntity } from "../entity/job-history.js"; @Provide() @Scope(ScopeEnum.Request, {allowDowngrade: true}) @@ -39,6 +41,9 @@ export class SiteInfoService extends BaseService { @Inject() siteIpService: SiteIpService; + @Inject() + jobHistoryService: JobHistoryService; + @Inject() cron: Cron; @@ -516,6 +521,7 @@ export class SiteInfoService extends BaseService { async triggerJobOnce(userId?:number,projectId?:number) { logger.info(`站点证书检查开始执行[${userId??'所有用户'}_${projectId??'所有项目'}]`); const query:any = { disabled: false }; + let jobEntity :Partial = null; if(userId!=null){ query.userId = userId; if(projectId){ @@ -526,9 +532,19 @@ export class SiteInfoService extends BaseService { if (!setting.cron) { return; } + jobEntity = { + userId, + projectId, + type:"siteCertMonitor", + title: '站点证书检查', + result:"start", + startAt:new Date().getTime(), + } + await this.jobHistoryService.add(jobEntity); } let offset = 0; const limit = 50; + let count = 0; while (true) { const res = await this.page({ query: query, @@ -541,10 +557,20 @@ export class SiteInfoService extends BaseService { } offset += records.length; const isCommon = !userId; + count += records.length; await this.checkList(records,isCommon); } logger.info(`站点证书检查完成[${userId??'所有用户'}_${projectId??'所有项目'}]`); + if(jobEntity){ + await this.jobHistoryService.update({ + id: jobEntity.id, + result: "done", + content:`共检查${count}个站点`, + endAt:new Date().getTime(), + updateTime:new Date(), + }); + } } async batchDelete(ids: number[], userId: number,projectId?:number): Promise { diff --git a/packages/ui/certd-server/src/modules/pipeline/service/getter/notification-getter.ts b/packages/ui/certd-server/src/modules/pipeline/service/getter/notification-getter.ts index 9487666f2..a3987a276 100644 --- a/packages/ui/certd-server/src/modules/pipeline/service/getter/notification-getter.ts +++ b/packages/ui/certd-server/src/modules/pipeline/service/getter/notification-getter.ts @@ -23,4 +23,8 @@ export class NotificationGetter implements INotificationService { async send(req: NotificationSendReq): Promise { return await this.notificationService.send(req, this.userId, this.projectId); } + + async getBindUrl(url: string) { + return await this.notificationService.getBindUrl(url); + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48f9748b0..9e316433b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,7 +49,7 @@ importers: packages/core/acme-client: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../basic '@peculiar/x509': specifier: ^1.11.0 @@ -213,10 +213,10 @@ importers: packages/core/pipeline: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../basic '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../pro/plus-core dayjs: specifier: ^1.11.7 @@ -412,7 +412,7 @@ importers: packages/libs/lib-k8s: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@kubernetes/client-node': specifier: 0.21.0 @@ -452,19 +452,19 @@ importers: packages/libs/lib-server: dependencies: '@certd/acme-client': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../pro/plus-core '@midwayjs/cache': specifier: 3.14.0 @@ -610,16 +610,16 @@ importers: packages/plugins/plugin-cert: dependencies: '@certd/acme-client': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../plugin-lib psl: specifier: ^1.9.0 @@ -683,16 +683,16 @@ importers: specifier: ^3.964.0 version: 3.964.0(aws-crt@1.26.2) '@certd/acme-client': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../pro/plus-core '@kubernetes/client-node': specifier: 0.21.0 @@ -783,16 +783,16 @@ importers: packages/pro/commercial-core: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@certd/lib-server': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/lib-server '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../plus-core '@midwayjs/core': specifier: 3.20.11 @@ -868,16 +868,16 @@ importers: packages/pro/plugin-plus: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@certd/plugin-lib': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../plugins/plugin-lib '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../plus-core crypto-js: specifier: ^4.2.0 @@ -953,7 +953,7 @@ importers: packages/pro/plus-core: dependencies: '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic dayjs: specifier: ^1.11.7 @@ -1249,10 +1249,10 @@ importers: version: 0.1.3(zod@3.24.4) devDependencies: '@certd/lib-iframe': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/lib-iframe '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -1447,46 +1447,46 @@ importers: specifier: ^3.990.0 version: 3.990.0(aws-crt@1.26.2) '@certd/acme-client': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/acme-client '@certd/basic': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/basic '@certd/commercial-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../pro/commercial-core '@certd/cv4pve-api-javascript': specifier: ^8.4.2 version: 8.4.2 '@certd/jdcloud': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/lib-jdcloud '@certd/lib-huawei': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/lib-huawei '@certd/lib-k8s': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/lib-k8s '@certd/lib-server': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/lib-server '@certd/midway-flyway-js': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../libs/midway-flyway-js '@certd/pipeline': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../core/pipeline '@certd/plugin-cert': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../plugins/plugin-cert '@certd/plugin-lib': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../plugins/plugin-lib '@certd/plugin-plus': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../pro/plugin-plus '@certd/plus-core': - specifier: ^1.39.7 + specifier: ^1.39.8 version: link:../../pro/plus-core '@google-cloud/publicca': specifier: ^1.3.0 @@ -1719,6 +1719,9 @@ importers: wechatpay-node-v3: specifier: ^2.2.1 version: 2.2.1 + whoiser: + specifier: 2.0.0-beta.10 + version: 2.0.0-beta.10 xml2js: specifier: ^0.6.2 version: 0.6.2 @@ -10743,6 +10746,10 @@ packages: pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode-esm@1.0.15: + resolution: {integrity: sha512-pR7pzaunGU4g3v3vMIXD9WnrGUiEBs2ezcVYr9piTC/HVem9F+MJ+JNdSeYD9pK7EumLMUMO9F3Gsa3RS+o7pA==} + engines: {node: '>=20'} + punycode.js@2.3.1: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} @@ -12737,6 +12744,9 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + whoiser@2.0.0-beta.10: + resolution: {integrity: sha512-Y1uyLNR6zQnKj/mZPYtofs6u/9ZAnBzAuiTgU1aEMKjlnknrQdLyOKv9ErmIeF2mySMjGFj0tCam+DpxotBXHw==} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} @@ -20854,13 +20864,13 @@ snapshots: resolve: 1.22.10 semver: 6.3.1 - eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8): + eslint-plugin-prettier@3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8): dependencies: eslint: 7.32.0 prettier: 2.8.8 prettier-linter-helpers: 1.0.0 optionalDependencies: - eslint-config-prettier: 8.10.0(eslint@8.57.0) + eslint-config-prettier: 8.10.0(eslint@7.32.0) eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@8.57.0)(prettier@2.8.8): dependencies: @@ -23286,7 +23296,7 @@ snapshots: eslint: 7.32.0 eslint-config-prettier: 8.10.0(eslint@7.32.0) eslint-plugin-node: 11.1.0(eslint@7.32.0) - eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@8.57.0))(eslint@7.32.0)(prettier@2.8.8) + eslint-plugin-prettier: 3.4.1(eslint-config-prettier@8.10.0(eslint@7.32.0))(eslint@7.32.0)(prettier@2.8.8) execa: 5.1.1 inquirer: 7.3.3 json5: 2.2.3 @@ -24561,6 +24571,8 @@ snapshots: end-of-stream: 1.4.4 once: 1.4.0 + punycode-esm@1.0.15: {} + punycode.js@2.3.1: {} punycode@1.4.1: {} @@ -26843,6 +26855,10 @@ snapshots: dependencies: isexe: 3.1.1 + whoiser@2.0.0-beta.10: + dependencies: + punycode-esm: 1.0.15 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0