Merge branch 'v2-dev' into v2

This commit is contained in:
xiaojunnuo
2024-11-19 11:20:49 +08:00
61 changed files with 532 additions and 105 deletions

View File

@@ -35,6 +35,12 @@ export class SysPlusController extends BaseController {
return this.ok(true);
}
@Post('/getVipTrial', { summary: 'sys:settings:edit' })
async getVipTrial(@Body(ALL) body) {
const res = await this.plusService.getVipTrial();
return this.ok(res);
}
//
// @Get('/test', { summary: Constants.per.guest })
// async test() {

View File

@@ -49,19 +49,31 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
}
private async doRequestApi(url: string, data: any = null, method = 'post') {
const res = await this.http.request<any, any>({
url,
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.access.apiToken}`,
},
data,
});
if (!res.success) {
throw new Error(`${JSON.stringify(res.errors)}`);
try {
const res = await this.http.request<any, any>({
url,
method,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.access.apiToken}`,
},
data,
});
if (!res.success) {
throw new Error(`${JSON.stringify(res.errors)}`);
}
return res;
} catch (e: any) {
const data = e.response?.data;
if (data && data.success === false && data.errors && data.errors.length > 0) {
if (data.errors[0].code === 81058) {
this.logger.info('dns解析记录重复');
return null;
}
}
throw e;
}
return res;
}
/**
@@ -88,14 +100,30 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
type: type,
ttl: 60,
});
const record = res.result as CloudflareRecord;
this.logger.info(`添加域名解析成功:fullRecord=${fullRecord},value=${value}`);
this.logger.info(`dns解析记录:${JSON.stringify(record)}`);
let record: any = null;
if (res == null) {
//重复的记录
this.logger.info(`dns解析记录重复无需重复添加`);
record = await this.findRecord(zoneId, options);
} else {
record = res.result as CloudflareRecord;
this.logger.info(`添加域名解析成功:fullRecord=${fullRecord},value=${value}`);
this.logger.info(`dns解析记录:${JSON.stringify(record)}`);
}
//本接口需要返回本次创建的dns解析记录这个记录会在删除的时候用到
return record;
}
async findRecord(zoneId: string, options: CreateRecordOptions): Promise<CloudflareRecord | null> {
const { fullRecord, value } = options;
const url = `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?type=TXT&name=${fullRecord}&content=${value}`;
const res = await this.doRequestApi(url, null, 'get');
if (res.result.length === 0) {
return null;
}
return res.result[0] as CloudflareRecord;
}
/**
* 删除dns解析记录,清理申请痕迹
* @param options
@@ -105,7 +133,7 @@ export class CloudflareDnsProvider extends AbstractDnsProvider<CloudflareRecord>
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value);
if (!record) {
this.logger.info('record不存在');
this.logger.info('record为空,不执行删除');
return;
}
//这里调用删除txt dns解析记录接口

View File

@@ -1,6 +1,7 @@
import { AbstractTaskPlugin, IsTaskPlugin, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertInfo, CertReader } from '@certd/plugin-cert';
import { isDev } from '@certd/basic';
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-plus';
import { optionsUtils } from '@certd/basic/dist/utils/util.options.js';
@IsTaskPlugin({
name: 'demoTest',
@@ -13,8 +14,6 @@ import { isDev } from '@certd/basic';
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
// 你开发的插件要删除此项,否则不会在生产环墋中显示
deprecated: isDev() ? '测试插件,生产环境不显示' : undefined,
})
export class DemoTestPlugin extends AbstractTaskPlugin {
//测试参数
@@ -65,6 +64,10 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
//前端可以展示,当前申请的证书域名列表
certDomains!: string[];
//授权选择框
@TaskInput({
title: 'demo授权',
@@ -78,7 +81,23 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
})
accessId!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '从后端获取选项',
helper: '选择时可以从后端获取选项',
typeName: 'demoTest',
action: DemoTestPlugin.prototype.onGetSiteList.name,
//当以下参数变化时,触发获取选项
watches: ['certDomains', 'accessId'],
required: true,
})
)
siteName!: string | string[];
//插件实例化时执行的方法
async onInstance() {}
//插件执行方法
async execute(): Promise<void> {
const { select, text, cert, accessId } = this;
@@ -102,6 +121,40 @@ export class DemoTestPlugin extends AbstractTaskPlugin {
this.logger.info('switch:', this.switch);
this.logger.info('授权id:', accessId);
}
//此方法演示,如何让前端在添加插件时可以从后端获取选项,这里是后端返回选项的方法
async onGetSiteList() {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
// @ts-ignore
const access = await this.accessService.getById(this.accessId);
// const siteRes = await this.ctx.http.request({
// url: '你的服务端获取选项的请求地址',
// method: 'GET',
// data: {
// token:access.xxxx
// }, //请求参数
// });
//以下是模拟数据
const siteRes = [
{ id: 1, siteName: 'site1.com' },
{ id: 2, siteName: 'site2.com' },
{ id: 3, siteName: 'site2.com' },
];
//转换为前端所需要的格式
const options = siteRes.map((item: any) => {
return {
value: item.siteName,
label: item.siteName,
domain: item.siteName,
};
});
//将站点域名名称根据证书域名进行匹配分组,分成匹配的和不匹配的两组选项,返回给前端,供用户选择
return optionsUtils.buildGroupOptions(options, this.certDomains);
}
}
//实例化一下,注册插件
new DemoTestPlugin();

View File

@@ -105,6 +105,10 @@ export class HuaweiDnsProvider extends AbstractDnsProvider {
async removeRecord(options: RemoveRecordOptions<any>): Promise<any> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
if (!record) {
this.logger.info('解析记录recordId为空不执行删除', fullRecord, value);
return;
}
const req: ApiRequestOptions = {
url: `${this.dnsEndpoint}/v2/zones/${record.zone_id}/recordsets/${record.id}`,
method: 'DELETE',

View File

@@ -0,0 +1,28 @@
import { IsAccess, AccessInput, BaseAccess } from '@certd/pipeline';
/**
* 这个注解将注册一个授权配置
* 在certd的后台管理系统中用户可以选择添加此类型的授权
*/
@IsAccess({
name: 'namesilo',
title: 'namesilo授权',
desc: '',
})
export class NamesiloAccess extends BaseAccess {
/**
* 授权属性配置
*/
@AccessInput({
title: 'API Key',
component: {
placeholder: 'api key',
},
helper: '前往 [获取API Key](https://www.namesilo.com/account/api-manager)',
required: true,
encrypt: true,
})
apiKey = '';
}
new NamesiloAccess();

