关于毅融盾支持Certd自动部署的公告
前言
关于毅融盾
毅融盾是我们近期发布的全新平台,深度融合网宿与白山 CDN 资源,提供多源智能调度、全网极速分发与立体安全防护,助力业务实现高可用、低延迟的边缘加速。
痛点与解决方案
随着 SSL 证书有效期不断缩短(从传统的 1-2 年逐步过渡至 90 天),频繁手动更换证书已成为运维工作中的痛点。手动操作不仅耗时费力,还容易因疏忽导致证书过期,进而影响业务可用性。
为解决这一难题,我们自主研发了毅融盾 CDN 的 SSL 证书自动部署模块,并成功将其集成至Certd系统中。然而遗憾的是,该功能暂未被Certd 官方项目采纳。
为让更多有需要的用户受益,我们决定将实现方案整理成教程分享出来,供大家参考。教程中涉及的所有修改文件均已附于文末附件中。
一、导入部署插件


二、创建流水线并选择部署插件并配置



获取域名ID
- 登录毅融盾 CDN 控制台
- 进入「我的域名」页面
- 点击目标域名的「配置」按钮
- 从浏览器地址栏复制域名 ID
注意事项
- 域名ID 必须与证书域名完全匹配
- 首次部署建议手动验证证书是否生效
- 建议开启自动续期,确保证书持续有效
插件:
name: HYRCDNDeploy
icon: fa-brands:keycdn:#007AFF
title: 毅融盾-部署证书至CDN
group: cdn
desc: 自动登录毅融盾平台并将 SSL 证书部署到指定 CDN 域名
setting: null
sysSetting: null
type: custom
disabled: false
version: 1.0.0
pluginType: deploy
author: hydun
default:
strategy:
runStrategy: 1
input:
cert:
title: 前置任务证书
helper: 请选择前置任务产生的证书(需包含 fullchain 和 privateKey)
component:
name: output-selector
vModel: modelValue
from:
- ':cert:'
required: true
certDomains:
title: 当前证书域名
component:
name: cert-domains-getter
mergeScript: |
return {
component: {
inputKey: ctx.compute(({ form }) => form.cert)
}
}
required: false
username:
title: 控制台账号
helper: 毅融盾 CDN 控制台登录账号
required: true
password:
title: 控制台密码
helper: 毅融盾 CDN 控制台登录密码
encrypt: true
required: true
domainId:
title: 域名ID
helper: 在毅融盾控制台域名列表查看,一般为数字
required: true
showRunStrategy: false
content: |-
const { AbstractTaskPlugin } = await import("@certd/pipeline");
return class YrdcdnDeployTask extends AbstractTaskPlugin {
cert;
certDomains;
username;
password;
domainId;
async execute() {
this.logger.info("开始部署证书到毅融盾 CDN");
// 必要参数校验
if (!this.username) throw new Error("缺少配置:username(控制台账号)");
if (!this.password) throw new Error("缺少配置:password(控制台密码)");
if (!this.domainId) throw new Error("缺少配置:domainId(域名ID)");
// 可选:打印证书对象(调试用)
// this.logger.info("证书对象内容: ", JSON.stringify(this.cert, null, 2));
// 自动适配证书字段
const fullchain = this.cert?.fullchain || this.cert?.crt;
const privatekey = this.cert?.privateKey || this.cert?.key;
if (!fullchain || !privatekey) {
throw new Error("证书内容不完整,请检查前置任务输出的证书字段");
}
// 1. 登录获取 Token(表单格式)
this.logger.info("正在登录毅融盾平台...");
const formBody = `userAccount=${encodeURIComponent(this.username)}&userPwd=${encodeURIComponent(this.password)}&remember=true`;
const loginRes = await this.ctx.http.request({
url: "https://rcdn.hydun.com/login/loginUser",
method: "post",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
data: formBody,
});
// 获取响应体(可能是字符串或对象)
const responseBody = loginRes?.data ?? loginRes;
this.logger.info("登录响应类型:", typeof responseBody);
this.logger.info("登录响应内容(前100字符):", typeof responseBody === 'string' ? responseBody.substring(0, 100) : JSON.stringify(responseBody).substring(0, 100));
let token = null;
// 情况1:响应是纯字符串(JWT token),直接视为成功
if (typeof responseBody === 'string' && responseBody.trim().length > 0 && responseBody.includes('.')) {
// 简单判断:包含两个点,很像JWT
token = responseBody.trim();
this.logger.info("检测到直接返回的Token字符串,登录成功");
}
// 情况2:响应是JSON对象,包含 code 字段
else if (responseBody && typeof responseBody === 'object') {
if (responseBody.code === "SUCCESS") {
token = responseBody.data;
this.logger.info("登录成功(JSON格式),Token已获取");
} else {
const errMsg = responseBody.message || responseBody.msg || "未知错误";
throw new Error(`登录失败:${errMsg},完整响应:${JSON.stringify(responseBody)}`);
}
}
// 情况3:其他情况(空字符串、无效内容)
else {
throw new Error(`登录失败:响应格式异常,内容:${typeof responseBody === 'string' ? responseBody : JSON.stringify(responseBody)}`);
}
if (!token) {
throw new Error("登录成功但未提取到Token");
}
// 2. 部署证书(JSON + Cookie)
this.logger.info(`正在为域名ID ${this.domainId} 部署证书...`);
const deployRes = await this.ctx.http.request({
url: "https://rcdn.hydun.com/CdnDomainHttps/httpsConfiguration",
method: "post",
headers: {
"Content-Type": "application/json",
Cookie: `kuocai_cdn_token=${token}`,
},
data: {
doMainId: this.domainId,
https: {
certificate_name: `certd_${Date.now()}`,
certificate_source: "0",
certificate_value: fullchain,
https_status: "on",
private_key: privatekey,
},
},
});
const deployBody = deployRes?.data ?? deployRes;
// 部署接口可能也返回纯文本或JSON,同样做兼容
if (typeof deployBody === 'string' && deployBody.includes('SUCCESS')) {
this.logger.info(`域名ID ${this.domainId} 证书部署成功(纯文本响应)!`);
} else if (deployBody && typeof deployBody === 'object' && deployBody.code === "SUCCESS") {
this.logger.info(`域名ID ${this.domainId} 证书部署成功!`);
} else {
const errMsg = deployBody?.message || deployBody?.msg || (typeof deployBody === 'string' ? deployBody : JSON.stringify(deployBody));
throw new Error(`证书部署失败:${errMsg}`);
}
}
};






























还没有人评论,快来抢沙发吧~