From c53bb7cf677faa32729709ae0c10359db5194d7a Mon Sep 17 00:00:00 2001 From: xiaojunnuo Date: Mon, 7 Jul 2025 00:10:51 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E8=AF=81=E4=B9=A6=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A=E4=B9=89dns=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/basic/src/utils/index.ts | 1 + packages/core/basic/src/utils/util.cache.ts | 47 ++++++++++- .../ui/certd-client/src/style/antdv4.less | 5 ++ .../src/views/certd/monitor/site/crud.tsx | 3 +- .../src/views/certd/monitor/site/ip/crud.tsx | 1 + .../certd/monitor/site/setting/index.vue | 4 +- packages/ui/certd-server/package.json | 1 - .../src/modules/mine/service/models.ts | 1 + .../mine/service/user-settings-service.ts | 21 ++++- .../src/modules/monitor/service/dns-custom.ts | 79 +++++++++++++++++++ .../monitor/service/site-info-service.ts | 12 ++- .../monitor/service/site-ip-service.ts | 63 ++++++++------- .../modules/monitor/service/site-tester.ts | 45 ++++------- 13 files changed, 217 insertions(+), 66 deletions(-) create mode 100644 packages/ui/certd-server/src/modules/monitor/service/dns-custom.ts diff --git a/packages/core/basic/src/utils/index.ts b/packages/core/basic/src/utils/index.ts index 94b2109f..e3ea06d6 100644 --- a/packages/core/basic/src/utils/index.ts +++ b/packages/core/basic/src/utils/index.ts @@ -34,6 +34,7 @@ import { locker } from "./util.lock.js"; import { mitter } from "./util.mitter.js"; import * as request from "./util.request.js"; +export * from "./util.cache.js"; export const utils = { sleep, http, diff --git a/packages/core/basic/src/utils/util.cache.ts b/packages/core/basic/src/utils/util.cache.ts index a9788758..460e1673 100644 --- a/packages/core/basic/src/utils/util.cache.ts +++ b/packages/core/basic/src/utils/util.cache.ts @@ -1,8 +1,53 @@ // LRUCache -import { LRUCache } from 'lru-cache'; +import { LRUCache } from "lru-cache"; export const cache = new LRUCache({ max: 1000, ttl: 1000 * 60 * 10, }); + +export class LocalCache { + cache: Map; + constructor(opts: { clearInterval?: number } = {}) { + this.cache = new Map(); + setInterval(() => { + this.clearExpires(); + }, opts.clearInterval ?? 5 * 60 * 1000); + } + + get(key: string): V | undefined { + const entry = this.cache.get(key); + if (!entry) { + return undefined; + } + + // 检查是否过期 + if (Date.now() > entry.expiresAt) { + this.cache.delete(key); + return undefined; + } + + return entry.value; + } + + set(key: string, value: V, ttl = 300000) { + // 默认5分钟 (300000毫秒) + this.cache.set(key, { + value, + expiresAt: Date.now() + ttl, + }); + } + + clear() { + this.cache.clear(); + } + + clearExpires() { + for (const [key, entry] of this.cache) { + if (entry.expiresAt < Date.now()) { + this.cache.delete(key); + } + } + } +} diff --git a/packages/ui/certd-client/src/style/antdv4.less b/packages/ui/certd-client/src/style/antdv4.less index e5dd6c23..12a1d6aa 100644 --- a/packages/ui/certd-client/src/style/antdv4.less +++ b/packages/ui/certd-client/src/style/antdv4.less @@ -67,3 +67,8 @@ footer { margin-inline-end: calc(-3em - 8px); padding-inline-end: calc(3em + 8px); } + + +.ant-progress .ant-progress-text{ + width:3em; +} \ No newline at end of file diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx index 0cced755..d8610ad6 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/site/crud.tsx @@ -47,6 +47,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const { openSiteIpMonitorDialog } = useSiteIpMonitor(); const { openSiteImportDialog } = useSiteImport(); return { + id: "siteMonitorCrud", crudOptions: { request: { pageRequest, @@ -117,7 +118,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat }, rowHandle: { fixed: "right", - width: 240, + width: 280, buttons: { check: { order: 0, diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx b/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx index 1da523c9..b591ecec 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx +++ b/packages/ui/certd-client/src/views/certd/monitor/site/ip/crud.tsx @@ -45,6 +45,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat const { openSiteIpImportDialog } = useSiteIpMonitor(); return { crudOptions: { + id: "siteIpCrud", request: { pageRequest, addRequest, diff --git a/packages/ui/certd-client/src/views/certd/monitor/site/setting/index.vue b/packages/ui/certd-client/src/views/certd/monitor/site/setting/index.vue index e394a17d..178be300 100644 --- a/packages/ui/certd-client/src/views/certd/monitor/site/setting/index.vue +++ b/packages/ui/certd-client/src/views/certd/monitor/site/setting/index.vue @@ -17,12 +17,12 @@
{{ t("certd.monitor.setting.monitorRetryTimes") }}
- +
diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json index e7db0f18..f419dd82 100644 --- a/packages/ui/certd-server/package.json +++ b/packages/ui/certd-server/package.json @@ -82,7 +82,6 @@ "cross-env": "^7.0.3", "crypto-js": "^4.2.0", "dayjs": "^1.11.7", - "dns2": "^2.1.0", "form-data": "^4.0.0", "glob": "^11.0.0", "https-proxy-agent": "^7.0.5", 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 b4aadf27..f74cebdb 100644 --- a/packages/ui/certd-server/src/modules/mine/service/models.ts +++ b/packages/ui/certd-server/src/modules/mine/service/models.ts @@ -27,6 +27,7 @@ export class UserSiteMonitorSetting extends BaseSettings { notificationId?:number= 0; cron?:string = undefined; retryTimes?:number = 3; + dnsServer?:string[] = undefined; } export class UserEmailSetting extends BaseSettings { diff --git a/packages/ui/certd-server/src/modules/mine/service/user-settings-service.ts b/packages/ui/certd-server/src/modules/mine/service/user-settings-service.ts index b036a9e9..d6b28950 100644 --- a/packages/ui/certd-server/src/modules/mine/service/user-settings-service.ts +++ b/packages/ui/certd-server/src/modules/mine/service/user-settings-service.ts @@ -3,8 +3,13 @@ import { InjectEntityModel } from "@midwayjs/typeorm"; import { Repository } from "typeorm"; import { BaseService, BaseSettings } from "@certd/lib-server"; import { UserSettingsEntity } from "../entity/user-settings.js"; -import { mergeUtils } from "@certd/basic"; +import { LocalCache, mergeUtils } from "@certd/basic"; const {merge} = mergeUtils + +const UserSettingCache = new LocalCache({ + clearInterval: 5 * 60 * 1000, +}); + /** * 授权 */ @@ -75,14 +80,26 @@ export class UserSettingsService extends BaseService { } - async getSetting( userId: number,type: any): Promise { + async getSetting( userId: number,type: any, cache:boolean = false): Promise { if(!userId){ throw new Error('userId is required'); } const key = type.__key__; + const cacheKey = key + '_' + userId; + if (cache) { + const settings: T = UserSettingCache.get(cacheKey); + if (settings) { + return settings; + } + } + let newSetting: T = new type(); const savedSettings = await this.getSettingByKey(key, userId); newSetting = merge(newSetting, savedSettings); + + if (cache) { + UserSettingCache.set(cacheKey, newSetting); + } return newSetting; } diff --git a/packages/ui/certd-server/src/modules/monitor/service/dns-custom.ts b/packages/ui/certd-server/src/modules/monitor/service/dns-custom.ts new file mode 100644 index 00000000..39d321cb --- /dev/null +++ b/packages/ui/certd-server/src/modules/monitor/service/dns-custom.ts @@ -0,0 +1,79 @@ +import { LocalCache } from '@certd/basic'; +import dnsSdk from 'dns' +const dns = dnsSdk.promises + +export class DnsCustom{ + resolver: any; + + constructor(dnsServers:string[]) { + const resolver = new dns.Resolver(); + resolver.setServers(dnsServers); + this.resolver = resolver; + } + async resolve(hostname:string,options:any):Promise{ + // { family: undefined, hints: 0, all: true } + + const cnames = await this.resolver.resolveCname(hostname) + let cnameIps = [] + // deep + if (cnames && cnames.length > 0) { + for (let cname of cnames) { + const cnameIp = await this.resolve(cname,options) + if (cnameIp && cnameIp.length > 0) { + cnameIps.push(...cnameIp) + } + } + } + let v4 = [] + let v6 = [] + + const {family, all} = options + if(family === 6 && !all){ + v6= await this.resolver.resolve6(hostname) + } + if(family === 4 && !all){ + v4 = await this.resolver.resolve4(hostname) + } + + if(all){ + v4 = await this.resolver.resolve4(hostname) + v6 = await this.resolver.resolve6(hostname) + } + + return [...v4,...v6,...cnameIps] + } + + async resolve4(hostname:string,options:any):Promise{ + return await this.resolver.resolve4(hostname,options) + } + async resolve6(hostname:string,options:any):Promise{ + return await this.resolver.resolve6(hostname,options) + } + async resolveAny(hostname:string,options:any):Promise{ + return await this.resolver.resolveAny(hostname,options) + } + + async resolveCname(hostname:string,options:any):Promise{ + return await this.resolver.resolveCname(hostname,options) + } + + +} + +export class DnsContainer{ + bucket: LocalCache = new LocalCache() + + constructor() {} + getDns(server:string[]){ + const key = server.join(',') + let dns = this.bucket.get(key) + if (dns){ + return dns + } + dns = new DnsCustom(server) + this.bucket.set(key,dns) + return dns + } +} + +export const dnsContainer = new DnsContainer() 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 f39bb34e..62126bcf 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 @@ -15,6 +15,7 @@ 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"; +import { dnsContainer } from "./dns-custom.js"; @Provide() @Scope(ScopeEnum.Request, {allowDowngrade: true}) @@ -108,6 +109,14 @@ export class SiteInfoService extends BaseService { if (!site?.domain) { throw new Error("站点域名不能为空"); } + + const setting = await this.userSettingsService.getSetting(site.userId, UserSiteMonitorSetting); + const dnsServer = setting.dnsServer + let resolver = null + if (dnsServer && dnsServer.length > 0) { + resolver = dnsContainer.getDns(dnsServer) as any + } + try { await this.update({ id: site.id, @@ -117,7 +126,8 @@ export class SiteInfoService extends BaseService { const res = await siteTester.test({ host: site.domain, port: site.httpsPort, - retryTimes + retryTimes, + resolver }); const certi: PeerCertificate = res.certificate; diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts b/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts index 5eb0f251..e79f4a83 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-ip-service.ts @@ -7,11 +7,15 @@ import {NotificationService} from "../../pipeline/service/notification-service.j import {UserSuiteService} from "@certd/commercial-core"; import {UserSettingsService} from "../../mine/service/user-settings-service.js"; import {SiteIpEntity} from "../entity/site-ip.js"; -import dns from "dns"; -import {logger, safePromise} from "@certd/basic"; +import dnsSdk from "dns"; +import {logger} from "@certd/basic"; import dayjs from "dayjs"; import {siteTester} from "./site-tester.js"; import {PeerCertificate} from "tls"; +import { UserSiteMonitorSetting } from "../../mine/service/models.js"; +import { dnsContainer } from "./dns-custom.js"; + +const dns = dnsSdk.promises; @Provide() @Scope(ScopeEnum.Request, { allowDowngrade: true }) @@ -62,11 +66,20 @@ export class SiteIpService extends BaseService { async sync(entity: SiteInfoEntity,check:boolean = true) { const domain = entity.domain; + + const setting = await this.userSettingsService.getSetting(entity.userId, UserSiteMonitorSetting); + + const dnsServer = setting.dnsServer + let resolver = dns + if (dnsServer && dnsServer.length > 0) { + resolver = dnsContainer.getDns(dnsServer) as any + } + //从域名解析中获取所有ip - const ips = await this.getAllIpsFromDomain(domain); + const ips = await this.getAllIpsFromDomain(domain,resolver); if (ips.length === 0 ) { logger.warn(`没有发现${domain}的IP`) - return + return } const oldIps = await this.repository.find({ @@ -86,7 +99,7 @@ export class SiteIpService extends BaseService { hasChanged = false } } - + if(hasChanged){ logger.info(`发现${domain}的IP变化,需要更新,旧IP:${oldIps.map(ip=>ip.ipAddress).join(",")},新IP:${ips.join(",")}`) //有变化需要更新 @@ -213,30 +226,26 @@ export class SiteIpService extends BaseService { }) } - async getAllIpsFromDomain(domain: string) { - const getFromV4 = safePromise((resolve, reject) => { - dns.resolve4(domain, (err, addresses) => { - if (err) { - logger.error(`[${domain}] resolve4 error`, err) - resolve([]) - return; - } - resolve(addresses); - }); - }); + async getAllIpsFromDomain(domain: string,resolver:any = dns):Promise { + const getFromV4 = async ():Promise => { + try{ + return await resolver.resolve4(domain); + }catch (err) { + logger.error(`[${domain}] resolve4 error`, err) + return [] + } + } + const getFromV6 = async ():Promise => { + try{ + return await resolver.resolve6(domain); + }catch (err) { + logger.error(`[${domain}] resolve6 error`, err) + return [] + } + } - const getFromV6 = safePromise((resolve, reject) => { - dns.resolve6(domain, (err, addresses) => { - if (err) { - logger.error("[${domain}] resolve6 error", err) - resolve([]) - return; - } - resolve(addresses); - }); - }); - return Promise.all([getFromV4, getFromV6]).then(res => { + return Promise.all([getFromV4(), getFromV6()]).then(res => { return [...res[0], ...res[1]]; }); } diff --git a/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts b/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts index 06f41557..a25e64ce 100644 --- a/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts +++ b/packages/ui/certd-server/src/modules/monitor/service/site-tester.ts @@ -2,7 +2,6 @@ import { logger, safePromise, utils } from "@certd/basic"; import { merge } from "lodash-es"; import https from "https"; import { PeerCertificate } from "tls"; -// import { TCPClient } from "dns2"; export type SiteTestReq = { host: string; // 只用域名部分 @@ -11,7 +10,7 @@ export type SiteTestReq = { retryTimes?: number; ipAddress?: string; - dnsServer?: string[]; + resolver?: any; }; export type SiteTestRes = { @@ -52,6 +51,7 @@ export class SiteTester { req ); + let customLookup = null if (req.ipAddress) { //使用固定的ip const ipAddress = req.ipAddress; @@ -61,37 +61,20 @@ export class SiteTester { servername: options.host }; options.host = ipAddress; + }else if (req.resolver ) { + // 非ip address 请求时 + const resolver = req.resolver + customLookup = async (hostname:string, options:any, callback)=> { + console.log(hostname, options); + + // { family: undefined, hints: 0, all: true } + const res = await resolver.resolve(hostname, options) + console.log("custom lookup res:",res) + callback(null, res); + } } - // let dnsClients = []; - // if (req.dnsServer && req.dnsServer.length > 0) { - // for (let dns of req.dnsServer) { - // const dnsClient = TCPClient({ dns }); - // dnsClients.push(dnsClient); - // } - // } - - // async function customLookup(hostname, options, callback) { - // for (let client of dnsClients) { - // try { - // const result = await client.resolve(hostname, options); - // return callback(null, result); - // } catch (e) { - // this.logger.error(e); - // } - // } - // try { - // // 使用自定义DNS解析 - // const response = await dnsClients - // const address = response.answers[0].address; - // callback(null, address, 4); - // } catch (err) { - // // 解析失败时回退到系统DNS - // require('dns').lookup(hostname, options, callback); - // } - // } - - options.agent = new https.Agent({ keepAlive: false }); + options.agent = new https.Agent({ keepAlive: false, lookup: customLookup }); // 创建 HTTPS 请求 const requestPromise = safePromise((resolve, reject) => {