mirror of
https://github.com/certd/certd.git
synced 2026-07-20 05:37:32 +08:00
Compare commits
3 Commits
b35e7b0702
...
1ae185d0bc
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ae185d0bc | |||
| 82276b53a8 | |||
| d5882f16be |
@@ -6,7 +6,7 @@ name: AliyunDeployCertToESA
|
||||
title: 阿里云-部署至ESA
|
||||
icon: svg:icon-aliyun
|
||||
group: aliyun
|
||||
desc: 部署证书到阿里云ESA(边缘安全加速),自动删除过期证书
|
||||
desc: 部署证书到阿里云ESA(边缘安全加速),支持边缘证书和SaaS证书两种模式
|
||||
needPlus: false
|
||||
input:
|
||||
cert:
|
||||
@@ -32,7 +32,6 @@ input:
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
template: false
|
||||
required: false
|
||||
order: 0
|
||||
@@ -70,6 +69,20 @@ input:
|
||||
type: aliyun
|
||||
required: true
|
||||
order: 0
|
||||
deployMode:
|
||||
title: 部署模式
|
||||
value: edge
|
||||
component:
|
||||
name: a-radio-group
|
||||
vModel: value
|
||||
options:
|
||||
- label: 边缘证书
|
||||
value: edge
|
||||
- label: SaaS证书
|
||||
value: saas
|
||||
helper: 边缘证书:将证书部署到站点的边缘节点;SaaS证书:将证书部署到站点的SaaS域名
|
||||
required: true
|
||||
order: 0
|
||||
siteIds:
|
||||
title: 站点
|
||||
component:
|
||||
@@ -96,9 +109,40 @@ input:
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
helper: 请选择要部署证书的站点
|
||||
order: 0
|
||||
saasDomainIds:
|
||||
title: SaaS域名
|
||||
helper: 请选择要部署证书的SaaS域名(SaaS证书模式下必选)
|
||||
component:
|
||||
name: remote-select
|
||||
vModel: value
|
||||
mode: tags
|
||||
type: plugin
|
||||
action: onGetCustomHostnameList
|
||||
search: false
|
||||
pager: false
|
||||
single: false
|
||||
watches:
|
||||
- certDomains
|
||||
- accessId
|
||||
- siteIds
|
||||
- accessId
|
||||
- regionId
|
||||
required: false
|
||||
mergeScript: |2-
|
||||
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.deployMode === 'saas'
|
||||
}),
|
||||
component:{
|
||||
form: ctx.compute(({form})=>{
|
||||
return form
|
||||
})
|
||||
},
|
||||
}
|
||||
order: 0
|
||||
certLimit:
|
||||
title: 免费证书数量限制
|
||||
value: 2
|
||||
@@ -111,4 +155,4 @@ input:
|
||||
output: {}
|
||||
pluginType: deploy
|
||||
type: builtIn
|
||||
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-esa/index.js
|
||||
scriptFilePath: /plugins/plugin-aliyun/plugin/deploy-to-esa/index.js
|
||||
@@ -310,27 +310,27 @@ export class RuntimeDepsService {
|
||||
});
|
||||
}
|
||||
|
||||
async importRuntime(specifier: string,logger?:ILogger) {
|
||||
async importRuntime(specifier: string, logger?: ILogger) {
|
||||
if (this.isNativeImportSpecifier(specifier)) {
|
||||
return await import(specifier);
|
||||
}
|
||||
|
||||
const resolved = await this.resolveImportSpecifier(specifier,logger);
|
||||
const resolved = await this.resolveImportSpecifier(specifier, logger);
|
||||
return await import(pathToFileURL(resolved).href);
|
||||
}
|
||||
|
||||
private async resolveImportSpecifier(specifier: string,logger?:ILogger) {
|
||||
private async resolveImportSpecifier(specifier: string, logger?: ILogger) {
|
||||
try {
|
||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||
} catch (runtimeError: any) {
|
||||
if (!this.isModuleNotFoundError(runtimeError)) {
|
||||
throw runtimeError;
|
||||
}
|
||||
return await this.resolveMissingRuntimeSpecifier(specifier, runtimeError,logger);
|
||||
return await this.resolveMissingRuntimeSpecifier(specifier, runtimeError, logger);
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveMissingRuntimeSpecifier(specifier: string, runtimeError: any,logger?:ILogger) {
|
||||
private async resolveMissingRuntimeSpecifier(specifier: string, runtimeError: any, logger?: ILogger) {
|
||||
const packageName = this.parsePackageName(specifier);
|
||||
const lazyRange = this.lazyDependencies?.[packageName];
|
||||
if (!lazyRange) {
|
||||
@@ -341,7 +341,7 @@ export class RuntimeDepsService {
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.ensureLazyDependency(packageName,logger);
|
||||
await this.ensureLazyDependency(packageName, logger);
|
||||
return this.resolveRuntimeSpecifier(specifier).resolved;
|
||||
} catch (lazyError: any) {
|
||||
return this.resolveProjectSpecifier(specifier, lazyError).resolved;
|
||||
@@ -392,7 +392,7 @@ export class RuntimeDepsService {
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
private async ensureLazyDependency(packageName: string,logger?:ILogger) {
|
||||
private async ensureLazyDependency(packageName: string, logger?: ILogger) {
|
||||
const range = this.lazyDependencies?.[packageName];
|
||||
if (!range) {
|
||||
throw new Error(`动态依赖未安装且未配置懒加载版本: ${packageName}`);
|
||||
@@ -400,7 +400,7 @@ export class RuntimeDepsService {
|
||||
const dependencies = {
|
||||
[packageName]: range,
|
||||
};
|
||||
await this.ensureDependencies({ dependencies,logger });
|
||||
await this.ensureDependencies({ dependencies, logger });
|
||||
}
|
||||
|
||||
private isModuleNotFoundError(error: any) {
|
||||
@@ -444,7 +444,7 @@ export class RuntimeDepsService {
|
||||
let [pluginType, subtype, name] = parts;
|
||||
if (parts.length === 2) {
|
||||
name = subtype;
|
||||
}else if (parts.length === 3) {
|
||||
} else if (parts.length === 3) {
|
||||
//无修改
|
||||
} else {
|
||||
const ownerName = owner?.name || pluginKey;
|
||||
|
||||
@@ -131,7 +131,7 @@ export class RoleService extends BaseService<RoleEntity> {
|
||||
async delete(id: any) {
|
||||
const idArr = this.resolveIdArr(id);
|
||||
//@ts-ignore
|
||||
const urs:any = await this.userRoleService.find({ where: { roleId: In(idArr) } });
|
||||
const urs: any = await this.userRoleService.find({ where: { roleId: In(idArr) } });
|
||||
if (urs.length > 0) {
|
||||
throw new Error("该角色已被用户使用,无法删除");
|
||||
}
|
||||
|
||||
@@ -254,13 +254,16 @@ export class DeployCertToAliyunCDN extends AbstractTaskPlugin {
|
||||
const client = await this.getClient(access);
|
||||
|
||||
const pager = new Pager(data);
|
||||
const params = {
|
||||
DomainName: data.searchKey,
|
||||
const params: any = {
|
||||
PageSize: pager.pageSize || 100,
|
||||
PageNumber: pager.pageNo || 1,
|
||||
DomainSearchType: "fuzzy_match",
|
||||
};
|
||||
|
||||
if (data.searchKey) {
|
||||
params.DomainName = data.searchKey;
|
||||
}
|
||||
|
||||
const requestOption = {
|
||||
method: "POST",
|
||||
formatParams: false,
|
||||
|
||||
@@ -100,7 +100,7 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
|
||||
if (this.domainMatchMode === "auto") {
|
||||
const { result, deployedList } = await this.autoMatchedDeploy({
|
||||
targetName: "CDN加速域名",
|
||||
targetName: "DCDN加速域名",
|
||||
uploadCert: async () => {
|
||||
return await sslClient.uploadCertOrGet(this.cert);
|
||||
},
|
||||
@@ -190,13 +190,15 @@ export class DeployCertToAliyunDCDN extends AbstractTaskPlugin {
|
||||
|
||||
const client = await this.getClient(access);
|
||||
const pager = new Pager(data);
|
||||
const params = {
|
||||
DomainName: data.searchKey,
|
||||
const params: any = {
|
||||
PageSize: pager.pageSize || 200,
|
||||
PageNumber: pager.pageNo || 1,
|
||||
DomainSearchType: "fuzzy_match",
|
||||
};
|
||||
|
||||
if (data.searchKey) {
|
||||
params.DomainName = data.searchKey;
|
||||
}
|
||||
const requestOption = {
|
||||
method: "POST",
|
||||
formatParams: false,
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
/// <reference types="mocha" />
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { AliyunDeployCertToESA } from "./index.js";
|
||||
|
||||
describe("AliyunDeployCertToESA", () => {
|
||||
it("has deployMode field with default value 'edge'", () => {
|
||||
const input = (AliyunDeployCertToESA as any).define.input.deployMode;
|
||||
assert.equal(input.value, "edge");
|
||||
assert.equal(input.component.name, "a-radio-group");
|
||||
assert.deepEqual(input.component.options, [
|
||||
{ label: "边缘证书", value: "edge" },
|
||||
{ label: "SaaS证书", value: "saas" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("has saasDomainIds field with conditional show", () => {
|
||||
const input = (AliyunDeployCertToESA as any).define.input.saasDomainIds;
|
||||
assert.equal(input.component.name, "remote-select");
|
||||
assert.equal(input.component.action, "onGetCustomHostnameList");
|
||||
assert.equal(input.required, false);
|
||||
assert.match(input.mergeScript, /form.deployMode === 'saas'/);
|
||||
});
|
||||
|
||||
it("executeSaaS throws error when no site is selected", async () => {
|
||||
const plugin = new AliyunDeployCertToESA();
|
||||
plugin.logger = { info: () => undefined } as any;
|
||||
plugin.deployMode = "saas";
|
||||
plugin.siteIds = [];
|
||||
|
||||
await assert.rejects(
|
||||
() => (plugin as any).executeSaaS(null, null, 1, "test"),
|
||||
/SaaS证书模式下请先选择站点/
|
||||
);
|
||||
});
|
||||
|
||||
it("executeSaaS throws error when multiple sites are selected", async () => {
|
||||
const plugin = new AliyunDeployCertToESA();
|
||||
plugin.logger = { info: () => undefined } as any;
|
||||
plugin.deployMode = "saas";
|
||||
plugin.siteIds = ["site1", "site2"];
|
||||
|
||||
await assert.rejects(
|
||||
() => (plugin as any).executeSaaS(null, null, 1, "test"),
|
||||
/SaaS证书模式下站点只能单选/
|
||||
);
|
||||
});
|
||||
|
||||
it("executeSaaS throws error when no SaaS domains selected", async () => {
|
||||
const plugin = new AliyunDeployCertToESA();
|
||||
plugin.logger = { info: () => undefined } as any;
|
||||
plugin.deployMode = "saas";
|
||||
plugin.siteIds = ["site1"];
|
||||
plugin.saasDomainIds = [];
|
||||
|
||||
await assert.rejects(
|
||||
() => (plugin as any).executeSaaS(null, null, 1, "test"),
|
||||
/SaaS证书模式下请选择要部署的SaaS域名/
|
||||
);
|
||||
});
|
||||
|
||||
it("executeSaaS calls UpdateCustomHostname for each selected SaaS domain", async () => {
|
||||
const plugin = new AliyunDeployCertToESA();
|
||||
plugin.logger = { info: () => undefined, error: () => undefined } as any;
|
||||
plugin.deployMode = "saas";
|
||||
plugin.siteIds = ["site1"];
|
||||
plugin.saasDomainIds = ["1001", "1002"];
|
||||
plugin.regionId = "cn-hangzhou";
|
||||
|
||||
const calledHostnameIds: number[] = [];
|
||||
const mockClient = {
|
||||
doRequest: async (req: any) => {
|
||||
calledHostnameIds.push(req.data.body.HostnameId);
|
||||
return {};
|
||||
},
|
||||
};
|
||||
|
||||
await (plugin as any).executeSaaS(mockClient, null, 12345, "test-cert");
|
||||
|
||||
assert.deepEqual(calledHostnameIds, [1001, 1002]);
|
||||
});
|
||||
|
||||
it("executeSaaS handles Certificate.Duplicated error gracefully", async () => {
|
||||
const plugin = new AliyunDeployCertToESA();
|
||||
plugin.logger = { info: () => undefined, error: () => undefined } as any;
|
||||
plugin.deployMode = "saas";
|
||||
plugin.siteIds = ["site1"];
|
||||
plugin.saasDomainIds = ["1001"];
|
||||
plugin.regionId = "cn-hangzhou";
|
||||
|
||||
let callCount = 0;
|
||||
const mockClient = {
|
||||
doRequest: async (req: any) => {
|
||||
callCount++;
|
||||
throw new Error("Certificate.Duplicated");
|
||||
},
|
||||
};
|
||||
|
||||
await (plugin as any).executeSaaS(mockClient, null, 12345, "test-cert");
|
||||
assert.equal(callCount, 1);
|
||||
});
|
||||
|
||||
it("executeEdge calls SetCertificate for each site", async () => {
|
||||
const plugin = new AliyunDeployCertToESA();
|
||||
plugin.logger = { info: () => undefined, error: () => undefined } as any;
|
||||
plugin.siteIds = ["site1", "site2"];
|
||||
plugin.certLimit = 2;
|
||||
|
||||
const calledSites: string[] = [];
|
||||
const mockClient = {
|
||||
doRequest: async (req: any) => {
|
||||
if (req.action === "SetCertificate") {
|
||||
calledSites.push(req.data.body.SiteId);
|
||||
}
|
||||
return { Result: [] };
|
||||
},
|
||||
};
|
||||
|
||||
await (plugin as any).executeEdge(mockClient, 12345, "test-cert");
|
||||
|
||||
assert.deepEqual(calledSites, ["site1", "site2"]);
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import dayjs from "dayjs";
|
||||
title: "阿里云-部署至ESA",
|
||||
icon: "svg:icon-aliyun",
|
||||
group: pluginGroups.aliyun.key,
|
||||
desc: "部署证书到阿里云ESA(边缘安全加速),自动删除过期证书",
|
||||
desc: "部署证书到阿里云ESA(边缘安全加速),支持边缘证书和SaaS证书两种模式",
|
||||
needPlus: false,
|
||||
default: {
|
||||
strategy: {
|
||||
@@ -76,6 +76,22 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
})
|
||||
accessId!: string;
|
||||
|
||||
@TaskInput({
|
||||
title: "部署模式",
|
||||
value: "edge",
|
||||
component: {
|
||||
name: "a-radio-group",
|
||||
vModel: "value",
|
||||
options: [
|
||||
{ label: "边缘证书", value: "edge" },
|
||||
{ label: "SaaS证书", value: "saas" },
|
||||
],
|
||||
},
|
||||
helper: "边缘证书:将证书部署到站点的边缘节点;SaaS证书:将证书部署到站点的SaaS域名",
|
||||
required: true,
|
||||
})
|
||||
deployMode!: "edge" | "saas";
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "站点",
|
||||
@@ -86,6 +102,29 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
)
|
||||
siteIds!: string[];
|
||||
|
||||
@TaskInput(
|
||||
createRemoteSelectInputDefine({
|
||||
title: "SaaS域名",
|
||||
helper: "请选择要部署证书的SaaS域名(SaaS证书模式下必选)",
|
||||
action: AliyunDeployCertToESA.prototype.onGetCustomHostnameList.name,
|
||||
watches: ["siteIds", "accessId", "regionId"],
|
||||
required: false,
|
||||
mergeScript: `
|
||||
return {
|
||||
show: ctx.compute(({form})=>{
|
||||
return form.deployMode === 'saas'
|
||||
}),
|
||||
component:{
|
||||
form: ctx.compute(({form})=>{
|
||||
return form
|
||||
})
|
||||
},
|
||||
}
|
||||
`,
|
||||
})
|
||||
)
|
||||
saasDomainIds!: string[];
|
||||
|
||||
@TaskInput({
|
||||
title: "免费证书数量限制",
|
||||
value: 2,
|
||||
@@ -135,20 +174,27 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
this.logger.info("开始部署证书到阿里云");
|
||||
this.logger.info("开始部署证书到阿里云ESA");
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
|
||||
const client = await this.getClient(access);
|
||||
|
||||
const { certId, certName } = await this.getAliyunCertId(access);
|
||||
|
||||
if (this.deployMode === "saas") {
|
||||
await this.executeSaaS(client, certId, certName);
|
||||
} else {
|
||||
await this.executeEdge(client, certId, certName);
|
||||
}
|
||||
}
|
||||
|
||||
async executeEdge(client: AliyunClientV2, certId: number, certName: string) {
|
||||
this.logger.info("边缘证书模式");
|
||||
for (const siteId of this.siteIds) {
|
||||
await this.clearSiteLimitCert(client, siteId);
|
||||
try {
|
||||
const res = await client.doRequest({
|
||||
// 接口名称
|
||||
action: "SetCertificate",
|
||||
// 接口版本
|
||||
version: "2024-09-10",
|
||||
data: {
|
||||
body: {
|
||||
@@ -159,7 +205,7 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
},
|
||||
},
|
||||
});
|
||||
this.logger.info(`部署站点[${siteId}]证书成功:${JSON.stringify(res)}`);
|
||||
this.logger.info(`部署站点[${siteId}]边缘证书成功:${JSON.stringify(res)}`);
|
||||
} catch (e) {
|
||||
if (e.message.includes("Certificate.Duplicated")) {
|
||||
this.logger.info(`站点[${siteId}]证书已存在,无需重复部署`);
|
||||
@@ -176,6 +222,47 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
async executeSaaS(client: AliyunClientV2, certId: number, certName: string) {
|
||||
this.logger.info("SaaS证书模式");
|
||||
|
||||
if (!this.siteIds || this.siteIds.length === 0) {
|
||||
throw new Error("SaaS证书模式下请先选择站点");
|
||||
}
|
||||
if (this.siteIds.length > 1) {
|
||||
throw new Error(`SaaS证书模式下站点只能单选,当前已选择 ${this.siteIds.length} 个站点,请修改为只选择一个站点`);
|
||||
}
|
||||
|
||||
if (!this.saasDomainIds || this.saasDomainIds.length === 0) {
|
||||
throw new Error("SaaS证书模式下请选择要部署的SaaS域名");
|
||||
}
|
||||
|
||||
for (const hostnameId of this.saasDomainIds) {
|
||||
this.logger.info(`开始更新SaaS域名[${hostnameId}]证书`);
|
||||
try {
|
||||
const res = await client.doRequest({
|
||||
action: "UpdateCustomHostname",
|
||||
version: "2024-09-10",
|
||||
data: {
|
||||
body: {
|
||||
HostnameId: parseInt(hostnameId, 10),
|
||||
SslFlag: "on",
|
||||
CertType: "cas",
|
||||
CasId: certId,
|
||||
CasRegion: this.regionId,
|
||||
},
|
||||
},
|
||||
});
|
||||
this.logger.info(`更新SaaS域名[${hostnameId}]证书成功:${JSON.stringify(res)}`);
|
||||
} catch (e) {
|
||||
if (e.message?.includes("Certificate.Duplicated")) {
|
||||
this.logger.info(`SaaS域名[${hostnameId}]证书已存在,无需重复部署`);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getClient(access: AliyunAccess) {
|
||||
const endpoint = `esa.${this.regionId}.aliyuncs.com`;
|
||||
return access.getClient(endpoint);
|
||||
@@ -210,6 +297,48 @@ export class AliyunDeployCertToESA extends AbstractTaskPlugin {
|
||||
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
|
||||
}
|
||||
|
||||
async onGetCustomHostnameList(data: any) {
|
||||
if (!this.accessId) {
|
||||
throw new Error("请选择Access授权");
|
||||
}
|
||||
if (!this.siteIds || this.siteIds.length === 0) {
|
||||
throw new Error("请先选择站点");
|
||||
}
|
||||
if (this.siteIds.length > 1) {
|
||||
throw new Error("SaaS模式下站点只能单选,请先修改站点选择");
|
||||
}
|
||||
|
||||
const siteId = this.siteIds[0];
|
||||
const access = await this.getAccess<AliyunAccess>(this.accessId);
|
||||
const client = await this.getClient(access);
|
||||
|
||||
const res = await client.doRequest({
|
||||
action: "ListCustomHostnames",
|
||||
version: "2024-09-10",
|
||||
data: {
|
||||
body: {
|
||||
SiteId: parseInt(siteId, 10),
|
||||
PageSize: 500,
|
||||
PageNumber: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const hostnames = res?.Hostnames;
|
||||
if (!hostnames || hostnames.length === 0) {
|
||||
throw new Error("该站点下没有找到SaaS域名,请先在ESA控制台添加SaaS域名");
|
||||
}
|
||||
|
||||
const options = hostnames.map((item: any) => {
|
||||
return {
|
||||
label: `${item.Hostname}(${item.Status})`,
|
||||
value: String(item.HostnameId),
|
||||
domain: item.Hostname,
|
||||
};
|
||||
});
|
||||
return this.ctx.utils.options.buildGroupOptions(options, this.certDomains);
|
||||
}
|
||||
|
||||
async clearSiteExpiredCert(client: AliyunClientV2, siteId: string) {
|
||||
this.logger.info(`开始清理站点[${siteId}]过期证书`);
|
||||
const certListRes = await client.doRequest({
|
||||
|
||||
@@ -54,13 +54,7 @@ export class AsiaIspClient {
|
||||
/**
|
||||
* 生成 HMAC-SHA1 签名,结果做 URL-safe Base64(替换 + → -,/ → _)
|
||||
*/
|
||||
private buildSignature(opts: {
|
||||
body?: any;
|
||||
method: string;
|
||||
nonce: string;
|
||||
queryString: string;
|
||||
timestamp: string;
|
||||
}): string {
|
||||
private buildSignature(opts: { body?: any; method: string; nonce: string; queryString: string; timestamp: string }): string {
|
||||
const { body, method, nonce, queryString, timestamp } = opts;
|
||||
const sk = this.config.accessKeySecret;
|
||||
|
||||
@@ -71,13 +65,7 @@ export class AsiaIspClient {
|
||||
pieces.push(`body=${JSON.stringify(body)}`);
|
||||
}
|
||||
|
||||
pieces.push(
|
||||
`method=${method}`,
|
||||
`nonce=${nonce}`,
|
||||
`queryString=${queryString}`,
|
||||
`timestamp=${timestamp}`,
|
||||
`uri=${URI}`
|
||||
);
|
||||
pieces.push(`method=${method}`, `nonce=${nonce}`, `queryString=${queryString}`, `timestamp=${timestamp}`, `uri=${URI}`);
|
||||
|
||||
const message = pieces.join("&");
|
||||
const hmac = crypto.createHmac("sha1", sk).update(message).digest("base64");
|
||||
@@ -88,11 +76,7 @@ export class AsiaIspClient {
|
||||
/**
|
||||
* 通用 API 请求(完全对齐 Python 实现)
|
||||
*/
|
||||
async doRequest(req: {
|
||||
method: string;
|
||||
action: string;
|
||||
data?: any;
|
||||
}): Promise<any> {
|
||||
async doRequest(req: { method: string; action: string; data?: any }): Promise<any> {
|
||||
const { method, action, data } = req;
|
||||
const nonce = String(Math.floor(Math.random() * 90000000) + 10000000);
|
||||
const timestamp = Date.now().toString();
|
||||
@@ -205,9 +189,8 @@ export class AsiaIspClient {
|
||||
return certId;
|
||||
} catch (e: any) {
|
||||
const msg = e.message || "";
|
||||
const isExists = msg.includes("Certificate already exists") || e.code ==='80003' ||
|
||||
msg.includes("Certificate note name already exists") || e.code ==='80010'
|
||||
//返回数据: {"code":"80010","msg":"Certificate note name already exists","data":null}
|
||||
const isExists = msg.includes("Certificate already exists") || e.code === "80003" || msg.includes("Certificate note name already exists") || e.code === "80010";
|
||||
//返回数据: {"code":"80010","msg":"Certificate note name already exists","data":null}
|
||||
if (!isExists) {
|
||||
throw e;
|
||||
}
|
||||
@@ -240,11 +223,7 @@ export class AsiaIspClient {
|
||||
* 部署证书到 CDN 域名(修改域名配置,绑定证书)
|
||||
* PUT /openapi/v3/stat?action=domainModify
|
||||
*/
|
||||
async deployCertToDomain(req: {
|
||||
domain: string;
|
||||
certId: number;
|
||||
protocol: string;
|
||||
}): Promise<void> {
|
||||
async deployCertToDomain(req: { domain: string; certId: number; protocol: string }): Promise<void> {
|
||||
await this.doRequest({
|
||||
method: "PUT",
|
||||
action: "domainModify",
|
||||
@@ -256,4 +235,4 @@ export class AsiaIspClient {
|
||||
});
|
||||
this.logger.info(`部署证书到域名成功: ${req.domain}, certId=${req.certId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
/// <reference types="mocha" />
|
||||
/// <reference types="node" />
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { TelegramNotification } from "./index.js";
|
||||
|
||||
describe("TelegramNotification.replaceText", () => {
|
||||
let notification: TelegramNotification;
|
||||
|
||||
beforeEach(() => {
|
||||
notification = new TelegramNotification();
|
||||
});
|
||||
|
||||
it("转义横杠字符 -", () => {
|
||||
const result = notification.replaceText("defense-gameliti-one");
|
||||
assert.equal(result, "defense\\-gameliti\\-one");
|
||||
});
|
||||
|
||||
it("转义下划线 _", () => {
|
||||
const result = notification.replaceText("hello_world");
|
||||
assert.equal(result, "hello\\_world");
|
||||
});
|
||||
|
||||
it("转义星号 *", () => {
|
||||
const result = notification.replaceText("*bold text*");
|
||||
assert.equal(result, "\\*bold text\\*");
|
||||
});
|
||||
|
||||
it("转义方括号 []", () => {
|
||||
const result = notification.replaceText("[link]");
|
||||
assert.equal(result, "\\[link\\]");
|
||||
});
|
||||
|
||||
it("转义圆括号 ()", () => {
|
||||
const result = notification.replaceText("(url)");
|
||||
assert.equal(result, "\\(url\\)");
|
||||
});
|
||||
|
||||
it("转义波浪号 ~", () => {
|
||||
const result = notification.replaceText("~strike~");
|
||||
assert.equal(result, "\\~strike\\~");
|
||||
});
|
||||
|
||||
it("转义反引号 `", () => {
|
||||
const result = notification.replaceText("`code`");
|
||||
assert.equal(result, "\\`code\\`");
|
||||
});
|
||||
|
||||
it("转义大于号 >", () => {
|
||||
const result = notification.replaceText(">quote");
|
||||
assert.equal(result, "\\>quote");
|
||||
});
|
||||
|
||||
it("转义井号 #", () => {
|
||||
const result = notification.replaceText("#hashtag");
|
||||
assert.equal(result, "\\#hashtag");
|
||||
});
|
||||
|
||||
it("转义加号 +", () => {
|
||||
const result = notification.replaceText("+1");
|
||||
assert.equal(result, "\\+1");
|
||||
});
|
||||
|
||||
it("转义等号 =", () => {
|
||||
const result = notification.replaceText("a=b");
|
||||
assert.equal(result, "a\\=b");
|
||||
});
|
||||
|
||||
it("转义竖线 |", () => {
|
||||
const result = notification.replaceText("a|b");
|
||||
assert.equal(result, "a\\|b");
|
||||
});
|
||||
|
||||
it("转义花括号 {}", () => {
|
||||
const result = notification.replaceText("{key: value}");
|
||||
assert.equal(result, "\\{key: value\\}");
|
||||
});
|
||||
|
||||
it("转义点号 .", () => {
|
||||
const result = notification.replaceText("example.com");
|
||||
assert.equal(result, "example\\.com");
|
||||
});
|
||||
|
||||
it("转义感叹号 !", () => {
|
||||
const result = notification.replaceText("!important");
|
||||
assert.equal(result, "\\!important");
|
||||
});
|
||||
|
||||
it("转义反斜杠 \\", () => {
|
||||
const result = notification.replaceText("path\\to\\file");
|
||||
assert.equal(result, "path\\\\to\\\\file");
|
||||
});
|
||||
|
||||
it("普通文本不做修改", () => {
|
||||
const result = notification.replaceText("Hello World 123");
|
||||
assert.equal(result, "Hello World 123");
|
||||
});
|
||||
|
||||
it("空字符串返回空字符串", () => {
|
||||
const result = notification.replaceText("");
|
||||
assert.equal(result, "");
|
||||
});
|
||||
|
||||
it("混合多种特殊字符全部正确转义", () => {
|
||||
const input = "_*[]()~`>#+-=|{}.!\\";
|
||||
const result = notification.replaceText(input);
|
||||
assert.equal(result, "\\_\\*\\[\\]\\(\\)\\~\\`\\>\\#\\+\\-\\=\\|\\{\\}\\.\\!\\\\");
|
||||
});
|
||||
|
||||
it("域名中的点号和横杠都被转义", () => {
|
||||
const result = notification.replaceText("sub-domain.example.com");
|
||||
assert.equal(result, "sub\\-domain\\.example\\.com");
|
||||
});
|
||||
});
|
||||
@@ -60,7 +60,7 @@ export class TelegramNotification extends BaseNotification {
|
||||
|
||||
replaceText(text: string) {
|
||||
// .*()<> 等都需要用\\进行替换
|
||||
return text.replace(/[_*[\]()~`>#\+\-=|{}.!]/g, "\\$&");
|
||||
return text.replace(/[_*[\]()~`>#+\-=|{}.!\\]/g, "\\$&");
|
||||
// .replace(/([\\_*`|!.[\](){}>+#=~-])/gm, '\\$1')
|
||||
// return text.replace(/[\\.*()<>]/g, '\\$&');
|
||||
}
|
||||
|
||||
+1
-1
@@ -121,4 +121,4 @@ export class BaotaAutoDeploySiteCert extends AbstractPlusTaskPlugin {
|
||||
};
|
||||
}
|
||||
}
|
||||
new BaotaAutoDeploySiteCert();
|
||||
new BaotaAutoDeploySiteCert();
|
||||
|
||||
Reference in New Issue
Block a user