perf: cname 域名映射记录可读性优化

pull/243/head
xiaojunnuo 2024-11-01 18:09:32 +08:00
parent 7ad4b55ee0
commit b1117ed54a
17 changed files with 190 additions and 35 deletions

View File

@ -9,7 +9,7 @@ export * from './util.merge.js';
export * from './util.cache.js';
import sleep from './util.sleep.js';
import { http } from './util.request.js';
import { nanoid } from 'nanoid';
import { mergeUtils } from './util.merge.js';
import { sp } from './util.sp.js';
import { hashUtils } from './util.hash.js';
@ -20,7 +20,8 @@ import { cache } from './util.cache.js';
import dayjs from 'dayjs';
import { domainUtils } from './util.domain.js';
import { optionsUtils } from './util.options.js';
import { nanoid } from 'nanoid';
import * as id from './util.id.js';
export const utils = {
sleep,
http,
@ -32,6 +33,7 @@ export const utils = {
mergeUtils,
cache,
nanoid,
id,
dayjs,
domain: domainUtils,
options: optionsUtils,

View File

@ -0,0 +1,3 @@
import { customAlphabet } from 'nanoid';
export const simpleNanoId = customAlphabet('1234567890abcdefghijklmopqrstuvwxyz', 10);

View File

@ -12,13 +12,18 @@ export class PlusService {
plusServerBaseUrls: string[];
async getPlusRequestService() {
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
const subjectId = await this.getSubjectId();
return new PlusRequestService({
plusServerBaseUrls: this.plusServerBaseUrls,
subjectId: installInfo.siteId,
subjectId,
});
}
async getSubjectId() {
const installInfo: SysInstallInfo = await this.sysSettingsService.getSetting(SysInstallInfo);
return installInfo.siteId;
}
async requestWithoutSign(config: any) {
const plusRequestService = await this.getPlusRequestService();
return await plusRequestService.requestWithoutSign(config);

View File

@ -26,10 +26,10 @@
"dependencies": {
"@ant-design/colors": "^7.0.2",
"@ant-design/icons-vue": "^6.1.0",
"@fast-crud/fast-crud": "^1.22.2",
"@fast-crud/fast-extends": "^1.22.2",
"@fast-crud/ui-antdv4": "^1.22.2",
"@fast-crud/ui-interface": "^1.22.2",
"@fast-crud/fast-crud": "^1.22.3",
"@fast-crud/fast-extends": "^1.22.3",
"@fast-crud/ui-antdv4": "^1.22.3",
"@fast-crud/ui-interface": "^1.22.3",
"@iconify/vue": "^4.1.1",
"@soerenmartius/vue3-clipboard": "^0.1.2",
"@vue-js-cron/light": "^4.0.5",

View File

@ -15,7 +15,11 @@
<fs-values-format v-model="cnameRecord.status" :dict="statusDict" />
</td>
<td class="center">
<a-button v-if="cnameRecord.status !== 'valid'" type="primary" size="small" :loading="loading" @click="doVerify"></a-button>
<template v-if="cnameRecord.status !== 'valid'">
<a-button type="primary" size="small" :loading="loading" @click="doVerify"></a-button>
<cname-tip :record="cnameRecord"></cname-tip>
</template>
<div v-else class="helper">不要删除CNAME</div>
</td>
</tr>
@ -27,6 +31,7 @@ import { CnameRecord, GetByDomain } from "/@/components/plugins/cert/domains-ver
import { ref, watch } from "vue";
import { dict } from "@fast-crud/fast-crud";
import * as api from "./api.js";
import CnameTip from "./cname-tip.vue";
const statusDict = dict({
data: [
{ label: "待设置CNAME", value: "cname", color: "warning" },

View File

@ -0,0 +1,19 @@
<template>
<a-tooltip :overlay-style="{ maxWidth: '400px' }">
<template #title>
<div>
<div>多试几次如果仍然无法验证通过请按如下步骤排查问题</div>
<div>1. 解析记录应该添加在{{ record.domain }}域名下</div>
<div>2. 要添加的是CNAME类型的记录不是TXT</div>
<div>3. 核对记录值是否是:{{ record.recordValue }}</div>
</div>
</template>
<fs-icon class="ml-5 pointer" icon="mingcute:question-line"></fs-icon>
</a-tooltip>
</template>
<script lang="ts" setup>
const props = defineProps<{
record: any;
}>();
</script>

View File

@ -64,18 +64,22 @@ const steps = ref<Step[]>([
{
image: "/static/doc/images/3-add-success.png",
title: "流水线创建成功",
descriptions: ["此时证书申请任务已经建好", "点击手动触发即可测试证书申请", "接下来演示如何添加部署任务"]
descriptions: ["点击手动触发即可申请证书"]
},
{
title: "接下来演示如何自动部署证书",
descriptions: ["如果您只需要申请证书,那么到这一步就可以了"]
}
]
},
{
title: "添加部署证书任务",
description: "演示部署到主机上的Nginx",
description: "这里演示部署证书到Nginx",
items: [
{
image: "/static/doc/images/5-1-add-host.png",
title: "添加nginx部署任务",
descriptions: ["演示第一个部署任务部署到nginx"]
title: "添加证书部署任务",
descriptions: ["这里演示自动部署证书到nginx", "Certd提供茫茫多的部署插件满足您的各种部署需求"]
},
{
image: "/static/doc/images/5-2-add-host.png",
@ -94,8 +98,8 @@ const steps = ref<Step[]>([
},
{
image: "/static/doc/images/5-5-plugin-list.png",
title: "还可以添加其他更多部署任务",
descriptions: ["插件列表"]
title: "本系统提供茫茫多的部署插件",
descriptions: ["您可以根据自身需求将证书部署到各种应用和平台"]
}
]
},

View File

@ -155,7 +155,7 @@ function openUpgrade() {
title = "续期专业版/升级商业版";
}
modal.confirm({
const modalRef = modal.confirm({
title,
async onOk() {
return await doActive();
@ -206,6 +206,10 @@ function openUpgrade() {
</a-col>
);
}
function goAccount() {
router.push("/sys/account");
modalRef.destroy();
}
return (
<div class="mt-10 mb-10 vip-active-modal">
<div class="vip-type-vs">
@ -226,6 +230,9 @@ function openUpgrade() {
没有激活码
{activationCodeGetWay}
</div>
<div class="mt-10">
激活码使用过一次之后不可再次使用如果要更换站点<a onClick={goAccount}>绑定账号</a>然后"转移VIP"即可
</div>
</div>
</div>
);

View File

@ -165,7 +165,7 @@ onMounted(async () => {
await settingStore.checkUrlBound();
});
function menuClick(menu) {
function menuClick(menu: any) {
routerUtils.open(menu.path);
}

View File

@ -6,7 +6,7 @@ import { AddReq, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, dict, Edi
import { useUserStore } from "/@/store/modules/user";
import { useSettingStore } from "/@/store/modules/settings";
import { message } from "ant-design-vue";
import CnameTip from "/@/components/plugins/cert/domains-verify-plan-editor/cname-tip.vue";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const router = useRouter();
const { t } = useI18n();
@ -126,11 +126,18 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
component: {
onDictChange: ({ form, dict }: any) => {
if (!form.cnameProviderId) {
const item = dict.data.find((item: any) => item.isDefault);
const item = dict.data.find((item: any) => item.isDefault && !item.disabled);
if (item) {
form.cnameProviderId = item.id;
}
}
},
renderLabel(item: any) {
if (item.title) {
return `${item.domain}<${item.title}>`;
} else {
return item.domain;
}
}
},
helper: {
@ -147,7 +154,13 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}
},
column: {
show: false
cellRender({ value }) {
if (value < 0) {
return <a-tag color={"green"}>CNAME</a-tag>;
} else {
return <a-tag color={"blue"}>CNAME</a-tag>;
}
}
}
},
status: {
@ -183,6 +196,7 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
if (row.status === "valid") {
return "-";
}
async function doVerify() {
row._validating_ = true;
try {
@ -199,9 +213,12 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
}
}
return (
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
</a-button>
<div>
<a-button onClick={doVerify} loading={row._validating_} size={"small"} type={"primary"}>
</a-button>
<CnameTip record={row} />
</div>
);
}
}

View File

@ -41,6 +41,7 @@ export class FileController extends BaseController {
}
const filePath = this.fileService.getFile(key, userId);
this.ctx.response.attachment(filePath);
this.ctx.response.set('Cache-Control', 'public,max-age=2592000');
await send(this.ctx, filePath);
}
}

View File

@ -21,7 +21,7 @@ export class CnameProviderController extends BaseController {
@Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body: any) {
body.userId = this.getUserId();
const res = await this.providerService.find({});
const res = await this.providerService.list({});
return this.ok(res);
}
}

View File

@ -40,7 +40,8 @@ export class CnameRecordController extends CrudController<CnameRecordService> {
@Post('/list', { summary: Constants.per.authOnly })
async list(@Body(ALL) body: any) {
body.userId = this.getUserId();
return super.list(body);
const list = await this.getService().list(body);
return this.ok(list);
}
@Post('/add', { summary: Constants.per.authOnly })

View File

@ -34,4 +34,6 @@ export class CnameProviderEntity {
default: () => 'CURRENT_TIMESTAMP',
})
updateTime: Date;
title: string;
}

View File

@ -1,8 +1,9 @@
import { Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService, ValidateException } from '@certd/lib-server';
import { CnameProviderEntity } from '../entity/cname_provider.js';
import { BaseService, ListReq, ValidateException } from '@certd/lib-server';
import { CnameProviderEntity } from '../entity/cname-provider.js';
import { CommonProviders } from './common-provider.js';
/**
*
@ -19,7 +20,7 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
}
async getDefault() {
return await this.repository.findOne({ where: { isDefault: true } });
return await this.repository.findOne({ where: { isDefault: true, disabled: false } });
}
/**
*
@ -80,10 +81,24 @@ export class CnameProviderService extends BaseService<CnameProviderEntity> {
if (def) {
return def;
}
const founds = await this.repository.find({ take: 1, order: { createTime: 'DESC' } });
const founds = await this.repository.find({ take: 1, order: { createTime: 'DESC' }, where: { disabled: false } });
if (founds && founds.length > 0) {
return founds[0];
}
return null;
return CommonProviders[0] as CnameProviderEntity;
}
async list(req: ListReq): Promise<any[]> {
const list = await super.list(req);
return [...list, ...CommonProviders];
}
async info(id: any, infoIgnoreProperty?: any): Promise<any | null> {
if (id < 0) {
//使用公共provider
return CommonProviders.find(p => p.id === id);
}
return await super.info(id, infoIgnoreProperty);
}
}

View File

@ -1,16 +1,16 @@
import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseService, ValidateException } from '@certd/lib-server';
import { BaseService, PlusService, ValidateException } from '@certd/lib-server';
import { CnameRecordEntity, CnameRecordStatusType } from '../entity/cname-record.js';
import { v4 as uuidv4 } from 'uuid';
import { createDnsProvider, IDnsProvider, parseDomain } from '@certd/plugin-cert';
import { cache, CnameProvider, http, logger, utils } from '@certd/pipeline';
import { AccessService } from '../../pipeline/service/access-service.js';
import { isDev } from '../../../utils/env.js';
import { walkTxtRecord } from '@certd/acme-client';
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';
type CnameCheckCacheValue = {
validating: boolean;
@ -34,6 +34,10 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
@Inject()
accessService: AccessService;
@Inject()
plusService: PlusService;
//@ts-ignore
getRepository() {
return this.repository;
@ -85,8 +89,8 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
}
param.hostRecord = hostRecord;
const cnameKey = uuidv4().replaceAll('-', '');
param.recordValue = `${cnameKey}.${cnameProvider.domain}`;
const cnameKey = utils.id.simpleNanoId();
param.recordValue = `${param.domain}.${cnameKey}.${cnameProvider.domain}`;
}
async update(param: any) {
@ -189,6 +193,15 @@ export class CnameRecordService extends BaseService<CnameRecordEntity> {
if (cnameProvider == null) {
throw new ValidateException(`CNAME服务:${bean.cnameProviderId} 已被删除请修改CNAME记录重新选择CNAME服务`);
}
if (cnameProvider.id < 0) {
//公共CNAME
return new CommonDnsProvider({
config: cnameProvider,
plusService: this.plusService,
});
}
const access = await this.accessService.getById(cnameProvider.accessId, cnameProvider.userId);
const context = { access, logger, http, utils };
const dnsProvider: IDnsProvider = await createDnsProvider({

View File

@ -0,0 +1,61 @@
import { CreateRecordOptions, DnsProviderContext, IDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { PlusService } from '@certd/lib-server';
export type CnameProvider = {
id: number;
domain: string;
title: string;
};
export const CommonProviders = [
{
id: -1,
domain: 'cname.certd.com.cn',
title: '公共CNAME服务',
},
];
export class CommonDnsProvider implements IDnsProvider {
ctx: DnsProviderContext;
config: CnameProvider;
plusService: PlusService;
constructor(opts: { config: CnameProvider; plusService: PlusService }) {
this.config = opts.config;
this.plusService = opts.plusService;
}
async onInstance() {}
async createRecord(options: CreateRecordOptions) {
if (!this.config.domain.endsWith(options.domain)) {
throw new Error('cname服务域名不匹配');
}
const res = await this.plusService.requestWithoutSign({
url: '/activation/certd/cname/recordCreate',
data: {
subjectId: this.plusService.getSubjectId(),
domain: options.domain,
hostRecord: options.hostRecord,
recordValue: options.value,
providerId: this.config.id,
},
});
return res;
}
async removeRecord(options: RemoveRecordOptions<any>) {
const res = await this.plusService.requestWithoutSign({
url: '/activation/certd/cname/recordRemove',
data: {
subjectId: this.plusService.getSubjectId(),
domain: options.recordReq.domain,
hostRecord: options.recordReq.hostRecord,
recordValue: options.recordReq.value,
recordId: options.recordRes.id,
providerId: this.config.id,
},
});
return res;
}
setCtx(ctx: DnsProviderContext): void {
this.ctx = ctx;
}
}