View File

@@ -0,0 +1,109 @@
import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from '@certd/plugin-cert';
import { Autowire } from '@certd/pipeline';
import { HttpClient, ILogger } from '@certd/basic';
import qs from 'qs';
import { NamesiloAccess } from './access.js';
import { merge } from 'lodash-es';
export type NamesiloRecord = {
record_id: string;
};
// 这里通过IsDnsProvider注册一个dnsProvider
@IsDnsProvider({
name: 'namesilo',
title: 'namesilo',
desc: 'namesilo dns provider',
// 这里是对应的 cloudflare的access类型名称
accessType: 'namesilo',
})
export class NamesiloDnsProvider extends AbstractDnsProvider<NamesiloRecord> {
// 通过Autowire传递context
@Autowire()
logger!: ILogger;
access!: NamesiloAccess;
http!: HttpClient;
async onInstance() {
//一些初始化的操作
// 也可以通过ctx成员变量传递context 与Autowire效果一样
this.access = this.ctx.access as NamesiloAccess;
this.http = this.ctx.http;
}
private async doRequest(url: string, params: any = null) {
params = merge(
{
version: 1,
type: 'json',
key: this.access.apiKey,
},
params
);
const qsString = qs.stringify(params);
url = `${url}?${qsString}`;
const res = await this.http.request<any, any>({
url,
baseURL: 'https://www.namesilo.com',
method: 'get',
headers: {
'Content-Type': 'application/json',
},
});
if (res.reply?.code !== '300') {
throw new Error(`${JSON.stringify(res.reply.detail)}`);
}
return res.reply;
}
/**
* 创建dns解析记录用于验证域名所有权
*/
async createRecord(options: CreateRecordOptions): Promise<NamesiloRecord> {
/**
* fullRecord: '_acme-challenge.test.example.com',
* value: 一串uuid
* type: 'TXT',
* domain: 'example.com'
*/
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, type, domain);
//domain=namesilo.com&rrtype=A&rrhost=test&rrvalue=55.55.55.55&rrttl=7207
const record: any = await this.doRequest('/api/dnsAddRecord', {
domain,
rrtype: type,
rrhost: hostRecord,
rrvalue: value,
rrttl: 10,
});
//本接口需要返回本次创建的dns解析记录这个记录会在删除的时候用到
return record;
}
/**
* 删除dns解析记录,清理申请痕迹
* @param options
*/
async removeRecord(options: RemoveRecordOptions<NamesiloRecord>): Promise<void> {
const { fullRecord, value } = options.recordReq;
const record = options.recordRes;
this.logger.info('删除域名解析:', fullRecord, value);
if (!record && !record.record_id) {
this.logger.info('record为空不执行删除');
return;
}
//这里调用删除txt dns解析记录接口
const recordId = record.record_id;
await this.doRequest('/api/dnsDeleteRecord', {
domain: options.recordReq.domain,
record_id: recordId,
});
this.logger.info(`删除域名解析成功:fullRecord=${fullRecord},value=${value}`);
}
}
//实例化这个provider将其自动注册到系统中
new NamesiloDnsProvider();

View File

@@ -0,0 +1,2 @@
export * from './dns-provider.js';
export * from './access.js';

View File

@@ -65,15 +65,33 @@ export class TencentDnsProvider extends AbstractDnsProvider {
} catch (e: any) {
if (e?.code === 'InvalidParameter.DomainRecordExist') {
this.logger.info('域名解析已存在,无需重复添加:', fullRecord, value);
return {};
return await this.findRecord(options);
}
throw e;
}
}
async findRecord(options: CreateRecordOptions): Promise<any> {
const params = {
Domain: options.domain,
RecordType: [options.type],
Keyword: options.hostRecord,
RecordValue: options.value,
};
const ret = await this.client.DescribeRecordFilterList(params);
if (ret.RecordList && ret.RecordList.length > 0) {
this.logger.info('已存在解析记录:', ret.RecordList);
return ret.RecordList[0];
}
return {};
}
async removeRecord(options: RemoveRecordOptions<any>) {
const { fullRecord, value, domain } = options.recordReq;
const record = options.recordRes;
if (!record) {
this.logger.info('解析记录recordId为空不执行删除', fullRecord, value);
}
const params = {
Domain: domain,
RecordId: record.RecordId,