perf: 站点证书监控支持定时设置,重试次数设置

pull/436/head
xiaojunnuo 2025-06-06 18:20:30 +08:00
parent a00453c83a
commit d3c2f8eb43
7 changed files with 147 additions and 45 deletions

View File

@ -1,17 +1,7 @@
<template> <template>
<div class="cron-editor"> <div class="cron-editor">
<div class="flex-o"> <div class="flex-o">
<cron-light <cron-light :disabled="disabled" :readonly="readonly" :period="period" class="flex-o cron-ant" locale="zh-CN" format="quartz" :model-value="modelValue" @update:model-value="onUpdate" @error="onError" />
:disabled="disabled"
:readonly="readonly"
:period="period"
class="flex-o cron-ant"
locale="zh-CN"
format="quartz"
:model-value="modelValue"
@update:model-value="onUpdate"
@error="onError"
/>
</div> </div>
<div class="mt-5 flex"> <div class="mt-5 flex">
<a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input> <a-input :disabled="true" :readonly="readonly" :value="modelValue" @change="onChange"></a-input>
@ -27,12 +17,13 @@ import parser from "cron-parser";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
defineOptions({ defineOptions({
name: "CronEditor" name: "CronEditor",
}); });
const props = defineProps<{ const props = defineProps<{
modelValue?: string; modelValue?: string;
disabled?: boolean; disabled?: boolean;
readonly?: boolean; readonly?: boolean;
allowEveryMin?: boolean;
}>(); }>();
const period = ref<string>(""); const period = ref<string>("");
@ -58,9 +49,12 @@ const onUpdate = (value: string) => {
if (arr[0] === "*") { if (arr[0] === "*") {
arr[0] = "0"; arr[0] = "0";
} }
if (arr[1] === "*") { if (!props.allowEveryMin) {
arr[1] = "0"; if (arr[1] === "*") {
arr[1] = "0";
}
} }
value = arr.join(" "); value = arr.join(" ");
emit("update:modelValue", value); emit("update:modelValue", value);

View File

@ -3,6 +3,8 @@ import { request } from "/src/api/service";
const apiPrefix = "/monitor/site/setting"; const apiPrefix = "/monitor/site/setting";
export type UserSiteMonitorSetting = { export type UserSiteMonitorSetting = {
notificationId?: number; notificationId?: number;
retryTimes?: number;
cron?: string;
}; };
export async function SiteMonitorSettingsGet() { export async function SiteMonitorSettingsGet() {

View File

@ -11,6 +11,19 @@
</div> </div>
<div class="helper">设置通知渠道</div> <div class="helper">设置通知渠道</div>
</a-form-item> </a-form-item>
<a-form-item label="重试次数" :name="['retryTimes']">
<div class="flex">
<a-input-number v-model:value="formState.retryTimes" />
</div>
<div class="helper">监控请求重试次数</div>
</a-form-item>
<a-form-item label="监控定时设置" :name="['cron']">
<div class="flex">
<cron-editor v-model="formState.cron" :disabled="!settingsStore.isPlus" :allow-every-min="userStore.isAdmin" />
<vip-button class="ml-5" mode="button"></vip-button>
</div>
<div class="helper">定时触发监控</div>
</a-form-item>
<a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }"> <a-form-item label=" " :colon="false" :wrapper-col="{ span: 16 }">
<loading-button type="primary" html-type="button" :click="doSave">保存</loading-button> <loading-button type="primary" html-type="button" :click="doSave">保存</loading-button>
</a-form-item> </a-form-item>
@ -27,8 +40,10 @@ import { notification } from "ant-design-vue";
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import { useSettingStore } from "/src/store/settings"; import { useSettingStore } from "/src/store/settings";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue"; import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { useUserStore } from "/@/store/user";
const settingsStore = useSettingStore(); const settingsStore = useSettingStore();
const userStore = useUserStore();
defineOptions({ defineOptions({
name: "UserSecurity", name: "UserSecurity",
}); });

View File

@ -154,4 +154,5 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
await this.service.saveSetting(userId, setting); await this.service.saveSetting(userId, setting);
return this.ok({}); return this.ok({});
} }
} }

View File

@ -4,6 +4,8 @@ import { logger } from '@certd/basic';
import { SysSettingsService } from '@certd/lib-server'; import { SysSettingsService } from '@certd/lib-server';
import { SiteInfoService } from '../monitor/index.js'; import { SiteInfoService } from '../monitor/index.js';
import { Cron } from '../cron/cron.js'; import { Cron } from '../cron/cron.js';
import {UserSettingsService} from "../mine/service/user-settings-service.js";
import {UserSiteMonitorSetting} from "../mine/service/models.js";
@Autoload() @Autoload()
@Scope(ScopeEnum.Request, { allowDowngrade: true }) @Scope(ScopeEnum.Request, { allowDowngrade: true })
@ -22,6 +24,8 @@ export class AutoCRegisterCron {
@Inject() @Inject()
sysSettingsService: SysSettingsService; sysSettingsService: SysSettingsService;
@Inject()
userSettingsService: UserSettingsService;
@Inject() @Inject()
siteInfoService: SiteInfoService; siteInfoService: SiteInfoService;
@ -39,39 +43,30 @@ export class AutoCRegisterCron {
// console.log('meta', meta); // console.log('meta', meta);
// const metas = listPropertyDataFromClass(CLASS_KEY, this.echoPlugin); // const metas = listPropertyDataFromClass(CLASS_KEY, this.echoPlugin);
// console.log('metas', metas); // console.log('metas', metas);
this.registerSiteMonitorCron(); await this.registerSiteMonitorCron();
} }
registerSiteMonitorCron() { async registerSiteMonitorCron() {
const job = async () => { //先注册公共job
logger.info('站点证书检查开始执行'); await this.siteInfoService.registerSiteMonitorJob()
let offset = 0; //注册用户独立的检查时间
const limit = 50; const monitorSettingList = await this.userSettingsService.list({
while (true) { query:{
const res = await this.siteInfoService.page({ key: UserSiteMonitorSetting.__key__,
query: { disabled: false },
page: { offset, limit },
});
const { records } = res;
if (records.length === 0) {
break;
}
offset += records.length;
await this.siteInfoService.checkList(records);
} }
})
for (const item of monitorSettingList) {
const setting = item.setting ?? JSON.parse(item.setting)
if(!setting?.cron){
continue
}
await this.siteInfoService.registerSiteMonitorJob(item.userId)
}
logger.info('站点证书检查完成');
};
this.cron.register({
name: 'siteMonitor',
cron: '0 0 0 * * *',
job,
});
if (this.immediateTriggerSiteMonitor) { if (this.immediateTriggerSiteMonitor) {
job(); logger.info(`立即触发一次站点证书检查任务`)
await this.siteInfoService.triggerJobOnce()
} }
} }
} }

