129 lines
3.9 KiB
TypeScript
129 lines
3.9 KiB
TypeScript
import { setMeta, getMeta, type ApiMethodMeta } from './meta';
|
||
import { AxiosHttpClient, type BaseError } from '../client';
|
||
|
||
// ApiError错误接口
|
||
export interface ApiError {
|
||
status: number; // 状态码(来自response的status)
|
||
statusText: string; // 状态文本(来自response的msg)
|
||
code: string; // 错误码(字符串)
|
||
message: string; // 报错信息(字符串)
|
||
// 没有别的细节了,错误码直接对照常量映射
|
||
}
|
||
|
||
// 客户端请求参数接口
|
||
export interface ClientRequestParams {
|
||
method: string;
|
||
url: string;
|
||
query?: Record<string, string | number>;
|
||
body?: any;
|
||
}
|
||
|
||
// 创建 AxiosHttpClient 单例实例
|
||
const apiClientInstance = new AxiosHttpClient();
|
||
|
||
/**
|
||
* 创建 HTTP 方法装饰器的通用函数
|
||
* @param method HTTP 方法类型
|
||
* @returns 装饰器函数
|
||
*/
|
||
export function createHttpMethodDecorator(method: ApiMethodMeta['method']) {
|
||
return function(path: string) {
|
||
return function(_target: Object, context: ClassMethodDecoratorContext) {
|
||
// 确保只应用于方法
|
||
if (context.kind !== 'method') {
|
||
throw new Error(`HTTP method decorator (${method}) can only be applied to methods`);
|
||
}
|
||
|
||
// 获取原始方法
|
||
const originalMethod = _target[context.name as keyof Object] as Function;
|
||
|
||
// 设置元数据
|
||
setMeta(originalMethod, 'api', {
|
||
method,
|
||
path
|
||
} as ApiMethodMeta);
|
||
};
|
||
};
|
||
}
|
||
|
||
|
||
/**
|
||
* 为 API 客户端类绑定 HTTP 客户端实例的装饰器
|
||
* @description 该装饰器对当前类注入 AxiosHttpClient 实例
|
||
*/
|
||
export function ApiClientBound<T extends { new (...args: any[]): { client: AxiosHttpClient } }>(target: T, _context: ClassDecoratorContext<T>) {
|
||
// 定义一个新类,继承自目标类
|
||
return class extends target {
|
||
// 在构造函数中初始化客户端实例
|
||
constructor(...args: any[]) {
|
||
super(...args);
|
||
// 添加客户端实例
|
||
this.client = apiClientInstance;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 请求执行装饰器,自动处理 API 调用逻辑
|
||
*/
|
||
export function Request(target: Object, context: ClassMethodDecoratorContext) {
|
||
const methodName = String(context.name);
|
||
|
||
// 确保只应用于方法
|
||
if (context.kind !== 'method') {
|
||
throw new Error('Request decorator can only be applied to methods');
|
||
}
|
||
|
||
// 保存原始方法
|
||
const originalMethod = target[context.name as keyof Object] as Function;
|
||
|
||
// 定义新的方法实现
|
||
async function replacementMethod(this: { client: { request: (params: ClientRequestParams) => Promise<any> } }, ...args: any[]) {
|
||
// 获取 API 元数据
|
||
const meta = getMeta(originalMethod, 'api') as ApiMethodMeta | undefined;
|
||
if (!meta) {
|
||
throw new Error(`Missing API metadata on ${methodName}. Please use an HTTP method decorator (GET, POST, etc.)`);
|
||
}
|
||
|
||
const { method, path } = meta;
|
||
|
||
// 处理请求参数
|
||
const params = args[0] ?? {};
|
||
const body = args[1];
|
||
|
||
// 替换路径中的动态参数段
|
||
let finalPath = path.replace(/:([\w]+)/g, (_, key) => {
|
||
const paramValue = params[key];
|
||
if (paramValue === undefined) {
|
||
throw new Error(`Missing required path parameter '${key}' for API endpoint '${path}'`);
|
||
}
|
||
return String(paramValue);
|
||
});
|
||
|
||
// 执行请求并处理错误
|
||
try {
|
||
return await this.client.request({
|
||
method,
|
||
url: finalPath,
|
||
query: params,
|
||
body,
|
||
});
|
||
} catch (error: BaseError | any) {
|
||
// 确保错误对象符合 ApiError 接口
|
||
const apiError: ApiError = {
|
||
status: error.response?.status || 500,
|
||
statusText: error.response?.msg || 'Internal Server Error',
|
||
code: error?.code || 'UNKNOWN_ERROR',
|
||
message: error?.message || 'An unexpected error occurred',
|
||
};
|
||
throw apiError;
|
||
}
|
||
}
|
||
|
||
// TS5+直接返回新方法定义
|
||
return replacementMethod;
|
||
}
|
||
|
||
|
||
|