mirror of
https://github.com/certd/certd.git
synced 2026-04-24 20:57:26 +08:00
fix: 修复cn域名获取不到到期时间的问题
This commit is contained in:
@@ -2,7 +2,7 @@ import { http, logger, utils } from '@certd/basic';
|
|||||||
import { AccessService, BaseService, isEnterprise } from '@certd/lib-server';
|
import { AccessService, BaseService, isEnterprise } from '@certd/lib-server';
|
||||||
import { doPageTurn, Pager, PageRes } from '@certd/pipeline';
|
import { doPageTurn, Pager, PageRes } from '@certd/pipeline';
|
||||||
import { DomainVerifiers } from "@certd/plugin-cert";
|
import { DomainVerifiers } from "@certd/plugin-cert";
|
||||||
import { createDnsProvider, dnsProviderRegistry, DomainParser, parseDomainByPsl } from "@certd/plugin-lib";
|
import { createDnsProvider, dnsProviderRegistry, DomainParser } from "@certd/plugin-lib";
|
||||||
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
|
||||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
@@ -11,13 +11,14 @@ import { In, LessThan, Not, Repository } from 'typeorm';
|
|||||||
import { BackTask, taskExecutor } from '../../basic/service/task-executor.js';
|
import { BackTask, taskExecutor } from '../../basic/service/task-executor.js';
|
||||||
import { CnameRecordEntity } from "../../cname/entity/cname-record.js";
|
import { CnameRecordEntity } from "../../cname/entity/cname-record.js";
|
||||||
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
||||||
|
import { Cron } from '../../cron/cron.js';
|
||||||
import { UserDomainImportSetting, UserDomainMonitorSetting } from '../../mine/service/models.js';
|
import { UserDomainImportSetting, UserDomainMonitorSetting } from '../../mine/service/models.js';
|
||||||
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
import { UserSettingsService } from '../../mine/service/user-settings-service.js';
|
||||||
import { JobHistoryService } from '../../monitor/service/job-history-service.js';
|
import { JobHistoryService } from '../../monitor/service/job-history-service.js';
|
||||||
import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js';
|
import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js';
|
||||||
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
|
import { SubDomainService } from "../../pipeline/service/sub-domain-service.js";
|
||||||
import { DomainEntity } from '../entity/domain.js';
|
import { DomainEntity } from '../entity/domain.js';
|
||||||
import { Cron } from '../../cron/cron.js';
|
import { TldClient } from './tld-client.js';
|
||||||
|
|
||||||
export interface SyncFromProviderReq {
|
export interface SyncFromProviderReq {
|
||||||
userId: number;
|
userId: number;
|
||||||
@@ -487,50 +488,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
|||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
})
|
})
|
||||||
|
|
||||||
const dnsJson = await http.request({
|
const tldClient = new TldClient();
|
||||||
url: "https://data.iana.org/rdap/dns.json",
|
|
||||||
method: "GET",
|
|
||||||
})
|
|
||||||
const rdapMap: Record<string, string> = {}
|
|
||||||
for (const item of dnsJson.services) {
|
|
||||||
// [["store","work"], ["https://rdap.centralnic.com/store/"]],
|
|
||||||
const suffixes = item[0]
|
|
||||||
const urls = item[1]
|
|
||||||
for (const suffix of suffixes) {
|
|
||||||
rdapMap[suffix] = urls[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDomainExpirationDate = async (domain: string) => {
|
|
||||||
const parsed = parseDomainByPsl(domain)
|
|
||||||
const mainDomain = parsed.domain || ''
|
|
||||||
if (mainDomain !== domain) {
|
|
||||||
req.task.addError(`【${domain}】为子域名,跳过同步`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const suffix = parsed.tld || ''
|
|
||||||
const rdapUrl = rdapMap[suffix]
|
|
||||||
if (!rdapUrl) {
|
|
||||||
req.task.addError(`【${domain}】未找到${suffix}的rdap地址`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// https://rdap.nic.work/domain/handsfree.work
|
|
||||||
const rdap = await http.request({
|
|
||||||
url: `${rdapUrl}domain/${domain}`,
|
|
||||||
method: "GET",
|
|
||||||
})
|
|
||||||
|
|
||||||
let res: any = {}
|
|
||||||
const events = rdap.events || []
|
|
||||||
for (const item of events) {
|
|
||||||
if (item.eventAction === 'expiration') {
|
|
||||||
res.expirationDate = dayjs(item.eventDate).valueOf()
|
|
||||||
} else if (item.eventAction === 'registration') {
|
|
||||||
res.registrationDate = dayjs(item.eventDate).valueOf()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
const query: any = {
|
const query: any = {
|
||||||
challengeType: "dns",
|
challengeType: "dns",
|
||||||
}
|
}
|
||||||
@@ -561,10 +519,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
|||||||
const itemHandle = async (item: any) => {
|
const itemHandle = async (item: any) => {
|
||||||
req.task.incrementCurrent()
|
req.task.incrementCurrent()
|
||||||
try {
|
try {
|
||||||
const res = await getDomainExpirationDate(item.domain)
|
const res = await tldClient.getDomainExpirationDate(item.domain)
|
||||||
if (!res) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { expirationDate, registrationDate } = res
|
const { expirationDate, registrationDate } = res
|
||||||
if (!expirationDate) {
|
if (!expirationDate) {
|
||||||
req.task.addError(`【${item.domain}】获取域名${item.domain}过期时间失败`)
|
req.task.addError(`【${item.domain}】获取域名${item.domain}过期时间失败`)
|
||||||
@@ -581,6 +536,7 @@ export class DomainService extends BaseService<DomainEntity> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMsg = `【${item.domain}】${error.message ?? error}`
|
const errorMsg = `【${item.domain}】${error.message ?? error}`
|
||||||
req.task.addError(errorMsg)
|
req.task.addError(errorMsg)
|
||||||
|
logger.error(errorMsg)
|
||||||
} finally {
|
} finally {
|
||||||
await utils.sleep(1000)
|
await utils.sleep(1000)
|
||||||
}
|
}
|
||||||
@@ -645,14 +601,17 @@ export class DomainService extends BaseService<DomainEntity> {
|
|||||||
|
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
const { expirationDate } = item
|
const { expirationDate } = item
|
||||||
|
const leftDays = dayjs(expirationDate).diff(dayjs(), 'day')
|
||||||
|
//@ts-ignore
|
||||||
|
item.leftDays = leftDays
|
||||||
if (expirationDate < now) {
|
if (expirationDate < now) {
|
||||||
hasExpireDomains.push(item.domain)
|
hasExpireDomains.push(item)
|
||||||
} else {
|
} else {
|
||||||
willExpireDomains.push(item.domain)
|
willExpireDomains.push(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = `域名过期检查:共${total}个域名,即将过期${willExpireDomains.length}个域名,已过期${hasExpireDomains.length}个域名`
|
const title = `域名过期检查:即将过期 ${willExpireDomains.length} 个域名,已过期 ${hasExpireDomains.length} 个域名,共 ${total} 个域名`
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.jobHistoryService.update({
|
await this.jobHistoryService.update({
|
||||||
@@ -672,8 +631,15 @@ export class DomainService extends BaseService<DomainEntity> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//发送通知
|
//发送通知
|
||||||
const content = `即将过期域名【${willExpireDomains.length}】:${willExpireDomains.join(',')}
|
const willExpireDomainsStr = willExpireDomains.map(item => `${item.domain} (剩余${item.leftDays}天)`).join('\n ')
|
||||||
\n已过期域名【${hasExpireDomains.length}】:${hasExpireDomains.join(',')}`
|
const hasExpireDomainsStr = hasExpireDomains.map(item => `${item.domain} (已过期${item.leftDays}天)`).join('\n ')
|
||||||
|
const content = `您有域名即将过期,请尽快续费
|
||||||
|
|
||||||
|
即将过期域名: ${willExpireDomains.length} 个 (有效期<${expireDays}天)
|
||||||
|
${willExpireDomainsStr}
|
||||||
|
|
||||||
|
已过期域名: ${hasExpireDomains.length} 个
|
||||||
|
${hasExpireDomainsStr}`
|
||||||
const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId });
|
const taskService = this.taskServiceBuilder.create({ userId: userId, projectId: projectId });
|
||||||
|
|
||||||
const notificationService = await taskService.getNotificationService()
|
const notificationService = await taskService.getNotificationService()
|
||||||
@@ -686,7 +652,10 @@ export class DomainService extends BaseService<DomainEntity> {
|
|||||||
title: title,
|
title: title,
|
||||||
content: content,
|
content: content,
|
||||||
url: url,
|
url: url,
|
||||||
notificationType: DOMAIN_EXPIRE_CHECK_TYPE
|
errorMessage: title,
|
||||||
|
notificationType: DOMAIN_EXPIRE_CHECK_TYPE,
|
||||||
|
willExpireDomains,
|
||||||
|
hasExpireDomains,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,128 @@
|
|||||||
|
|
||||||
|
import { http, logger } from '@certd/basic';
|
||||||
|
import { parseDomainByPsl } from "@certd/plugin-lib";
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
export interface DomainInfo {
|
||||||
|
expirationDate?: number;
|
||||||
|
registrationDate?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TldClient {
|
||||||
|
private rdapMap: Record<string, string> = {}
|
||||||
|
private isInitialized = false;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (this.isInitialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const dnsJson = await http.request({
|
||||||
|
url: "https://data.iana.org/rdap/dns.json",
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
for (const item of dnsJson.services) {
|
||||||
|
const suffixes = item[0]
|
||||||
|
const urls = item[1]
|
||||||
|
for (const suffix of suffixes) {
|
||||||
|
this.rdapMap[suffix] = urls[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isInitialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDomainExpirationDate(domain: string): Promise<DomainInfo> {
|
||||||
|
await this.init();
|
||||||
|
|
||||||
|
const parsed = parseDomainByPsl(domain)
|
||||||
|
const mainDomain = parsed.domain || ''
|
||||||
|
if (mainDomain !== domain) {
|
||||||
|
const message= `【${domain}】为子域名,无法获取过期时间`
|
||||||
|
logger.warn(message)
|
||||||
|
throw new Error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await this.getDomainExpirationByRdap(domain, parsed.tld || '')
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error.message)
|
||||||
|
return await this.getDomainExpirationByWhoiser(domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDomainExpirationByRdap(domain: string, suffix: string): Promise<DomainInfo> {
|
||||||
|
const rdapUrl = this.rdapMap[suffix]
|
||||||
|
if (!rdapUrl) {
|
||||||
|
throw new Error(`【${domain}】未找到${suffix}的rdap地址`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rdap = await http.request({
|
||||||
|
url: `${rdapUrl}domain/${domain}`,
|
||||||
|
method: "GET",
|
||||||
|
})
|
||||||
|
|
||||||
|
let res: DomainInfo = {}
|
||||||
|
const events = rdap.events || []
|
||||||
|
for (const item of events) {
|
||||||
|
if (item.eventAction === 'expiration') {
|
||||||
|
res.expirationDate = dayjs(item.eventDate).valueOf()
|
||||||
|
} else if (item.eventAction === 'registration') {
|
||||||
|
res.registrationDate = dayjs(item.eventDate).valueOf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDomainExpirationByWhoiser(domain: string): Promise<DomainInfo> {
|
||||||
|
const whoiser = await import("whoiser")
|
||||||
|
const result = await whoiser.whoisDomain(domain, {
|
||||||
|
follow: 2,
|
||||||
|
timeout: 5000
|
||||||
|
})
|
||||||
|
|
||||||
|
let res: DomainInfo = {}
|
||||||
|
/**
|
||||||
|
* {
|
||||||
|
"Domain Status": [
|
||||||
|
"ok",
|
||||||
|
],
|
||||||
|
"Name Server": [
|
||||||
|
"dns21.hichina.com",
|
||||||
|
"dns22.hichina.com",
|
||||||
|
],
|
||||||
|
text: [
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
"Domain Name": "docmirror.cn",
|
||||||
|
ROID: "20200907s10001s31265717-cn",
|
||||||
|
"Registrant Name": "肖君诺",
|
||||||
|
"Registrant Email": "252959493@qq.com",
|
||||||
|
Registrar: "阿里巴巴云计算(北京)有限公司",
|
||||||
|
"Created Date": "2020-09-07 09:22:54",
|
||||||
|
"Expiry Date": "2026-09-07 09:22:54",
|
||||||
|
DNSSEC: "unsigned",
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (const server in result) {
|
||||||
|
const data = result[server] as any
|
||||||
|
if (data['Expiry Date']) {
|
||||||
|
res.expirationDate = dayjs(data['Expiry Date']).valueOf()
|
||||||
|
}
|
||||||
|
if (data['Created Date']) {
|
||||||
|
res.registrationDate = dayjs(data['Created Date']).valueOf()
|
||||||
|
}
|
||||||
|
if (res.expirationDate && res.registrationDate) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!res.expirationDate) {
|
||||||
|
throw new Error(`【${domain}】whois查询未找到过期时间`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user