chore: format

This commit is contained in:
xiaojunnuo
2026-05-31 01:41:33 +08:00
parent acd440106b
commit 4b57a0d729
557 changed files with 12530 additions and 14039 deletions
@@ -1,64 +1,64 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { PipelineEntity } from "../../pipeline/entity/pipeline.js";
@Entity('cd_cert_info')
@Entity("cd_cert_info")
export class CertInfoEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
@Column({ name: "user_id", comment: "用户id" })
userId: number;
@Column({ name: 'domain', comment: '主域名' })
@Column({ name: "domain", comment: "主域名" })
domain: string;
@Column({ name: 'domains', comment: '域名' })
@Column({ name: "domains", comment: "域名" })
domains: string;
@Column({ name: 'domain_count', comment: '域名数量' })
@Column({ name: "domain_count", comment: "域名数量" })
domainCount: number;
@Column({ name: 'wildcard_domain_count', comment: '泛域名数量', default: 0 })
@Column({ name: "wildcard_domain_count", comment: "泛域名数量", default: 0 })
wildcardDomainCount: number;
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
@Column({ name: "pipeline_id", comment: "关联流水线id" })
pipelineId: number;
@Column({ name: 'apply_time', comment: '申请时间' })
@Column({ name: "apply_time", comment: "申请时间" })
applyTime: number;
@Column({ name: 'from_type', comment: '来源' })
@Column({ name: "from_type", comment: "来源" })
fromType: string;
@Column({ name: 'cert_provider', comment: '证书颁发机构' })
@Column({ name: "cert_provider", comment: "证书颁发机构" })
certProvider: string;
@Column({ name: 'effective_time', comment: '生效时间' })
@Column({ name: "effective_time", comment: "生效时间" })
effectiveTime: number;
@Column({ name: 'expires_time', comment: '过期时间' })
@Column({ name: "expires_time", comment: "过期时间" })
expiresTime: number;
@Column({ name: 'cert_info', comment: '证书详情' })
@Column({ name: "cert_info", comment: "证书详情" })
certInfo: string;
@Column({ name: 'cert_file', comment: '证书下载' })
@Column({ name: "cert_file", comment: "证书下载" })
certFile: string;
@Column({ name: 'project_id', comment: '项目id' })
@Column({ name: "project_id", comment: "项目id" })
projectId: number;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
name: "create_time",
comment: "创建时间",
default: () => "CURRENT_TIMESTAMP",
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
name: "update_time",
comment: "修改时间",
default: () => "CURRENT_TIMESTAMP",
})
updateTime: Date;
@@ -1,50 +1,49 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { PipelineEntity } from '../../pipeline/entity/pipeline.js';
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { PipelineEntity } from "../../pipeline/entity/pipeline.js";
@Entity('cd_job_history')
@Entity("cd_job_history")
export class JobHistoryEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
@Column({ name: "user_id", comment: "用户id" })
userId: number;
@Column({ name: 'project_id', comment: '项目id' })
@Column({ name: "project_id", comment: "项目id" })
projectId: number;
@Column({ name: 'type', comment: '类型' })
@Column({ name: "type", comment: "类型" })
type: string;
@Column({ name: 'title', comment: '标题' })
@Column({ name: "title", comment: "标题" })
title: string;
@Column({ name: 'content', comment: '内容' })
@Column({ name: "content", comment: "内容" })
content: string;
@Column({ name: 'related_id', comment: '关联id' })
@Column({ name: "related_id", comment: "关联id" })
relatedId: string;
@Column({ name: 'result', comment: '结果' })
@Column({ name: "result", comment: "结果" })
result: string;
@Column({ name: 'start_at', comment: '开始时间' })
@Column({ name: "start_at", comment: "开始时间" })
startAt: number;
@Column({ name: 'end_at', comment: '结束时间' })
@Column({ name: "end_at", comment: "结束时间" })
endAt: number;
@Column({
name: 'create_time',
comment: '创建时间',
default: () => 'CURRENT_TIMESTAMP',
name: "create_time",
comment: "创建时间",
default: () => "CURRENT_TIMESTAMP",
})
createTime: Date;
@Column({
name: 'update_time',
comment: '修改时间',
default: () => 'CURRENT_TIMESTAMP',
name: "update_time",
comment: "修改时间",
default: () => "CURRENT_TIMESTAMP",
})
updateTime: Date;
@@ -1,84 +1,82 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
/**
*/
@Entity('cd_site_info')
@Entity("cd_site_info")
export class SiteInfoEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
@Column({ name: "user_id", comment: "用户id" })
userId: number;
@Column({ name: 'name', comment: '站点名称', length: 100 })
@Column({ name: "name", comment: "站点名称", length: 100 })
name: string;
@Column({ name: 'domain', comment: '域名', length: 100 })
@Column({ name: "domain", comment: "域名", length: 100 })
domain: string;
@Column({ name: 'https_port', comment: '端口' })
@Column({ name: "https_port", comment: "端口" })
httpsPort: number;
@Column({ name: 'cert_domains', comment: '证书域名', length: 4096 })
@Column({ name: "cert_domains", comment: "证书域名", length: 4096 })
certDomains: string;
@Column({ name: 'cert_info', comment: '证书详情', length: 4096 })
@Column({ name: "cert_info", comment: "证书详情", length: 4096 })
certInfo: string;
@Column({ name: 'cert_status', comment: '证书状态', length: 100 })
@Column({ name: "cert_status", comment: "证书状态", length: 100 })
certStatus: string;
@Column({ name: 'cert_provider', comment: '证书颁发机构', length: 100 })
@Column({ name: "cert_provider", comment: "证书颁发机构", length: 100 })
certProvider: string;
@Column({ name: 'cert_effective_time', comment: '证书生效时间' })
@Column({ name: "cert_effective_time", comment: "证书生效时间" })
certEffectiveTime: number;
@Column({ name: 'cert_expires_time', comment: '证书到期时间' })
@Column({ name: "cert_expires_time", comment: "证书到期时间" })
certExpiresTime: number;
@Column({ name: 'last_check_time', comment: '上次检查时间' })
@Column({ name: "last_check_time", comment: "上次检查时间" })
lastCheckTime: number;
@Column({ name: 'check_status', comment: '检查状态' })
@Column({ name: "check_status", comment: "检查状态" })
checkStatus: string;
@Column({ name: 'error', comment: '错误信息' })
@Column({ name: "error", comment: "错误信息" })
error: string;
@Column({ name: 'pipeline_id', comment: '关联流水线id' })
@Column({ name: "pipeline_id", comment: "关联流水线id" })
pipelineId: number;
@Column({ name: 'cert_info_id', comment: '证书id' })
@Column({ name: "cert_info_id", comment: "证书id" })
certInfoId: number;
@Column({ name: 'ip_check', comment: '是否检查IP' })
@Column({ name: "ip_check", comment: "是否检查IP" })
ipCheck: boolean;
@Column({ name: 'ip_sync_auto', comment: '是否自动同步IP' })
@Column({ name: "ip_sync_auto", comment: "是否自动同步IP" })
ipSyncAuto: boolean;
@Column({ name: 'ip_sync_mode', comment: 'IP同步模式' })
@Column({ name: "ip_sync_mode", comment: "IP同步模式" })
ipSyncMode: string;
@Column({ name: 'ip_ignore_coherence', comment: '忽略证书一致性' })
@Column({ name: "ip_ignore_coherence", comment: "忽略证书一致性" })
ipIgnoreCoherence: boolean;
@Column({ name: 'ip_count', comment: 'ip数量' })
ipCount: number
@Column({ name: "ip_count", comment: "ip数量" })
ipCount: number;
@Column({ name: 'ip_error_count', comment: 'ip异常数量' })
ipErrorCount: number
@Column({ name: "ip_error_count", comment: "ip异常数量" })
ipErrorCount: number;
@Column({ name: 'disabled', comment: '禁用启用' })
@Column({ name: "disabled", comment: "禁用启用" })
disabled: boolean;
@Column({ name: 'remark', comment: '备注', length: 512 })
@Column({ name: "remark", comment: "备注", length: 512 })
remark: string;
@Column({ name: 'group_id', comment: '分组id' })
@Column({ name: "group_id", comment: "分组id" })
groupId: number;
@Column({ name: 'ip_address', comment: 'IP地址', length: 128 })
@Column({ name: "ip_address", comment: "IP地址", length: 128 })
ipAddress: string;
@Column({ name: 'project_id', comment: '项目id' })
@Column({ name: "project_id", comment: "项目id" })
projectId: number;
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
createTime: Date;
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
updateTime: Date;
}
@@ -1,43 +1,43 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
/**
*/
@Entity('cd_site_ip')
@Entity("cd_site_ip")
export class SiteIpEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ name: 'user_id', comment: '用户id' })
@Column({ name: "user_id", comment: "用户id" })
userId: number;
@Column({ name: 'site_id', comment: '站点id' })
@Column({ name: "site_id", comment: "站点id" })
siteId: number;
@Column({ name: 'ip_address', comment: 'IP', length: 100 })
@Column({ name: "ip_address", comment: "IP", length: 100 })
ipAddress: string;
@Column({ name: 'cert_domains', comment: '证书域名', length: 4096 })
@Column({ name: "cert_domains", comment: "证书域名", length: 4096 })
certDomains: string;
@Column({ name: 'cert_status', comment: '证书状态', length: 100 })
@Column({ name: "cert_status", comment: "证书状态", length: 100 })
certStatus: string;
@Column({ name: 'cert_provider', comment: '证书颁发机构', length: 100 })
@Column({ name: "cert_provider", comment: "证书颁发机构", length: 100 })
certProvider: string;
@Column({ name: 'cert_expires_time', comment: '证书到期时间' })
@Column({ name: "cert_expires_time", comment: "证书到期时间" })
certExpiresTime: number;
@Column({ name: 'last_check_time', comment: '上次检查时间' })
@Column({ name: "last_check_time", comment: "上次检查时间" })
lastCheckTime: number;
@Column({ name: 'check_status', comment: '检查状态' })
@Column({ name: "check_status", comment: "检查状态" })
checkStatus: string;
@Column({ name: 'error', comment: '错误信息' })
@Column({ name: "error", comment: "错误信息" })
error: string;
@Column({ name: 'from', comment: '来源' })
from: string
@Column({ name: 'remark', comment: '备注' })
@Column({ name: "from", comment: "来源" })
from: string;
@Column({ name: "remark", comment: "备注" })
remark: string;
@Column({ name: "disabled", comment: "禁用启用" })
disabled: boolean;
@Column({ name: 'project_id', comment: '项目id' })
@Column({ name: "project_id", comment: "项目id" })
projectId: number;
@Column({ name: 'create_time', comment: '创建时间', default: () => 'CURRENT_TIMESTAMP' })
@Column({ name: "create_time", comment: "创建时间", default: () => "CURRENT_TIMESTAMP" })
createTime: Date;
@Column({ name: 'update_time', comment: '修改时间', default: () => 'CURRENT_TIMESTAMP' })
@Column({ name: "update_time", comment: "修改时间", default: () => "CURRENT_TIMESTAMP" })
updateTime: Date;
}
@@ -10,25 +10,23 @@ import { CertInfoService } from "../service/cert-info-service.js";
import { DomainService } from "../../cert/service/domain-service.js";
import { DomainVerifierGetter } from "../../pipeline/service/getter/domain-verifier-getter.js";
@Provide("CertInfoFacade")
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CertInfoFacade {
export class CertInfoFacade {
@Inject()
pipelineService: PipelineService;
@Inject()
certInfoService: CertInfoService ;
certInfoService: CertInfoService;
@Inject()
domainService: DomainService
domainService: DomainService;
@Inject()
userSettingsService : UserSettingsService
userSettingsService: UserSettingsService;
async getCertInfo(req: { domains?: string; certId?: number; userId: number, projectId:number, autoApply?:boolean,format?:string }) {
const { domains, certId, userId,projectId } = req;
async getCertInfo(req: { domains?: string; certId?: number; userId: number; projectId: number; autoApply?: boolean; format?: string }) {
const { domains, certId, userId, projectId } = req;
if (certId) {
return await this.certInfoService.getCertInfoById({ id: certId, userId, projectId });
}
@@ -38,41 +36,38 @@ export class CertInfoFacade {
message: "参数错误,certId和domains必须传一个",
});
}
const domainArr = domains.split(',');
const domainArr = domains.split(",");
const matchedList = await this.certInfoService.getMatchCertList({domains:domainArr,userId,projectId})
const matchedList = await this.certInfoService.getMatchCertList({ domains: domainArr, userId, projectId });
if (matchedList.length === 0 ) {
if(req.autoApply === true){
if (matchedList.length === 0) {
if (req.autoApply === true) {
//自动申请,先创建自动申请流水线
const pipeline:PipelineEntity = await this.createAutoPipeline({domains:domainArr,userId,projectId})
await this.triggerApplyPipeline({pipelineId:pipeline.id})
}else{
const pipeline: PipelineEntity = await this.createAutoPipeline({ domains: domainArr, userId, projectId });
await this.triggerApplyPipeline({ pipelineId: pipeline.id });
} else {
throw new CodeException({
...Constants.res.openCertNotFound,
message:"在证书仓库中没有找到匹配域名的证书,请先创建证书流水线,或传入autoApply参数,自动创建"
message: "在证书仓库中没有找到匹配域名的证书,请先创建证书流水线,或传入autoApply参数,自动创建",
});
}
}
let matched = this.getMinixMatched(matchedList);
const matched = this.getMinixMatched(matchedList);
if (!matched) {
if(req.autoApply === true){
if (req.autoApply === true) {
//如果没有找到有效期内的证书,则自动触发一次申请
const first = matchedList[0]
await this.triggerApplyPipeline({pipelineId:first.pipelineId})
return
}else{
const first = matchedList[0];
await this.triggerApplyPipeline({ pipelineId: first.pipelineId });
return;
} else {
throw new CodeException({
...Constants.res.openCertNotFound,
message:"证书已过期,请触发流水线申请,或者传入autoApply参数,自动触发"
message: "证书已过期,请触发流水线申请,或者传入autoApply参数,自动触发",
});
}
}
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId,projectId,format:req.format });
return await this.certInfoService.getCertInfoById({ id: matched.id, userId: userId, projectId, format: req.format });
}
public getMinixMatched(matchedList: CertInfoEntity[]) {
@@ -103,59 +98,55 @@ export class CertInfoFacade {
return matched;
}
async createAutoPipeline(req:{domains:string[],userId:number,projectId:number}){
async createAutoPipeline(req: { domains: string[]; userId: number; projectId: number }) {
const verifierGetter = new DomainVerifierGetter(req.userId, req.projectId, this.domainService);
const verifierGetter = new DomainVerifierGetter(req.userId, req.projectId, this.domainService)
const allDomains = []
const allDomains = [];
for (const item of req.domains) {
allDomains.push(item.replaceAll("*.",""))
allDomains.push(item.replaceAll("*.", ""));
}
const verifiers = await verifierGetter.getVerifiers(allDomains)
const verifiers = await verifierGetter.getVerifiers(allDomains);
for (const item of allDomains) {
if (!verifiers[item]){
if (!verifiers[item]) {
throw new CodeException({
...Constants.res.openDomainNoVerifier,
message:`域名${item}没有配置校验方式,请先在域名管理页面配置`,
data:{
domain:item
}
message: `域名${item}没有配置校验方式,请先在域名管理页面配置`,
data: {
domain: item,
},
});
}
}
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId,null, UserEmailSetting)
if(!userEmailSetting.list){
throw new CodeException(Constants.res.openEmailNotFound)
const userEmailSetting = await this.userSettingsService.getSetting<UserEmailSetting>(req.userId, null, UserEmailSetting);
if (!userEmailSetting.list) {
throw new CodeException(Constants.res.openEmailNotFound);
}
const email = userEmailSetting.list[0]
const email = userEmailSetting.list[0];
return await this.pipelineService.createAutoPipeline({
domains: req.domains,
email,
projectId: req.projectId,
userId: req.userId,
from: "OpenAPI"
})
}
async triggerApplyPipeline(req:{pipelineId:number}){
//查询流水线状态
const status = await this.pipelineService.getStatus(req.pipelineId)
if (status != 'running') {
await this.pipelineService.trigger(req.pipelineId)
await utils.sleep(1000)
}
const certInfo = await this.certInfoService.getByPipelineId(req.pipelineId)
throw new CodeException({
...Constants.res.openCertApplying,
data:{
pipelineId:req.pipelineId,
certId:certInfo?.id
}
from: "OpenAPI",
});
}
async triggerApplyPipeline(req: { pipelineId: number }) {
//查询流水线状态
const status = await this.pipelineService.getStatus(req.pipelineId);
if (status != "running") {
await this.pipelineService.trigger(req.pipelineId);
await utils.sleep(1000);
}
const certInfo = await this.certInfoService.getByPipelineId(req.pipelineId);
throw new CodeException({
...Constants.res.openCertApplying,
data: {
pipelineId: req.pipelineId,
certId: certInfo?.id,
},
});
}
}
@@ -1,5 +1,5 @@
export * from './entity/site-info.js';
export * from './entity/cert-info.js';
export * from "./entity/site-info.js";
export * from "./entity/cert-info.js";
export * from './service/cert-info-service.js';
export * from './service/site-info-service.js';
export * from "./service/cert-info-service.js";
export * from "./service/site-info-service.js";
@@ -12,10 +12,9 @@ export type UploadCertReq = {
fromType?: string;
userId?: number;
projectId?: number;
file?:any
file?: any;
};
@Provide("CertInfoService")
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class CertInfoService extends BaseService<CertInfoEntity> {
@@ -32,19 +31,19 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
}
async getUserDomainCount(userId: number) {
if (userId==null) {
throw new Error('userId is required');
if (userId == null) {
throw new Error("userId is required");
}
return await this.repository.sum('domainCount', {
return await this.repository.sum("domainCount", {
userId,
});
}
async getUserWildcardDomainCount(userId: number) {
if (userId == null) {
throw new Error('userId is required');
throw new Error("userId is required");
}
return await this.repository.sum('wildcardDomainCount', {
return await this.repository.sum("wildcardDomainCount", {
userId,
});
}
@@ -56,7 +55,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
return domains.filter(domain => domain?.trim().toLowerCase().startsWith("*.")).length;
}
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[],fromType?:string) {
async updateDomains(pipelineId: number, userId: number, projectId: number, domains: string[], fromType?: string) {
const found = await this.repository.findOne({
where: {
pipelineId,
@@ -73,26 +72,26 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
bean.pipelineId = pipelineId;
bean.userId = userId;
bean.projectId = projectId;
bean.fromType = fromType
bean.fromType = fromType;
if (!domains || domains.length === 0) {
return;
}
}
if (!domains || domains.length === 0) {
bean.domain = '';
bean.domains = '';
bean.domain = "";
bean.domains = "";
bean.domainCount = 0;
bean.wildcardDomainCount = 0;
} else {
bean.domain = domains[0];
bean.domains = domains.join(',');
bean.domains = domains.join(",");
bean.domainCount = domains.length;
bean.wildcardDomainCount = this.countWildcardDomains(domains);
}
await this.addOrUpdate(bean);
return bean.id
return bean.id;
}
async deleteByPipelineId(id: number) {
@@ -104,12 +103,12 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
});
}
async getMatchCertList(params: { domains: string[]; userId: number,projectId?:number }) {
const { domains, userId,projectId } = params;
async getMatchCertList(params: { domains: string[]; userId: number; projectId?: number }) {
const { domains, userId, projectId } = params;
if (!domains) {
throw new CodeException({
...Constants.res.openCertNotFound,
message:"域名不能为空"
message: "域名不能为空",
});
}
@@ -117,27 +116,27 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
select: {
id: true,
domains: true,
expiresTime:true,
pipelineId:true,
expiresTime: true,
pipelineId: true,
},
where: {
userId,
projectId,
},
order: {
id: 'DESC',
id: "DESC",
},
});
//遍历查找
return list.filter(item => {
const itemDomains = item.domains.split(',');
const itemDomains = item.domains.split(",");
return utils.domain.match(domains, itemDomains);
});
}
async getCertInfoById(req: { id: number; userId: number,projectId:number,format?:string }) {
async getCertInfoById(req: { id: number; userId: number; projectId: number; format?: string }) {
const entity = await this.info(req.id);
if (!entity || entity.userId !== req.userId ) {
if (!entity || entity.userId !== req.userId) {
throw new CodeException(Constants.res.openCertNotFound);
}
if (req.projectId && entity.projectId !== req.projectId) {
@@ -153,13 +152,13 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
...certReader.toCertInfo(req.format),
detail: {
id: entity.id,
domains: entity.domains.split(','),
domains: entity.domains.split(","),
notAfter: certReader.expires,
},
};
}
async updateCertByPipelineId(pipelineId: number, cert: CertInfo,file?:string,fromType = 'pipeline') {
async updateCertByPipelineId(pipelineId: number, cert: CertInfo, file?: string, fromType = "pipeline") {
const found = await this.repository.findOne({
where: {
pipelineId,
@@ -169,14 +168,14 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
id: found?.id,
certReader: new CertReader(cert),
fromType,
file
file,
});
return bean;
}
private async updateCert(req: UploadCertReq) {
const bean = new CertInfoEntity();
const { id, fromType,userId, certReader } = req;
const { id, fromType, userId, certReader } = req;
if (id) {
bean.id = id;
} else {
@@ -186,7 +185,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
bean.certInfo = JSON.stringify(certInfo);
bean.applyTime = new Date().getTime();
const domains = certReader.detail.domains.altNames;
bean.domains = domains.join(',');
bean.domains = domains.join(",");
bean.domain = domains[0];
bean.domainCount = domains.length;
bean.wildcardDomainCount = this.countWildcardDomains(domains);
@@ -195,8 +194,8 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
bean.certProvider = certReader.detail.issuer.commonName;
bean.userId = userId;
bean.projectId = req.projectId;
if(req.file){
bean.certFile = req.file
if (req.file) {
bean.certFile = req.file;
}
await this.addOrUpdate(bean);
return bean;
@@ -210,7 +209,7 @@ export class CertInfoService extends BaseService<CertInfoEntity> {
});
}
async count({ userId,projectId }: { userId: number,projectId?:number }) {
async count({ userId, projectId }: { userId: number; projectId?: number }) {
const total = await this.repository.count({
where: {
userId,
@@ -1,17 +1,17 @@
import {LocalCache, logger} from '@certd/basic';
import dnsSdk, {AnyRecord} from 'dns'
import {LookupAddress} from "node:dns";
import { LocalCache, logger } from "@certd/basic";
import dnsSdk, { AnyRecord } from "dns";
import { LookupAddress } from "node:dns";
const dns = dnsSdk.promises
const dns = dnsSdk.promises;
export class DnsCustom{
export class DnsCustom {
private resolver: dnsSdk.promises.Resolver;
// private cache = new LRUCache<string, any>({
// max: 1000,
// ttl: 1000 * 60 * 5,
// });
constructor(dnsServers:string[]) {
constructor(dnsServers: string[]) {
const resolver = new dns.Resolver();
resolver.setServers(dnsServers);
this.resolver = resolver;
@@ -27,103 +27,100 @@ export class DnsCustom{
// this.cache.set(cacheKey,res)
// return res
// }
async lookup(hostname:string,options?:{ family: any, hints: number, all: boolean }):Promise<LookupAddress[]>{
async lookup(hostname: string, options?: { family: any; hints: number; all: boolean }): Promise<LookupAddress[]> {
// { family: undefined, hints: 0, all: true }
let v4:LookupAddress[] = []
let v6:LookupAddress[] = []
let errors = []
const queryV6 = async ()=>{
try{
const list = await this.resolver.resolve6(hostname)
let v4: LookupAddress[] = [];
let v6: LookupAddress[] = [];
const errors = [];
const queryV6 = async () => {
try {
const list = await this.resolver.resolve6(hostname);
if (list && list.length > 0) {
v6 = list.map(item=>{
v6 = list.map(item => {
return {
address: item,
family: 6
}
})
family: 6,
};
});
}
}catch (e) {
logger.warn("query v6 error",e)
errors.push(e)
} catch (e) {
logger.warn("query v6 error", e);
errors.push(e);
}
}
const queryV4 = async ()=>{
try{
const list =await this.resolver.resolve4(hostname)
};
const queryV4 = async () => {
try {
const list = await this.resolver.resolve4(hostname);
if (list && list.length > 0) {
v4 = list.map(item=>{
v4 = list.map(item => {
return {
address: item,
family: 4
}
})
family: 4,
};
});
}
}catch (e) {
logger.warn("query v4 error",e)
errors.push(e)
} catch (e) {
logger.warn("query v4 error", e);
errors.push(e);
}
};
const queries: Promise<any>[] = [];
const { family, all } = options;
if (all) {
queries.push(queryV6());
queries.push(queryV4());
} else {
if (family === 6) {
queries.push(queryV6());
}
if (family === 4) {
queries.push(queryV4());
}
}
const queries:Promise<any>[] = []
const {family, all} = options
if (all){
queries.push(queryV6())
queries.push(queryV4())
}else{
if(family === 6 ){
queries.push(queryV6())
}
if(family === 4 ){
queries.push(queryV4())
}
}
await Promise.all(queries)
const res = [...v4,...v6]
if(res.length === 0){
if (errors.length > 0){
const e = new Error(errors[0])
await Promise.all(queries);
const res = [...v4, ...v6];
if (res.length === 0) {
if (errors.length > 0) {
const e = new Error(errors[0]);
// @ts-ignore
e.errors = errors
throw e
e.errors = errors;
throw e;
}
}
return res
return res;
}
async resolve4(hostname:string):Promise<string[]>{
return await this.resolver.resolve4(hostname)
async resolve4(hostname: string): Promise<string[]> {
return await this.resolver.resolve4(hostname);
}
async resolve6(hostname:string):Promise<string[]>{
return await this.resolver.resolve6(hostname)
async resolve6(hostname: string): Promise<string[]> {
return await this.resolver.resolve6(hostname);
}
async resolveAny(hostname:string):Promise<AnyRecord[]>{
return await this.resolver.resolveAny(hostname)
async resolveAny(hostname: string): Promise<AnyRecord[]> {
return await this.resolver.resolveAny(hostname);
}
async resolveCname(hostname:string):Promise<string[]>{
return await this.resolver.resolveCname(hostname)
async resolveCname(hostname: string): Promise<string[]> {
return await this.resolver.resolveCname(hostname);
}
}
export class DnsContainer{
bucket: LocalCache<DnsCustom> = new LocalCache()
export class DnsContainer {
bucket: LocalCache<DnsCustom> = new LocalCache();
constructor() {}
getDns(server:string[]){
const key = server.join(',')
let dns = this.bucket.get(key)
if (dns){
return dns
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
dns = new DnsCustom(server);
this.bucket.set(key, dns);
return dns;
}
}
export const dnsContainer = new DnsContainer()
export const dnsContainer = new DnsContainer();
@@ -5,14 +5,12 @@ import { Repository } from "typeorm";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { JobHistoryEntity } from "../entity/job-history.js";
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class JobHistoryService extends BaseService<JobHistoryEntity> {
@InjectEntityModel(JobHistoryEntity)
repository: Repository<JobHistoryEntity>;
@Inject()
userSettingsService: UserSettingsService;
@@ -1,20 +1,20 @@
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, Constants, isEnterprise, NeedSuiteException, NeedVIPException, SysSettingsService} from "@certd/lib-server";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {In, Repository} from "typeorm";
import {SiteInfoEntity} from "../entity/site-info.js";
import {siteTester} from "./site-tester.js";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, Constants, isEnterprise, NeedSuiteException, NeedVIPException, SysSettingsService } from "@certd/lib-server";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { In, Repository } from "typeorm";
import { SiteInfoEntity } from "../entity/site-info.js";
import { siteTester } from "./site-tester.js";
import dayjs from "dayjs";
import {logger, utils} from "@certd/basic";
import {PeerCertificate} from "tls";
import {NotificationService} from "../../pipeline/service/notification-service.js";
import {isComm, isPlus} from "@certd/plus-core";
import {UserSuiteService} from "@certd/commercial-core";
import {UserSettingsService} from "../../mine/service/user-settings-service.js";
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 { logger, utils } from "@certd/basic";
import { PeerCertificate } from "tls";
import { NotificationService } from "../../pipeline/service/notification-service.js";
import { isComm, isPlus } from "@certd/plus-core";
import { UserSuiteService } from "@certd/commercial-core";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
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";
import { merge } from "lodash-es";
import { JobHistoryService } from "./job-history-service.js";
@@ -23,7 +23,7 @@ import { UserService } from "../../sys/authority/service/user-service.js";
import { ProjectService } from "../../sys/enterprise/service/project-service.js";
@Provide()
@Scope(ScopeEnum.Request, {allowDowngrade: true})
@Scope(ScopeEnum.Request, { allowDowngrade: true })
export class SiteInfoService extends BaseService<SiteInfoEntity> {
@InjectEntityModel(SiteInfoEntity)
repository: Repository<SiteInfoEntity>;
@@ -87,18 +87,18 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
await this.checkMonitorLimit(data.userId);
data.disabled = false;
const found = await this.repository.findOne({
where: {
domain: data.domain,
userId: data.userId,
httpsPort: data.httpsPort || 443
}
httpsPort: data.httpsPort || 443,
},
});
if (found) {
return {id: found.id};
return { id: found.id };
}
return await super.add(data);
@@ -113,11 +113,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
async getUserMonitorCount(userId: number) {
if (userId==null) {
if (userId == null) {
throw new Error("userId is required");
}
return await this.repository.count({
where: {userId}
where: { userId },
});
}
@@ -132,18 +132,18 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
throw new Error("站点域名不能为空");
}
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId,site.projectId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer
let customDns = null
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, site.projectId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer;
let customDns = null;
if (dnsServer && dnsServer.length > 0) {
customDns = dnsContainer.getDns(dnsServer) as any
customDns = dnsContainer.getDns(dnsServer) as any;
}
try {
await this.update({
id: site.id,
checkStatus: "checking",
lastCheckTime: dayjs().valueOf()
lastCheckTime: dayjs().valueOf(),
});
const res = await siteTester.test({
host: site.domain,
@@ -177,56 +177,56 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
certExpiresTime: dayjs(expires).valueOf(),
lastCheckTime: dayjs().valueOf(),
error: null,
checkStatus: "ok"
checkStatus: "ok",
};
logger.info(`测试站点成功:id=${updateData.id},site=${site.name},certEffectiveTime=${updateData.certEffectiveTime},expiresTime=${updateData.certExpiresTime}`)
logger.info(`测试站点成功:id=${updateData.id},site=${site.name},certEffectiveTime=${updateData.certEffectiveTime},expiresTime=${updateData.certExpiresTime}`);
if (site.ipCheck) {
delete updateData.checkStatus
delete updateData.checkStatus;
}
await this.update(updateData);
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId,site.projectId, UserSiteMonitorSetting)
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(site.userId, site.projectId, UserSiteMonitorSetting);
merge(site,updateData)
merge(site, updateData);
//检查ip
await this.checkAllIp(site,retryTimes,setting);
await this.checkAllIp(site, retryTimes, setting);
if (!notify) {
return;
}
try {
await this.sendExpiresNotify(site.id,setting);
await this.sendExpiresNotify(site.id, setting);
} catch (e) {
logger.error("send notify error", e);
}
} catch (e) {
logger.error("check site error", e);
let message = e.message
if (!message){
message = e.code
let message = e.message;
if (!message) {
message = e.code;
}
if (e.errors &&e.errors.length > 0){
message += "\n"+e.errors.map((item:any)=>item.message).join("\n")
if (e.errors && e.errors.length > 0) {
message += "\n" + e.errors.map((item: any) => item.message).join("\n");
}
await this.update({
id: site.id,
checkStatus: "error",
lastCheckTime: dayjs().valueOf(),
error: message
error: message,
});
if (!notify) {
return;
}
try {
await this.sendCheckErrorNotify(site.id,false,setting);
await this.sendCheckErrorNotify(site.id, false, setting);
} catch (e) {
logger.error("send notify error", e);
}
}
}
async checkAllIp(site: SiteInfoEntity,retryTimes = null,setting: UserSiteMonitorSetting) {
async checkAllIp(site: SiteInfoEntity, retryTimes = null, setting: UserSiteMonitorSetting) {
if (!site.ipCheck) {
return;
}
@@ -248,11 +248,11 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
errorMessage += `${item.ipAddress}${item.error} \n`;
} 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 if (isExpired){
} else if (isExpired) {
errorMessage += `${item.ipAddress}:证书已过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`;
}else if (isWillExpired){
} else if (isWillExpired) {
errorMessage += `${item.ipAddress}:证书将过期(过期时间:${dayjs(item.certExpiresTime).format("YYYY-MM-DD")}) \n`;
}else {
} else {
errorCount--;
}
}
@@ -262,7 +262,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
id: site.id,
checkStatus: "ok",
error: "",
ipErrorCount: 0
ipErrorCount: 0,
});
return;
}
@@ -270,20 +270,19 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
id: site.id,
checkStatus: "error",
error: errorMessage,
ipErrorCount: errorCount
ipErrorCount: errorCount,
});
try {
await this.sendCheckErrorNotify(site.id, true,setting);
await this.sendCheckErrorNotify(site.id, true, setting);
} catch (e) {
logger.error("send notify error", e);
}
};
if (site.ipSyncAuto === false) {
await this.siteIpService.checkAll(site, retryTimes,onFinished);
}else{
await this.siteIpService.syncAndCheck(site, retryTimes,onFinished);
await this.siteIpService.checkAll(site, retryTimes, onFinished);
} else {
await this.siteIpService.syncAndCheck(site, retryTimes, onFinished);
}
}
/**
@@ -298,13 +297,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
throw new Error("站点不存在");
}
this.doCheck(site, notify, retryTimes).catch((err) => {
this.doCheck(site, notify, retryTimes).catch(err => {
logger.error("check site error", err);
});
return
return;
}
async sendCheckErrorNotify(siteId: number, fromIpCheck = false,setting: UserSiteMonitorSetting) {
async sendCheckErrorNotify(siteId: number, fromIpCheck = false, setting: UserSiteMonitorSetting) {
const site = await this.info(siteId);
const url = await this.notificationService.getBindUrl("#/certd/monitor/site");
// 发邮件
@@ -319,13 +318,13 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
content: `站点名称: ${site.name} \n站点域名: ${site.domain} \n错误信息:${site.error}`,
errorMessage: site.error,
notificationType: "siteCheckError",
}
},
},
site.userId
);
}
async sendExpiresNotify(siteId: number,setting: UserSiteMonitorSetting) {
async sendExpiresNotify(siteId: number, setting: UserSiteMonitorSetting) {
const tipDays = setting?.certValidDays || 10;
const site = await this.info(siteId);
const expires = site.certExpiresTime;
@@ -345,7 +344,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
url,
errorMessage: "站点证书即将过期",
notificationType: "siteCertExpireRemind",
}
},
},
site.userId
);
@@ -362,7 +361,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
url,
errorMessage: "站点证书已过期",
notificationType: "siteCertExpireRemind",
}
},
},
site.userId
);
@@ -370,45 +369,44 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
async checkList(sites: SiteInfoEntity[]) {
const cache = {}
const getFromCache = async (userId: number,projectId?: number) =>{
const key = `${userId}_${projectId??""}`
const cache = {};
const getFromCache = async (userId: number, projectId?: number) => {
const key = `${userId}_${projectId ?? ""}`;
if (cache[key]) {
return cache[key];
}
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting)
cache[key] = setting
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
cache[key] = setting;
return setting;
}
};
for (const site of sites) {
const setting = await getFromCache(site.userId,site.projectId)
let retryTimes = setting?.retryTimes
this.doCheck(site,true,retryTimes).catch(e => {
const setting = await getFromCache(site.userId, site.projectId);
const retryTimes = setting?.retryTimes;
this.doCheck(site, true, retryTimes).catch(e => {
logger.error(`检查站点证书失败,${site.domain}`, e.message);
});
await utils.sleep(100);
}
}
async getSetting(userId: number,projectId?: number) {
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
async getSetting(userId: number, projectId?: number) {
return await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
}
async saveSetting(userId: number,projectId: number, bean: UserSiteMonitorSetting) {
await this.userSettingsService.saveSetting(userId,projectId, bean);
if(bean.cron){
async saveSetting(userId: number, projectId: number, bean: UserSiteMonitorSetting) {
await this.userSettingsService.saveSetting(userId, projectId, bean);
if (bean.cron) {
//注册job
await this.registerSiteMonitorJob(userId,projectId);
}else{
this.clearSiteMonitorJob(userId,projectId);
await this.registerSiteMonitorJob(userId, projectId);
} else {
this.clearSiteMonitorJob(userId, projectId);
}
}
async ipCheckChange(req: { id: any; ipCheck: any }) {
await this.update({
id: req.id,
ipCheck: req.ipCheck
ipCheck: req.ipCheck,
});
if (req.ipCheck) {
const site = await this.info(req.id);
@@ -419,7 +417,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
async disabledChange(req: { disabled: any; id: any }) {
await this.update({
id: req.id,
disabled: req.disabled
disabled: req.disabled,
});
if (!req.disabled) {
const site = await this.info(req.id);
@@ -427,7 +425,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
}
}
async doImport(req: { text: string; userId: number,groupId?:number,projectId?:number }) {
async doImport(req: { text: string; userId: number; groupId?: number; projectId?: number }) {
if (!req.text) {
throw new Error("text is required");
}
@@ -459,7 +457,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
if (arr.length > 2) {
name = arr[2] || domain;
}
let remark:string = "";
let remark = "";
if (arr.length > 3) {
remark = arr[3] || "";
}
@@ -471,7 +469,7 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
userId: req.userId,
remark,
groupId: req.groupId,
projectId: req.projectId
projectId: req.projectId,
});
}
@@ -485,77 +483,77 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
await batchAdd(list);
}
clearSiteMonitorJob(userId: number,projectId?: number) {
this.cron.remove(`siteMonitor_${userId}_${projectId||""}`);
clearSiteMonitorJob(userId: number, projectId?: number) {
this.cron.remove(`siteMonitor_${userId}_${projectId || ""}`);
}
async registerSiteMonitorJob(userId?: number,projectId?: number) {
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId, UserSiteMonitorSetting);
async registerSiteMonitorJob(userId?: number, projectId?: number) {
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
if (!setting.cron) {
return;
}
//注册个人的 或项目的
this.cron.register({
name: `siteMonitor_${userId}_${projectId||""}`,
name: `siteMonitor_${userId}_${projectId || ""}`,
cron: setting.cron,
job: () => this.triggerJobOnce(userId,projectId),
job: () => this.triggerJobOnce(userId, projectId),
});
}
async triggerCommonJob(){
async triggerCommonJob() {
//遍历用户
const userIds = await this.userService.getAllUserIds()
const userIds = await this.userService.getAllUserIds();
for (const userId of userIds) {
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,null,UserSiteMonitorSetting)
if(setting && setting.cron){
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, null, UserSiteMonitorSetting);
if (setting && setting.cron) {
//该用户有自定义检查时间,跳过公共job
continue
continue;
}
await this.triggerJobOnce(userId)
await this.triggerJobOnce(userId);
}
//遍历项目
const projectIds = await this.projectService.getAllProjectIds()
const projectIds = await this.projectService.getAllProjectIds();
for (const projectId of projectIds) {
const userId = Constants.enterpriseUserId
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId,projectId,UserSiteMonitorSetting)
if(setting && setting.cron){
const userId = Constants.enterpriseUserId;
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(userId, projectId, UserSiteMonitorSetting);
if (setting && setting.cron) {
//该项目有自定义检查时间,跳过公共job
continue
continue;
}
await this.triggerJobOnce(userId,projectId)
await this.triggerJobOnce(userId, projectId);
}
}
async triggerJobOnce(userId?:number,projectId?:number) {
if(userId==null){
async triggerJobOnce(userId?: number, projectId?: number) {
if (userId == null) {
throw new Error("userId is required");
}
const query:any = { disabled: false };
const query: any = { disabled: false };
query.userId = userId;
if(projectId){
if (projectId) {
query.projectId = projectId;
}
const siteCount = await this.repository.count({
where: query,
});
if (siteCount === 0) {
logger.info(`用户/项目[${userId}_${projectId||""}]没有站点证书需要检查`)
logger.info(`用户/项目[${userId}_${projectId || ""}]没有站点证书需要检查`);
return;
}
logger.info(`站点证书检查开始执行[${userId}_${projectId||""}]`);
let jobEntity :Partial<JobHistoryEntity> = null;
jobEntity = {
logger.info(`站点证书检查开始执行[${userId}_${projectId || ""}]`);
let jobEntity: Partial<JobHistoryEntity> = null;
jobEntity = {
userId,
projectId,
type:"siteCertMonitor",
title: '站点证书检查',
result:"start",
startAt:new Date().getTime(),
}
type: "siteCertMonitor",
title: "站点证书检查",
result: "start",
startAt: new Date().getTime(),
};
await this.jobHistoryService.add(jobEntity);
let offset = 0;
const limit = 50;
@@ -575,17 +573,17 @@ export class SiteInfoService extends BaseService<SiteInfoEntity> {
await this.checkList(records);
}
logger.info(`站点证书检查完成[${userId}_${projectId||""}]`);
logger.info(`站点证书检查完成[${userId}_${projectId || ""}]`);
await this.jobHistoryService.update({
id: jobEntity.id,
result: "done",
content:`共检查${count}个站点`,
endAt:new Date().getTime(),
updateTime:new Date(),
content: `共检查${count}个站点`,
endAt: new Date().getTime(),
updateTime: new Date(),
});
}
async batchDelete(ids: number[], userId: number,projectId?:number): Promise<void> {
async batchDelete(ids: number[], userId: number, projectId?: number): Promise<void> {
await this.repository.delete({
id: In(ids),
userId,
@@ -1,21 +1,21 @@
import {Inject, Provide, Scope, ScopeEnum} from "@midwayjs/core";
import {BaseService, SysSettingsService} from "@certd/lib-server";
import {InjectEntityModel} from "@midwayjs/typeorm";
import {Repository} from "typeorm";
import {SiteInfoEntity} from "../entity/site-info.js";
import {NotificationService} from "../../pipeline/service/notification-service.js";
import {UserSuiteService} from "@certd/commercial-core";
import {UserSettingsService} from "../../mine/service/user-settings-service.js";
import {SiteIpEntity} from "../entity/site-ip.js";
import { Inject, Provide, Scope, ScopeEnum } from "@midwayjs/core";
import { BaseService, SysSettingsService } from "@certd/lib-server";
import { InjectEntityModel } from "@midwayjs/typeorm";
import { Repository } from "typeorm";
import { SiteInfoEntity } from "../entity/site-info.js";
import { NotificationService } from "../../pipeline/service/notification-service.js";
import { UserSuiteService } from "@certd/commercial-core";
import { UserSettingsService } from "../../mine/service/user-settings-service.js";
import { SiteIpEntity } from "../entity/site-ip.js";
import dnsSdk from "dns";
import {logger} from "@certd/basic";
import { logger } from "@certd/basic";
import dayjs from "dayjs";
import {siteTester} from "./site-tester.js";
import {PeerCertificate} from "tls";
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;
const dns = dnsSdk.promises;
@Provide()
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@@ -47,12 +47,11 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
throw new Error("userId is required");
}
data.disabled = false;
const res= await super.add(data);
await this.updateIpCount(data.siteId)
return res
const res = await super.add(data);
await this.updateIpCount(data.siteId);
return res;
}
async update(data: any) {
if (!data.id) {
throw new Error("id is required");
@@ -61,52 +60,52 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
await super.update(data);
}
async sync(entity: SiteInfoEntity,check:boolean = true) {
async sync(entity: SiteInfoEntity, check = true) {
const domain = entity.domain;
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(entity.userId,entity.projectId, UserSiteMonitorSetting);
const setting = await this.userSettingsService.getSetting<UserSiteMonitorSetting>(entity.userId, entity.projectId, UserSiteMonitorSetting);
const dnsServer = setting.dnsServer
let resolver = dns
const dnsServer = setting.dnsServer;
let resolver = dns;
if (dnsServer && dnsServer.length > 0) {
resolver = dnsContainer.getDns(dnsServer) as any
resolver = dnsContainer.getDns(dnsServer) as any;
}
//从域名解析中获取所有ip
const ips = await this.getAllIpsFromDomain(domain,resolver,entity.ipSyncMode);
if (ips.length === 0 ) {
logger.warn(`没有发现${domain}的IP`)
return
const ips = await this.getAllIpsFromDomain(domain, resolver, entity.ipSyncMode);
if (ips.length === 0) {
logger.warn(`没有发现${domain}的IP`);
return;
}
const oldIps = await this.repository.find({
where:{
siteId: entity.id,
from:"sync"
}
})
where: {
siteId: entity.id,
from: "sync",
},
});
let hasChanged = true
if (oldIps.length === ips.length ){
let hasChanged = true;
if (oldIps.length === ips.length) {
//检查是否有变化
const oldIpList = oldIps.map(ip=>ip.ipAddress).sort().join(",")
const newIpList = ips.sort().join(",")
if(oldIpList === newIpList){
//无变化
hasChanged = false
}
const oldIpList = oldIps
.map(ip => ip.ipAddress)
.sort()
.join(",");
const newIpList = ips.sort().join(",");
if (oldIpList === newIpList) {
//无变化
hasChanged = false;
}
}
if(hasChanged){
logger.info(`发现${domain}的IP变化,需要更新,旧IP:${oldIps.map(ip=>ip.ipAddress).join(",")},新IP:${ips.join(",")}`)
if (hasChanged) {
logger.info(`发现${domain}的IP变化,需要更新,旧IP:${oldIps.map(ip => ip.ipAddress).join(",")},新IP:${ips.join(",")}`);
//有变化需要更新
//删除所有的ip
await this.repository.delete({
siteId: entity.id,
from: "sync"
from: "sync",
});
//添加新的ip
@@ -116,36 +115,36 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
userId: entity.userId,
siteId: entity.id,
from: "sync",
disabled:false,
disabled: false,
});
await this.updateIpCount(entity.id)
await this.updateIpCount(entity.id);
}
}
if (check){
if (check) {
await this.checkAll(entity);
}
}
async check(ipId: number, domain: string, port: number,retryTimes = null ,tipDays = 10) {
if(!ipId){
return
async check(ipId: number, domain: string, port: number, retryTimes = null, tipDays = 10) {
if (!ipId) {
return;
}
const entity = await this.info(ipId);
if (!entity) {
return;
}
logger.info(`开始测试站点ip: id=${entity.id},ip=${entity.ipAddress}`)
logger.info(`开始测试站点ip: id=${entity.id},ip=${entity.ipAddress}`);
try {
await this.update({
id: entity.id,
checkStatus: "checking",
lastCheckTime: dayjs().valueOf()
lastCheckTime: dayjs().valueOf(),
});
const res = await siteTester.test({
host: domain,
port: port,
retryTimes : retryTimes??3,
ipAddress: entity.ipAddress
retryTimes: retryTimes ?? 3,
ipAddress: entity.ipAddress,
});
const certi: PeerCertificate = res.certificate;
@@ -170,39 +169,38 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
certExpiresTime: dayjs(expires).valueOf(),
lastCheckTime: dayjs().valueOf(),
error: null,
checkStatus: "ok"
checkStatus: "ok",
};
await this.update(updateData);
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`)
logger.info(`测试站点ip成功: id=${updateData.id},ip=${entity.ipAddress},expiresTime=${updateData.certExpiresTime}`);
return {
...updateData,
ipAddress: entity.ipAddress,
}
};
} catch (e) {
logger.error("check site ip error", e);
await this.update({
id: entity.id,
checkStatus: "error",
lastCheckTime: dayjs().valueOf(),
error: e.message
error: e.message,
});
return {
id: entity.id,
ipAddress: entity.ipAddress,
error: e.message
}
error: e.message,
};
}
}
async checkAll(siteInfo: SiteInfoEntity,retryTimes = null,onFinish?: (e: any) => void) {
async checkAll(siteInfo: SiteInfoEntity, retryTimes = null, onFinish?: (e: any) => void) {
const siteId = siteInfo.id;
const ips = await this.repository.find({
where: {
siteId: siteId
}
siteId: siteId,
},
});
const domain = siteInfo.domain;
const port = siteInfo.httpsPort;
@@ -210,44 +208,44 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
for (const item of ips) {
const func = async () => {
try {
return await this.check(item.id, domain, port,retryTimes);
return await this.check(item.id, domain, port, retryTimes);
} catch (e) {
logger.error("check site item error", e);
return {
...item,
error:e.message
}
error: e.message,
};
}
}
};
promiseList.push(func());
}
Promise.all(promiseList).then((res)=>{
const finished = res.filter(item=>{
return item!=null
})
Promise.all(promiseList).then(res => {
const finished = res.filter(item => {
return item != null;
});
if (onFinish) {
onFinish && onFinish(finished)
onFinish && onFinish(finished);
}
})
});
}
async getAllIpsFromDomain(domain: string,resolver:any = dns,ipSyncMode:string = "all"):Promise<string[]> {
const getFromV4 = async ():Promise<string[]> => {
try{
async getAllIpsFromDomain(domain: string, resolver: any = dns, ipSyncMode = "all"): Promise<string[]> {
const getFromV4 = async (): Promise<string[]> => {
try {
return await resolver.resolve4(domain);
}catch (err) {
logger.warn(`[${domain}] resolve4 error`, err)
return []
} catch (err) {
logger.warn(`[${domain}] resolve4 error`, err);
return [];
}
}
const getFromV6 = async ():Promise<string[]> => {
try{
};
const getFromV6 = async (): Promise<string[]> => {
try {
return await resolver.resolve6(domain);
}catch (err) {
logger.warn(`[${domain}] resolve6 error`, err)
return []
} catch (err) {
logger.warn(`[${domain}] resolve6 error`, err);
return [];
}
}
};
if (ipSyncMode === "ipv4") {
return await getFromV4();
@@ -261,24 +259,25 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
});
}
async updateIpCount(siteId:number){
async updateIpCount(siteId: number) {
const count = await this.repository.count({
where:{
siteId
}
})
await this.siteInfoRepository.update({
//where
id:siteId,
},
where: {
siteId,
},
});
await this.siteInfoRepository.update(
{
//where
id: siteId,
},
{
//update
ipCount:count
})
ipCount: count,
}
);
}
async doImport(req: { text: string; userId:number, siteId:number,projectId?:number }) {
async doImport(req: { text: string; userId: number; siteId: number; projectId?: number }) {
if (!req.text) {
throw new Error("text is required");
}
@@ -289,9 +288,9 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
const siteEntity = await this.siteInfoRepository.findOne({
where: {
id: req.siteId,
userId:req.userId,
projectId:req.projectId
}
userId: req.userId,
projectId: req.projectId,
},
});
if (!siteEntity) {
throw new Error(`站点${req.siteId}不存在`);
@@ -307,11 +306,11 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
continue;
}
list.push({
ipAddress:item,
ipAddress: item,
userId: userId,
siteId: req.siteId,
from: "import",
disabled:false,
disabled: false,
projectId: req.projectId,
});
}
@@ -324,8 +323,8 @@ export class SiteIpService extends BaseService<SiteIpEntity> {
await batchAdd(list);
}
async syncAndCheck(siteEntity:SiteInfoEntity,retryTimes = null,onFinish?: (e: any) => void){
await this.sync(siteEntity,false);
await this.checkAll(siteEntity,retryTimes,onFinish);
async syncAndCheck(siteEntity: SiteInfoEntity, retryTimes = null, onFinish?: (e: any) => void) {
await this.sync(siteEntity, false);
await this.checkAll(siteEntity, retryTimes, onFinish);
}
}
@@ -2,7 +2,7 @@ import { logger, safePromise, utils } from "@certd/basic";
import { merge } from "lodash-es";
import https from "https";
import { PeerCertificate } from "tls";
import {DnsCustom} from "./dns-custom.js";
import { DnsCustom } from "./dns-custom.js";
export type SiteTestReq = {
host: string; // 只用域名部分
@@ -20,8 +20,8 @@ export type SiteTestRes = {
export class SiteTester {
async test(req: SiteTestReq): Promise<SiteTestRes> {
const req_ = {...req}
delete req_.customDns
const req_ = { ...req };
delete req_.customDns;
logger.info("测试站点:", JSON.stringify(req_));
const maxRetryTimes = req.retryTimes == null ? 3 : req.retryTimes;
let tryCount = 0;
@@ -49,40 +49,40 @@ export class SiteTester {
{
port: 443,
method: "GET",
rejectUnauthorized: false
rejectUnauthorized: false,
},
req
);
let customLookup = null
let customLookup = null;
if (req.ipAddress) {
//使用固定的ip
const ipAddress = req.ipAddress;
options.headers = {
host: options.host,
//sni
servername: options.host
servername: options.host,
};
options.host = ipAddress;
}else if (req.customDns ) {
} else if (req.customDns) {
// 非ip address 请求时
const customDns = req.customDns
customLookup = async (hostname:string, options:any, callback)=> {
const customDns = req.customDns;
customLookup = async (hostname: string, options: any, callback) => {
console.log(hostname, options);
// { family: undefined, hints: 0, all: true }
const res = await customDns.lookup(hostname, options)
console.log("custom lookup res:",res)
const res = await customDns.lookup(hostname, options);
console.log("custom lookup res:", res);
if (!res || res.length === 0) {
callback(new Error("没有解析到IP"));
}
callback(null, res);
}
};
}
const agentOptions:any = { keepAlive: false };
const agentOptions: any = { keepAlive: false };
if (customLookup) {
agentOptions.lookup = customLookup
agentOptions.lookup = customLookup;
}
options.agent = new https.Agent(agentOptions);
@@ -95,19 +95,19 @@ export class SiteTester {
});
// ✅ 关键:在 'socket' 事件中获取证书(握手完成后立即执行)
req.on('socket', (socket:any) => {
socket.on('secureConnect', () => {
req.on("socket", (socket: any) => {
socket.on("secureConnect", () => {
// TLS握手完成,证书已经可用
const certificate = socket.getPeerCertificate();
if (certificate.subject) {
logger.info('证书获取成功', certificate.subject);
logger.info("证书获取成功", certificate.subject);
resolve({
certificate
certificate,
});
}else{
} else {
logger.warn("证书信息为空");
resolve({
certificate: null
certificate: null,
});
}
});