View File

@ -25,6 +25,8 @@ export class UserSiteMonitorSetting extends BaseSettings {
static __key__ = "user.site.monitor"; static __key__ = "user.site.monitor";
notificationId?:number= 0; notificationId?:number= 0;
cron?:string = undefined;
retryTimes?:number = 3;
} }
export class UserEmailSetting extends BaseSettings { export class UserEmailSetting extends BaseSettings {

View File

@ -14,6 +14,7 @@ import {UserSettingsService} from "../../mine/service/user-settings-service.js";
import {UserSiteMonitorSetting} from "../../mine/service/models.js"; import {UserSiteMonitorSetting} from "../../mine/service/models.js";
import {SiteIpService} from "./site-ip-service.js"; import {SiteIpService} from "./site-ip-service.js";
import {SiteIpEntity} from "../entity/site-ip.js"; import {SiteIpEntity} from "../entity/site-ip.js";
import {Cron} from "../../cron/cron.js";
@Provide() @Provide()
@Scope(ScopeEnum.Request, {allowDowngrade: true}) @Scope(ScopeEnum.Request, {allowDowngrade: true})
@ -36,6 +37,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
@Inject() @Inject()
siteIpService: SiteIpService; siteIpService: SiteIpService;
@Inject()
cron: Cron;
//@ts-ignore //@ts-ignore
getRepository() { getRepository() {
return this.repository; return this.repository;
@ -307,15 +312,34 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
const sites = await this.repository.find({ const sites = await this.repository.find({
where: {userId} where: {userId}
}); });
this.checkList(sites); this.checkList(sites,false);
} }
async checkList(sites: SiteInfoEntity[]) { async checkList(sites: SiteInfoEntity[],isCommon: boolean) {
const cache = {}
const getFromCache = async (userId: number) =>{
if (cache[userId]) {
return cache[userId];
}
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting)
cache[userId] = setting
return setting;
}
for (const site of sites) { for (const site of sites) {
this.doCheck(site).catch(e => { let retryTimes = 3;
const setting = await getFromCache(site.userId)
if (isCommon) {
//公共的检查排除有设置cron的用户
if (setting?.cron) {
//设置了cron跳过公共检查
continue;
}
retryTimes = setting.retryTimes
}
this.doCheck(site,true,retryTimes).catch(e => {
logger.error(`检查站点证书失败,${site.domain}`, e.message); logger.error(`检查站点证书失败,${site.domain}`, e.message);
}); });
await utils.sleep(200); await utils.sleep(100);
} }
} }
@ -325,6 +349,12 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async saveSetting(userId: number, bean: UserSiteMonitorSetting) { async saveSetting(userId: number, bean: UserSiteMonitorSetting) {
await this.userSettingsService.saveSetting(userId, bean); await this.userSettingsService.saveSetting(userId, bean);
if(bean.cron){
//注册job
await this.registerSiteMonitorJob(userId);
}else{
this.clearSiteMonitorJob(userId);
}
} }
async ipCheckChange(req: { id: any; ipCheck: any }) { async ipCheckChange(req: { id: any; ipCheck: any }) {
@ -401,4 +431,67 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}; };
await batchAdd(list); await batchAdd(list);
} }
clearSiteMonitorJob(userId: number) {
this.cron.remove(`siteMonitor-${userId}`);
}
async registerSiteMonitorJob(userId?: number) {
if(!userId){
//注册公共job
logger.info(`注册站点证书检查定时任务`)
this.cron.register({
name: 'siteMonitor',
cron: '0 0 0 * * *',
job:async ()=>{
await this.triggerJobOnce()
},
});
logger.info(`注册站点证书检查定时任务完成`)
}else{
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
if (!setting.cron) {
return;
}
//注册个人的
this.cron.register({
name: `siteMonitor-${userId}`,
cron: setting.cron,
job: () => this.triggerJobOnce(userId),
});
}
}
async triggerJobOnce(userId?:number) {
logger.info(`站点证书检查开始执行[${userId??'所有用户'}]`);
const query:any = { disabled: false };
if(userId){
query.userId = userId;
//判断是否已关闭
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, UserSiteMonitorSetting);
if (!setting.cron) {
return;
}
}
let offset = 0;
const limit = 50;
while (true) {
const res = await this.page({
query: query,
page: { offset, limit },
});
const { records } = res;
if (records.length === 0) {
break;
}
offset += records.length;
const isCommon = !userId;
await this.checkList(records,isCommon);
}
logger.info(`站点证书检查完成[${userId??'所有用户'}]`);
}
} }