Commit 136cbb1e3bc056c88cfa21fca612d3ab72b4d119
Committed by
GitHub
1 parent
78535bdd
feat: add request retry (#1553)
Showing
11 changed files
with
111 additions
and
3 deletions
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'; |