Skip to content

Commit

Permalink
perf: request support to set how to return response (#5436)
Browse files Browse the repository at this point in the history
* feat: request support to set how to return response

* docs: typo

* fix: test unit

* test: add request responseReturn test
  • Loading branch information
mynetfan authored Jan 19, 2025
1 parent 3f0f4d5 commit 5611f6c
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 86 deletions.
23 changes: 6 additions & 17 deletions apps/web-antd/src/api/request.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
});

Expand Down Expand Up @@ -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过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
Expand All @@ -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 });
22 changes: 6 additions & 16 deletions apps/web-ele/src/api/request.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
});

Expand Down Expand Up @@ -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过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
Expand All @@ -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 });
22 changes: 6 additions & 16 deletions apps/web-naive/src/api/request.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
});

Expand Down Expand Up @@ -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过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
Expand All @@ -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 });
23 changes: 23 additions & 0 deletions packages/effects/request/src/request-client/request-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
50 changes: 37 additions & 13 deletions packages/effects/request/src/request-client/request-client.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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,
};
Expand All @@ -54,6 +54,24 @@ class RequestClient {
this.addResponseInterceptor =
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);
this.upload = fileUploader.upload.bind(fileUploader);
Expand All @@ -65,14 +83,17 @@ class RequestClient {
/**
* 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' });
}

/**
* 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' });
}

Expand All @@ -82,7 +103,7 @@ class RequestClient {
public post<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig,
config?: RequestClientConfig,
): Promise<T> {
return this.request<T>(url, { ...config, data, method: 'POST' });
}
Expand All @@ -93,15 +114,18 @@ class RequestClient {
public put<T = any>(
url: string,
data?: any,
config?: AxiosRequestConfig,
config?: RequestClientConfig,
): Promise<T> {
return this.request<T>(url, { ...config, data, method: 'PUT' });
}

/**
* 通用的请求方法
*/
public async request<T>(url: string, config: AxiosRequestConfig): Promise<T> {
public async request<T>(
url: string,
config: RequestClientConfig,
): Promise<T> {
try {
const response: AxiosResponse<T> = await this.instance({
url,
Expand Down
28 changes: 21 additions & 7 deletions packages/effects/request/src/request-client/types.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
import type {
AxiosRequestConfig,
AxiosResponse,
CreateAxiosDefaults,
InternalAxiosRequestConfig,
} from 'axios';

type RequestResponse<T = any> = AxiosResponse<T>;
type ExtendOptions = {
/** 响应数据的返回方式。
* raw: 原始的AxiosResponse,包括headers、status等。
* 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 =
| 'application/json;charset=utf-8'
| 'application/octet-stream;charset=utf-8'
| '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<any>
| Promise<InternalAxiosRequestConfig<any>>;
| (ExtendOptions & InternalAxiosRequestConfig<any>)
| Promise<ExtendOptions & InternalAxiosRequestConfig<any>>;
rejected?: (error: any) => any;
}

interface ResponseInterceptorConfig<T = any> {
fulfilled?: (
response: AxiosResponse<T>,
) => AxiosResponse | Promise<AxiosResponse>;
response: RequestResponse<T>,
) => Promise<RequestResponse> | RequestResponse;
rejected?: (error: any) => any;
}

Expand All @@ -45,6 +58,7 @@ interface HttpResponse<T = any> {
export type {
HttpResponse,
MakeErrorMessageFn,
RequestClientConfig,
RequestClientOptions,
RequestContentType,
RequestInterceptorConfig,
Expand Down
23 changes: 6 additions & 17 deletions playground/src/api/request.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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,
});

Expand Down Expand Up @@ -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过期的处理
client.addResponseInterceptor(
authenticateResponseInterceptor({
Expand All @@ -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 });

0 comments on commit 5611f6c

Please sign in to comment.