2024-06-02 20:50:51 +08:00
|
|
|
|
import type {
|
|
|
|
|
AxiosInstance,
|
|
|
|
|
AxiosRequestConfig,
|
|
|
|
|
AxiosResponse,
|
|
|
|
|
CreateAxiosDefaults,
|
|
|
|
|
InternalAxiosRequestConfig,
|
|
|
|
|
} from 'axios';
|
|
|
|
|
|
2024-07-11 22:50:21 +08:00
|
|
|
|
import type {
|
|
|
|
|
MakeAuthorizationFn,
|
|
|
|
|
MakeErrorMessageFn,
|
2024-07-28 14:29:05 +08:00
|
|
|
|
MakeRequestHeadersFn,
|
2024-07-11 22:50:21 +08:00
|
|
|
|
RequestClientOptions,
|
|
|
|
|
} from './types';
|
2024-06-08 19:49:06 +08:00
|
|
|
|
|
2024-07-23 00:03:59 +08:00
|
|
|
|
import { $t } from '@vben/locales';
|
|
|
|
|
import { merge } from '@vben/utils';
|
2024-06-02 20:50:51 +08:00
|
|
|
|
|
|
|
|
|
import axios from 'axios';
|
|
|
|
|
|
|
|
|
|
import { FileDownloader } from './modules/downloader';
|
|
|
|
|
import { InterceptorManager } from './modules/interceptor';
|
|
|
|
|
import { FileUploader } from './modules/uploader';
|
|
|
|
|
|
|
|
|
|
class RequestClient {
|
|
|
|
|
private instance: AxiosInstance;
|
|
|
|
|
private makeAuthorization: MakeAuthorizationFn | undefined;
|
2024-07-11 22:50:21 +08:00
|
|
|
|
private makeErrorMessage: MakeErrorMessageFn | undefined;
|
2024-07-28 14:29:05 +08:00
|
|
|
|
private makeRequestHeaders: MakeRequestHeadersFn | undefined;
|
2024-07-11 22:50:21 +08:00
|
|
|
|
|
2024-06-02 20:50:51 +08:00
|
|
|
|
public addRequestInterceptor: InterceptorManager['addRequestInterceptor'];
|
|
|
|
|
public addResponseInterceptor: InterceptorManager['addResponseInterceptor'];
|
|
|
|
|
public download: FileDownloader['download'];
|
|
|
|
|
public upload: FileUploader['upload'];
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造函数,用于创建Axios实例
|
2024-06-08 16:33:49 +08:00
|
|
|
|
* @param options - Axios请求配置,可选
|
2024-06-02 20:50:51 +08:00
|
|
|
|
*/
|
|
|
|
|
constructor(options: RequestClientOptions = {}) {
|
2024-06-30 14:09:44 +08:00
|
|
|
|
this.bindMethods();
|
2024-06-02 20:50:51 +08:00
|
|
|
|
// 合并默认配置和传入的配置
|
|
|
|
|
const defaultConfig: CreateAxiosDefaults = {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json;charset=utf-8',
|
|
|
|
|
},
|
|
|
|
|
// 默认超时时间
|
|
|
|
|
timeout: 10_000,
|
|
|
|
|
};
|
2024-07-28 14:29:05 +08:00
|
|
|
|
const {
|
|
|
|
|
makeAuthorization,
|
|
|
|
|
makeErrorMessage,
|
|
|
|
|
makeRequestHeaders,
|
|
|
|
|
...axiosConfig
|
|
|
|
|
} = options;
|
2024-06-02 20:50:51 +08:00
|
|
|
|
const requestConfig = merge(axiosConfig, defaultConfig);
|
|
|
|
|
|
|
|
|
|
this.instance = axios.create(requestConfig);
|
|
|
|
|
this.makeAuthorization = makeAuthorization;
|
2024-07-28 14:29:05 +08:00
|
|
|
|
this.makeRequestHeaders = makeRequestHeaders;
|
2024-07-11 22:50:21 +08:00
|
|
|
|
this.makeErrorMessage = makeErrorMessage;
|
2024-06-02 20:50:51 +08:00
|
|
|
|
|
|
|
|
|
// 实例化拦截器管理器
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
// 设置默认的拦截器
|
|
|
|
|
this.setupInterceptors();
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-30 14:09:44 +08:00
|
|
|
|
private bindMethods() {
|
|
|
|
|
const propertyNames = Object.getOwnPropertyNames(
|
|
|
|
|
Object.getPrototypeOf(this),
|
|
|
|
|
);
|
|
|
|
|
propertyNames.forEach((propertyName) => {
|
|
|
|
|
const propertyValue = (this as any)[propertyName];
|
|
|
|
|
if (
|
|
|
|
|
typeof propertyValue === 'function' &&
|
|
|
|
|
propertyName !== 'constructor'
|
|
|
|
|
) {
|
|
|
|
|
(this as any)[propertyName] = propertyValue.bind(this);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-28 14:29:05 +08:00
|
|
|
|
private setupDefaultResponseInterceptor() {
|
2024-07-11 20:11:11 +08:00
|
|
|
|
this.addRequestInterceptor(
|
|
|
|
|
(config: InternalAxiosRequestConfig) => {
|
|
|
|
|
const authorization = this.makeAuthorization?.(config);
|
|
|
|
|
if (authorization) {
|
|
|
|
|
const { token } = authorization.tokenHandler?.() ?? {};
|
2024-08-07 08:57:56 +08:00
|
|
|
|
config.headers[authorization.key || 'Authorization'] =
|
|
|
|
|
`Bearer ${token}`;
|
2024-07-11 20:11:11 +08:00
|
|
|
|
}
|
2024-07-28 14:29:05 +08:00
|
|
|
|
|
|
|
|
|
const requestHeader = this.makeRequestHeaders?.(config);
|
|
|
|
|
|
|
|
|
|
if (requestHeader) {
|
|
|
|
|
for (const [key, value] of Object.entries(requestHeader)) {
|
|
|
|
|
config.headers[key] = value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 20:11:11 +08:00
|
|
|
|
return config;
|
|
|
|
|
},
|
|
|
|
|
(error: any) => Promise.reject(error),
|
|
|
|
|
);
|
|
|
|
|
this.addResponseInterceptor(
|
|
|
|
|
(response: AxiosResponse) => {
|
|
|
|
|
return response;
|
|
|
|
|
},
|
|
|
|
|
(error: any) => {
|
|
|
|
|
if (axios.isCancel(error)) {
|
|
|
|
|
return Promise.reject(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const err: string = error?.toString?.() ?? '';
|
|
|
|
|
let errMsg = '';
|
|
|
|
|
if (err?.includes('Network Error')) {
|
|
|
|
|
errMsg = $t('fallback.http.networkError');
|
|
|
|
|
} else if (error?.message?.includes?.('timeout')) {
|
|
|
|
|
errMsg = $t('fallback.http.requestTimeout');
|
|
|
|
|
}
|
|
|
|
|
if (errMsg) {
|
2024-07-11 22:50:21 +08:00
|
|
|
|
this.makeErrorMessage?.(errMsg);
|
2024-07-11 20:11:11 +08:00
|
|
|
|
return Promise.reject(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let errorMessage = error?.response?.data?.error?.message ?? '';
|
|
|
|
|
const status = error?.response?.status;
|
|
|
|
|
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 400: {
|
|
|
|
|
errorMessage = $t('fallback.http.badRequest');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case 401: {
|
|
|
|
|
errorMessage = $t('fallback.http.unauthorized');
|
2024-07-11 22:50:21 +08:00
|
|
|
|
this.makeAuthorization?.().unAuthorizedHandler?.();
|
2024-07-11 20:11:11 +08:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 403: {
|
|
|
|
|
errorMessage = $t('fallback.http.forbidden');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// 404请求不存在
|
|
|
|
|
case 404: {
|
|
|
|
|
errorMessage = $t('fallback.http.notFound');
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
case 408: {
|
|
|
|
|
errorMessage = $t('fallback.http.requestTimeout');
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
default: {
|
|
|
|
|
errorMessage = $t('fallback.http.internalServerError');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-11 22:50:21 +08:00
|
|
|
|
this.makeErrorMessage?.(errorMessage);
|
2024-07-11 20:11:11 +08:00
|
|
|
|
return Promise.reject(error);
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-06-02 20:50:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setupInterceptors() {
|
|
|
|
|
// 默认拦截器
|
2024-07-11 20:11:11 +08:00
|
|
|
|
this.setupDefaultResponseInterceptor();
|
2024-06-02 20:50:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE请求方法
|
|
|
|
|
*/
|
|
|
|
|
public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
|
|
|
return this.request<T>(url, { ...config, method: 'DELETE' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 09:18:19 +08:00
|
|
|
|
/**
|
|
|
|
|
* DELETE请求方法 成功会弹出msg
|
|
|
|
|
*/
|
|
|
|
|
public deleteWithMsg<T = any>(
|
|
|
|
|
url: string,
|
|
|
|
|
config?: AxiosRequestConfig,
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
return this.request<T>(url, {
|
|
|
|
|
...config,
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
successMessageMode: 'message',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 20:50:51 +08:00
|
|
|
|
/**
|
|
|
|
|
* GET请求方法
|
|
|
|
|
*/
|
|
|
|
|
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
|
|
|
|
|
return this.request<T>(url, { ...config, method: 'GET' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST请求方法
|
|
|
|
|
*/
|
|
|
|
|
public post<T = any>(
|
|
|
|
|
url: string,
|
|
|
|
|
data?: any,
|
|
|
|
|
config?: AxiosRequestConfig,
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
return this.request<T>(url, { ...config, data, method: 'POST' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 09:18:19 +08:00
|
|
|
|
/**
|
|
|
|
|
* POST请求方法 成功会弹出msg
|
|
|
|
|
*/
|
|
|
|
|
public postWithMsg<T = any>(
|
|
|
|
|
url: string,
|
|
|
|
|
data?: any,
|
|
|
|
|
config?: AxiosRequestConfig,
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
return this.request<T>(url, {
|
|
|
|
|
...config,
|
|
|
|
|
data,
|
|
|
|
|
method: 'POST',
|
|
|
|
|
successMessageMode: 'message',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 20:50:51 +08:00
|
|
|
|
/**
|
|
|
|
|
* PUT请求方法
|
|
|
|
|
*/
|
|
|
|
|
public put<T = any>(
|
|
|
|
|
url: string,
|
|
|
|
|
data?: any,
|
|
|
|
|
config?: AxiosRequestConfig,
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
return this.request<T>(url, { ...config, data, method: 'PUT' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 09:18:19 +08:00
|
|
|
|
/**
|
|
|
|
|
* PUT请求方法 成功会弹出msg
|
|
|
|
|
*/
|
|
|
|
|
public putWithMsg<T = any>(
|
|
|
|
|
url: string,
|
|
|
|
|
data?: any,
|
|
|
|
|
config?: AxiosRequestConfig,
|
|
|
|
|
): Promise<T> {
|
|
|
|
|
return this.request<T>(url, {
|
|
|
|
|
...config,
|
|
|
|
|
data,
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
successMessageMode: 'message',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-02 20:50:51 +08:00
|
|
|
|
/**
|
|
|
|
|
* 通用的请求方法
|
|
|
|
|
*/
|
|
|
|
|
public async request<T>(url: string, config: AxiosRequestConfig): Promise<T> {
|
|
|
|
|
try {
|
|
|
|
|
const response: AxiosResponse<T> = await this.instance({
|
|
|
|
|
url,
|
|
|
|
|
...config,
|
|
|
|
|
});
|
|
|
|
|
return response as T;
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
throw error.response ? error.response.data : error;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export { RequestClient };
|