perf(spaceship): 新增Spaceship DNS插件和授权模块

添加Spaceship DNS提供商插件和授权模块,支持域名解析管理
更新相关文档和技能说明,优化错误处理和日志记录
移除调试日志,更新README项目列表
This commit is contained in:
xiaojunnuo
2026-04-02 00:10:28 +08:00
parent 74c5259af8
commit 21aec77e5c
8 changed files with 267 additions and 7 deletions
@@ -271,7 +271,7 @@ export function createAxiosService({ logger }: { logger: ILogger }) {
}
const originalRequest = error.config || {};
logger.info(`config`, originalRequest);
// logger.info(`config`, originalRequest);
const retry = originalRequest.retry || {};
if (retry.status && retry.status.includes(status)) {
if (retry.max > 0 && retry.count < retry.max) {
@@ -0,0 +1,148 @@
import { IsAccess, AccessInput, BaseAccess, PageSearch } from "@certd/pipeline";
@IsAccess({
name: "spaceship",
title: "Spaceship.com 授权",
icon: "clarity:plugin-line",
desc: "Spaceship.com API 授权插件"
})
export class SpaceshipAccess extends BaseAccess {
@AccessInput({
title: "API Key",
component: {
placeholder: "请输入 API Key"
},
required: true,
encrypt: true,
helper: "前往 [获取 API Key](https://www.spaceship.com/application/api-manager/)"
})
apiKey = "";
@AccessInput({
title: "API Secret",
component: {
name: "a-input-password",
vModel: "value",
placeholder: "请输入 API Secret"
},
required: true,
encrypt: true
})
apiSecret = "";
@AccessInput({
title: "测试",
component: {
name: "api-test",
action: "TestRequest"
},
helper: "测试 API 连接是否正常"
})
testRequest = true;
async onTestRequest() {
await this.GetDomainList({});
return "ok";
}
async doRequest(options: {
url: string;
method: 'GET' | 'POST' | 'DELETE';
params?: any;
data?: any;
}) {
const headers = {
"X-Api-Key": this.apiKey,
"X-Api-Secret": this.apiSecret
};
try {
const res = await this.ctx.http.request({
url: options.url,
method: options.method,
headers,
params: options.params,
data: options.data
});
return res;
} catch (error: any) {
const errorMsg = [];
const status = error.status || error.response?.status;
if (error.response) {
const headers = error.response.headers;
const data = error.response.data;
errorMsg.push(`API 请求失败: ${status}`);
if (headers['spaceship-error-code']) {
errorMsg.push(`错误代码: ${headers['spaceship-error-code']}`);
}
if (headers['spaceship-operation-id']) {
errorMsg.push(`操作ID: ${headers['spaceship-operation-id']}`);
}
if (data && data.detail) {
errorMsg.push(`错误详情: ${data.detail}`);
}
this.ctx.logger.error(`Spaceship API 错误: ${errorMsg.join(' | ')}`);
} else if (error.request) {
errorMsg.push(`请求发送失败: ${error.message}`);
this.ctx.logger.error(`Spaceship API 请求发送失败: ${error.message}`);
} else {
errorMsg.push(`请求配置错误: ${error.message}`);
this.ctx.logger.error(`Spaceship API 请求配置错误: ${error.message}`);
}
const error2 = new Error(errorMsg.join(' | '));
//@ts-ignore
error2.status = status;
throw error2;
}
}
async GetDomainList(req: PageSearch) {
const take = req.pageSize || 100;
const skip = ((req.pageNo || 1) - 1) * take;
const res = await this.doRequest({
url: "https://spaceship.dev/api/v1/domains",
method: "GET",
params: {
take,
skip
}
});
return {
total: res.total || 0,
list: res.items || []
};
}
async getDomainInfo(domain: string) {
try {
const res = await this.doRequest({
url: `https://spaceship.dev/api/v1/domains/${domain}`,
method: "GET"
});
return res;
} catch (error: any) {
if (error.status === 404) {
throw new Error(`域名 ${domain} 不存在于当前账号中`);
}
throw error;
}
}
getCacheKey() {
const hashStr = this.apiKey + this.apiSecret;
const hashCode = this.ctx.utils.hash.sha256(hashStr);
return `spaceship-${hashCode}`;
}
}
new SpaceshipAccess();
@@ -0,0 +1,95 @@
import { AbstractDnsProvider, CreateRecordOptions, DomainRecord, IsDnsProvider, RemoveRecordOptions } from "@certd/plugin-cert";
import { SpaceshipAccess } from "./access.js";
import { PageRes, PageSearch } from "@certd/pipeline";
export type SpaceshipRecord = {
id: string;
name: string;
type: string;
content: string;
domainId: string;
};
@IsDnsProvider({
name: "spaceship",
title: "Spaceship",
desc: "Spaceship 域名解析",
icon: "clarity:plugin-line",
accessType: "spaceship",
order: 99
})
export class SpaceshipProvider extends AbstractDnsProvider<SpaceshipRecord> {
access!: SpaceshipAccess;
async onInstance() {
this.access = this.ctx.access as SpaceshipAccess;
}
async createRecord(options: CreateRecordOptions): Promise<SpaceshipRecord> {
const { fullRecord, hostRecord, value, type, domain } = options;
this.logger.info("添加域名解析:", fullRecord, value, type, domain);
await this.access.getDomainInfo(domain);
const recordRes = await this.access.doRequest({
url: `https://spaceship.dev/api/v1/domains/${domain}/records`,
method: "POST",
data: {
force: false,
items: [
{
type: type,
value: value,
name: hostRecord,
ttl: 300
}
]
}
});
return {
id: recordRes.items[0].id,
name: hostRecord,
type: type,
content: value,
domainId: domain
};
}
async removeRecord(options: RemoveRecordOptions<SpaceshipRecord>): Promise<void> {
const recordRes = options.recordRes;
this.logger.info("删除域名解析:", recordRes);
await this.access.doRequest({
url: `https://spaceship.dev/api/v1/domains/${recordRes.domainId}/records`,
method: "DELETE",
data: {
Records: [
{
type: recordRes.type,
value: recordRes.content,
name: recordRes.name
}
]
}
});
this.logger.info("删除域名解析成功:", recordRes.name);
}
async getDomainListPage(req: PageSearch): Promise<PageRes<DomainRecord>> {
const res = await this.access.GetDomainList(req);
const list = res.list.map((item: any) => ({
domain: item.name,
id: item.name
}));
return {
total: res.total || 0,
list: list || []
};
}
}
new SpaceshipProvider();
@@ -0,0 +1,2 @@
import "./access.js";
import "./dns-provider.js";