From 6401f0bb5569ca7aff4f2dd18bf18c736cd17595 Mon Sep 17 00:00:00 2001 From: "DESKTOP-31IBRMI\\Administrator" <1509326266@qq.com> Date: Fri, 23 Aug 2024 12:40:25 +0800 Subject: [PATCH] feat(projects): synchronize updates to soybean's axios --- packages/axios/package.json | 4 +--- packages/axios/src/index.ts | 28 +++++++++++++----------- packages/axios/src/options.ts | 4 +++- src/service/request/index.ts | 41 ++++++++++++++++++++--------------- src/service/request/shared.ts | 29 ++++++++++++++++++++++++- src/service/request/type.ts | 6 +++++ 6 files changed, 77 insertions(+), 35 deletions(-) create mode 100644 src/service/request/type.ts diff --git a/packages/axios/package.json b/packages/axios/package.json index 4bb186b..3126f14 100644 --- a/packages/axios/package.json +++ b/packages/axios/package.json @@ -6,9 +6,7 @@ }, "typesVersions": { "*": { - "*": [ - "./src/*" - ] + "*": ["./src/*"] } }, "dependencies": { diff --git a/packages/axios/src/index.ts b/packages/axios/src/index.ts index 08846c2..0415bc3 100644 --- a/packages/axios/src/index.ts +++ b/packages/axios/src/index.ts @@ -1,5 +1,5 @@ import axios, { AxiosError } from 'axios'; -import type { AxiosResponse, CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios'; +import type { AxiosResponse, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios'; import axiosRetry from 'axios-retry'; import { nanoid } from '@sa/utils'; import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options'; @@ -22,7 +22,7 @@ function createCommonRequest( const axiosConf = createAxiosConfig(axiosConfig); const instance = axios.create(axiosConf); - const cancelTokenSourceMap = new Map(); + const abortControllerMap = new Map(); // config axios retry const retryOptions = createRetryOptions(axiosConf); @@ -35,10 +35,12 @@ function createCommonRequest( const requestId = nanoid(); config.headers.set(REQUEST_ID_KEY, requestId); - // config cancel token - const cancelTokenSource = axios.CancelToken.source(); - config.cancelToken = cancelTokenSource.token; - cancelTokenSourceMap.set(requestId, cancelTokenSource); + // config abort controller + if (!config.signal) { + const abortController = new AbortController(); + config.signal = abortController.signal; + abortControllerMap.set(requestId, abortController); + } // handle config by hook const handledConfig = opts.onRequest?.(config) || config; @@ -79,18 +81,18 @@ function createCommonRequest( ); function cancelRequest(requestId: string) { - const cancelTokenSource = cancelTokenSourceMap.get(requestId); - if (cancelTokenSource) { - cancelTokenSource.cancel(); - cancelTokenSourceMap.delete(requestId); + const abortController = abortControllerMap.get(requestId); + if (abortController) { + abortController.abort(); + abortControllerMap.delete(requestId); } } function cancelAllRequest() { - cancelTokenSourceMap.forEach(cancelTokenSource => { - cancelTokenSource.cancel(); + abortControllerMap.forEach(abortController => { + abortController.abort(); }); - cancelTokenSourceMap.clear(); + abortControllerMap.clear(); } return { diff --git a/packages/axios/src/options.ts b/packages/axios/src/options.ts index ff59fa7..b170c0e 100644 --- a/packages/axios/src/options.ts +++ b/packages/axios/src/options.ts @@ -20,7 +20,9 @@ export function createDefaultOptions(options?: Partial) { const retryConfig: IAxiosRetryConfig = { - retries: 3 + retries: 0, + shouldResetTimeout: true, + retryDelay: () => 1000 }; Object.assign(retryConfig, config); diff --git a/src/service/request/index.ts b/src/service/request/index.ts index ad8a9c7..e5e01bb 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -1,18 +1,17 @@ import type { AxiosResponse } from 'axios'; import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'; - +import { resetStore } from '@/store/slice/auth'; +import { store } from '@/store'; import { $t } from '@/locales'; import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; -import { handleRefreshToken } from './shared'; +import { handleRefreshToken, showErrorMsg } from './shared'; +import type { RequestInstanceState } from './type'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); -interface InstanceState { - /** whether the request is refreshing token */ - isRefreshingToken: boolean; -} -export const request = createFlatRequest( + +export const request = createFlatRequest( { baseURL, headers: { @@ -36,35 +35,44 @@ export const request = createFlatRequest( return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE; }, async onBackendFail(response, instance) { - function handleLogout() {} + const responseCode = String(response.data.code); + + function handleLogout() { + store.dispatch(resetStore()); + } function logoutAndCleanup() { handleLogout(); window.removeEventListener('beforeunload', handleLogout); + + request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg); } // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; - if (logoutCodes.includes(response.data.code)) { + if (logoutCodes.includes(responseCode)) { handleLogout(); return null; } // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; - if (modalLogoutCodes.includes(response.data.code)) { + if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) { + request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg]; + // prevent the user from refreshing the page window.addEventListener('beforeunload', handleLogout); window.$modal?.error({ - title: 'Error', - content: response.data.code, + title: $t('common.error'), + content: response.data.msg, okText: $t('common.confirm'), maskClosable: false, + keyboard: false, onOk() { logoutAndCleanup(); }, - onCancel() { + onClose() { logoutAndCleanup(); } }); @@ -75,7 +83,7 @@ export const request = createFlatRequest( // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes` const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; - if (expiredTokenCodes.includes(response.data.code) && !request.state.isRefreshingToken) { + if (expiredTokenCodes.includes(responseCode) && !request.state.isRefreshingToken) { request.state.isRefreshingToken = true; const refreshConfig = await handleRefreshToken(response.config); @@ -101,7 +109,7 @@ export const request = createFlatRequest( // get backend error message and code if (error.code === BACKEND_ERROR_CODE) { message = error.response?.data?.msg || message; - backendErrorCode = error.response?.data?.code || ''; + backendErrorCode = String(error.response?.data?.code || ''); } // the error message is displayed in the modal @@ -116,7 +124,7 @@ export const request = createFlatRequest( return; } - window.$message?.error?.(message); + showErrorMsg(request.state, message); } } ); @@ -150,7 +158,6 @@ export const demoRequest = createRequest( }, onError(error) { // when the request is fail, you can show error message - console.log(error); let message = error.message; diff --git a/src/service/request/shared.ts b/src/service/request/shared.ts index db5067d..b8d139f 100644 --- a/src/service/request/shared.ts +++ b/src/service/request/shared.ts @@ -1,7 +1,9 @@ import type { AxiosRequestConfig } from 'axios'; - +import { resetStore } from '@/store/slice/auth'; +import { store } from '@/store'; import { localStg } from '@/utils/storage'; import { fetchRefreshToken } from '../api'; +import type { RequestInstanceState } from './type'; /** * refresh token @@ -23,5 +25,30 @@ export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) { return config; } + store.dispatch(resetStore()); + return null; } + +export function showErrorMsg(state: RequestInstanceState, message: string) { + if (!state.errMsgStack?.length) { + state.errMsgStack = []; + } + + const isExist = state.errMsgStack.includes(message); + + if (!isExist) { + state.errMsgStack.push(message); + + window.$message?.error({ + content: message, + onClose: () => { + state.errMsgStack = state.errMsgStack.filter(msg => msg !== message); + + setTimeout(() => { + state.errMsgStack = []; + }, 5000); + } + }); + } +} diff --git a/src/service/request/type.ts b/src/service/request/type.ts new file mode 100644 index 0000000..b2a5f9c --- /dev/null +++ b/src/service/request/type.ts @@ -0,0 +1,6 @@ +export interface RequestInstanceState { + /** whether the request is refreshing token */ + isRefreshingToken: boolean; + /** the request error message stack */ + errMsgStack: string[]; +}