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>
<div class="cron-editor">
<div class="flex-o">
<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"
/>
<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" />
</div>
<div class="mt-5 flex">
<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 dayjs from "dayjs";
defineOptions({
name: "CronEditor"
name: "CronEditor",
});
const props = defineProps<{
modelValue?: string;
disabled?: boolean;
readonly?: boolean;
allowEveryMin?: boolean;
}>();
const period = ref<string>("");
@ -58,9 +49,12 @@ const onUpdate = (value: string) => {
if (arr[0] === "*") {
arr[0] = "0";
}
if (arr[1] === "*") {
arr[1] = "0";
if (!props.allowEveryMin) {
if (arr[1] === "*") {
arr[1] = "0";
}
}
value = arr.join(" ");
emit("update:modelValue", value);

View File

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

View File

@ -11,6 +11,19 @@
</div>
<div class="helper">设置通知渠道</div>
</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 }">
<loading-button type="primary" html-type="button" :click="doSave">保存</loading-button>
</a-form-item>
@ -27,8 +40,10 @@ import { notification } from "ant-design-vue";
import { merge } from "lodash-es";
import { useSettingStore } from "/src/store/settings";
import NotificationSelector from "/@/views/certd/notification/notification-selector/index.vue";
import { useUserStore } from "/@/store/user";
const settingsStore = useSettingStore();
const userStore = useUserStore();
defineOptions({
name: "UserSecurity",
});

View File

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

View File

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

View File

@ -25,6 +25,8 @@ export class UserSiteMonitorSetting extends BaseSettings {
static __key__ = "user.site.monitor";
notificationId?:number= 0;
cron?:string = undefined;
retryTimes?:number = 3;
}
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 {SiteIpService} from "./site-ip-service.js";
import {SiteIpEntity} from "../entity/site-ip.js";
import {Cron} from "../../cron/cron.js";
@Provide()
@Scope(ScopeEnum.Request, {allowDowngrade: true})
@ -36,6 +37,10 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
@Inject()
siteIpService: SiteIpService;
@Inject()
cron: Cron;
//@ts-ignore
getRepository() {
return this.repository;
@ -307,15 +312,34 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
const sites = await this.repository.find({
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) {
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);
});
await utils.sleep(200);
await utils.sleep(100);
}
}
@ -325,6 +349,12 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async saveSetting(userId: number, bean: UserSiteMonitorSetting) {
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 }) {
@ -401,4 +431,67 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
};
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??'所有用户'}]`);
}
}