Commit 136cbb1e3bc056c88cfa21fca612d3ab72b4d119

Authored by Captain
Committed by GitHub
1 parent 78535bdd

feat: add request retry (#1553)

mock/sys/user.ts
@@ -111,4 +111,12 @@ export default [ @@ -111,4 +111,12 @@ export default [
111 return resultSuccess(undefined, { message: 'Token has been destroyed' }); 111 return resultSuccess(undefined, { message: 'Token has been destroyed' });
112 }, 112 },
113 }, 113 },
  114 + {
  115 + url: '/basic-api/testRetry',
  116 + statusCode: 405,
  117 + method: 'get',
  118 + response: () => {
  119 + return resultError('Error!');
  120 + },
  121 + },
114 ] as MockMethod[]; 122 ] as MockMethod[];
src/api/sys/user.ts
@@ -8,6 +8,7 @@ enum Api { @@ -8,6 +8,7 @@ enum Api {
8 Logout = '/logout', 8 Logout = '/logout',
9 GetUserInfo = '/getUserInfo', 9 GetUserInfo = '/getUserInfo',
10 GetPermCode = '/getPermCode', 10 GetPermCode = '/getPermCode',
  11 + TestRetry = '/testRetry',
11 } 12 }
12 13
13 /** 14 /**
@@ -39,3 +40,16 @@ export function getPermCode() { @@ -39,3 +40,16 @@ export function getPermCode() {
39 export function doLogout() { 40 export function doLogout() {
40 return defHttp.get({ url: Api.Logout }); 41 return defHttp.get({ url: Api.Logout });
41 } 42 }
  43 +
  44 +export function testRetry() {
  45 + return defHttp.get(
  46 + { url: Api.TestRetry },
  47 + {
  48 + retryRequest: {
  49 + isOpenRetry: true,
  50 + count: 5,
  51 + waitTime: 1000,
  52 + },
  53 + },
  54 + );
  55 +}
src/locales/lang/en/routes/demo.ts
@@ -92,6 +92,7 @@ export default { @@ -92,6 +92,7 @@ export default {
92 breadcrumb: 'Breadcrumbs', 92 breadcrumb: 'Breadcrumbs',
93 breadcrumbFlat: 'Flat Mode', 93 breadcrumbFlat: 'Flat Mode',
94 breadcrumbFlatDetail: 'Flat mode details', 94 breadcrumbFlatDetail: 'Flat mode details',
  95 + requestDemo: 'Retry request demo',
95 96
96 breadcrumbChildren: 'Level mode', 97 breadcrumbChildren: 'Level mode',
97 breadcrumbChildrenDetail: 'Level mode detail', 98 breadcrumbChildrenDetail: 'Level mode detail',
src/locales/lang/zh-CN/routes/demo.ts
@@ -88,6 +88,7 @@ export default { @@ -88,6 +88,7 @@ export default {
88 ws: 'websocket测试', 88 ws: 'websocket测试',
89 breadcrumb: '面包屑导航', 89 breadcrumb: '面包屑导航',
90 breadcrumbFlat: '平级模式', 90 breadcrumbFlat: '平级模式',
  91 + requestDemo: '测试请求重试',
91 breadcrumbFlatDetail: '平级详情', 92 breadcrumbFlatDetail: '平级详情',
92 breadcrumbChildren: '层级模式', 93 breadcrumbChildren: '层级模式',
93 breadcrumbChildrenDetail: '层级详情', 94 breadcrumbChildrenDetail: '层级详情',
src/router/routes/modules/demo/feat.ts
@@ -32,6 +32,15 @@ const feat: AppRouteModule = { @@ -32,6 +32,15 @@ const feat: AppRouteModule = {
32 }, 32 },
33 }, 33 },
34 { 34 {
  35 + path: 'request',
  36 + name: 'RequestDemo',
  37 + // @ts-ignore
  38 + component: () => import('/@/views/demo/feat/request-demo/index.vue'),
  39 + meta: {
  40 + title: t('routes.demo.feat.requestDemo'),
  41 + },
  42 + },
  43 + {
35 path: 'session-timeout', 44 path: 'session-timeout',
36 name: 'SessionTimeout', 45 name: 'SessionTimeout',
37 component: () => import('/@/views/demo/feat/session-timeout/index.vue'), 46 component: () => import('/@/views/demo/feat/session-timeout/index.vue'),
src/utils/http/axios/Axios.ts
@@ -111,7 +111,10 @@ export class VAxios { @@ -111,7 +111,10 @@ export class VAxios {
111 // Response result interceptor error capture 111 // Response result interceptor error capture
112 responseInterceptorsCatch && 112 responseInterceptorsCatch &&
113 isFunction(responseInterceptorsCatch) && 113 isFunction(responseInterceptorsCatch) &&
114 - this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch); 114 + this.axiosInstance.interceptors.response.use(undefined, (error) => {
  115 + // @ts-ignore
  116 + responseInterceptorsCatch(this.axiosInstance, error);
  117 + });
115 } 118 }
116 119
117 /** 120 /**
src/utils/http/axios/axiosRetry.ts 0 → 100644
  1 +import { AxiosError, AxiosInstance } from 'axios';
  2 +/**
  3 + * 请求重试机制
  4 + */
  5 +
  6 +export class AxiosRetry {
  7 + /**
  8 + * 重试
  9 + */
  10 + retry(AxiosInstance: AxiosInstance, error: AxiosError) {
  11 + // @ts-ignore
  12 + const { config } = error.response;
  13 + const { waitTime, count } = config?.requestOptions?.retryRequest;
  14 + config.__retryCount = config.__retryCount || 0;
  15 + if (config.__retryCount >= count) {
  16 + return Promise.reject(error);
  17 + }
  18 + config.__retryCount += 1;
  19 + return this.delay(waitTime).then(() => AxiosInstance(config));
  20 + }
  21 +
  22 + /**
  23 + * 延迟
  24 + */
  25 + private delay(waitTime: number) {
  26 + return new Promise((resolve) => setTimeout(resolve, waitTime));
  27 + }
  28 +}
