perf: 支持雨云dns解析以及雨云证书更新

pull/436/head
xiaojunnuo 2025-06-12 23:51:21 +08:00
parent 83543487e7
commit 43c7a19849
5 changed files with 230 additions and 70 deletions

View File

@ -49,38 +49,38 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
pageRequest, pageRequest,
addRequest, addRequest,
editRequest, editRequest,
delRequest delRequest,
}, },
toolbar: { toolbar: {
show: false show: false,
}, },
search: { search: {
show: false show: false,
}, },
form: { form: {
wrapper: { wrapper: {
width: "1050px" width: "1050px",
} },
}, },
rowHandle: { rowHandle: {
width: 200 width: 200,
}, },
table: { table: {
scroll: { scroll: {
x: 800 x: 800,
}, },
rowSelection: { rowSelection: {
type: "radio", type: "radio",
selectedRowKeys: selectedRowKey, selectedRowKeys: selectedRowKey,
onChange: onSelectChange onChange: onSelectChange,
}, },
customRow: (record: any) => { customRow: (record: any) => {
return { return {
onClick: () => { onClick: () => {
onSelectChange([record.id]); onSelectChange([record.id]);
} // 点击行 }, // 点击行
}; };
} },
}, },
columns: { columns: {
id: { id: {
@ -88,25 +88,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id", key: "id",
type: "number", type: "number",
column: { column: {
width: 50 width: 50,
}, },
form: { form: {
show: false show: false,
} },
}, },
name: { name: {
title: "名称", title: "名称",
search: { search: {
show: true show: true,
}, },
type: ["text"], type: ["text"],
form: { form: {
rules: [{ required: true, message: "请填写名称" }], rules: [{ required: true, message: "请填写名称" }],
helper: "随便填,当多个相同类型的授权时,便于区分" helper: "随便填,当多个相同类型的授权时,便于区分",
}, },
column: { column: {
width: 200 width: 200,
} },
}, },
from: { from: {
title: "级别", title: "级别",
@ -114,29 +114,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
dict: dict({ dict: dict({
data: [ data: [
{ label: "系统", value: "sys" }, { label: "系统", value: "sys" },
{ label: "用户", value: "user" } { label: "用户", value: "user" },
] ],
}), }),
search: { search: {
show: false show: false,
}, },
form: { form: {
show: false show: false,
}, },
column: { column: {
width: 100, width: 100,
align: "center", align: "center",
component: { component: {
color: "auto" color: "auto",
}, },
order: 10 order: 10,
}, },
valueBuilder: ({ row, key, value }) => { valueBuilder: ({ row, key, value }) => {
row[key] = row.userId > 0 ? "user" : "sys"; row[key] = row.userId > 0 ? "user" : "sys";
} },
}, },
...commonColumnsDefine ...commonColumnsDefine,
} },
} },
}; };
} }

View File

@ -24,3 +24,4 @@ export * from './plugin-notification/index.js'
export * from './plugin-flex/index.js' export * from './plugin-flex/index.js'
export * from './plugin-farcdn/index.js' export * from './plugin-farcdn/index.js'
export * from './plugin-fnos/index.js' export * from './plugin-fnos/index.js'
export * from './plugin-rainyun/index.js'

View File

