perf: 优化站点监控,支持设置忽略主站证书一致性,支持开启和关闭自动同步ip

This commit is contained in:
xiaojunnuo
2026-01-09 12:25:56 +08:00
parent ad2aa2eff5
commit 26f75c71ba
9 changed files with 134 additions and 54 deletions
@@ -282,6 +282,9 @@ export default {
disabled: "Enable/Disable", disabled: "Enable/Disable",
ipCheck: "Enable IP Check", ipCheck: "Enable IP Check",
ipSyncAuto: "Enable IP Sync Auto", ipSyncAuto: "Enable IP Sync Auto",
ipSyncMode: "IP Sync Mode",
ipIgnoreCoherence: "Ignore Certificate Coherence",
ipIgnoreCoherenceHelper: "Enable to ignore certificate coherence check, only check certificate expiration time",
selectRequired: "Please select", selectRequired: "Please select",
ipCheckConfirm: "Are you sure to {status} IP check?", ipCheckConfirm: "Are you sure to {status} IP check?",
ipCount: "IP Count", ipCount: "IP Count",
@@ -286,9 +286,16 @@ export default {
disabled: "禁用启用", disabled: "禁用启用",
ipCheck: "开启IP检查", ipCheck: "开启IP检查",
ipSyncAuto: "自动同步IP", ipSyncAuto: "自动同步IP",
ipSyncMode: "IP同步模式",
ipSyncModeHelper: "选择仅检查IPv4或IPv6,或检查所有IP",
ipSyncModeAll: "检查所有IP",
ipSyncModeIPV4Only: "仅检查IPv4",
ipSyncModeIPV6Only: "仅检查IPv6",
selectRequired: "请选择", selectRequired: "请选择",
ipCheckConfirm: "确定{status}IP检查?", ipCheckConfirm: "确定{status}IP检查?",
ipCount: "IP数量", ipCount: "IP数量",
ipIgnoreCoherence: "忽略证书一致性",
ipIgnoreCoherenceHelper: "开启后,即使IP上的证书与站点证书不一致,也会被认为是正常,仅校验证书过期时间",
checkStatus: "检查状态", checkStatus: "检查状态",
pipelineId: "关联流水线ID", pipelineId: "关联流水线ID",
certInfoId: "证书ID", certInfoId: "证书ID",
@@ -148,7 +148,6 @@ async function refreshTarget(value: any) {
type: "", type: "",
}; };
} }
debugger
} }
watch( watch(
@@ -610,6 +610,46 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}, },
}, },
}, },
checkStatus: {
title: t("certd.monitor.checkStatus"),
search: {
show: false,
},
type: "dict-select",
dict: checkStatusDict,
form: {
show: false,
},
column: {
width: 100,
align: "center",
sorter: true,
cellRender({ value, row }) {
return (
<a-tooltip title={row.error}>
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format>
</a-tooltip>
);
},
},
},
// error: {
// title: "错误信息",
// search: {
// show: false
// },
// type: "text",
// form: {
// show: false
// },
// column: {
// width: 200,
// sorter: true,
// cellRender({ value }) {
// return <a-tooltip title={value}>{value}</a-tooltip>;
// }
// }
// },
ipCheck: { ipCheck: {
title: t("certd.monitor.ipCheck"), title: t("certd.monitor.ipCheck"),
type: "dict-switch", type: "dict-switch",
@@ -672,46 +712,51 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
align: "center", align: "center",
}, },
}, },
checkStatus: { ipSyncMode: {
title: t("certd.monitor.checkStatus"), title: t("certd.monitor.ipSyncMode"),
search: {
show: false,
},
type: "dict-select", type: "dict-select",
dict: checkStatusDict, dict: dict({
data: [
{ label: t("certd.monitor.ipSyncModeAll"), value: "all" },
{ label: t("certd.monitor.ipSyncModeIPV4Only"), value: "ipv4" },
{ label: t("certd.monitor.ipSyncModeIPV6Only"), value: "ipv6" },
],
}),
form: { form: {
show: false, value: "all",
show: compute(({ form }) => {
return form.ipSyncAuto;
}),
helper: t("certd.monitor.ipSyncModeHelper"),
}, },
column: { column: {
width: 100, width: 100,
align: "center",
sorter: true, sorter: true,
cellRender({ value, row }) { align: "center",
return ( },
<a-tooltip title={row.error}> },
<fs-values-format v-model={value} dict={checkStatusDict}></fs-values-format> ipIgnoreCoherence: {
</a-tooltip> title: t("certd.monitor.ipIgnoreCoherence"),
); type: "dict-switch",
}, dict: dict({
data: [
{ label: t("common.enabled"), value: true, color: "green" },
{ label: t("common.disabled"), value: false, color: "gray" },
],
}),
form: {
value: false,
show: compute(({ form }) => {
return form.ipCheck;
}),
helper: t("certd.monitor.ipIgnoreCoherenceHelper"),
},
column: {
width: 100,
sorter: true,
align: "center",
}, },
}, },
// error: {
// title: "错误信息",
// search: {
// show: false
// },
// type: "text",
// form: {
// show: false
// },
// column: {
// width: 200,
// sorter: true,
// cellRender({ value }) {
// return <a-tooltip title={value}>{value}</a-tooltip>;
// }
// }
// },
pipelineId: { pipelineId: {
title: t("certd.monitor.pipelineId"), title: t("certd.monitor.pipelineId"),
search: { search: {
@@ -1,4 +1,6 @@
ALTER TABLE cd_site_info ADD COLUMN ip_sync_auto boolean DEFAULT (1); ALTER TABLE cd_site_info ADD COLUMN ip_sync_auto boolean;
ALTER TABLE cd_site_info ADD COLUMN ip_sync_mode varchar(20);
ALTER TABLE cd_site_info ADD COLUMN ip_ignore_coherence boolean;
ALTER TABLE pi_pipeline ADD COLUMN webhook_key varchar(100); ALTER TABLE pi_pipeline ADD COLUMN webhook_key varchar(100);
ALTER TABLE pi_pipeline ADD COLUMN trigger_count integer DEFAULT (0); ALTER TABLE pi_pipeline ADD COLUMN trigger_count integer DEFAULT (0);
@@ -5,6 +5,7 @@ import { SiteInfoService } from "../../../modules/monitor/service/site-info-serv
import { UserSiteMonitorSetting } from "../../../modules/mine/service/models.js"; import { UserSiteMonitorSetting } from "../../../modules/mine/service/models.js";
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import {SiteIpService} from "../../../modules/monitor/service/site-ip-service.js"; import {SiteIpService} from "../../../modules/monitor/service/site-ip-service.js";
import { utils } from "@certd/basic";
/** /**
*/ */
@@ -104,6 +105,7 @@ export class SiteInfoController extends CrudController<SiteInfoService> {
async check(@Body('id') id: number) { async check(@Body('id') id: number) {
await this.service.checkUserId(id, this.getUserId()); await this.service.checkUserId(id, this.getUserId());
await this.service.check(id, true, 0); await this.service.check(id, true, 0);
await utils.sleep(1000);
return this.ok(); return this.ok();
} }
@@ -49,6 +49,12 @@ export class SiteInfoEntity {
@Column({ name: 'ip_sync_auto', comment: '是否自动同步IP' }) @Column({ name: 'ip_sync_auto', comment: '是否自动同步IP' })
ipSyncAuto: boolean; ipSyncAuto: boolean;
@Column({ name: 'ip_sync_mode', comment: 'IP同步模式' })
ipSyncMode: string;
@Column({ name: 'ip_ignore_coherence', comment: '忽略证书一致性' })
ipIgnoreCoherence: boolean;
@Column({ name: 'ip_count', comment: 'ip数量' }) @Column({ name: 'ip_count', comment: 'ip数量' })
ipCount: number ipCount: number
@@ -162,16 +162,16 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
} }
await this.update(updateData); await this.update(updateData);
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
//检查ip //检查ip
await this.checkAllIp(site,retryTimes); await this.checkAllIp(site,retryTimes,setting);
if (!notify) { if (!notify) {
return; return;
} }
try { try {
await this.sendExpiresNotify(site.id); await this.sendExpiresNotify(site.id,setting);
} catch (e) { } catch (e) {
logger.error("send notify error", e); logger.error("send notify error", e);
} }
@@ -187,18 +187,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
return; return;
} }
try { try {
await this.sendCheckErrorNotify(site.id); await this.sendCheckErrorNotify(site.id,false,setting);
} catch (e) { } catch (e) {
logger.error("send notify error", e); logger.error("send notify error", e);
} }
} }
} }
async checkAllIp(site: SiteInfoEntity,retryTimes = null) { async checkAllIp(site: SiteInfoEntity,retryTimes = null,setting: UserSiteMonitorSetting) {
if (!site.ipCheck) { if (!site.ipCheck) {
return; return;
} }
const certExpiresTime = site.certExpiresTime; const certExpiresTime = site.certExpiresTime;
const tipDays = setting?.certValidDays || 10;
const onFinished = async (list: SiteIpEntity[]) => { const onFinished = async (list: SiteIpEntity[]) => {
let errorCount = 0; let errorCount = 0;
let errorMessage = ""; let errorMessage = "";
@@ -207,11 +208,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
continue; continue;
} }
errorCount++; errorCount++;
const isExpired = dayjs().valueOf() > dayjs(item.certExpiresTime).valueOf();
const isWillExpired = dayjs().valueOf() > dayjs(item.certExpiresTime).subtract(tipDays, "day").valueOf();
if (item.error) { if (item.error) {
errorMessage += `${item.ipAddress}${item.error} \n`; errorMessage += `${item.ipAddress}${item.error} \n`;
} else if (item.certExpiresTime !== certExpiresTime) { } else if (item.certExpiresTime !== certExpiresTime && !site.ipIgnoreCoherence) {
errorMessage += `${item.ipAddress}:与主站证书过期时间不一致(主站:${dayjs(certExpiresTime).format("YYYY-MM-DD")}IP${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`; errorMessage += `${item.ipAddress}:与主站证书过期时间不一致(主站:${dayjs(certExpiresTime).format("YYYY-MM-DD")}IP${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`;
} else { } else if (isExpired){
errorMessage += `${item.ipAddress}:证书已过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`;
}else if (isWillExpired){
errorMessage += `${item.ipAddress}:证书将过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`;
}else {
errorCount--; errorCount--;
} }
} }
@@ -232,12 +241,12 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
ipErrorCount: errorCount ipErrorCount: errorCount
}); });
try { try {
await this.sendCheckErrorNotify(site.id, true); await this.sendCheckErrorNotify(site.id, true,setting);
} catch (e) { } catch (e) {
logger.error("send notify error", e); logger.error("send notify error", e);
} }
}; };
if (!site.ipSyncAuto) { if (site.ipSyncAuto === false) {
await this.siteIpService.checkAll(site, retryTimes,onFinished); await this.siteIpService.checkAll(site, retryTimes,onFinished);
}else{ }else{
await this.siteIpService.syncAndCheck(site, retryTimes,onFinished); await this.siteIpService.syncAndCheck(site, retryTimes,onFinished);
@@ -246,7 +255,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
} }
/** /**
* 检查 * 检查,不等待返回
* @param id * @param id
* @param notify * @param notify
* @param retryTimes * @param retryTimes
@@ -256,13 +265,16 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (!site) { if (!site) {
throw new Error("站点不存在"); throw new Error("站点不存在");
} }
return await this.doCheck(site, notify, retryTimes);
this.doCheck(site, notify, retryTimes).catch((err) => {
logger.error("check site error", err);
});
return
} }
async sendCheckErrorNotify(siteId: number, fromIpCheck = false) { async sendCheckErrorNotify(siteId: number, fromIpCheck = false,setting: UserSiteMonitorSetting) {
const site = await this.info(siteId); const site = await this.info(siteId);
const url = await this.notificationService.getBindUrl("#/certd/monitor/site"); const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
// 发邮件 // 发邮件
await this.notificationService.send( await this.notificationService.send(
{ {
@@ -281,11 +293,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
); );
} }
async sendExpiresNotify(siteId: number) { async sendExpiresNotify(siteId: number,setting: UserSiteMonitorSetting) {
const site = await this.info(siteId);
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
const tipDays = setting?.certValidDays || 10; const tipDays = setting?.certValidDays || 10;
const site = await this.info(siteId);
const expires = site.certExpiresTime; const expires = site.certExpiresTime;
const validDays = dayjs(expires).diff(dayjs(), "day"); const validDays = dayjs(expires).diff(dayjs(), "day");
const url = await this.notificationService.getBindUrl("#/certd/monitor/site"); const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
@@ -76,7 +76,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
} }
//从域名解析中获取所有ip //从域名解析中获取所有ip
const ips = await this.getAllIpsFromDomain(domain,resolver); const ips = await this.getAllIpsFromDomain(domain,resolver,entity.ipSyncMode);
if (ips.length === 0 ) { if (ips.length === 0 ) {
logger.warn(`没有发现${domain}的IP`) logger.warn(`没有发现${domain}的IP`)
return return
@@ -126,7 +126,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
} }
} }
async check(ipId: number, domain: string, port: number,retryTimes = null) { async check(ipId: number, domain: string, port: number,retryTimes = null ,tipDays = 10) {
if(!ipId){ if(!ipId){
return return
} }
@@ -231,7 +231,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
}) })
} }
async getAllIpsFromDomain(domain: string,resolver:any = dns):Promise<string[]> { async getAllIpsFromDomain(domain: string,resolver:any = dns,ipSyncMode:string = "all"):Promise<string[]> {
const getFromV4 = async ():Promise<string[]> => { const getFromV4 = async ():Promise<string[]> => {
try{ try{
return await resolver.resolve4(domain); return await resolver.resolve4(domain);
@@ -249,6 +249,12 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
} }
} }
if (ipSyncMode === "ipv4") {
return await getFromV4();
}
if (ipSyncMode === "ipv6") {
return await getFromV6();
}
return Promise.all([getFromV4(), getFromV6()]).then(res => { return Promise.all([getFromV4(), getFromV6()]).then(res => {
return [...res[0], ...res[1]]; return [...res[0], ...res[1]];