perf: 新增阿里云直播证书部署插件

This commit is contained in:
xiaojunnuo
2026-05-22 19:06:55 +08:00
parent b30f02a1fb
commit 8edb6f8727
8 changed files with 173 additions and 8 deletions
+2 -1
View File
@@ -33,4 +33,5 @@ test.js
.history
/logs
.pnpm-lock.yaml
pnpm-lock.yaml
pnpm-lock.yaml
.studio/
+2 -1
View File
@@ -20,5 +20,6 @@
"scm.repositories.visible": 9,
"scm.repositories.explorer": false,
"scm.repositories.selectionMode": "multiple",
"scm.repositories.sortOrder": "discovery time"
"scm.repositories.sortOrder": "discovery time",
"git.ignoreLimitWarning": true
}
+4 -1
View File
@@ -1,4 +1,4 @@
# Certd 开发 Agent 上下文
# Certd 开发 Agent 上下文
这个文件是给在本仓库工作的开发 agent 看的常驻项目说明。后续会话进入仓库后,应先读取它,再按任务需要查看具体代码,避免每次都重新全量扫描项目。
@@ -218,3 +218,6 @@ Get-ChildItem packages\ui\certd-client\src\views\certd
- 单个 monorepo 包运行单元测试时,优先使用 `corepack pnpm --dir <包目录> test:unit`,例如 `corepack pnpm --dir packages\ui\certd-server test:unit``corepack pnpm --dir packages\core\basic test:unit``corepack pnpm --dir packages\plugins\plugin-lib test:unit`;也可以用包名过滤,例如 `corepack pnpm --filter @certd/ui-server test:unit`。前端 `packages\ui\certd-client` 暂时不跑单元测试。
- 前端 TS/Vue/locale 等文件改动后,优先只对本次改动文件运行项目现有自动格式化/修复;Windows/PowerShell 下 Prettier 已验证可用命令为 `packages\ui\certd-client\node_modules\.bin\prettier.cmd --write <files>`ESLint 可用命令为 `packages\ui\certd-client\node_modules\.bin\eslint.cmd --fix <files>`;不要运行 `vue-tsc` / `pnpm tsc`;不要为了格式化无关文件而扩大 diff。项目保留了 `tslint` 依赖,但当前主要使用 ESLint + Prettier。
- 优先对改动包运行聚焦的测试;后端可按包运行单元测试,前端优先使用 Prettier/ESLint 做改动文件验证。只有跨包影响明显时再考虑全 monorepo 构建。
- 不要主动运行 `pnpm install` 安装依赖:用户会事先准备好 `node_modules`。如果 `pnpm install``test:unit` 因缺少依赖、TTY 或网络问题失败,立即停止尝试,告知用户解决环境问题。
@@ -13,7 +13,7 @@ export interface ICertInfoGetter {
export type CertInfo = {
crt: string; //fullchain证书
key: string; //私钥
csr: string; //csr
csr?: string; //csr
oc?: string; //仅证书,非fullchain证书
ic?: string; //中间证书
pfx?: string;
@@ -0,0 +1,159 @@
import { AbstractTaskPlugin, IsTaskPlugin, PageSearch, pluginGroups, RunStrategy, TaskInput } from '@certd/pipeline';
import { CertApplyPluginNames } from '@certd/plugin-cert';
import { CertInfo, createCertDomainGetterInputDefine, createRemoteSelectInputDefine } from '@certd/plugin-lib';
import { AliyunAccess } from '../../../plugin-lib/aliyun/access/index.js';
import { AliyunSslClient, CasCertId } from '../../../plugin-lib/aliyun/lib/index.js';
@IsTaskPlugin({
name: 'DeployCertToAliyunLive',
title: '阿里云-部署至直播(Live',
icon: 'svg:icon-aliyun',
group: pluginGroups.aliyun.key,
desc: '部署证书到阿里云视频直播(Live)域名',
needPlus: false,
default: {
strategy: {
runStrategy: RunStrategy.SkipWhenSucceed,
},
},
})
export class DeployCertToAliyunLive extends AbstractTaskPlugin {
@TaskInput({
title: '域名证书',
helper: '请选择前置任务输出的域名证书',
component: {
name: 'output-selector',
from: [...CertApplyPluginNames, 'uploadCertToAliyun'],
},
template: false,
required: true,
})
cert!: CertInfo | CasCertId;
@TaskInput(createCertDomainGetterInputDefine({ props: { required: false } }))
certDomains!: string[];
@TaskInput({
title: 'Access授权',
helper: '阿里云授权AccessKeyId、AccessKeySecret',
component: {
name: 'access-selector',
type: 'aliyun',
},
required: true,
})
accessId!: string;
@TaskInput({
title: '证书服务接入点',
helper: '不会选就按默认',
value: 'cas.aliyuncs.com',
component: {
name: 'a-select',
options: [
{ value: 'cas.aliyuncs.com', label: '中国大陆' },
{ value: 'cas.ap-southeast-1.aliyuncs.com', label: '新加坡' },
{ value: 'cas.eu-central-1.aliyuncs.com', label: '德国(法兰克福)' },
],
},
required: true,
})
endpoint!: string;
@TaskInput(
createRemoteSelectInputDefine({
title: '直播域名',
helper: '请选择要部署证书的直播域名',
typeName: 'DeployCertToAliyunLive',
action: DeployCertToAliyunLive.prototype.onGetDomainList.name,
watches: ['certDomains', 'accessId'],
pager: true,
search: true,
})
)
domainList!: string[];
async onInstance() {}
async execute(): Promise<void> {
this.logger.info('开始部署证书到阿里云直播');
const access = await this.getAccess<AliyunAccess>(this.accessId);
if (this.cert == null) {
throw new Error('域名证书参数为空,请检查前置任务');
}
const client = await this.getClient(access);
const sslClient = new AliyunSslClient({
access,
logger: this.logger,
endpoint: this.endpoint || 'cas.aliyuncs.com',
});
// 确保证书已上传到 CAS,统一使用 cas 方式部署
const casCert = await sslClient.uploadCertOrGet(this.cert);
// const certName = this.appendTimeSuffix(this.certName || casCert.certName);
for (const domain of this.domainList) {
const res = await client.doRequest({
action: 'SetLiveDomainCertificate',
version: '2016-11-01',
protocol: 'HTTPS',
data: {
query: {
DomainName: domain,
CertName: casCert.certName,
CertType: 'cas',
SSLProtocol: 'on',
CertId: casCert.certId,
},
},
});
this.logger.info('部署直播域名[' + domain + ']证书成功:' + JSON.stringify(res));
}
}
async getClient(access: AliyunAccess) {
const endpoint = 'live.aliyuncs.com';
return access.getClient(endpoint);
}
async onGetDomainList(data: PageSearch) {
if (!this.accessId) {
throw new Error('请选择Access授权');
}
const access = await this.getAccess<AliyunAccess>(this.accessId);
const client = await this.getClient(access);
const res = await client.doRequest({
action: 'DescribeLiveUserDomains',
version: '2016-11-01',
protocol: 'HTTPS',
data: {
query: {
DomainName: data.searchKey || undefined,
PageNumber: data.pageNo || 1,
PageSize: data.pageSize || 50,
},
},
});
const list = res?.Domains?.PageData;
if (!list || list.length === 0) {
throw new Error('没有找到直播域名,请先在阿里云添加直播域名');
}
const options = list.map((item: any) => {
return {
label: item.DomainName,
value: item.DomainName,
domain: item.DomainName,
};
});
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
}
}
new DeployCertToAliyunLive();
@@ -1,4 +1,4 @@
export * from './deploy-to-cdn/index.js';
export * from './deploy-to-cdn/index.js';
export * from './deploy-to-dcdn/index.js';
export * from './deploy-to-oss/index.js';
export * from './upload-to-aliyun/index.js';
@@ -10,8 +10,9 @@ export * from './deploy-to-fc/index.js';
export * from './deploy-to-esa/index.js';
export * from './deploy-to-ga/index.js';
export * from './deploy-to-vod/index.js';
export * from './deploy-to-live/index.js';
export * from './deploy-to-apigateway/index.js';
export * from './deploy-to-apig/index.js';
export * from './deploy-to-ack/index.js';
export * from './deploy-to-all/index.js';
export * from './delete-expiring-cert/index.js';
export * from './delete-expiring-cert/index.js';
@@ -38,7 +38,7 @@ export type Providers = {
export type CertInfo = {
crt: string; //fullchain证书
key: string; //私钥
csr: string; //csr
csr?: string; //csr
oc?: string; //仅证书,非fullchain证书
ic?: string; //中间证书
pfx?: string;
@@ -194,7 +194,7 @@ cert.jksjks格式证书文件,java服务器使用
return pem;
}
formatCerts(cert: { crt: string; key: string; csr: string }) {
formatCerts(cert: { crt: string; key: string; csr?: string }) {
const newCert: CertInfo = {
crt: this.formatCert(cert.crt),
key: this.formatCert(cert.key),