212 lines
5.5 KiB
TypeScript
212 lines
5.5 KiB
TypeScript
import type { AxiosInstance, AxiosResponse } from 'axios';
|
|
|
|
import type { RequestClientConfig, RequestClientOptions } from './types';
|
|
|
|
import { bindMethods, isString, merge } from '@vben/utils';
|
|
|
|
import axios from 'axios';
|
|
|
|
import { FileDownloader } from './modules/downloader';
|
|
import { InterceptorManager } from './modules/interceptor';
|
|
import { SSE } from './modules/sse';
|
|
import { FileUploader } from './modules/uploader';
|
|
|
|
function stringifyQuery(
|
|
params: Record<string, any>,
|
|
arrayFormat: 'brackets' | 'comma' | 'indices' | 'repeat',
|
|
) {
|
|
const searchParams = new URLSearchParams();
|
|
|
|
for (const [key, value] of Object.entries(params ?? {})) {
|
|
if (value === undefined || value === null) {
|
|
continue;
|
|
}
|
|
|
|
if (!Array.isArray(value)) {
|
|
searchParams.append(key, value);
|
|
continue;
|
|
}
|
|
|
|
if (arrayFormat === 'comma') {
|
|
searchParams.append(key, value.join(','));
|
|
continue;
|
|
}
|
|
|
|
value.forEach((item, index) => {
|
|
const arrayKey =
|
|
arrayFormat === 'brackets'
|
|
? `${key}[]`
|
|
: arrayFormat === 'indices'
|
|
? `${key}[${index}]`
|
|
: key;
|
|
searchParams.append(arrayKey, item);
|
|
});
|
|
}
|
|
|
|
return searchParams.toString();
|
|
}
|
|
|
|
function getParamsSerializer(
|
|
paramsSerializer: RequestClientOptions['paramsSerializer'],
|
|
) {
|
|
if (isString(paramsSerializer)) {
|
|
switch (paramsSerializer) {
|
|
case 'brackets': {
|
|
return (params: any) =>
|
|
stringifyQuery(params, 'brackets');
|
|
}
|
|
case 'comma': {
|
|
return (params: any) => stringifyQuery(params, 'comma');
|
|
}
|
|
case 'indices': {
|
|
return (params: any) =>
|
|
stringifyQuery(params, 'indices');
|
|
}
|
|
case 'repeat': {
|
|
return (params: any) => stringifyQuery(params, 'repeat');
|
|
}
|
|
}
|
|
}
|
|
return paramsSerializer;
|
|
}
|
|
|
|
class RequestClient {
|
|
public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];
|
|
|
|
public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];
|
|
public download: FileDownloader['download'];
|
|
|
|
public readonly instance: AxiosInstance;
|
|
// 是否正在刷新token
|
|
public isRefreshing = false;
|
|
public postSSE: SSE['postSSE'];
|
|
// 刷新token队列
|
|
public refreshTokenQueue: ((token: string) => void)[] = [];
|
|
// SSE 401 时用于刷新 token 的回调
|
|
public refreshToken?: () => Promise<string>;
|
|
public requestSSE: SSE['requestSSE'];
|
|
public upload: FileUploader['upload'];
|
|
|
|
/**
|
|
* 构造函数,用于创建Axios实例
|
|
* @param options - Axios请求配置,可选
|
|
*/
|
|
constructor(options: RequestClientOptions = {}) {
|
|
// 合并默认配置和传入的配置
|
|
const defaultConfig: RequestClientOptions = {
|
|
headers: {
|
|
'Content-Type': 'application/json;charset=utf-8',
|
|
},
|
|
responseReturn: 'raw',
|
|
// 默认超时时间
|
|
timeout: 60_000,
|
|
};
|
|
const { ...axiosConfig } = options;
|
|
const requestConfig = merge(axiosConfig, defaultConfig);
|
|
requestConfig.paramsSerializer = getParamsSerializer(
|
|
requestConfig.paramsSerializer,
|
|
);
|
|
this.instance = axios.create(requestConfig);
|
|
|
|
bindMethods(this);
|
|
|
|
// 实例化拦截器管理器
|
|
const interceptorManager = new InterceptorManager(this.instance);
|
|
this.addRequestInterceptor =
|
|
interceptorManager.addRequestInterceptor.bind(interceptorManager);
|
|
this.addResponseInterceptor =
|
|
interceptorManager.addResponseInterceptor.bind(interceptorManager);
|
|
|
|
// 实例化文件上传器
|
|
const fileUploader = new FileUploader(this);
|
|
this.upload = fileUploader.upload.bind(fileUploader);
|
|
// 实例化文件下载器
|
|
const fileDownloader = new FileDownloader(this);
|
|
this.download = fileDownloader.download.bind(fileDownloader);
|
|
// 实例化SSE模块
|
|
const sse = new SSE(this);
|
|
this.postSSE = sse.postSSE.bind(sse);
|
|
this.requestSSE = sse.requestSSE.bind(sse);
|
|
}
|
|
|
|
/**
|
|
* DELETE请求方法
|
|
*/
|
|
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?: RequestClientConfig): Promise<T> {
|
|
return this.request<T>(url, { ...config, method: 'GET' });
|
|
}
|
|
|
|
/**
|
|
* 获取基础URL
|
|
*/
|
|
public getBaseUrl() {
|
|
return this.instance.defaults.baseURL;
|
|
}
|
|
|
|
/**
|
|
* POST请求方法
|
|
*/
|
|
public post<T = any>(
|
|
url: string,
|
|
data?: any,
|
|
config?: RequestClientConfig,
|
|
): Promise<T> {
|
|
return this.request<T>(url, { ...config, data, method: 'POST' });
|
|
}
|
|
|
|
/**
|
|
* PUT请求方法
|
|
*/
|
|
public put<T = any>(
|
|
url: string,
|
|
data?: any,
|
|
config?: RequestClientConfig,
|
|
): Promise<T> {
|
|
return this.request<T>(url, { ...config, data, method: 'PUT' });
|
|
}
|
|
|
|
/**
|
|
* PATCH请求方法
|
|
*/
|
|
public patch<T = any>(
|
|
url: string,
|
|
data?: any,
|
|
config?: RequestClientConfig,
|
|
): Promise<T> {
|
|
return this.request<T>(url, { ...config, data, method: 'PATCH' });
|
|
}
|
|
|
|
/**
|
|
* 通用的请求方法
|
|
*/
|
|
public async request<T>(
|
|
url: string,
|
|
config: RequestClientConfig,
|
|
): Promise<T> {
|
|
try {
|
|
const response: AxiosResponse<T> = await this.instance({
|
|
url,
|
|
...config,
|
|
...(config.paramsSerializer
|
|
? { paramsSerializer: getParamsSerializer(config.paramsSerializer) }
|
|
: {}),
|
|
});
|
|
return response as T;
|
|
} catch (error: any) {
|
|
throw error.response ? error.response.data : error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export { RequestClient };
|