Sanakey
2024-08-29 45b43f4ff4bea965638166ff619db1ef5afcad70
提交 | 用户 | age
2f6253 1 // axios配置  可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
2 // The axios configuration can be changed according to the project, just change the file, other files can be left unchanged
3
c118e8 4 import type { AxiosInstance, AxiosResponse } from 'axios';
4f35b9 5 import { clone } from 'lodash-es';
4d2fb0 6 import type { RequestOptions, Result } from '#/axios';
b7ce74 7 import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform';
V 8 import { VAxios } from './Axios';
2f6253 9 import { checkStatus } from './checkStatus';
4d2fb0 10 import { useGlobSetting } from '@/hooks/setting';
X 11 import { useMessage } from '@/hooks/web/useMessage';
12 import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum';
13 import { isString, isUndefined, isNull, isEmpty } from '@/utils/is';
14 import { getToken } from '@/utils/auth';
15 import { setObjToUrlParams, deepMerge } from '@/utils';
16 import { useErrorLogStoreWithOut } from '@/store/modules/errorLog';
17 import { useI18n } from '@/hooks/web/useI18n';
50f94b 18 import { joinTimestamp, formatRequestDate } from './helper';
4d2fb0 19 import { useUserStoreWithOut } from '@/store/modules/user';
X 20 import { AxiosRetry } from '@/utils/http/axios/axiosRetry';
b1f78c 21 import axios from 'axios';
2f6253 22
737b1b 23 const globSetting = useGlobSetting();
50f94b 24 const urlPrefix = globSetting.urlPrefix;
17d16a 25 const { createMessage, createErrorModal, createSuccessModal } = useMessage();
2f6253 26
27 /**
28  * @description: 数据处理,方便区分多种处理方式
29  */
30 const transform: AxiosTransform = {
31   /**
c0e40f 32    * @description: 处理响应数据。如果数据不是预期格式,可直接抛出错误
2f6253 33    */
c0e40f 34   transformResponseHook: (res: AxiosResponse<Result>, options: RequestOptions) => {
962f90 35     const { t } = useI18n();
b6d5b0 36     const { isTransformResponse, isReturnNativeResponse } = options;
56d8af 37     // 是否返回原生响应头 比如:需要获取响应头时使用该属性
Z 38     if (isReturnNativeResponse) {
39       return res;
40     }
2f6253 41     // 不进行任何处理,直接返回
42     // 用于页面代码可能需要直接获取code,data,message这些信息时开启
b6d5b0 43     if (!isTransformResponse) {
2f6253 44       return res.data;
45     }
46     // 错误的时候返回
47
48     const { data } = res;
49     if (!data) {
50       // return '[HTTP] Request has no return value';
b218f1 51       throw new Error(t('sys.api.apiRequestFailed'));
2f6253 52     }
53     //  这里 code,result,message为 后台统一的字段,需要在 types.ts内修改为项目自己的接口返回格式
54     const { code, result, message } = data;
55
56     // 这里逻辑可以根据项目进行修改
57     const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
b69dcd 58     if (hasSuccess) {
17d16a 59       let successMsg = message;
bc0990 60
f91d77 61       if (isNull(successMsg) || isUndefined(successMsg) || isEmpty(successMsg)) {
bc0990 62         successMsg = t(`sys.api.operationSuccess`);
17d16a 63       }
bc0990 64
17d16a 65       if (options.successMessageMode === 'modal') {
M 66         createSuccessModal({ title: t('sys.api.successTip'), content: successMsg });
67       } else if (options.successMessageMode === 'message') {
68         createMessage.success(successMsg);
69       }
e83cb0 70       return result;
2f6253 71     }
b69dcd 72
73     // 在此处根据自己项目的实际情况对不同的code执行不同的操作
74     // 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
49b66e 75     let timeoutMsg = '';
b69dcd 76     switch (code) {
77       case ResultEnum.TIMEOUT:
49b66e 78         timeoutMsg = t('sys.api.timeoutMessage');
6544f8 79         const userStore = useUserStoreWithOut();
236ddf 80         // 被动登出,带redirect地址
X 81         userStore.logout(false);
49f39d 82         break;
b69dcd 83       default:
84         if (message) {
49b66e 85           timeoutMsg = message;
b69dcd 86         }
2f6253 87     }
49b66e 88
9ba157 89     // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
49b66e 90     // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
X 91     if (options.errorMessageMode === 'modal') {
92       createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });
93     } else if (options.errorMessageMode === 'message') {
94       createMessage.error(timeoutMsg);
95     }
96
97     throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));
2f6253 98   },
99
100   // 请求之前处理config
101   beforeRequestHook: (config, options) => {
996f2f 102     const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
2f6253 103
104     if (joinPrefix) {
50f94b 105       config.url = `${urlPrefix}${config.url}`;
2f6253 106     }
107
108     if (apiUrl && isString(apiUrl)) {
109       config.url = `${apiUrl}${config.url}`;
110     }
ac1a36 111     const params = config.params || {};
49f39d 112     const data = config.data || false;
113     formatDate && data && !isString(data) && formatRequestDate(data);
6b3195 114     if (config.method?.toUpperCase() === RequestEnum.GET) {
ac1a36 115       if (!isString(params)) {
c96002 116         // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
50f94b 117         config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
2f6253 118       } else {
119         // 兼容restful风格
50f94b 120         config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
34c09f 121         config.params = undefined;
2f6253 122       }
123     } else {
ac1a36 124       if (!isString(params)) {
V 125         formatDate && formatRequestDate(params);
b1f78c 126         if (
L 127           Reflect.has(config, 'data') &&
128           config.data &&
129           (Object.keys(config.data).length > 0 || config.data instanceof FormData)
130         ) {
49f39d 131           config.data = data;
132           config.params = params;
133         } else {
134           // 非GET请求如果没有提供data,则将params视为data
135           config.data = params;
136           config.params = undefined;
137         }
2f6253 138         if (joinParamsToUrl) {
49f39d 139           config.url = setObjToUrlParams(
140             config.url as string,
56a966 141             Object.assign({}, config.params, config.data),
49f39d 142           );
2f6253 143         }
144       } else {
145         // 兼容restful风格
ac1a36 146         config.url = config.url + params;
34c09f 147         config.params = undefined;
2f6253 148       }
149     }
150     return config;
151   },
152
153   /**
154    * @description: 请求拦截器处理
155    */
b6d5b0 156   requestInterceptors: (config, options) => {
2f6253 157     // 请求之前处理config
b7ce74 158     const token = getToken();
d509e8 159     if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
2f6253 160       // jwt token
590288 161       (config as Recordable).headers.Authorization = options.authenticationScheme
b6d5b0 162         ? `${options.authenticationScheme} ${token}`
V 163         : token;
2f6253 164     }
165     return config;
166   },
167
168   /**
49b66e 169    * @description: 响应拦截器处理
X 170    */
171   responseInterceptors: (res: AxiosResponse<any>) => {
172     return res;
173   },
174
175   /**
2f6253 176    * @description: 响应错误处理
177    */
c118e8 178   responseInterceptorsCatch: (axiosInstance: AxiosInstance, error: any) => {
962f90 179     const { t } = useI18n();
215d8b 180     const errorLogStore = useErrorLogStoreWithOut();
V 181     errorLogStore.addAjaxErrorInfo(error);
49b66e 182     const { response, code, message, config } = error || {};
X 183     const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
de5bf7 184     const msg: string = response?.data?.error?.message ?? '';
V 185     const err: string = error?.toString?.() ?? '';
49b66e 186     let errMessage = '';
X 187
b1f78c 188     if (axios.isCancel(error)) {
L 189       return Promise.reject(error);
190     }
191
2f6253 192     try {
193       if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
49b66e 194         errMessage = t('sys.api.apiTimeoutMessage');
2f6253 195       }
6b3195 196       if (err?.includes('Network Error')) {
49b66e 197         errMessage = t('sys.api.networkExceptionMsg');
X 198       }
199
200       if (errMessage) {
201         if (errorMessageMode === 'modal') {
202           createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
203         } else if (errorMessageMode === 'message') {
204           createMessage.error(errMessage);
205         }
206         return Promise.reject(error);
2f6253 207       }
208     } catch (error) {
590288 209       throw new Error(error as unknown as string);
2f6253 210     }
49b66e 211
X 212     checkStatus(error?.response?.status, msg, errorMessageMode);
136cbb 213
C 214     // 添加自动重试机制 保险起见 只针对GET请求
215     const retryRequest = new AxiosRetry();
216     const { isOpenRetry } = config.requestOptions.retryRequest;
217     config.method?.toUpperCase() === RequestEnum.GET &&
218       isOpenRetry &&
362740 219       error?.response?.status !== 401 &&
136cbb 220       // @ts-ignore
C 221       retryRequest.retry(axiosInstance, error);
661db0 222     return Promise.reject(error);
2f6253 223   },
224 };
225
226 function createAxios(opt?: Partial<CreateAxiosOptions>) {
227   return new VAxios(
19dc88 228     // 深度合并
2f6253 229     deepMerge(
230       {
b6d5b0 231         // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
V 232         // authentication schemes,e.g: Bearer
233         // authenticationScheme: 'Bearer',
234         authenticationScheme: '',
89d7b1 235         timeout: 60 * 1000,
2f6253 236         // 基础接口地址
61d4ef 237         // baseURL: globSetting.apiUrl,
996f2f 238
2f6253 239         headers: { 'Content-Type': ContentTypeEnum.JSON },
f646e3 240         // 如果是form-data格式
V 241         // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
2f6253 242         // 数据处理方式
4f35b9 243         transform: clone(transform),
2f6253 244         // 配置项,下面的选项都可以在独立的接口请求中覆盖
245         requestOptions: {
246           // 默认将prefix 添加到url
247           joinPrefix: true,
56d8af 248           // 是否返回原生响应头 比如:需要获取响应头时使用该属性
Z 249           isReturnNativeResponse: false,
2f6253 250           // 需要对返回数据进行处理
b6d5b0 251           isTransformResponse: true,
2f6253 252           // post请求的时候添加参数到url
253           joinParamsToUrl: false,
254           // 格式化提交参数时间
255           formatDate: true,
256           // 消息提示类型
4ce1d5 257           errorMessageMode: 'message',
2f6253 258           // 接口地址
259           apiUrl: globSetting.apiUrl,
7df9b5 260           // 接口拼接地址
L 261           urlPrefix: urlPrefix,
f646e3 262           //  是否加入时间戳
V 263           joinTime: true,
3b8ca4 264           // 忽略重复请求
V 265           ignoreCancelToken: true,
c99cf5 266           // 是否携带token
267           withToken: true,
136cbb 268           retryRequest: {
C 269             isOpenRetry: true,
270             count: 5,
271             waitTime: 100,
272           },
2f6253 273         },
274       },
56a966 275       opt || {},
V 276     ),
2f6253 277   );
278 }
279 export const defHttp = createAxios();
280
281 // other api url
282 // export const otherHttp = createAxios({
283 //   requestOptions: {
284 //     apiUrl: 'xxx',
7df9b5 285 //     urlPrefix: 'xxx',
2f6253 286 //   },
287 // });