2025-07-09 16:00:55 +08:00
|
|
|
import {Inject, Provide, Scope, ScopeEnum} from '@midwayjs/core';
|
|
|
|
|
import {InjectEntityModel} from '@midwayjs/typeorm';
|
2025-07-13 18:25:09 +08:00
|
|
|
import {In, Not, Repository} from 'typeorm';
|
2025-07-09 16:00:55 +08:00
|
|
|
import {AccessService, BaseService} from '@certd/lib-server';
|
|
|
|
|
import {DomainEntity} from '../entity/domain.js';
|
2025-07-13 18:25:09 +08:00
|
|
|
import {SubDomainService} from "../../pipeline/service/sub-domain-service.js";
|
2026-01-16 18:18:39 +08:00
|
|
|
import {createDnsProvider, DomainParser} from "@certd/plugin-lib";
|
2025-07-13 18:25:09 +08:00
|
|
|
import {DomainVerifiers} from "@certd/plugin-cert";
|
|
|
|
|
import { SubDomainsGetter } from '../../pipeline/service/getter/sub-domain-getter.js';
|
2025-07-13 23:08:00 +08:00
|
|
|
import { CnameRecordService } from '../../cname/service/cname-record-service.js';
|
|
|
|
|
import { CnameRecordEntity } from "../../cname/entity/cname-record.js";
|
2026-01-16 18:18:39 +08:00
|
|
|
import { http, logger, utils } from '@certd/basic';
|
|
|
|
|
import { TaskServiceBuilder } from '../../pipeline/service/getter/task-service-getter.js';
|
|
|
|
|
import { Pager } from '@certd/pipeline';
|
2025-07-09 16:00:55 +08:00
|
|
|
|
2026-01-16 18:18:39 +08:00
|
|
|
export interface SyncFromProviderReq {
|
|
|
|
|
userId: number;
|
|
|
|
|
dnsProviderType: string;
|
|
|
|
|
dnsProviderAccessId: string;
|
|
|
|
|
}
|
2025-07-09 16:00:55 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
@Provide()
|
|
|
|
|
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
|
|
|
|
export class DomainService extends BaseService<DomainEntity> {
|
|
|
|
|
@InjectEntityModel(DomainEntity)
|
|
|
|
|
repository: Repository<DomainEntity>;
|
|
|
|
|
|
|
|
|
|
@Inject()
|
|
|
|
|
accessService: AccessService;
|
2025-07-13 18:25:09 +08:00
|
|
|
@Inject()
|
|
|
|
|
subDomainService: SubDomainService;
|
2025-07-09 16:00:55 +08:00
|
|
|
|
2025-07-13 23:08:00 +08:00
|
|
|
@Inject()
|
|
|
|
|
cnameRecordService: CnameRecordService;
|
|
|
|
|
|
2026-01-16 18:18:39 +08:00
|
|
|
@Inject()
|
|
|
|
|
taskServiceBuilder: TaskServiceBuilder;
|
|
|
|
|
|
2025-07-09 16:00:55 +08:00
|
|
|
//@ts-ignore
|
|
|
|
|
getRepository() {
|
|
|
|
|
return this.repository;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-10 17:00:47 +08:00
|
|
|
async add(param) {
|
|
|
|
|
if (param.userId == null ){
|
|
|
|
|
throw new Error('userId 不能为空');
|
|
|
|
|
}
|
|
|
|
|
if (!param.domain) {
|
|
|
|
|
throw new Error('domain 不能为空');
|
|
|
|
|
}
|
|
|
|
|
const old = await this.repository.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
domain: param.domain,
|
|
|
|
|
userId: param.userId
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
if (old) {
|
|
|
|
|
throw new Error(`域名(${param.domain})不能重复`);
|
|
|
|
|
}
|
|
|
|
|
return await super.add(param);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async update(param) {
|
|
|
|
|
if (!param.id) {
|
|
|
|
|
throw new Error('id 不能为空');
|
|
|
|
|
}
|
|
|
|
|
const old = await this.info(param.id)
|
|
|
|
|
if (!old) {
|
|
|
|
|
throw new Error('domain记录不存在');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const same = await this.repository.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
domain: param.domain,
|
|
|
|
|
userId: old.userId,
|
|
|
|
|
id: Not(param.id)
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (same) {
|
|
|
|
|
throw new Error(`域名(${param.domain})不能重复`);
|
|
|
|
|
}
|
|
|
|
|
delete param.userId
|
|
|
|
|
return await super.update(param);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-13 18:25:09 +08:00
|
|
|
/**
|
|
|
|
|
*
|
|
|
|
|
* @param userId
|
|
|
|
|
* @param domains //去除* 且去重之后的域名列表
|
|
|
|
|
*/
|
|
|
|
|
async getDomainVerifiers(userId: number, domains: string[]):Promise<DomainVerifiers> {
|
|
|
|
|
|
|
|
|
|
const mainDomainMap:Record<string, string> = {}
|
|
|
|
|
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
|
|
|
|
const domainParser = new DomainParser(subDomainGetter)
|
|
|
|
|
|
|
|
|
|
const mainDomains = []
|
|
|
|
|
for (const domain of domains) {
|
|
|
|
|
const mainDomain = await domainParser.parse(domain);
|
|
|
|
|
mainDomainMap[domain] = mainDomain;
|
|
|
|
|
mainDomains.push(mainDomain)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//匹配DNS记录
|
|
|
|
|
let allDomains = [...domains,...mainDomains]
|
|
|
|
|
//去重
|
|
|
|
|
allDomains = [...new Set(allDomains)]
|
|
|
|
|
|
2025-07-13 23:08:00 +08:00
|
|
|
//从 domain 表中获取配置
|
2025-07-13 18:25:09 +08:00
|
|
|
const domainRecords = await this.find({
|
|
|
|
|
where: {
|
|
|
|
|
domain: In(allDomains),
|
2025-07-13 23:08:00 +08:00
|
|
|
userId,
|
|
|
|
|
disabled:false,
|
2025-07-13 18:25:09 +08:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const dnsMap = domainRecords.filter(item=>item.challengeType === 'dns').reduce((pre, item) => {
|
|
|
|
|
pre[item.domain] = item
|
|
|
|
|
return pre
|
|
|
|
|
}, {})
|
2025-07-13 23:08:00 +08:00
|
|
|
|
|
|
|
|
const httpMap = domainRecords.filter(item=>item.challengeType === 'http').reduce((pre, item) => {
|
2025-07-13 18:25:09 +08:00
|
|
|
pre[item.domain] = item
|
|
|
|
|
return pre
|
|
|
|
|
}, {})
|
2025-07-13 23:08:00 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
//从cname record表中获取配置
|
|
|
|
|
const cnameRecords = await this.cnameRecordService.find({
|
|
|
|
|
where: {
|
|
|
|
|
domain: In(allDomains),
|
|
|
|
|
userId,
|
|
|
|
|
status: "valid",
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const cnameMap = cnameRecords.reduce((pre, item) => {
|
2025-07-13 18:25:09 +08:00
|
|
|
pre[item.domain] = item
|
|
|
|
|
return pre
|
|
|
|
|
}, {})
|
|
|
|
|
|
2025-07-13 23:08:00 +08:00
|
|
|
//构建域名验证计划
|
2025-07-13 18:25:09 +08:00
|
|
|
const domainVerifiers:DomainVerifiers = {}
|
|
|
|
|
|
|
|
|
|
for (const domain of domains) {
|
|
|
|
|
const mainDomain = mainDomainMap[domain]
|
|
|
|
|
|
|
|
|
|
const dnsRecord = dnsMap[mainDomain]
|
|
|
|
|
if (dnsRecord) {
|
|
|
|
|
domainVerifiers[domain] = {
|
|
|
|
|
domain,
|
|
|
|
|
mainDomain,
|
|
|
|
|
type: 'dns',
|
|
|
|
|
dns: {
|
|
|
|
|
dnsProviderType: dnsRecord.dnsProviderType,
|
2025-07-13 23:08:00 +08:00
|
|
|
dnsProviderAccessId: dnsRecord.dnsProviderAccess
|
2025-07-13 18:25:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-07-13 23:58:07 +08:00
|
|
|
const cnameRecord:CnameRecordEntity = cnameMap[domain]
|
2025-07-13 18:25:09 +08:00
|
|
|
if (cnameRecord) {
|
|
|
|
|
domainVerifiers[domain] = {
|
|
|
|
|
domain,
|
|
|
|
|
mainDomain,
|
|
|
|
|
type: 'cname',
|
|
|
|
|
cname: {
|
2025-07-13 23:08:00 +08:00
|
|
|
domain: cnameRecord.domain,
|
|
|
|
|
hostRecord: cnameRecord.hostRecord,
|
|
|
|
|
recordValue: cnameRecord.recordValue
|
2025-07-13 18:25:09 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
2025-07-13 23:58:07 +08:00
|
|
|
const httpRecord = httpMap[domain]
|
2025-07-13 18:25:09 +08:00
|
|
|
if (httpRecord) {
|
|
|
|
|
domainVerifiers[domain] = {
|
|
|
|
|
domain,
|
|
|
|
|
mainDomain,
|
|
|
|
|
type: 'http',
|
|
|
|
|
http: {
|
|
|
|
|
httpUploaderType: httpRecord.httpUploaderType,
|
|
|
|
|
httpUploaderAccess: httpRecord.httpUploaderAccess,
|
|
|
|
|
httpUploadRootDir: httpRecord.httpUploadRootDir
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
domainVerifiers[domain] = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return domainVerifiers;
|
|
|
|
|
}
|
2026-01-16 18:18:39 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async syncFromProvider(req: SyncFromProviderReq) {
|
|
|
|
|
const { userId, dnsProviderType, dnsProviderAccessId } = req;
|
|
|
|
|
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
|
|
|
|
const domainParser = new DomainParser(subDomainGetter)
|
|
|
|
|
const serviceGetter = this.taskServiceBuilder.create({ userId });
|
|
|
|
|
const access = await this.accessService.getById(dnsProviderAccessId, userId);
|
|
|
|
|
const context = { access, logger, http, utils, domainParser, serviceGetter };
|
|
|
|
|
// 翻页查询dns的记录
|
|
|
|
|
const dnsProvider = await createDnsProvider({dnsProviderType,context})
|
|
|
|
|
|
|
|
|
|
const pager = new Pager({
|
|
|
|
|
pageNo: 1,
|
|
|
|
|
pageSize: 100,
|
|
|
|
|
})
|
|
|
|
|
const challengeType = "dns"
|
|
|
|
|
|
|
|
|
|
const importDomain = async(domainRecord: any) =>{
|
|
|
|
|
const domain = domainRecord.domain
|
|
|
|
|
const old = await this.findOne({
|
|
|
|
|
where: {
|
|
|
|
|
domain,
|
|
|
|
|
userId,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
if (old) {
|
2026-01-20 00:13:05 +08:00
|
|
|
const updateObj :any={
|
|
|
|
|
id: old.id,
|
|
|
|
|
registrationDate: domainRecord.registrationDate,
|
|
|
|
|
expirationDate: domainRecord.expirationDate,
|
|
|
|
|
}
|
|
|
|
|
if (old.fromType !== 'manual'){
|
|
|
|
|
//如果不是手动的,更新校验配置
|
|
|
|
|
updateObj.dnsProviderType = dnsProviderType
|
|
|
|
|
updateObj.dnsProviderAccess = dnsProviderAccessId
|
|
|
|
|
updateObj.challengeType = challengeType
|
|
|
|
|
}
|
2026-01-16 18:18:39 +08:00
|
|
|
//更新
|
2026-01-20 00:13:05 +08:00
|
|
|
await this.update(updateObj)
|
2026-01-16 18:18:39 +08:00
|
|
|
} else {
|
|
|
|
|
//添加
|
|
|
|
|
await this.add({
|
|
|
|
|
userId,
|
|
|
|
|
domain,
|
|
|
|
|
dnsProviderType,
|
|
|
|
|
dnsProviderAccess: dnsProviderAccessId,
|
|
|
|
|
challengeType,
|
2026-01-20 00:13:05 +08:00
|
|
|
disabled: false,
|
|
|
|
|
fromType: 'auto',
|
|
|
|
|
registrationDate: domainRecord.registrationDate,
|
|
|
|
|
expirationDate: domainRecord.expirationDate,
|
2026-01-16 18:18:39 +08:00
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const start = async ()=>{
|
|
|
|
|
let count = 0
|
|
|
|
|
while(true){
|
|
|
|
|
const pageRes = await dnsProvider.getDomainListPage(pager)
|
|
|
|
|
if(!pageRes || !pageRes.list || pageRes.list.length === 0){
|
|
|
|
|
//遍历完成
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
//处理
|
|
|
|
|
for (const domainRecord of pageRes.list) {
|
|
|
|
|
await importDomain(domainRecord)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
count += pageRes.list.length
|
|
|
|
|
if(pageRes.total>0 && count >= pageRes.total){
|
|
|
|
|
//遍历完成
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
pager.pageNo++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start()
|
|
|
|
|
|
|
|
|
|
}
|
2025-07-09 16:00:55 +08:00
|
|
|
}
|