mirror of https://github.com/certd/certd
perf: 优化cname检查,当有冲突的cname记录时,给出提示
parent
49f26b4049
commit
e639a8f9f1
|
@ -203,6 +203,7 @@ export const agents: any;
|
||||||
export function setLogger(fn: (message: any, ...args: any[]) => void): void;
|
export function setLogger(fn: (message: any, ...args: any[]) => void): void;
|
||||||
|
|
||||||
export function walkTxtRecord(record: any): Promise<string[]>;
|
export function walkTxtRecord(record: any): Promise<string[]>;
|
||||||
|
export function getAuthoritativeDnsResolver(record:string): Promise<any>;
|
||||||
|
|
||||||
export const CancelError: typeof CancelError;
|
export const CancelError: typeof CancelError;
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
</td>
|
</td>
|
||||||
<td class="status center flex-center">
|
<td class="status center flex-center">
|
||||||
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
|
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
|
||||||
|
<a-tooltip v-if="row.error" :title="cnameRecord.error">
|
||||||
|
<fs-icon class="ml-5 color-red" icon="ion:warning-outline"></fs-icon>
|
||||||
|
</a-tooltip>
|
||||||
</td>
|
</td>
|
||||||
<td class="center">
|
<td class="center">
|
||||||
<template v-if="cnameRecord.status !== 'valid'">
|
<template v-if="cnameRecord.status !== 'valid'">
|
||||||
|
|
|
@ -21,7 +21,7 @@ import CnameRecordInfo from "/@/components/plugins/cert/domains-verify-plan-edit
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "CnameVerifyPlan"
|
name: "CnameVerifyPlan",
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(["update:modelValue", "change"]);
|
const emit = defineEmits(["update:modelValue", "change"]);
|
||||||
|
|
|
@ -120,10 +120,10 @@ function install(app: App, options: any = {}) {
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
fixed: "right",
|
fixed: "right",
|
||||||
buttons: {
|
buttons: {
|
||||||
view: { type: "link", text: null, icon: "ion:eye-outline" },
|
view: { type: "link", text: null, icon: "ion:eye-outline", tooltip: { title: "查看" } },
|
||||||
copy: { show: true, type: "link", text: null, icon: "ion:copy-outline" },
|
copy: { show: true, type: "link", text: null, icon: "ion:copy-outline", tooltip: { title: "复制" } },
|
||||||
edit: { type: "link", text: null, icon: "ion:create-outline" },
|
edit: { type: "link", text: null, icon: "ion:create-outline", tooltip: { title: "编辑" } },
|
||||||
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline" },
|
remove: { type: "link", style: { color: "red" }, text: null, icon: "ion:trash-outline", tooltip: { title: "删除" } },
|
||||||
},
|
},
|
||||||
dropdown: {
|
dropdown: {
|
||||||
more: {
|
more: {
|
||||||
|
|
|
@ -122,7 +122,7 @@ export const certdResources = [
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "OpenKey",
|
title: "开放接口密钥",
|
||||||
name: "OpenKey",
|
name: "OpenKey",
|
||||||
path: "/certd/open/openkey",
|
path: "/certd/open/openkey",
|
||||||
component: "/certd/open/openkey/index.vue",
|
component: "/certd/open/openkey/index.vue",
|
||||||
|
|
|
@ -2,7 +2,7 @@ import * as api from "./api";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { Ref, ref } from "vue";
|
import { Ref, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
import { useUserStore } from "/@/store/user";
|
import { useUserStore } from "/@/store/user";
|
||||||
import { useSettingStore } from "/@/store/settings";
|
import { useSettingStore } from "/@/store/settings";
|
||||||
import { message } from "ant-design-vue";
|
import { message } from "ant-design-vue";
|
||||||
|
@ -31,7 +31,15 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
const selectedRowKeys: Ref<any[]> = ref([]);
|
||||||
context.selectedRowKeys = selectedRowKeys;
|
context.selectedRowKeys = selectedRowKeys;
|
||||||
|
const dictRef = dict({
|
||||||
|
data: [
|
||||||
|
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
||||||
|
{ label: "验证中", value: "validating", color: "blue" },
|
||||||
|
{ label: "验证成功", value: "valid", color: "green" },
|
||||||
|
{ label: "验证失败", value: "failed", color: "red" },
|
||||||
|
{ label: "验证超时", value: "timeout", color: "red" },
|
||||||
|
],
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -174,21 +182,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
status: {
|
status: {
|
||||||
title: "状态",
|
title: "状态",
|
||||||
type: "dict-select",
|
type: "dict-select",
|
||||||
dict: dict({
|
dict: dictRef,
|
||||||
data: [
|
|
||||||
{ label: "待设置CNAME", value: "cname", color: "warning" },
|
|
||||||
{ label: "验证中", value: "validating", color: "blue" },
|
|
||||||
{ label: "验证成功", value: "valid", color: "green" },
|
|
||||||
{ label: "验证失败", value: "failed", color: "red" },
|
|
||||||
{ label: "验证超时", value: "timeout", color: "red" },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
addForm: {
|
addForm: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
column: {
|
column: {
|
||||||
width: 120,
|
width: 120,
|
||||||
align: "center",
|
align: "center",
|
||||||
|
cellRender({ value, row }) {
|
||||||
|
return (
|
||||||
|
<div class={"flex flex-center"}>
|
||||||
|
<fs-values-format modelValue={value} dict={dictRef}></fs-values-format>
|
||||||
|
{row.error && (
|
||||||
|
<a-tooltip title={row.error}>
|
||||||
|
<fs-icon class={"ml-5 color-red"} icon="ion:warning-outline"></fs-icon>
|
||||||
|
</a-tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
triggerValidate: {
|
triggerValidate: {
|
||||||
|
|
|
@ -223,6 +223,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
play: {
|
play: {
|
||||||
order: -999,
|
order: -999,
|
||||||
title: "运行流水线",
|
title: "运行流水线",
|
||||||
|
tooltip: { title: "运行流水线" },
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ant-design:play-circle-outlined",
|
icon: "ant-design:play-circle-outlined",
|
||||||
click({ row }) {
|
click({ row }) {
|
||||||
|
@ -276,6 +277,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
viewCert: {
|
viewCert: {
|
||||||
order: 3,
|
order: 3,
|
||||||
title: "查看证书",
|
title: "查看证书",
|
||||||
|
tooltip: { title: "查看证书" },
|
||||||
type: "link",
|
type: "link",
|
||||||
icon: "ph:certificate",
|
icon: "ph:certificate",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
|
@ -286,6 +288,7 @@ export default function ({ crudExpose, context: { groupDictRef, selectedRowKeys
|
||||||
order: 4,
|
order: 4,
|
||||||
type: "link",
|
type: "link",
|
||||||
title: "下载证书",
|
title: "下载证书",
|
||||||
|
tooltip: { title: "下载证书" },
|
||||||
icon: "ant-design:download-outlined",
|
icon: "ant-design:download-outlined",
|
||||||
async click({ row }) {
|
async click({ row }) {
|
||||||
await downloadCert(row);
|
await downloadCert(row);
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
ALTER TABLE cd_cname_record ADD COLUMN "error" varchar(4096);
|
||||||
|
ALTER TABLE sys_user ADD COLUMN "valid_time" integer;
|
|
@ -26,6 +26,9 @@ export class CnameRecordEntity {
|
||||||
@Column({ comment: '验证状态', length: 20 })
|
@Column({ comment: '验证状态', length: 20 })
|
||||||
status: string;
|
status: string;
|
||||||
|
|
||||||
|
@Column({ comment: '错误信息' })
|
||||||
|
error: string
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
comment: '创建时间',
|
comment: '创建时间',
|
||||||
name: 'create_time',
|
name: 'create_time',
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {CnameRecordEntity, CnameRecordStatusType} from '../entity/cname-record.j
|
||||||
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
|
import {createDnsProvider, IDnsProvider} from '@certd/plugin-cert';
|
||||||
import {CnameProvider, CnameRecord} from '@certd/pipeline';
|
import {CnameProvider, CnameRecord} from '@certd/pipeline';
|
||||||
import {cache, http, isDev, logger, utils} from '@certd/basic';
|
import {cache, http, isDev, logger, utils} from '@certd/basic';
|
||||||
import {walkTxtRecord} from '@certd/acme-client';
|
import {getAuthoritativeDnsResolver, walkTxtRecord} from '@certd/acme-client';
|
||||||
import {CnameProviderService} from './cname-provider-service.js';
|
import {CnameProviderService} from './cname-provider-service.js';
|
||||||
import {CnameProviderEntity} from '../entity/cname-provider.js';
|
import {CnameProviderEntity} from '../entity/cname-provider.js';
|
||||||
import {CommonDnsProvider} from './common-provider.js';
|
import {CommonDnsProvider} from './common-provider.js';
|
||||||
|
@ -22,11 +22,12 @@ type CnameCheckCacheValue = {
|
||||||
intervalId?: NodeJS.Timeout;
|
intervalId?: NodeJS.Timeout;
|
||||||
dnsProvider?: IDnsProvider;
|
dnsProvider?: IDnsProvider;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 授权
|
* 授权
|
||||||
*/
|
*/
|
||||||
@Provide()
|
@Provide()
|
||||||
@Scope(ScopeEnum.Request, { allowDowngrade: true })
|
@Scope(ScopeEnum.Request, {allowDowngrade: true})
|
||||||
export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
@InjectEntityModel(CnameRecordEntity)
|
@InjectEntityModel(CnameRecordEntity)
|
||||||
repository: Repository<CnameRecordEntity>;
|
repository: Repository<CnameRecordEntity>;
|
||||||
|
@ -47,6 +48,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
getRepository() {
|
getRepository() {
|
||||||
return this.repository;
|
return this.repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增
|
* 新增
|
||||||
* @param param 数据
|
* @param param 数据
|
||||||
|
@ -62,7 +64,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
param.domain = param.domain.substring(2);
|
param.domain = param.domain.substring(2);
|
||||||
}
|
}
|
||||||
param.domain = param.domain.trim()
|
param.domain = param.domain.trim()
|
||||||
const info = await this.getRepository().findOne({ where: { domain: param.domain,userId: param.userId } });
|
const info = await this.getRepository().findOne({where: {domain: param.domain, userId: param.userId}});
|
||||||
if (info) {
|
if (info) {
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -77,14 +79,14 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
} else {
|
} else {
|
||||||
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||||
}
|
}
|
||||||
await this.cnameProviderChanged(param.userId,param, cnameProvider);
|
await this.cnameProviderChanged(param.userId, param, cnameProvider);
|
||||||
|
|
||||||
param.status = 'cname';
|
param.status = 'cname';
|
||||||
const { id } = await super.add(param);
|
const {id} = await super.add(param);
|
||||||
return await this.info(id);
|
return await this.info(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async cnameProviderChanged(userId:number,param: any, cnameProvider: CnameProviderEntity) {
|
private async cnameProviderChanged(userId: number, param: any, cnameProvider: CnameProviderEntity) {
|
||||||
param.cnameProviderId = cnameProvider.id;
|
param.cnameProviderId = cnameProvider.id;
|
||||||
|
|
||||||
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
const subDomainGetter = new SubDomainsGetter(userId, this.subDomainService)
|
||||||
|
@ -117,7 +119,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
}
|
}
|
||||||
if (old.cnameProviderId !== param.cnameProviderId) {
|
if (old.cnameProviderId !== param.cnameProviderId) {
|
||||||
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
const cnameProvider = await this.cnameProviderService.info(param.cnameProviderId);
|
||||||
await this.cnameProviderChanged(old.userId,param, cnameProvider);
|
await this.cnameProviderChanged(old.userId, param, cnameProvider);
|
||||||
param.status = 'cname';
|
param.status = 'cname';
|
||||||
}
|
}
|
||||||
return await super.update(param);
|
return await super.update(param);
|
||||||
|
@ -157,10 +159,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
throw new ValidateException('userId不能为空');
|
throw new ValidateException('userId不能为空');
|
||||||
}
|
}
|
||||||
let record = await this.getRepository().findOne({ where: { domain, userId } });
|
let record = await this.getRepository().findOne({where: {domain, userId}});
|
||||||
if (record == null) {
|
if (record == null) {
|
||||||
if (createOnNotFound) {
|
if (createOnNotFound) {
|
||||||
record = await this.add({ domain, userId });
|
record = await this.add({domain, userId});
|
||||||
} else {
|
} else {
|
||||||
throw new ValidateException(`找不到${domain}的CNAME记录`);
|
throw new ValidateException(`找不到${domain}的CNAME记录`);
|
||||||
}
|
}
|
||||||
|
@ -208,7 +210,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
ttl = 30 * 1000;
|
ttl = 30 * 1000;
|
||||||
}
|
}
|
||||||
const testRecordValue = 'certd-cname-verify';
|
const testRecordValue = `certd-cname-verify-${bean.id}`;
|
||||||
|
|
||||||
const buildDnsProvider = async () => {
|
const buildDnsProvider = async () => {
|
||||||
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
|
const cnameProvider = await this.cnameProviderService.info(bean.cnameProviderId);
|
||||||
|
@ -228,7 +230,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
|
||||||
const context = { access, logger, http, utils,domainParser };
|
const context = {access, logger, http, utils, domainParser};
|
||||||
const dnsProvider: IDnsProvider = await createDnsProvider({
|
const dnsProvider: IDnsProvider = await createDnsProvider({
|
||||||
dnsProviderType: cnameProvider.dnsProviderType,
|
dnsProviderType: cnameProvider.dnsProviderType,
|
||||||
context,
|
context,
|
||||||
|
@ -239,7 +241,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
const clearVerifyRecord = async () => {
|
const clearVerifyRecord = async () => {
|
||||||
cache.delete(cacheKey);
|
cache.delete(cacheKey);
|
||||||
try {
|
try {
|
||||||
let dnsProvider =value.dnsProvider
|
let dnsProvider = value.dnsProvider
|
||||||
if (!dnsProvider) {
|
if (!dnsProvider) {
|
||||||
dnsProvider = await buildDnsProvider();
|
dnsProvider = await buildDnsProvider();
|
||||||
}
|
}
|
||||||
|
@ -271,6 +273,9 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
|
|
||||||
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
logger.info(`检查CNAME配置 ${fullDomain} ${testRecordValue}`);
|
||||||
|
|
||||||
|
//检查是否有重复的acme配置
|
||||||
|
await this.checkRepeatAcmeChallengeRecords(fullDomain,bean.recordValue)
|
||||||
|
|
||||||
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
|
// const txtRecords = await dns.promises.resolveTxt(fullDomain);
|
||||||
// if (txtRecords.length) {
|
// if (txtRecords.length) {
|
||||||
// records = [].concat(...txtRecords);
|
// records = [].concat(...txtRecords);
|
||||||
|
@ -286,7 +291,7 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
if (success) {
|
if (success) {
|
||||||
clearInterval(value.intervalId);
|
clearInterval(value.intervalId);
|
||||||
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
|
logger.info(`检测到CNAME配置,修改状态 ${fullDomain} ${testRecordValue}`);
|
||||||
await this.updateStatus(bean.id, 'valid');
|
await this.updateStatus(bean.id, 'valid', "");
|
||||||
value.pass = true;
|
value.pass = true;
|
||||||
await clearVerifyRecord()
|
await clearVerifyRecord()
|
||||||
return success;
|
return success;
|
||||||
|
@ -318,18 +323,87 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
|
||||||
value.validating = true;
|
value.validating = true;
|
||||||
value.recordReq = req;
|
value.recordReq = req;
|
||||||
value.recordRes = recordRes;
|
value.recordRes = recordRes;
|
||||||
await this.updateStatus(bean.id, 'validating');
|
await this.updateStatus(bean.id, 'validating', "");
|
||||||
|
|
||||||
value.intervalId = setInterval(async () => {
|
value.intervalId = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
await checkRecordValue();
|
await checkRecordValue();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error('检查cname出错:', e);
|
logger.error('检查cname出错:', e);
|
||||||
|
await this.updateError(bean.id, e.message);
|
||||||
}
|
}
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateStatus(id: number, status: CnameRecordStatusType) {
|
async updateStatus(id: number, status: CnameRecordStatusType, error?: string) {
|
||||||
await this.getRepository().update(id, { status });
|
const updated: any = {status}
|
||||||
|
if (error != null) {
|
||||||
|
updated.error = error
|
||||||
|
}
|
||||||
|
await this.getRepository().update(id, updated);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateError(id: number, error: string) {
|
||||||
|
await this.getRepository().update(id, {error});
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkRepeatAcmeChallengeRecords(acmeRecordDomain: string,targetCnameDomain:string) {
|
||||||
|
|
||||||
|
let dnsResolver = null
|
||||||
|
try{
|
||||||
|
dnsResolver = await getAuthoritativeDnsResolver(acmeRecordDomain)
|
||||||
|
}catch (e) {
|
||||||
|
logger.error(`获取${acmeRecordDomain}的权威DNS服务器失败,${e.message}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let cnameRecords = []
|
||||||
|
try{
|
||||||
|
cnameRecords = await dnsResolver.resolveCname(acmeRecordDomain);
|
||||||
|
}catch (e) {
|
||||||
|
logger.error(`查询CNAME记录失败:${e.message}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
targetCnameDomain = targetCnameDomain.toLowerCase()
|
||||||
|
if (cnameRecords.length > 0) {
|
||||||
|
for (const cnameRecord of cnameRecords) {
|
||||||
|
if(cnameRecord.toLowerCase() !== targetCnameDomain){
|
||||||
|
//确保只有一个cname记录
|
||||||
|
throw new Error(`${acmeRecordDomain}存在多个CNAME记录,请删除多余的CNAME记录:${cnameRecord}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保权威服务器里面没有纯粹的TXT记录
|
||||||
|
let txtRecords = []
|
||||||
|
try{
|
||||||
|
const txtRecordRes = await dnsResolver.resolveTxt(acmeRecordDomain);
|
||||||
|
|
||||||
|
if (txtRecordRes && txtRecordRes.length > 0) {
|
||||||
|
logger.info(`找到 ${txtRecordRes.length} 条 TXT记录( ${acmeRecordDomain})`);
|
||||||
|
logger.info(`TXT records: ${JSON.stringify(txtRecords)}`);
|
||||||
|
txtRecords = txtRecords.concat(...txtRecordRes);
|
||||||
|
}
|
||||||
|
}catch (e) {
|
||||||
|
logger.error(`查询Txt记录失败:${e.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (txtRecords.length === 0) {
|
||||||
|
//如果权威服务器中查不到txt,无需继续检查
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (cnameRecords.length > 0) {
|
||||||
|
// 从cname记录中获取txt记录
|
||||||
|
// 对比是否存在,如果不存在于cname中获取的txt中,说明本体有创建多余的txt记录
|
||||||
|
const res = await walkTxtRecord(cnameRecords[0]);
|
||||||
|
if (res.length > 0) {
|
||||||
|
for (const txtRecord of txtRecords) {
|
||||||
|
if (!res.includes(txtRecord)) {
|
||||||
|
throw new Error(`${acmeRecordDomain}存在多个TXT记录,请删除多余的TXT记录:${txtRecord}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,10 @@ export class UserEntity {
|
||||||
|
|
||||||
@Column({ comment: '状态 0:禁用 1:启用', default: 1 })
|
@Column({ comment: '状态 0:禁用 1:启用', default: 1 })
|
||||||
status: number;
|
status: number;
|
||||||
|
|
||||||
|
@Column({ name: 'valid_time', comment: '有效期', nullable: true })
|
||||||
|
validTime: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
name: 'create_time',
|
name: 'create_time',
|
||||||
comment: '创建时间',
|
comment: '创建时间',
|
||||||
|
|
Loading…
Reference in New Issue