From 8f5ffcf521a7080bddc2075df966222ec8397147 Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Tue, 3 Mar 2026 22:19:48 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E6=8B=A6=E6=88=AA=E5=99=A8=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/request.ts | 107 +++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 69 deletions(-) diff --git a/src/utils/request.ts b/src/utils/request.ts index 7fe7e974..63fcdc18 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -5,10 +5,10 @@ import { useUserStoreHook } from "@/store/modules/user"; import { usePermissionStoreHook } from "@/store/modules/permission"; import { AuthStorage, redirectToLogin } from "@/utils/auth"; -// ============================================ -// HTTP 请求实例 -// ============================================ +// 记录已重试的请求,防止无限循环 +const retriedConfigs = new WeakSet(); +// HTTP 请求实例 const http = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, timeout: 50000, @@ -16,10 +16,7 @@ const http = axios.create({ paramsSerializer: (params) => qs.stringify(params, { arrayFormat: "repeat" }), }); -// ============================================ // 请求拦截器 -// ============================================ - http.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const token = AuthStorage.getAccessToken(); @@ -35,14 +32,12 @@ http.interceptors.request.use( (error) => Promise.reject(error) ); -// ============================================ // 响应拦截器 -// ============================================ - http.interceptors.response.use( (response: AxiosResponse) => { - // 二进制数据直接返回 const { responseType } = response.config; + + // 二进制数据直接返回 if (responseType === "blob" || responseType === "arraybuffer") { return response; } @@ -52,7 +47,9 @@ http.interceptors.response.use( if (code === ApiCodeEnum.SUCCESS) { return data; } - return rejectWithMessage(msg, "系统出错"); + + ElMessage.error(msg || "系统出错"); + return Promise.reject(new Error(msg || "系统出错")); }, async (error) => { @@ -67,7 +64,29 @@ http.interceptors.response.use( // Token 过期:尝试刷新 token 后自动重试一次 if (code === ApiCodeEnum.ACCESS_TOKEN_INVALID) { - return retryWithRefresh(config); + // 已重试过,直接跳登录 + if (retriedConfigs.has(config)) { + await redirectToLogin("登录已过期,请重新登录"); + return Promise.reject(new Error("Token Invalid")); + } + + retriedConfigs.add(config); + + try { + const userStore = useUserStoreHook(); + await userStore.refreshTokenOnce(); + + const token = AuthStorage.getAccessToken(); + if (token) { + config.headers = config.headers || ({} as any); + (config.headers as any).Authorization = `Bearer ${token}`; + } + + return http(config); + } catch { + await redirectToLogin("登录已过期,请重新登录"); + return Promise.reject(new Error("Token refresh failed")); + } } // Refresh token 失效:无法续期,跳转登录 @@ -76,67 +95,17 @@ http.interceptors.response.use( return Promise.reject(new Error(msg || "Token Invalid")); } - // 权限不足:刷新权限快照(用户信息 + 动态路由)后提示 + // 权限不足:刷新权限快照后提示 if (code === ApiCodeEnum.PERMISSION_DENIED) { - return handlePermissionDenied(msg); + const permissionStore = usePermissionStoreHook(); + await permissionStore.reloadPermissionSnapshotOnce(); + ElMessage.error(msg || "权限不足"); + return Promise.reject(new Error(msg || "权限不足")); } - return rejectWithMessage(msg, "请求失败"); + ElMessage.error(msg || "请求失败"); + return Promise.reject(new Error(msg || "请求失败")); } ); -/** - * 权限不足处理:刷新权限快照(用户信息 + 动态路由),并给出提示 - * - * 刷新完成后仍然按失败处理,交由调用方的错误流处理 - */ -async function handlePermissionDenied(msg?: string): Promise { - const permissionStore = usePermissionStoreHook(); - await permissionStore.reloadPermissionSnapshotOnce(); - return rejectWithMessage(msg, "权限不足"); -} - -/** - * access token 过期后的自动续期与重试 - * - * - 刷新 token 走单飞(userStore.refreshTokenOnce) - * - 当前请求最多重试一次(__isTokenRetry 标记) - */ -async function retryWithRefresh(config: InternalAxiosRequestConfig): Promise { - const retryConfig = config as InternalAxiosRequestConfig & { __isTokenRetry?: boolean }; - if (retryConfig.__isTokenRetry) { - await redirectToLogin("登录已过期,请重新登录"); - return Promise.reject(new Error("Token Invalid")); - } - retryConfig.__isTokenRetry = true; - - try { - const userStore = useUserStoreHook(); - await userStore.refreshTokenOnce(); - - const token = AuthStorage.getAccessToken(); - if (token) { - retryConfig.headers = retryConfig.headers || ({} as any); - (retryConfig.headers as any).Authorization = `Bearer ${token}`; - } - - return http(retryConfig); - } catch { - await redirectToLogin("登录已过期,请重新登录"); - return Promise.reject(new Error("Token refresh failed")); - } -} - -/** - * 统一处理业务错误提示并拒绝 Promise - * - * @param msg 错误消息内容 - * @param fallback 默认兜底消息 - */ -function rejectWithMessage(msg: string | undefined, fallback: string): Promise { - const message = msg || fallback; - ElMessage.error(message); - return Promise.reject(new Error(message)); -} - export default http;