This commit is contained in:
dap 2025-01-19 19:20:24 +08:00
commit 429002ade2
7 changed files with 104 additions and 72 deletions

View File

@ -1,6 +1,7 @@
/** /**
* *
*/ */
import type { HttpResponse } from '@vben/request'; import type { HttpResponse } from '@vben/request';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
import type { HttpResponse } from '@vben/request'; import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
@ -20,8 +20,9 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) { function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({ const client = new RequestClient({
...options,
baseURL, baseURL,
}); });
@ -69,19 +70,6 @@ function createRequestClient(baseURL: string) {
}, },
}); });
// response数据解构
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw Object.assign({}, response, { response });
},
});
// token过期的处理 // token过期的处理
client.addResponseInterceptor( client.addResponseInterceptor(
authenticateResponseInterceptor({ authenticateResponseInterceptor({
@ -108,6 +96,8 @@ function createRequestClient(baseURL: string) {
return client; return client;
} }
export const requestClient = createRequestClient(apiURL); export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL }); export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
import type { HttpResponse } from '@vben/request'; import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
@ -19,8 +19,9 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) { function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({ const client = new RequestClient({
...options,
baseURL, baseURL,
}); });
@ -68,19 +69,6 @@ function createRequestClient(baseURL: string) {
}, },
}); });
// response数据解构
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw Object.assign({}, response, { response });
},
});
// token过期的处理 // token过期的处理
client.addResponseInterceptor( client.addResponseInterceptor(
authenticateResponseInterceptor({ authenticateResponseInterceptor({
@ -107,6 +95,8 @@ function createRequestClient(baseURL: string) {
return client; return client;
} }
export const requestClient = createRequestClient(apiURL); export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL }); export const baseRequestClient = new RequestClient({ baseURL: apiURL });

View File

@ -48,6 +48,29 @@ describe('requestClient', () => {
expect(response.data).toEqual(mockData); expect(response.data).toEqual(mockData);
}); });
it('should return different response types', async () => {
const mockData = { code: 0, msg: 'ok', data: 'response' };
mock.onGet('/test/diff').reply(200, mockData);
const responseRaw = await requestClient.get('/test/diff', {
responseReturn: 'raw',
});
expect(responseRaw.status).toBe(200);
expect(responseRaw.data).toEqual(mockData);
const responseBody = await requestClient.get('/test/diff', {
responseReturn: 'body',
});
expect(responseBody.code).toEqual(mockData.code);
expect(responseBody.msg).toEqual(mockData.msg);
expect(responseBody.data).toEqual(mockData.data);
const responseData = await requestClient.get('/test/diff', {
responseReturn: 'data',
});
expect(responseData).toEqual(mockData.data);
});
it('should handle network errors', async () => { it('should handle network errors', async () => {
mock.onGet('/test/error').networkError(); mock.onGet('/test/error').networkError();
try { try {

View File

@ -1,13 +1,13 @@
import type { import type { AxiosInstance, AxiosResponse } from 'axios';
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
CreateAxiosDefaults,
} from 'axios';
import type { RequestClientOptions } from './types'; import type {
HttpResponse,
RequestClientConfig,
RequestClientOptions,
} from './types';
import { bindMethods, merge } from '@vben/utils'; import { bindMethods, merge } from '@vben/utils';
import axios from 'axios'; import axios from 'axios';
import { FileDownloader } from './modules/downloader'; import { FileDownloader } from './modules/downloader';
@ -15,17 +15,17 @@ import { InterceptorManager } from './modules/interceptor';
import { FileUploader } from './modules/uploader'; import { FileUploader } from './modules/uploader';
class RequestClient { class RequestClient {
private readonly instance: AxiosInstance;
public addRequestInterceptor: InterceptorManager['addRequestInterceptor']; public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];
public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];
public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];
public download: FileDownloader['download']; public download: FileDownloader['download'];
// 是否正在刷新token // 是否正在刷新token
public isRefreshing = false; public isRefreshing = false;
// 刷新token队列 // 刷新token队列
public refreshTokenQueue: ((token: string) => void)[] = []; public refreshTokenQueue: ((token: string) => void)[] = [];
public upload: FileUploader['upload']; public upload: FileUploader['upload'];
private readonly instance: AxiosInstance;
/** /**
* Axios实例 * Axios实例
@ -33,10 +33,11 @@ class RequestClient {
*/ */
constructor(options: RequestClientOptions = {}) { constructor(options: RequestClientOptions = {}) {
// 合并默认配置和传入的配置 // 合并默认配置和传入的配置
const defaultConfig: CreateAxiosDefaults = { const defaultConfig: RequestClientOptions = {
headers: { headers: {
'Content-Type': 'application/json;charset=utf-8', 'Content-Type': 'application/json;charset=utf-8',
}, },
responseReturn: 'raw',
// 默认超时时间 // 默认超时时间
timeout: 10_000, timeout: 10_000,
}; };
@ -53,6 +54,24 @@ class RequestClient {
this.addResponseInterceptor = this.addResponseInterceptor =
interceptorManager.addResponseInterceptor.bind(interceptorManager); interceptorManager.addResponseInterceptor.bind(interceptorManager);
// 添加基础的响应处理,根据设置决定返回响应的哪一部分
this.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { config, data: responseData, status } = response;
if (config.responseReturn === 'raw') {
return response;
}
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return config.responseReturn === 'body' ? responseData : data;
}
throw Object.assign({}, response, { response });
},
});
// 实例化文件上传器 // 实例化文件上传器
const fileUploader = new FileUploader(this); const fileUploader = new FileUploader(this);
this.upload = fileUploader.upload.bind(fileUploader); this.upload = fileUploader.upload.bind(fileUploader);
@ -64,7 +83,10 @@ class RequestClient {
/** /**
* DELETE请求方法 * DELETE请求方法
*/ */
public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> { public delete<T = any>(
url: string,
config?: RequestClientConfig,
): Promise<T> {
return this.request<T>(url, { ...config, method: 'DELETE' }); return this.request<T>(url, { ...config, method: 'DELETE' });
} }
@ -85,7 +107,7 @@ class RequestClient {
/** /**
* GET请求方法 * GET请求方法
*/ */
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> { public get<T = any>(url: string, config?: RequestClientConfig): Promise<T> {
return this.request<T>(url, { ...config, method: 'GET' }); return this.request<T>(url, { ...config, method: 'GET' });
} }
@ -95,7 +117,7 @@ class RequestClient {
public post<T = any>( public post<T = any>(
url: string, url: string,
data?: any, data?: any,
config?: AxiosRequestConfig, config?: RequestClientConfig,
): Promise<T> { ): Promise<T> {
return this.request<T>(url, { ...config, data, method: 'POST' }); return this.request<T>(url, { ...config, data, method: 'POST' });
} }
@ -122,7 +144,7 @@ class RequestClient {
public put<T = any>( public put<T = any>(
url: string, url: string,
data?: any, data?: any,
config?: AxiosRequestConfig, config?: RequestClientConfig,
): Promise<T> { ): Promise<T> {
return this.request<T>(url, { ...config, data, method: 'PUT' }); return this.request<T>(url, { ...config, data, method: 'PUT' });
} }
@ -146,7 +168,10 @@ class RequestClient {
/** /**
* *
*/ */
public async request<T>(url: string, config: AxiosRequestConfig): Promise<T> { public async request<T>(
url: string,
config: RequestClientConfig,
): Promise<T> {
try { try {
const response: AxiosResponse<T> = await this.instance({ const response: AxiosResponse<T> = await this.instance({
url, url,

View File

@ -1,10 +1,23 @@
import type { import type {
AxiosRequestConfig,
AxiosResponse, AxiosResponse,
CreateAxiosDefaults, CreateAxiosDefaults,
InternalAxiosRequestConfig, InternalAxiosRequestConfig,
} from 'axios'; } from 'axios';
type RequestResponse<T = any> = AxiosResponse<T>; type ExtendOptions = {
/**
* raw: 原始的AxiosResponseheadersstatus等
* body: 返回响应数据的BODY部分
* data: 解构响应的BODY数据data节点数据
*/
responseReturn?: 'body' | 'data' | 'raw';
};
type RequestClientConfig<T = any> = AxiosRequestConfig<T> & ExtendOptions;
type RequestResponse<T = any> = AxiosResponse<T> & {
config: RequestClientConfig<T>;
};
type RequestContentType = type RequestContentType =
| 'application/json;charset=utf-8' | 'application/json;charset=utf-8'
@ -12,21 +25,21 @@ type RequestContentType =
| 'application/x-www-form-urlencoded;charset=utf-8' | 'application/x-www-form-urlencoded;charset=utf-8'
| 'multipart/form-data;charset=utf-8'; | 'multipart/form-data;charset=utf-8';
type RequestClientOptions = CreateAxiosDefaults; type RequestClientOptions = CreateAxiosDefaults & ExtendOptions;
interface RequestInterceptorConfig { interface RequestInterceptorConfig {
fulfilled?: ( fulfilled?: (
config: InternalAxiosRequestConfig, config: ExtendOptions & InternalAxiosRequestConfig,
) => ) =>
| InternalAxiosRequestConfig<any> | (ExtendOptions & InternalAxiosRequestConfig<any>)
| Promise<InternalAxiosRequestConfig<any>>; | Promise<ExtendOptions & InternalAxiosRequestConfig<any>>;
rejected?: (error: any) => any; rejected?: (error: any) => any;
} }
interface ResponseInterceptorConfig<T = any> { interface ResponseInterceptorConfig<T = any> {
fulfilled?: ( fulfilled?: (
response: AxiosResponse<T>, response: RequestResponse<T>,
) => AxiosResponse | Promise<AxiosResponse>; ) => Promise<RequestResponse> | RequestResponse;
rejected?: (error: any) => any; rejected?: (error: any) => any;
} }
@ -41,6 +54,7 @@ interface HttpResponse<T = any> {
export type { export type {
HttpResponse, HttpResponse,
MakeErrorMessageFn, MakeErrorMessageFn,
RequestClientConfig,
RequestClientOptions, RequestClientOptions,
RequestContentType, RequestContentType,
RequestInterceptorConfig, RequestInterceptorConfig,

View File

@ -1,7 +1,7 @@
/** /**
* *
*/ */
import type { HttpResponse } from '@vben/request'; import type { RequestClientOptions } from '@vben/request';
import { useAppConfig } from '@vben/hooks'; import { useAppConfig } from '@vben/hooks';
import { preferences } from '@vben/preferences'; import { preferences } from '@vben/preferences';
@ -20,8 +20,9 @@ import { refreshTokenApi } from './core';
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
function createRequestClient(baseURL: string) { function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const client = new RequestClient({ const client = new RequestClient({
...options,
baseURL, baseURL,
}); });
@ -69,20 +70,6 @@ function createRequestClient(baseURL: string) {
}, },
}); });
// response数据解构
client.addResponseInterceptor<HttpResponse>({
fulfilled: (response) => {
const { data: responseData, status } = response;
const { code, data } = responseData;
if (status >= 200 && status < 400 && code === 0) {
return data;
}
throw Object.assign({}, response, { response });
},
});
// token过期的处理 // token过期的处理
client.addResponseInterceptor( client.addResponseInterceptor(
authenticateResponseInterceptor({ authenticateResponseInterceptor({
@ -109,6 +96,8 @@ function createRequestClient(baseURL: string) {
return client; return client;
} }
export const requestClient = createRequestClient(apiURL); export const requestClient = createRequestClient(apiURL, {
responseReturn: 'data',
});
export const baseRequestClient = new RequestClient({ baseURL: apiURL }); export const baseRequestClient = new RequestClient({ baseURL: apiURL });