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,
addRequest,
editRequest,
delRequest
delRequest,
},
toolbar: {
show: false
show: false,
},
search: {
show: false
show: false,
},
form: {
wrapper: {
width: "1050px"
}
width: "1050px",
},
},
rowHandle: {
width: 200
width: 200,
},
table: {
scroll: {
x: 800
x: 800,
},
rowSelection: {
type: "radio",
selectedRowKeys: selectedRowKey,
onChange: onSelectChange
onChange: onSelectChange,
},
customRow: (record: any) => {
return {
onClick: () => {
onSelectChange([record.id]);
} // 点击行
}, // 点击行
};
}
},
},
columns: {
id: {
@ -88,25 +88,25 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id",
type: "number",
column: {
width: 50
width: 50,
},
form: {
show: false
}
show: false,
},
},
name: {
title: "名称",
search: {
show: true
show: true,
},
type: ["text"],
form: {
rules: [{ required: true, message: "请填写名称" }],
helper: "随便填,当多个相同类型的授权时,便于区分"
helper: "随便填,当多个相同类型的授权时,便于区分",
},
column: {
width: 200
}
width: 200,
},
},
from: {
title: "级别",
@ -114,29 +114,29 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
dict: dict({
data: [
{ label: "系统", value: "sys" },
{ label: "用户", value: "user" }
]
{ label: "用户", value: "user" },
],
}),
search: {
show: false
show: false,
},
form: {
show: false
show: false,
},
column: {
width: 100,
align: "center",
component: {
color: "auto"
color: "auto",
},
order: 10
order: 10,
},
valueBuilder: ({ row, key, value }) => {
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-farcdn/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 {HttpRequestConfig} from "@certd/basic";
import { CertInfo } from "@certd/plugin-cert";
/**
@ -41,13 +42,13 @@ export class RainyunAccess extends BaseAccess {
testRequest = true;
async onTestRequest() {
await this.getDomainList({});
await this.getDomainList({limit:1});
return "ok"
}
// {"columnFilters":{"domains.Domain":"domain"},"sort":[],"page":1,"perPage":20}
async getDomainList(req:{offset?:number,size?:number,query?:string}){
const size = req.size ?? 20;
async getDomainList(req:{offset?:number,limit?:number,query?:string}){
const size = req.limit ?? 20;
const offset = req.offset ?? 0;
let page = Math.floor(offset / size);
if(offset % size === 0 ){
@ -67,17 +68,63 @@ export class RainyunAccess extends BaseAccess {
return {
total: res.TotalRecords,
list: res.Records || []
list: res.Records || [],
limit: size,
offset: offset
}
}
async getDomainId(domain:string){
const res = await this.getDomainList({query: domain,size:1});
if (res.list.length === 0) {
throw new Error(`域名${domain}不存在 ` );
async getCertList(req:{offset?:number,limit?:number,query?:string}){
const size = req.limit ?? 20;
const offset = req.offset ?? 0;
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){

View File

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