@ -1,5 +1,6 @@
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline"; import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
import {HttpRequestConfig} from "@certd/basic"; import {HttpRequestConfig} from "@certd/basic";
import { CertInfo } from "@certd/plugin-cert";
/** /**
@ -41,13 +42,13 @@ export class RainyunAccess extends BaseAccess {
testRequest = true; testRequest = true;
async onTestRequest() { async onTestRequest() {
await this.getDomainList({}); await this.getDomainList({limit:1});
return "ok" return "ok"
} }
// {"columnFilters":{"domains.Domain":"domain"},"sort":[],"page":1,"perPage":20} // {"columnFilters":{"domains.Domain":"domain"},"sort":[],"page":1,"perPage":20}
async getDomainList(req:{offset?:number,size?:number,query?:string}){ async getDomainList(req:{offset?:number,limit?:number,query?:string}){
const size = req.size ?? 20; const size = req.limit ?? 20;
const offset = req.offset ?? 0; const offset = req.offset ?? 0;
let page = Math.floor(offset / size); let page = Math.floor(offset / size);
if(offset % size === 0 ){ if(offset % size === 0 ){
@ -67,17 +68,63 @@ export class RainyunAccess extends BaseAccess {
return { return {
total: res.TotalRecords, total: res.TotalRecords,
list: res.Records || [] list: res.Records || [],
limit: size,
offset: offset
} }
} }
async getDomainId(domain:string){ async getCertList(req:{offset?:number,limit?:number,query?:string}){
const res = await this.getDomainList({query: domain,size:1}); const size = req.limit ?? 20;
if (res.list.length === 0) { const offset = req.offset ?? 0;
throw new Error(`域名${domain}不存在 ` ); let page = Math.floor(offset / size);
if(offset % size === 0 ){
page++
} }
return res.list[0].ID; const options ={
columnFilters: {
Domain: req.query??""
},
sort:[],
page: page,
perPage: size,
}
const res = await this.doRequest({
url: `product/sslcenter/?options=${encodeURIComponent(JSON.stringify(options))}`,
method: "GET",
});
return {
total: res.TotalRecords,
list: res.Records || [],
limit: size,
offset: offset
}
}
async doCertReplace(req:{certId:number,cert:CertInfo}){
// /product/sslcenter/{id}
return await this.doRequest({
url: `product/sslcenter/${req.certId}`,
method: "PUT",
data: {
cert: req.cert.crt,
key: req.cert.key,
}
});
}
async getDomainId(domain:string){
const res = await this.getDomainList({query: domain,limit:1});
if (res.list.length === 0) {
throw new Error(`域名${domain}不存在` );
}
return res.list[0].id;
} }
async doRequest(req:HttpRequestConfig){ async doRequest(req:HttpRequestConfig){

View File

@ -1,65 +1,64 @@
import {AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions} from '@certd/plugin-cert'; import { AbstractDnsProvider, CreateRecordOptions, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import {RainyunAccess} from "./access.js"; import { RainyunAccess } from "./access.js";
@IsDnsProvider({ @IsDnsProvider({
name: 'rainyun', name: "rainyun",
title: '雨云', title: "雨云",
desc: '雨云DNS解析提供商', desc: "雨云DNS解析提供商",
accessType: 'rainyun', accessType: "rainyun",
icon: 'svg:icon-lucky', icon: "svg:icon-lucky",
order:0, order: 0
}) })
export class RainyunDnsProvider extends AbstractDnsProvider { export class RainyunDnsProvider extends AbstractDnsProvider {
client: any; client: any;
async onInstance() { async onInstance() {
} }
async createRecord(options: CreateRecordOptions): Promise<any> { async createRecord(options: CreateRecordOptions): Promise<any> {
const access: RainyunAccess = this.ctx.access as RainyunAccess const access: RainyunAccess = this.ctx.access as RainyunAccess;
const domainId = await access.getDomainId(options.domain); const domainId = await access.getDomainId(options.domain);
if (!domainId) { if (!domainId) {
throw new Error(`域名${options.domain}未找到`); throw new Error(`域名${options.domain}未找到`);
} }
const { fullRecord,hostRecord, value, type, domain } = options; const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info('添加域名解析:', fullRecord, value, domain); this.logger.info("添加域名解析:", fullRecord, value, domain);
const ret = await access.doRequest({ const ret = await access.doRequest({
url: `/product/domain/${domainId}/dns`, url: `/product/domain/${domainId}/dns`,
method: 'POST', method: "POST",
data: { data: {
host: hostRecord, host: hostRecord,
value: value, value: value,
type: type, level: 1,
line: 'DEFAULT', type: type,
ttl: 360, line: "DEFAULT",
}, ttl: 60
}); }
this.logger.info('添加域名解析成功:', JSON.stringify(options), ret.ID); });
return { this.logger.info("添加域名解析成功:", JSON.stringify(options), ret);
recordId:ret.ID, return {
domainId: domainId recordId: ret,
}; domainId: domainId
};
} }
async removeRecord(options: RemoveRecordOptions<any>): Promise<any> { async removeRecord(options: RemoveRecordOptions<any>): Promise<any> {
const { fullRecord, value } = options.recordReq; const { fullRecord, value } = options.recordReq;
const access: RainyunAccess = this.ctx.access as RainyunAccess const access: RainyunAccess = this.ctx.access as RainyunAccess;
const record = options.recordRes; const record = options.recordRes;
const ret = await access.doRequest({ const ret = await access.doRequest({
url: `/product/domain/${record.domainId}/dns`, url: `/product/domain/${record.domainId}/dns?record_id=${record.recordId}`,
method: 'DELETE', method: "DELETE",
data:{
record_id: record.recordId
}
}); });
this.logger.info('删除域名解析成功:', fullRecord, value, ret); this.logger.info("删除域名解析成功:", fullRecord, value, ret);
return ret; return ret;
} }
} }

View File

@ -0,0 +1,113 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageReq, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { RainyunAccess } from "../access.js";
@IsTaskPlugin({
//命名规范,插件类型+功能就是目录plugin-demo中的demo大写字母开头驼峰命名
name: "RainyunRefreshCert",
title: "雨云-更新证书",
desc: "app.rainyun.com",
icon: "svg:icon-lucky",
//插件分组
group: pluginGroups.cdn.key,
needPlus: false,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
//类名规范跟上面插件名称name一致
export class RainyunRefreshCert extends AbstractTaskPlugin {
//证书选择,此项必须要有
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
//授权选择框
@TaskInput({
title: "雨云授权",
component: {
name: "access-selector",
type: "rainyun" //固定授权类型
},
required: true //必填
})
accessId!: string;
//
@TaskInput(
createRemoteSelectInputDefine({
title: "证书Id",
helper: "要更新的rainyun证书id",
action: RainyunRefreshCert.prototype.onGetCertList.name
})
)
certList!: number[];
//插件实例化时执行的方法
async onInstance() {
}
//插件执行方法
async execute(): Promise<void> {
const access = await this.getAccess<RainyunAccess>(this.accessId);
for (const item of this.certList) {
this.logger.info(`----------- 开始更新证书:${item}`);
await access.doCertReplace({
certId: item,
cert: this.cert
});
this.logger.info(`----------- 更新证书${item}成功`);
}
this.logger.info("部署完成");
}
async onGetCertList(req: PageReq = {}) {
const access = await this.getAccess<RainyunAccess>(this.accessId);
const offset = req.offset ?? 0;
const limit = req.limit ?? 100;
const res = await access.getCertList({
offset,
limit
});
const total = res.total;
const list = res.list;
if (!list || list.length === 0) {
throw new Error("没有找到证书,请先在控制台上传一次证书且关联站点");
}
const options = list.map((item: any) => {
return {
label: `${item.Domain}<${item.ID}>`,
value: item.ID,
domain: item.Domain.split(",").map(item => item.trim())
};
});
return {
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
total: total,
offset: offset,
limit: limit
};
}
}
//实例化一下,注册插件
new RainyunRefreshCert();