Kirk Lin
2023-11-07 f91d777e4fbe9524f1a08f597b4f0e342f86d9a1
提交 | 用户 | 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';
b6d5b0 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';
ba068b 10 import { useGlobSetting } from '/@/hooks/setting';
2f6253 11 import { useMessage } from '/@/hooks/web/useMessage';
12 import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum';
f91d77 13 import { isString, isUndefined, isNull, isEmpty } from '/@/utils/is';
b7ce74 14 import { getToken } from '/@/utils/auth';
2f6253 15 import { setObjToUrlParams, deepMerge } from '/@/utils';
215d8b 16 import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
190112 17 import { useI18n } from '/@/hooks/web/useI18n';
50f94b 18 import { joinTimestamp, formatRequestDate } from './helper';
6544f8 19 import { useUserStoreWithOut } from '/@/store/modules/user';
136cbb 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();
80         userStore.logout(true);
49f39d 81         break;
b69dcd 82       default:
83         if (message) {
49b66e 84           timeoutMsg = message;
b69dcd 85         }
2f6253 86     }
49b66e 87
9ba157 88     // errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
49b66e 89     // errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
X 90     if (options.errorMessageMode === 'modal') {
91       createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg });
92     } else if (options.errorMessageMode === 'message') {
93       createMessage.error(timeoutMsg);
94     }
95
96     throw new Error(timeoutMsg || t('sys.api.apiRequestFailed'));
2f6253 97   },
98
99   // 请求之前处理config
100   beforeRequestHook: (config, options) => {
996f2f 101     const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
2f6253 102
103     if (joinPrefix) {
50f94b 104       config.url = `${urlPrefix}${config.url}`;
2f6253 105     }
106
107     if (apiUrl && isString(apiUrl)) {
108       config.url = `${apiUrl}${config.url}`;
109     }
ac1a36 110     const params = config.params || {};
49f39d 111     const data = config.data || false;
112     formatDate && data && !isString(data) && formatRequestDate(data);
6b3195 113     if (config.method?.toUpperCase() === RequestEnum.GET) {
ac1a36 114       if (!isString(params)) {
c96002 115         // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
50f94b 116         config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
2f6253 117       } else {
118         // 兼容restful风格
50f94b 119         config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
34c09f 120         config.params = undefined;
2f6253 121       }
122     } else {
ac1a36 123       if (!isString(params)) {
V 124         formatDate && formatRequestDate(params);
b1f78c 125         if (
L 126           Reflect.has(config, 'data') &&
127           config.data &&
128           (Object.keys(config.data).length > 0 || config.data instanceof FormData)
129         ) {
49f39d 130           config.data = data;
131           config.params = params;
132         } else {
133           // 非GET请求如果没有提供data,则将params视为data
134           config.data = params;
135           config.params = undefined;
136         }
2f6253 137         if (joinParamsToUrl) {
49f39d 138           config.url = setObjToUrlParams(
139             config.url as string,
56a966 140             Object.assign({}, config.params, config.data),
49f39d 141           );
2f6253 142         }
143       } else {
144         // 兼容restful风格
ac1a36 145         config.url = config.url + params;
34c09f 146         config.params = undefined;
2f6253 147       }
148     }
149     return config;
150   },
151
152   /**
153    * @description: 请求拦截器处理
154    */
b6d5b0 155   requestInterceptors: (config, options) => {
2f6253 156     // 请求之前处理config
b7ce74 157     const token = getToken();
d509e8 158     if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
2f6253 159       // jwt token
590288 160       (config as Recordable).headers.Authorization = options.authenticationScheme
b6d5b0 161         ? `${options.authenticationScheme} ${token}`
V 162         : token;
2f6253 163     }
164     return config;
165   },
166
167   /**
49b66e 168    * @description: 响应拦截器处理
X 169    */
170   responseInterceptors: (res: AxiosResponse<any>) => {
171     return res;
172   },
173
174   /**
2f6253 175    * @description: 响应错误处理
176    */
c118e8 177   responseInterceptorsCatch: (axiosInstance: AxiosInstance, error: any) => {
962f90 178     const { t } = useI18n();
215d8b 179     const errorLogStore = useErrorLogStoreWithOut();
V 180     errorLogStore.addAjaxErrorInfo(error);
49b66e 181     const { response, code, message, config } = error || {};
X 182     const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none';
de5bf7 183     const msg: string = response?.data?.error?.message ?? '';
V 184     const err: string = error?.toString?.() ?? '';
49b66e 185     let errMessage = '';
X 186
b1f78c 187     if (axios.isCancel(error)) {
L 188       return Promise.reject(error);
189     }
190
2f6253 191     try {
192       if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
49b66e 193         errMessage = t('sys.api.apiTimeoutMessage');
2f6253 194       }
6b3195 195       if (err?.includes('Network Error')) {
49b66e 196         errMessage = t('sys.api.networkExceptionMsg');
X 197       }
198
199       if (errMessage) {
200         if (errorMessageMode === 'modal') {
201           createErrorModal({ title: t('sys.api.errorTip'), content: errMessage });
202         } else if (errorMessageMode === 'message') {
203           createMessage.error(errMessage);
204         }
205         return Promise.reject(error);
2f6253 206       }
207     } catch (error) {
590288 208       throw new Error(error as unknown as string);
2f6253 209     }
49b66e 210
X 211     checkStatus(error?.response?.status, msg, errorMessageMode);
136cbb 212
C 213     // 添加自动重试机制 保险起见 只针对GET请求
214     const retryRequest = new AxiosRetry();
215     const { isOpenRetry } = config.requestOptions.retryRequest;
216     config.method?.toUpperCase() === RequestEnum.GET &&
217       isOpenRetry &&
218       // @ts-ignore
219       retryRequest.retry(axiosInstance, error);
661db0 220     return Promise.reject(error);
2f6253 221   },
222 };
223
224 function createAxios(opt?: Partial<CreateAxiosOptions>) {
225   return new VAxios(
19dc88 226     // 深度合并
2f6253 227     deepMerge(
228       {
b6d5b0 229         // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes
V 230         // authentication schemes,e.g: Bearer
231         // authenticationScheme: 'Bearer',
232         authenticationScheme: '',
2f6253 233         timeout: 10 * 1000,
234         // 基础接口地址
61d4ef 235         // baseURL: globSetting.apiUrl,
996f2f 236
2f6253 237         headers: { 'Content-Type': ContentTypeEnum.JSON },
f646e3 238         // 如果是form-data格式
V 239         // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
2f6253 240         // 数据处理方式
4f35b9 241         transform: clone(transform),
2f6253 242         // 配置项,下面的选项都可以在独立的接口请求中覆盖
243         requestOptions: {
244           // 默认将prefix 添加到url
245           joinPrefix: true,
56d8af 246           // 是否返回原生响应头 比如:需要获取响应头时使用该属性
Z 247           isReturnNativeResponse: false,
2f6253 248           // 需要对返回数据进行处理
b6d5b0 249           isTransformResponse: true,
2f6253 250           // post请求的时候添加参数到url
251           joinParamsToUrl: false,
252           // 格式化提交参数时间
253           formatDate: true,
254           // 消息提示类型
4ce1d5 255           errorMessageMode: 'message',
2f6253 256           // 接口地址
257           apiUrl: globSetting.apiUrl,
7df9b5 258           // 接口拼接地址
L 259           urlPrefix: urlPrefix,
f646e3 260           //  是否加入时间戳
V 261           joinTime: true,
3b8ca4 262           // 忽略重复请求
V 263           ignoreCancelToken: true,
c99cf5 264           // 是否携带token
265           withToken: true,
136cbb 266           retryRequest: {
C 267             isOpenRetry: true,
268             count: 5,
269             waitTime: 100,
270           },
2f6253 271         },
272       },
56a966 273       opt || {},
V 274     ),
2f6253 275   );
276 }
277 export const defHttp = createAxios();
278
279 // other api url
280 // export const otherHttp = createAxios({
281 //   requestOptions: {
282 //     apiUrl: 'xxx',
7df9b5 283 //     urlPrefix: 'xxx',
2f6253 284 //   },
285 // });