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

View File

@@ -282,6 +282,9 @@ export default {
disabled: "Enable/Disable",
ipCheck: "Enable IP Check",
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",
ipCheckConfirm: "Are you sure to {status} IP check?",
ipCount: "IP Count",

View File

@@ -286,9 +286,16 @@ export default {
disabled: "禁用启用",
ipCheck: "开启IP检查",
ipSyncAuto: "自动同步IP",
ipSyncMode: "IP同步模式",
ipSyncModeHelper: "选择仅检查IPv4或IPv6或检查所有IP",
ipSyncModeAll: "检查所有IP",
ipSyncModeIPV4Only: "仅检查IPv4",
ipSyncModeIPV6Only: "仅检查IPv6",
selectRequired: "请选择",
ipCheckConfirm: "确定{status}IP检查",
ipCount: "IP数量",
ipIgnoreCoherence: "忽略证书一致性",
ipIgnoreCoherenceHelper: "开启后即使IP上的证书与站点证书不一致也会被认为是正常仅校验证书过期时间",
checkStatus: "检查状态",
pipelineId: "关联流水线ID",
certInfoId: "证书ID",

View File

@@ -148,7 +148,6 @@ async function refreshTarget(value: any) {
type: "",
};
}
debugger
}
watch(

View File

@@ -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: {
title: t("certd.monitor.ipCheck"),
type: "dict-switch",
@@ -672,46 +712,51 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
align: "center",
},
},
checkStatus: {
title: t("certd.monitor.checkStatus"),
search: {
show: false,
},
ipSyncMode: {
title: t("certd.monitor.ipSyncMode"),
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: {
show: false,
value: "all",
show: compute(({ form }) => {
return form.ipSyncAuto;
}),
helper: t("certd.monitor.ipSyncModeHelper"),
},
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>
);
},
align: "center",
},
},
ipIgnoreCoherence: {
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: {
title: t("certd.monitor.pipelineId"),
search: {

View File

@@ -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 trigger_count integer DEFAULT (0);

View File

@@ -5,6 +5,7 @@ import { SiteInfoService } from "../../../modules/monitor/service/site-info-serv
import { UserSiteMonitorSetting } from "../../../modules/mine/service/models.js";
import { merge } from "lodash-es";
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) {
await this.service.checkUserId(id, this.getUserId());
await this.service.check(id, true, 0);
await utils.sleep(1000);
return this.ok();
}

View File

@@ -49,6 +49,12 @@ export class SiteInfoEntity {
@Column({ name: 'ip_sync_auto', comment: '是否自动同步IP' })
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数量' })
ipCount: number

View File

@@ -162,16 +162,16 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
await this.update(updateData);
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
//检查ip
await this.checkAllIp(site,retryTimes);
await this.checkAllIp(site,retryTimes,setting);
if (!notify) {
return;
}
try {
await this.sendExpiresNotify(site.id);
await this.sendExpiresNotify(site.id,setting);
} catch (e) {
logger.error("send notify error", e);
}
@@ -187,18 +187,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
return;
}
try {
await this.sendCheckErrorNotify(site.id);
await this.sendCheckErrorNotify(site.id,false,setting);
} catch (e) {
logger.error("send notify error", e);
}
}
}
async checkAllIp(site: SiteInfoEntity,retryTimes = null) {
async checkAllIp(site: SiteInfoEntity,retryTimes = null,setting: UserSiteMonitorSetting) {
if (!site.ipCheck) {
return;
}
const certExpiresTime = site.certExpiresTime;
const tipDays = setting?.certValidDays || 10;
const onFinished = async (list: SiteIpEntity[]) => {
let errorCount = 0;
let errorMessage = "";
@@ -207,11 +208,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
continue;
}
errorCount++;
const isExpired = dayjs().valueOf() > dayjs(item.certExpiresTime).valueOf();
const isWillExpired = dayjs().valueOf() > dayjs(item.certExpiresTime).subtract(tipDays, "day").valueOf();
if (item.error) {
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`;
} 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--;
}
}
@@ -232,12 +241,12 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
ipErrorCount: errorCount
});
try {
await this.sendCheckErrorNotify(site.id, true);
await this.sendCheckErrorNotify(site.id, true,setting);
} catch (e) {
logger.error("send notify error", e);
}
};
if (!site.ipSyncAuto) {
if (site.ipSyncAuto === false) {
await this.siteIpService.checkAll(site, retryTimes,onFinished);
}else{
await this.siteIpService.syncAndCheck(site, retryTimes,onFinished);
@@ -246,7 +255,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
/**
* 检查
* 检查,不等待返回
* @param id
* @param notify
* @param retryTimes
@@ -256,13 +265,16 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (!site) {
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 url = await this.notificationService.getBindUrl("#/certd/monitor/site");
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
// 发邮件
await this.notificationService.send(
{
@@ -281,11 +293,9 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
);
}
async sendExpiresNotify(siteId: number) {
const site = await this.info(siteId);
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, UserSiteMonitorSetting)
async sendExpiresNotify(siteId: number,setting: UserSiteMonitorSetting) {
const tipDays = setting?.certValidDays || 10;
const site = await this.info(siteId);
const expires = site.certExpiresTime;
const validDays = dayjs(expires).diff(dayjs(), "day");
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");

View File

@@ -76,7 +76,7 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
}
//从域名解析中获取所有ip
const ips = await this.getAllIpsFromDomain(domain,resolver);
const ips = await this.getAllIpsFromDomain(domain,resolver,entity.ipSyncMode);
if (ips.length === 0 ) {
logger.warn(`没有发现${domain}的IP`)
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){
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[]> => {
try{
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 [...res[0], ...res[1]];