From 136cbb1e3bc056c88cfa21fca612d3ab72b4d119 Mon Sep 17 00:00:00 2001 From: Captain <49203535+MssText@users.noreply.github.com> Date: 星期六, 19 三月 2022 00:07:34 +0800 Subject: [PATCH] feat: add request retry (#1553) --- src/utils/http/axios/index.ts | 205 +++++++++++++++++++++++++++++++------------------- 1 files changed, 126 insertions(+), 79 deletions(-) diff --git a/src/utils/http/axios/index.ts b/src/utils/http/axios/index.ts index af94a26..8c5c00e 100644 --- a/src/utils/http/axios/index.ts +++ b/src/utils/http/axios/index.ts @@ -2,27 +2,25 @@ // The axios configuration can be changed according to the project, just change the file, other files can be left unchanged import type { AxiosResponse } from 'axios'; -import type { CreateAxiosOptions, RequestOptions, Result } from './types'; +import { clone } from 'lodash-es'; +import type { RequestOptions, Result } from '/#/axios'; +import type { AxiosTransform, CreateAxiosOptions } from './axiosTransform'; import { VAxios } from './Axios'; -import { AxiosTransform } from './axiosTransform'; - import { checkStatus } from './checkStatus'; - import { useGlobSetting } from '/@/hooks/setting'; import { useMessage } from '/@/hooks/web/useMessage'; - import { RequestEnum, ResultEnum, ContentTypeEnum } from '/@/enums/httpEnum'; - import { isString } from '/@/utils/is'; +import { getToken } from '/@/utils/auth'; import { setObjToUrlParams, deepMerge } from '/@/utils'; -import { errorStore } from '/@/store/modules/error'; -import { errorResult } from './const'; +import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; import { useI18n } from '/@/hooks/web/useI18n'; -import { createNow, formatRequestDate } from './helper'; -import { userStore } from '/@/store/modules/user'; +import { joinTimestamp, formatRequestDate } from './helper'; +import { useUserStoreWithOut } from '/@/store/modules/user'; +import { AxiosRetry } from '/@/utils/http/axios/axiosRetry'; const globSetting = useGlobSetting(); -const prefix = globSetting.urlPrefix; +const urlPrefix = globSetting.urlPrefix; const { createMessage, createErrorModal } = useMessage(); /** @@ -30,14 +28,18 @@ */ const transform: AxiosTransform = { /** - * @description: 澶勭悊璇锋眰鏁版嵁 + * @description: 澶勭悊璇锋眰鏁版嵁銆傚鏋滄暟鎹笉鏄鏈熸牸寮忥紝鍙洿鎺ユ姏鍑洪敊璇� */ - transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => { + transformRequestHook: (res: AxiosResponse<Result>, options: RequestOptions) => { const { t } = useI18n(); - const { isTransformRequestResult } = options; + const { isTransformResponse, isReturnNativeResponse } = options; + // 鏄惁杩斿洖鍘熺敓鍝嶅簲澶� 姣斿锛氶渶瑕佽幏鍙栧搷搴斿ご鏃朵娇鐢ㄨ灞炴�� + if (isReturnNativeResponse) { + return res; + } // 涓嶈繘琛屼换浣曞鐞嗭紝鐩存帴杩斿洖 // 鐢ㄤ簬椤甸潰浠g爜鍙兘闇�瑕佺洿鎺ヨ幏鍙朿ode锛宒ata锛宮essage杩欎簺淇℃伅鏃跺紑鍚� - if (!isTransformRequestResult) { + if (!isTransformResponse) { return res.data; } // 閿欒鐨勬椂鍊欒繑鍥� @@ -45,83 +47,83 @@ const { data } = res; if (!data) { // return '[HTTP] Request has no return value'; - return errorResult; + throw new Error(t('sys.api.apiRequestFailed')); } // 杩欓噷 code锛宺esult锛宮essage涓� 鍚庡彴缁熶竴鐨勫瓧娈碉紝闇�瑕佸湪 types.ts鍐呬慨鏀逛负椤圭洰鑷繁鐨勬帴鍙h繑鍥炴牸寮� const { code, result, message } = data; // 杩欓噷閫昏緫鍙互鏍规嵁椤圭洰杩涜淇敼 const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS; - if (!hasSuccess) { - if (message) { - // errorMessageMode=鈥榤odal鈥欑殑鏃跺�欎細鏄剧ずmodal閿欒寮圭獥锛岃�屼笉鏄秷鎭彁绀猴紝鐢ㄤ簬涓�浜涙瘮杈冮噸瑕佺殑閿欒 - if (options.errorMessageMode === 'modal') { - createErrorModal({ title: t('sys.api.errorTip'), content: message }); - } else if (options.errorMessageMode === 'message') { - createMessage.error(message); - } - } - Promise.reject(new Error(message)); - return errorResult; - } - - // 鎺ュ彛璇锋眰鎴愬姛锛岀洿鎺ヨ繑鍥炵粨鏋� - if (code === ResultEnum.SUCCESS) { + if (hasSuccess) { return result; } - // 鎺ュ彛璇锋眰閿欒锛岀粺涓�鎻愮ず閿欒淇℃伅 - if (code === ResultEnum.ERROR) { - if (message) { - createMessage.error(data.message); - Promise.reject(new Error(message)); - } else { - const msg = t('sys.api.errorMessage'); - createMessage.error(msg); - Promise.reject(new Error(msg)); - } - return errorResult; + + // 鍦ㄦ澶勬牴鎹嚜宸遍」鐩殑瀹為檯鎯呭喌瀵逛笉鍚岀殑code鎵ц涓嶅悓鐨勬搷浣� + // 濡傛灉涓嶅笇鏈涗腑鏂綋鍓嶈姹傦紝璇穜eturn鏁版嵁锛屽惁鍒欑洿鎺ユ姏鍑哄紓甯稿嵆鍙� + let timeoutMsg = ''; + switch (code) { + case ResultEnum.TIMEOUT: + timeoutMsg = t('sys.api.timeoutMessage'); + const userStore = useUserStoreWithOut(); + userStore.setToken(undefined); + userStore.logout(true); + break; + default: + if (message) { + timeoutMsg = message; + } } - // 鐧诲綍瓒呮椂 - if (code === ResultEnum.TIMEOUT) { - const timeoutMsg = t('sys.api.timeoutMessage'); - createErrorModal({ - title: t('sys.api.operationFailed'), - content: timeoutMsg, - }); - Promise.reject(new Error(timeoutMsg)); - return errorResult; + + // errorMessageMode=鈥榤odal鈥欑殑鏃跺�欎細鏄剧ずmodal閿欒寮圭獥锛岃�屼笉鏄秷鎭彁绀猴紝鐢ㄤ簬涓�浜涙瘮杈冮噸瑕佺殑閿欒 + // errorMessageMode='none' 涓�鑸槸璋冪敤鏃舵槑纭〃绀轰笉甯屾湜鑷姩寮瑰嚭閿欒鎻愮ず + if (options.errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: timeoutMsg }); + } else if (options.errorMessageMode === 'message') { + createMessage.error(timeoutMsg); } - return errorResult; + + throw new Error(timeoutMsg || t('sys.api.apiRequestFailed')); }, // 璇锋眰涔嬪墠澶勭悊config beforeRequestHook: (config, options) => { - const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true } = options; + const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options; if (joinPrefix) { - config.url = `${prefix}${config.url}`; + config.url = `${urlPrefix}${config.url}`; } if (apiUrl && isString(apiUrl)) { config.url = `${apiUrl}${config.url}`; } const params = config.params || {}; + const data = config.data || false; + formatDate && data && !isString(data) && formatRequestDate(data); if (config.method?.toUpperCase() === RequestEnum.GET) { if (!isString(params)) { // 缁� get 璇锋眰鍔犱笂鏃堕棿鎴冲弬鏁帮紝閬垮厤浠庣紦瀛樹腑鎷挎暟鎹�� - config.params = Object.assign(params || {}, createNow(joinTime, false)); + config.params = Object.assign(params || {}, joinTimestamp(joinTime, false)); } else { // 鍏煎restful椋庢牸 - config.url = config.url + params + `${createNow(joinTime, true)}`; + config.url = config.url + params + `${joinTimestamp(joinTime, true)}`; config.params = undefined; } } else { if (!isString(params)) { formatDate && formatRequestDate(params); - config.data = params; - config.params = undefined; + if (Reflect.has(config, 'data') && config.data && Object.keys(config.data).length > 0) { + config.data = data; + config.params = params; + } else { + // 闈濭ET璇锋眰濡傛灉娌℃湁鎻愪緵data锛屽垯灏唒arams瑙嗕负data + config.data = params; + config.params = undefined; + } if (joinParamsToUrl) { - config.url = setObjToUrlParams(config.url as string, config.data); + config.url = setObjToUrlParams( + config.url as string, + Object.assign({}, config.params, config.data), + ); } } else { // 鍏煎restful椋庢牸 @@ -135,39 +137,67 @@ /** * @description: 璇锋眰鎷︽埅鍣ㄥ鐞� */ - requestInterceptors: (config) => { + requestInterceptors: (config, options) => { // 璇锋眰涔嬪墠澶勭悊config - const token = userStore.getTokenState; - if (token) { + const token = getToken(); + if (token && (config as Recordable)?.requestOptions?.withToken !== false) { // jwt token - config.headers.Authorization = token; + (config as Recordable).headers.Authorization = options.authenticationScheme + ? `${options.authenticationScheme} ${token}` + : token; } return config; }, /** + * @description: 鍝嶅簲鎷︽埅鍣ㄥ鐞� + */ + responseInterceptors: (res: AxiosResponse<any>) => { + return res; + }, + + /** * @description: 鍝嶅簲閿欒澶勭悊 */ - responseInterceptorsCatch: (error: any) => { + responseInterceptorsCatch: (axiosInstance: AxiosResponse, error: any) => { const { t } = useI18n(); - errorStore.setupErrorHandle(error); - const { response, code, message } = error || {}; + const errorLogStore = useErrorLogStoreWithOut(); + errorLogStore.addAjaxErrorInfo(error); + const { response, code, message, config } = error || {}; + const errorMessageMode = config?.requestOptions?.errorMessageMode || 'none'; const msg: string = response?.data?.error?.message ?? ''; const err: string = error?.toString?.() ?? ''; + let errMessage = ''; + try { if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) { - createMessage.error(t('sys.api.apiTimeoutMessage')); + errMessage = t('sys.api.apiTimeoutMessage'); } if (err?.includes('Network Error')) { - createErrorModal({ - title: t('sys.api.networkException'), - content: t('sys.api.networkExceptionMsg'), - }); + errMessage = t('sys.api.networkExceptionMsg'); + } + + if (errMessage) { + if (errorMessageMode === 'modal') { + createErrorModal({ title: t('sys.api.errorTip'), content: errMessage }); + } else if (errorMessageMode === 'message') { + createMessage.error(errMessage); + } + return Promise.reject(error); } } catch (error) { - throw new Error(error); + throw new Error(error as unknown as string); } - checkStatus(error?.response?.status, msg); + + checkStatus(error?.response?.status, msg, errorMessageMode); + + // 娣诲姞鑷姩閲嶈瘯鏈哄埗 淇濋櫓璧疯 鍙拡瀵笹ET璇锋眰 + const retryRequest = new AxiosRetry(); + const { isOpenRetry } = config.requestOptions.retryRequest; + config.method?.toUpperCase() === RequestEnum.GET && + isOpenRetry && + // @ts-ignore + retryRequest.retry(axiosInstance, error); return Promise.reject(error); }, }; @@ -176,22 +206,27 @@ return new VAxios( deepMerge( { + // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes + // authentication schemes锛宔.g: Bearer + // authenticationScheme: 'Bearer', + authenticationScheme: '', timeout: 10 * 1000, // 鍩虹鎺ュ彛鍦板潃 // baseURL: globSetting.apiUrl, - // 鎺ュ彛鍙兘浼氭湁閫氱敤鐨勫湴鍧�閮ㄥ垎锛屽彲浠ョ粺涓�鎶藉彇鍑烘潵 - prefixUrl: prefix, + headers: { 'Content-Type': ContentTypeEnum.JSON }, // 濡傛灉鏄痜orm-data鏍煎紡 // headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED }, // 鏁版嵁澶勭悊鏂瑰紡 - transform, + transform: clone(transform), // 閰嶇疆椤癸紝涓嬮潰鐨勯�夐」閮藉彲浠ュ湪鐙珛鐨勬帴鍙h姹備腑瑕嗙洊 requestOptions: { // 榛樿灏唒refix 娣诲姞鍒皍rl joinPrefix: true, + // 鏄惁杩斿洖鍘熺敓鍝嶅簲澶� 姣斿锛氶渶瑕佽幏鍙栧搷搴斿ご鏃朵娇鐢ㄨ灞炴�� + isReturnNativeResponse: false, // 闇�瑕佸杩斿洖鏁版嵁杩涜澶勭悊 - isTransformRequestResult: true, + isTransformResponse: true, // post璇锋眰鐨勬椂鍊欐坊鍔犲弬鏁板埌url joinParamsToUrl: false, // 鏍煎紡鍖栨彁浜ゅ弬鏁版椂闂� @@ -200,12 +235,23 @@ errorMessageMode: 'message', // 鎺ュ彛鍦板潃 apiUrl: globSetting.apiUrl, + // 鎺ュ彛鎷兼帴鍦板潃 + urlPrefix: urlPrefix, // 鏄惁鍔犲叆鏃堕棿鎴� joinTime: true, + // 蹇界暐閲嶅璇锋眰 + ignoreCancelToken: true, + // 鏄惁鎼哄甫token + withToken: true, + retryRequest: { + isOpenRetry: true, + count: 5, + waitTime: 100, + }, }, }, - opt || {} - ) + opt || {}, + ), ); } export const defHttp = createAxios(); @@ -214,5 +260,6 @@ // export const otherHttp = createAxios({ // requestOptions: { // apiUrl: 'xxx', +// urlPrefix: 'xxx', // }, // }); -- Gitblit v1.8.0