perf: 支持部署到goedge

This commit is contained in:
xiaojunnuo
2025-12-29 18:57:22 +08:00
parent 136e8dd7c5
commit 44bf4b1cc1
7 changed files with 403 additions and 5 deletions
@@ -138,6 +138,7 @@ const getOptions = async () => {
onError(err: any) {
hasError.value = true;
message.value = `获取选项出错:${err.message}`;
optionsRef.value = [];
},
showErrorNotify: false,
}
@@ -41,4 +41,5 @@ export * from './plugin-xinnetconnet/index.js'
export * from './plugin-oauth/index.js'
export * from './plugin-cmcc/index.js'
export * from './plugin-template/index.js'
export * from './plugin-ucloud/index.js'
export * from './plugin-ucloud/index.js'
export * from './plugin-goedge/index.js'
@@ -0,0 +1,264 @@
import {AccessInput, BaseAccess, IsAccess} from "@certd/pipeline";
import {HttpRequestConfig} from "@certd/basic";
import { CertInfo, CertReader } from "@certd/plugin-cert";
import dayjs from "dayjs";
/**
*/
@IsAccess({
name: "goedge",
title: "GoEdge授权",
icon: "fa:leaf:#6C6BF6",
order: 100
})
export class GoEdgeAccess extends BaseAccess {
@AccessInput({
title: "系统地址",
component: {
name: "a-input",
vModel: "value"
},
helper:"例如:http://yourdomain.com:8002 需要在API节点配置中开启HTTP访问地址",
encrypt: false,
required: true
})
endpoint!: string;
@AccessInput({
title: "用户类型",
component: {
name: "a-select",
vModel: "value",
options: [
{
label: "用户",
value: "user"
},
{
label: "管理员",
value: "admin"
}
]
},
encrypt: false,
required: true
})
userType!: string;
@AccessInput({
title: "accessKeyId",
helper:`用户AccessKey: 在”平台用户-用户-详情-AccessKey” 或 商业版的“访问控制” 中创建。
管理员AccessKey:在”系统用户-用户-详情-AccessKey” 中创建。`,
component: {
name: "a-input",
vModel: "value"
},
encrypt: false,
required: true
})
accessKeyId!: string;
@AccessInput({
title: "accessKey",
component: {
name: "a-input",
vModel: "value"
},
encrypt: true,
required: true
})
accessKey!: string;
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "点击测试接口是否正常"
})
testRequest = true;
accessToken: {expiresAt:number,token:string}
async onTestRequest() {
await this.getCertList({pageSize:1});
return "ok"
}
/**
*
* @param req "id": 600,
"isOn": true,
"name": "124.220.225.222",
"description": "",
"certData": null,
"keyData": null,
"serverName": "",
"isCA": false,
"isACME": false,
"timeBeginAt": 1763856000,
"timeEndAt": 1771718399,
"dnsNames": [
"124.220.225.222" //domain
],
"commonNames": [
"ZeroSSL ECC Domain Secure Site CA",
"USERTrust ECC Certification Authority"
],
"ocsp": null,
"ocspExpiresAt": 0,
"ocspError": ""
* @returns
*/
async getCertList(req:{pageNo?:number,pageSize?:number,query?:string,onlyUser?:boolean,userId?:number}){
const pageNo = req.pageNo ?? 1;
const pageSize = req.pageSize ?? 20;
const body:any = {
keyword: req.query??"",
offset: (pageNo-1)*pageSize,
size: pageSize,
}
if (req.onlyUser){
body["onlyUser"] = true;
}
if (req.userId){
body["userId"] = req.userId;
}
const countRes = await this.doRequest({
url: `/SSLCertService/countSSLCerts`,
method: "POST",
data:body
});
const total = countRes.count || 9999;
const res = await this.doRequest({
url: `/SSLCertService/listSSLCerts`,
method: "POST",
data:body
});
// this.ctx.logger.info("getCertList",JSON.stringify(res));
const sslCertsJSON = this.ctx.utils.hash.base64Decode(res.sslCertsJSON) || "[]";
const sslCerts = JSON.parse(sslCertsJSON) as CertInfo[];
return {
total: total,
list: sslCerts || [],
pageNo: pageNo,
pageSize: pageSize
}
}
async doCertReplace(req:{certId:number,cert:CertInfo}){
const res = await this.doRequest({
url: `/SSLCertService/findEnabledSSLCertConfig`,
method: "POST",
data: {
sslCertId: req.certId,
}
});
const sslCertJSON = this.ctx.utils.hash.base64Decode(res.sslCertJSON) || "{}";
const sslCert = JSON.parse(sslCertJSON) ;
const certReader = new CertReader(req.cert);
const dnsNames = certReader.getAllDomains()
// /product/sslcenter/{id}
return await this.doRequest({
url: `/SSLCertService/updateSSLCert`,
method: "POST",
data: {
sslCertId: req.certId,
certData: this.ctx.utils.hash.base64(req.cert.crt),
keyData: this.ctx.utils.hash.base64(req.cert.key),
isOn: sslCert.isOn,
name: sslCert.name || certReader.buildCertName(),
description: sslCert.description || "upload by certd",
serverName: sslCert.serverName,
timeBeginAt: certReader.detail.notBefore.getTime()/1000,
timeEndAt: certReader.detail.notAfter.getTime()/1000,
dnsNames: dnsNames,
/**
* // 是否启用
bool isOn;
// 名称
string name;
// 描述(备注)
string description;
string serverName;
bool isCA;
bytes certData;
bytes keyData;
int64 timeBeginAt;
int64 timeEndAt;
[]string dnsNames;
[]string commonNames;
*/
}
});
}
async getToken(){
// /APIAccessTokenService/getAPIAccessToken
if (this.accessToken && this.accessToken.expiresAt >dayjs().unix()){
return this.accessToken;
}
const res = await this.doRequest({
url: "/APIAccessTokenService/getAPIAccessToken",
method: "POST",
data: {
type: this.userType,
"accessKeyId": this.accessKeyId,
"accessKey": this.accessKey,
}
});
this.accessToken = res;
return res;
}
async doRequest(req:HttpRequestConfig){
const headers: Record<string,string> = {}
if(!req.url.endsWith("/getAPIAccessToken")){
if (!this.accessToken || this.accessToken.expiresAt < dayjs().unix()){
await this.getToken();
}
headers["X-Edge-Access-Token"] = this.accessToken.token;
}
let endpoint = this.endpoint;
if (endpoint.endsWith("/")){
endpoint = endpoint.slice(0,-1);
}
const res = await this.ctx.http.request({
url: req.url,
baseURL: endpoint,
method: req.method|| "POST",
data: req.data,
params: req.params,
headers:{
...headers,
...req.headers
},
// httpProxy: this.httpProxy||undefined,
});
if (res.code === 200) {
return res.data;
}
throw new Error(res.message || res);
}
}
new GoEdgeAccess();
@@ -0,0 +1,2 @@
export * from "./plugins/index.js";
export * from "./access.js";
@@ -0,0 +1 @@
export * from './plugin-refresh-cert.js'
@@ -0,0 +1,131 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from "@certd/pipeline";
import { CertApplyPluginNames, CertInfo } from "@certd/plugin-cert";
import { createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from "@certd/plugin-lib";
import { GoEdgeAccess } from "../access.js";
@IsTaskPlugin({
//命名规范,插件类型+功能(就是目录plugin-demo中的demo),大写字母开头,驼峰命名
name: "GoEdgeRefreshCert",
title: "GoEdge-更新证书",
desc: "GoEdge",
icon: "fa:leaf:#6C6BF6",
//插件分组
group: pluginGroups.cdn.key,
needPlus: false,
default: {
//默认值配置照抄即可
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed
}
}
})
//类名规范,跟上面插件名称(name)一致
export class GoEdgeRefreshCert extends AbstractTaskPlugin {
//证书选择,此项必须要有
@TaskInput({
title: "域名证书",
helper: "请选择前置任务输出的域名证书",
component: {
name: "output-selector",
from: [...CertApplyPluginNames]
}
// required: true, // 必填
})
cert!: CertInfo;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
//授权选择框
@TaskInput({
title: "GoEdge授权",
component: {
name: "access-selector",
type: "goedge" //固定授权类型
},
required: true //必填
})
accessId!: string;
//
@TaskInput({
title: "用户id",
component: {
name: "a-input-number",
vModel: "value"
},
helper:"用于查询用户证书,点击用户详情->浏览器地址中userId值,如:/users/user?userId=1\n如果为空,则查询管理员证书",
required: false //必填
})
userId!: number;
@TaskInput(
createRemoteSelectInputDefine({
title: "证书Id",
helper: "要更新的GoEdge证书id",
pager:true,
search:true,
action: GoEdgeRefreshCert.prototype.onGetCertList.name
})
)
certList!: number[];
//插件实例化时执行的方法
async onInstance() {
}
//插件执行方法
async execute(): Promise<void> {
const access = await this.getAccess<GoEdgeAccess>(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: PageSearch = {}) {
const access = await this.getAccess<GoEdgeAccess>(this.accessId);
const pageNo = req.pageNo ?? 1;
const pageSize = req.pageSize ?? 100;
const res = await access.getCertList({
pageNo,
pageSize,
query: req.searchKey,
userId: this.userId,
onlyUser: this.userId !== undefined
});
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.name}<${item.id}>`,
value: item.id,
domain: item.dnsNames || [],
title: item.dnsNames?.join(",") || ""
};
});
return {
list: this.ctx.utils.options.buildGroupOptions(options, this.certDomains),
total: total,
pageNo: pageNo,
pageSize: pageSize
};
}
}
//实例化一下,注册插件
new GoEdgeRefreshCert();
@@ -19,10 +19,8 @@ export class RainyunAccess extends BaseAccess {
title: "ApiKey",
component: {
placeholder: "api-key",
component: {
name: "a-input",
vModel: "value"
}
name: "a-input",
vModel: "value"
},
helper:"https://app.rainyun.com/account/settings/api-key",
encrypt: true,