src/utils/http/axios/axiosTransform.ts
@@ -48,5 +48,5 @@ export abstract class AxiosTransform { @@ -48,5 +48,5 @@ export abstract class AxiosTransform {
48 /** 48 /**
49 * @description: 请求之后的拦截器错误处理 49 * @description: 请求之后的拦截器错误处理
50 */ 50 */
51 - responseInterceptorsCatch?: (error: Error) => void; 51 + responseInterceptorsCatch?: (axiosInstance: AxiosResponse, error: Error) => void;
52 } 52 }
src/utils/http/axios/index.ts
@@ -17,6 +17,7 @@ import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; @@ -17,6 +17,7 @@ import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
17 import { useI18n } from '/@/hooks/web/useI18n'; 17 import { useI18n } from '/@/hooks/web/useI18n';
18 import { joinTimestamp, formatRequestDate } from './helper'; 18 import { joinTimestamp, formatRequestDate } from './helper';
19 import { useUserStoreWithOut } from '/@/store/modules/user'; 19 import { useUserStoreWithOut } from '/@/store/modules/user';
  20 +import { AxiosRetry } from '/@/utils/http/axios/axiosRetry';
20 21
21 const globSetting = useGlobSetting(); 22 const globSetting = useGlobSetting();
22 const urlPrefix = globSetting.urlPrefix; 23 const urlPrefix = globSetting.urlPrefix;
@@ -158,7 +159,7 @@ const transform: AxiosTransform = { @@ -158,7 +159,7 @@ const transform: AxiosTransform = {
158 /** 159 /**
159 * @description: 响应错误处理 160 * @description: 响应错误处理
160 */ 161 */
161 - responseInterceptorsCatch: (error: any) => { 162 + responseInterceptorsCatch: (axiosInstance: AxiosResponse, error: any) => {
162 const { t } = useI18n(); 163 const { t } = useI18n();
163 const errorLogStore = useErrorLogStoreWithOut(); 164 const errorLogStore = useErrorLogStoreWithOut();
164 errorLogStore.addAjaxErrorInfo(error); 165 errorLogStore.addAjaxErrorInfo(error);
@@ -189,6 +190,14 @@ const transform: AxiosTransform = { @@ -189,6 +190,14 @@ const transform: AxiosTransform = {
189 } 190 }
190 191
191 checkStatus(error?.response?.status, msg, errorMessageMode); 192 checkStatus(error?.response?.status, msg, errorMessageMode);
  193 +
  194 + // 添加自动重试机制 保险起见 只针对GET请求
  195 + const retryRequest = new AxiosRetry();
  196 + const { isOpenRetry } = config.requestOptions.retryRequest;
  197 + config.method?.toUpperCase() === RequestEnum.GET &&
  198 + isOpenRetry &&
  199 + // @ts-ignore
  200 + retryRequest.retry(axiosInstance, error);
192 return Promise.reject(error); 201 return Promise.reject(error);
193 }, 202 },
194 }; 203 };
@@ -234,6 +243,11 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) { @@ -234,6 +243,11 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
234 ignoreCancelToken: true, 243 ignoreCancelToken: true,
235 // 是否携带token 244 // 是否携带token
236 withToken: true, 245 withToken: true,
  246 + retryRequest: {
  247 + isOpenRetry: true,
  248 + count: 5,
  249 + waitTime: 100,
  250 + },
237 }, 251 },
238 }, 252 },
239 opt || {}, 253 opt || {},
src/views/demo/feat/request-demo/index.vue 0 → 100644
  1 +<template>
  2 + <div class="request-box">
  3 + <a-button @click="handleClick" color="primary"> 点击会重新发起请求5次 </a-button>
  4 + <p>打开浏览器的network面板,可以看到发出了六次请求</p>
  5 + </div>
  6 +</template>
  7 +<script lang="ts" setup>
  8 + import { testRetry } from '/@/api/sys/user';
  9 + // @ts-ignore
  10 + const handleClick = async () => {
  11 + await testRetry();
  12 + };
  13 +</script>
  14 +
  15 +<style lang="less">
  16 + .request-box {
  17 + margin: 50px;
  18 + }
  19 +
  20 + p {
  21 + margin-top: 10px;
  22 + }
  23 +</style>
types/axios.d.ts
@@ -23,8 +23,15 @@ export interface RequestOptions { @@ -23,8 +23,15 @@ export interface RequestOptions {
23 ignoreCancelToken?: boolean; 23 ignoreCancelToken?: boolean;
24 // Whether to send token in header 24 // Whether to send token in header
25 withToken?: boolean; 25 withToken?: boolean;
  26 + // 请求重试机制
  27 + retryRequest?: RetryRequest;
26 } 28 }
27 29
  30 +export interface RetryRequest {
  31 + isOpenRetry: boolean;
  32 + count: number;
  33 + waitTime: number;
  34 +}
28 export interface Result<T = any> { 35 export interface Result<T = any> {
29 code: number; 36 code: number;
30 type: 'success' | 'error' | 'warning'; 37 type: 'success' | 'error' | 'warning';