From 5611f6c7f5cd4bb9f0e1e3aeaa9c04f5785e347a Mon Sep 17 00:00:00 2001 From: Netfan Date: Sun, 19 Jan 2025 17:41:26 +0800 Subject: [PATCH] perf: request support to set how to return response (#5436) * feat: request support to set how to return response * docs: typo * fix: test unit * test: add request responseReturn test --- apps/web-antd/src/api/request.ts | 23 +++------ apps/web-ele/src/api/request.ts | 22 +++----- apps/web-naive/src/api/request.ts | 22 +++----- .../src/request-client/request-client.test.ts | 23 +++++++++ .../src/request-client/request-client.ts | 50 ++++++++++++++----- .../request/src/request-client/types.ts | 28 ++++++++--- playground/src/api/request.ts | 23 +++------ 7 files changed, 105 insertions(+), 86 deletions(-) diff --git a/apps/web-antd/src/api/request.ts b/apps/web-antd/src/api/request.ts index 67ef35e4..49429d49 100644 --- a/apps/web-antd/src/api/request.ts +++ b/apps/web-antd/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { HttpResponse } from '@vben/request'; +import type { RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -20,8 +20,9 @@ import { refreshTokenApi } from './core'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -function createRequestClient(baseURL: string) { +function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ + ...options, baseURL, }); @@ -69,20 +70,6 @@ function createRequestClient(baseURL: string) { }, }); - // response数据解构 - client.addResponseInterceptor({ - 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过期的处理 client.addResponseInterceptor( authenticateResponseInterceptor({ @@ -109,6 +96,8 @@ function createRequestClient(baseURL: string) { return client; } -export const requestClient = createRequestClient(apiURL); +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/web-ele/src/api/request.ts b/apps/web-ele/src/api/request.ts index a9514c81..21223a4b 100644 --- a/apps/web-ele/src/api/request.ts +++ b/apps/web-ele/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { HttpResponse } from '@vben/request'; +import type { RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -20,8 +20,9 @@ import { refreshTokenApi } from './core'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -function createRequestClient(baseURL: string) { +function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ + ...options, baseURL, }); @@ -69,19 +70,6 @@ function createRequestClient(baseURL: string) { }, }); - // response数据解构 - client.addResponseInterceptor({ - 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过期的处理 client.addResponseInterceptor( authenticateResponseInterceptor({ @@ -108,6 +96,8 @@ function createRequestClient(baseURL: string) { return client; } -export const requestClient = createRequestClient(apiURL); +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/apps/web-naive/src/api/request.ts b/apps/web-naive/src/api/request.ts index 72056e19..b09c3396 100644 --- a/apps/web-naive/src/api/request.ts +++ b/apps/web-naive/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { HttpResponse } from '@vben/request'; +import type { RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -19,8 +19,9 @@ import { refreshTokenApi } from './core'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -function createRequestClient(baseURL: string) { +function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ + ...options, baseURL, }); @@ -68,19 +69,6 @@ function createRequestClient(baseURL: string) { }, }); - // response数据解构 - client.addResponseInterceptor({ - 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过期的处理 client.addResponseInterceptor( authenticateResponseInterceptor({ @@ -107,6 +95,8 @@ function createRequestClient(baseURL: string) { return client; } -export const requestClient = createRequestClient(apiURL); +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); export const baseRequestClient = new RequestClient({ baseURL: apiURL }); diff --git a/packages/effects/request/src/request-client/request-client.test.ts b/packages/effects/request/src/request-client/request-client.test.ts index 2d94525e..fa7b9a86 100644 --- a/packages/effects/request/src/request-client/request-client.test.ts +++ b/packages/effects/request/src/request-client/request-client.test.ts @@ -48,6 +48,29 @@ describe('requestClient', () => { 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 () => { mock.onGet('/test/error').networkError(); try { diff --git a/packages/effects/request/src/request-client/request-client.ts b/packages/effects/request/src/request-client/request-client.ts index 5d78fde6..6a4630c0 100644 --- a/packages/effects/request/src/request-client/request-client.ts +++ b/packages/effects/request/src/request-client/request-client.ts @@ -1,11 +1,10 @@ -import type { - AxiosInstance, - AxiosRequestConfig, - AxiosResponse, - CreateAxiosDefaults, -} from 'axios'; +import type { AxiosInstance, AxiosResponse } from 'axios'; -import type { RequestClientOptions } from './types'; +import type { + HttpResponse, + RequestClientConfig, + RequestClientOptions, +} from './types'; import { bindMethods, merge } from '@vben/utils'; @@ -34,10 +33,11 @@ class RequestClient { */ constructor(options: RequestClientOptions = {}) { // 合并默认配置和传入的配置 - const defaultConfig: CreateAxiosDefaults = { + const defaultConfig: RequestClientOptions = { headers: { 'Content-Type': 'application/json;charset=utf-8', }, + responseReturn: 'raw', // 默认超时时间 timeout: 10_000, }; @@ -54,6 +54,24 @@ class RequestClient { this.addResponseInterceptor = interceptorManager.addResponseInterceptor.bind(interceptorManager); + // 添加基础的响应处理,根据设置决定返回响应的哪一部分 + this.addResponseInterceptor({ + 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); this.upload = fileUploader.upload.bind(fileUploader); @@ -65,14 +83,17 @@ class RequestClient { /** * DELETE请求方法 */ - public delete(url: string, config?: AxiosRequestConfig): Promise { + public delete( + url: string, + config?: RequestClientConfig, + ): Promise { return this.request(url, { ...config, method: 'DELETE' }); } /** * GET请求方法 */ - public get(url: string, config?: AxiosRequestConfig): Promise { + public get(url: string, config?: RequestClientConfig): Promise { return this.request(url, { ...config, method: 'GET' }); } @@ -82,7 +103,7 @@ class RequestClient { public post( url: string, data?: any, - config?: AxiosRequestConfig, + config?: RequestClientConfig, ): Promise { return this.request(url, { ...config, data, method: 'POST' }); } @@ -93,7 +114,7 @@ class RequestClient { public put( url: string, data?: any, - config?: AxiosRequestConfig, + config?: RequestClientConfig, ): Promise { return this.request(url, { ...config, data, method: 'PUT' }); } @@ -101,7 +122,10 @@ class RequestClient { /** * 通用的请求方法 */ - public async request(url: string, config: AxiosRequestConfig): Promise { + public async request( + url: string, + config: RequestClientConfig, + ): Promise { try { const response: AxiosResponse = await this.instance({ url, diff --git a/packages/effects/request/src/request-client/types.ts b/packages/effects/request/src/request-client/types.ts index b85dded4..e37db5c1 100644 --- a/packages/effects/request/src/request-client/types.ts +++ b/packages/effects/request/src/request-client/types.ts @@ -1,10 +1,23 @@ import type { + AxiosRequestConfig, AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig, } from 'axios'; -type RequestResponse = AxiosResponse; +type ExtendOptions = { + /** 响应数据的返回方式。 + * raw: 原始的AxiosResponse,包括headers、status等。 + * body: 返回响应数据的BODY部分。 + * data: 解构响应的BODY数据,只返回其中的data节点数据。 + */ + responseReturn?: 'body' | 'data' | 'raw'; +}; +type RequestClientConfig = AxiosRequestConfig & ExtendOptions; + +type RequestResponse = AxiosResponse & { + config: RequestClientConfig; +}; type RequestContentType = | 'application/json;charset=utf-8' @@ -12,21 +25,21 @@ type RequestContentType = | 'application/x-www-form-urlencoded;charset=utf-8' | 'multipart/form-data;charset=utf-8'; -type RequestClientOptions = CreateAxiosDefaults; +type RequestClientOptions = CreateAxiosDefaults & ExtendOptions; interface RequestInterceptorConfig { fulfilled?: ( - config: InternalAxiosRequestConfig, + config: ExtendOptions & InternalAxiosRequestConfig, ) => - | InternalAxiosRequestConfig - | Promise>; + | (ExtendOptions & InternalAxiosRequestConfig) + | Promise>; rejected?: (error: any) => any; } interface ResponseInterceptorConfig { fulfilled?: ( - response: AxiosResponse, - ) => AxiosResponse | Promise; + response: RequestResponse, + ) => Promise | RequestResponse; rejected?: (error: any) => any; } @@ -45,6 +58,7 @@ interface HttpResponse { export type { HttpResponse, MakeErrorMessageFn, + RequestClientConfig, RequestClientOptions, RequestContentType, RequestInterceptorConfig, diff --git a/playground/src/api/request.ts b/playground/src/api/request.ts index b1f6c060..49429d49 100644 --- a/playground/src/api/request.ts +++ b/playground/src/api/request.ts @@ -1,7 +1,7 @@ /** * 该文件可自行根据业务逻辑进行调整 */ -import type { HttpResponse } from '@vben/request'; +import type { RequestClientOptions } from '@vben/request'; import { useAppConfig } from '@vben/hooks'; import { preferences } from '@vben/preferences'; @@ -20,8 +20,9 @@ import { refreshTokenApi } from './core'; const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); -function createRequestClient(baseURL: string) { +function createRequestClient(baseURL: string, options?: RequestClientOptions) { const client = new RequestClient({ + ...options, baseURL, }); @@ -69,20 +70,6 @@ function createRequestClient(baseURL: string) { }, }); - // response数据解构 - client.addResponseInterceptor({ - 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过期的处理 client.addResponseInterceptor( authenticateResponseInterceptor({ @@ -109,6 +96,8 @@ function createRequestClient(baseURL: string) { return client; } -export const requestClient = createRequestClient(apiURL); +export const requestClient = createRequestClient(apiURL, { + responseReturn: 'data', +}); export const baseRequestClient = new RequestClient({ baseURL: apiURL });