refactor(GOV-010): 统一封装前端 Axios 请求

This commit is contained in:
2026-08-13 23:02:36 +08:00
parent 3a22fdeef3
commit 4105336cb2
10 changed files with 288 additions and 86 deletions
-39
View File
@@ -1,39 +0,0 @@
import { describe, expect, it } from 'vitest';
import { ApiRequestError, createApiClient } from './client';
describe('OpenAPI 请求客户端', () => {
it('使用调用方提供的 API 根地址请求公开路径', async () => {
let requestedUrl = '';
const client = createApiClient('http://127.0.0.1:3000', async (request) => {
requestedUrl = request.url;
return Response.json({
status: 'healthy',
process: 'api',
checkedAt: '2026-08-13T09:30:00.000Z',
dependencies: {
mysql: { status: 'healthy', detail: 'ready' },
redis: { status: 'healthy', detail: 'ready' },
rocketMq: { status: 'disabled', detail: 'disabled' },
},
});
});
await client.GET('/api/v1/health');
expect(requestedUrl).toBe('http://127.0.0.1:3000/api/v1/health');
});
it('把网络错误归一化为不泄漏底层连接信息的错误', async () => {
const client = createApiClient('http://127.0.0.1:3000', async () => {
throw new Error('connect ECONNREFUSED mysql://root:secret@internal');
});
await expect(client.GET('/api/v1/health')).rejects.toEqual(
expect.objectContaining({
name: 'ApiRequestError',
message: 'API 请求失败,请稍后重试',
}),
);
await expect(client.GET('/api/v1/health')).rejects.toBeInstanceOf(ApiRequestError);
});
});
-30
View File
@@ -1,30 +0,0 @@
import createClient, { type Client } from 'openapi-fetch';
import type { paths } from './generated/schema';
/** 统一的安全请求错误,不把浏览器或代理底层异常直接展示给用户。 */
export class ApiRequestError extends Error {
constructor() {
super('API 请求失败,请稍后重试');
this.name = 'ApiRequestError';
}
}
/** 创建受 OpenAPI paths 约束的客户端,并在边界统一清洗网络异常。 */
export function createApiClient(
baseUrl: string,
fetchImplementation: (request: Request) => Promise<Response> = globalThis.fetch,
): Client<paths> {
const client = createClient<paths>({
baseUrl: baseUrl.replace(/\/$/, ''),
fetch: fetchImplementation,
});
client.use({
onError() {
// 浏览器错误可能带出内网主机、代理或底层连接细节,统一转换为安全的用户提示。
return new ApiRequestError();
},
});
return client;
}