Captain
2022-03-19 136cbb1e3bc056c88cfa21fca612d3ab72b4d119
feat: add request retry (#1553)

2个文件已添加
9个文件已修改
114 ■■■■■ 已修改文件
mock/sys/user.ts 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/sys/user.ts 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/lang/en/routes/demo.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/locales/lang/zh-CN/routes/demo.ts 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/routes/modules/demo/feat.ts 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/http/axios/Axios.ts 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/http/axios/axiosRetry.ts 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/http/axios/axiosTransform.ts 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/http/axios/index.ts 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/demo/feat/request-demo/index.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
types/axios.d.ts 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
mock/sys/user.ts
@@ -111,4 +111,12 @@
      return resultSuccess(undefined, { message: 'Token has been destroyed' });
    },
  },
  {
    url: '/basic-api/testRetry',
    statusCode: 405,
    method: 'get',
    response: () => {
      return resultError('Error!');
    },
  },
] as MockMethod[];
src/api/sys/user.ts
@@ -8,6 +8,7 @@
  Logout = '/logout',
  GetUserInfo = '/getUserInfo',
  GetPermCode = '/getPermCode',
  TestRetry = '/testRetry',
}
/**
@@ -39,3 +40,16 @@
export function doLogout() {
  return defHttp.get({ url: Api.Logout });
}
export function testRetry() {
  return defHttp.get(
    { url: Api.TestRetry },
    {
      retryRequest: {
        isOpenRetry: true,
        count: 5,
        waitTime: 1000,
      },
    },
  );
}
src/locales/lang/en/routes/demo.ts
@@ -92,6 +92,7 @@
    breadcrumb: 'Breadcrumbs',
    breadcrumbFlat: 'Flat Mode',
    breadcrumbFlatDetail: 'Flat mode details',
    requestDemo: 'Retry request demo',
    breadcrumbChildren: 'Level mode',
    breadcrumbChildrenDetail: 'Level mode detail',
src/locales/lang/zh-CN/routes/demo.ts
@@ -88,6 +88,7 @@
    ws: 'websocket测试',
    breadcrumb: '面包屑导航',
    breadcrumbFlat: '平级模式',
    requestDemo: '测试请求重试',
    breadcrumbFlatDetail: '平级详情',
    breadcrumbChildren: '层级模式',
    breadcrumbChildrenDetail: '层级详情',
src/router/routes/modules/demo/feat.ts
@@ -32,6 +32,15 @@
      },
    },
    {
      path: 'request',
      name: 'RequestDemo',
      // @ts-ignore
      component: () => import('/@/views/demo/feat/request-demo/index.vue'),
      meta: {
        title: t('routes.demo.feat.requestDemo'),
      },
    },
    {
      path: 'session-timeout',
      name: 'SessionTimeout',
      component: () => import('/@/views/demo/feat/session-timeout/index.vue'),
src/utils/http/axios/Axios.ts
@@ -111,7 +111,10 @@
    // Response result interceptor error capture
    responseInterceptorsCatch &&
      isFunction(responseInterceptorsCatch) &&
      this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
      this.axiosInstance.interceptors.response.use(undefined, (error) => {
        // @ts-ignore
        responseInterceptorsCatch(this.axiosInstance, error);
      });
  }
  /**
src/utils/http/axios/axiosRetry.ts
New file
@@ -0,0 +1,28 @@
import { AxiosError, AxiosInstance } from 'axios';
/**
 *  请求重试机制
 */
export class AxiosRetry {
  /**
   * 重试
   */
  retry(AxiosInstance: AxiosInstance, error: AxiosError) {
    // @ts-ignore
    const { config } = error.response;
    const { waitTime, count } = config?.requestOptions?.retryRequest;
    config.__retryCount = config.__retryCount || 0;
    if (config.__retryCount >= count) {
      return Promise.reject(error);
    }
    config.__retryCount += 1;
    return this.delay(waitTime).then(() => AxiosInstance(config));
  }
  /**
   * 延迟
   */
  private delay(waitTime: number) {
    return new Promise((resolve) => setTimeout(resolve, waitTime));
  }
}
src/utils/http/axios/axiosTransform.ts
@@ -48,5 +48,5 @@
  /**
   * @description: 请求之后的拦截器错误处理
   */
  responseInterceptorsCatch?: (error: Error) => void;
  responseInterceptorsCatch?: (axiosInstance: AxiosResponse, error: Error) => void;
}
src/utils/http/axios/index.ts
@@ -17,6 +17,7 @@
import { useI18n } from '/@/hooks/web/useI18n';
import { joinTimestamp, formatRequestDate } from './helper';
import { useUserStoreWithOut } from '/@/store/modules/user';
import { AxiosRetry } from '/@/utils/http/axios/axiosRetry';
const globSetting = useGlobSetting();
const urlPrefix = globSetting.urlPrefix;
@@ -158,7 +159,7 @@
  /**
   * @description: 响应错误处理
   */
  responseInterceptorsCatch: (error: any) => {
  responseInterceptorsCatch: (axiosInstance: AxiosResponse, error: any) => {
    const { t } = useI18n();
    const errorLogStore = useErrorLogStoreWithOut();
    errorLogStore.addAjaxErrorInfo(error);
@@ -189,6 +190,14 @@
    }
    checkStatus(error?.response?.status, msg, errorMessageMode);
    // 添加自动重试机制 保险起见 只针对GET请求
    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);
  },
};
@@ -234,6 +243,11 @@
          ignoreCancelToken: true,
          // 是否携带token
          withToken: true,
          retryRequest: {
            isOpenRetry: true,
            count: 5,
            waitTime: 100,
          },
        },
      },
      opt || {},
src/views/demo/feat/request-demo/index.vue
New file
@@ -0,0 +1,23 @@
<template>
  <div class="request-box">
    <a-button @click="handleClick" color="primary"> 点击会重新发起请求5次 </a-button>
    <p>打开浏览器的network面板,可以看到发出了六次请求</p>
  </div>
</template>
<script lang="ts" setup>
  import { testRetry } from '/@/api/sys/user';
  // @ts-ignore
  const handleClick = async () => {
    await testRetry();
  };
</script>
<style lang="less">
  .request-box {
    margin: 50px;
  }
  p {
    margin-top: 10px;
  }
</style>
types/axios.d.ts
@@ -23,8 +23,15 @@
  ignoreCancelToken?: boolean;
  // Whether to send token in header
  withToken?: boolean;
  // 请求重试机制
  retryRequest?: RetryRequest;
}
export interface RetryRequest {
  isOpenRetry: boolean;
  count: number;
  waitTime: number;
}
export interface Result<T = any> {
  code: number;
  type: 'success' | 'error' | 'warning';