From 1e3babee0c9b60075f0fb4d0348bfdede3cd9f93 Mon Sep 17 00:00:00 2001 From: repair-agent Date: Mon, 9 Mar 2026 10:10:03 +0800 Subject: [PATCH] fix: auto repair bugs #50 --- ...r_test_bug_50.cpython-313-pytest-9.0.2.pyc | Bin 7595 -> 8546 bytes src/api/request.ts | 137 +++++++++++++++--- 2 files changed, 117 insertions(+), 20 deletions(-) diff --git a/__pycache__/repair_test_bug_50.cpython-313-pytest-9.0.2.pyc b/__pycache__/repair_test_bug_50.cpython-313-pytest-9.0.2.pyc index 2a08ea550aaa3f83f59a3bf20784e5cbd092e234..3cd5e94b0212117719542003a57ace05fd3b72b3 100644 GIT binary patch literal 8546 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 aWZ|qDtMOB729!U|omI2k_~~*Jl>Z0(lb?hD literal 7595 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 diff --git a/src/api/request.ts b/src/api/request.ts index 59aca11..1fde7e4 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -1,5 +1,5 @@ import axios from 'axios'; -import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios'; +import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios'; // API 基础配置 // 开发环境使用代理,生产环境使用环境变量 @@ -62,6 +62,65 @@ function reportToLogCenter(error: Error, context?: Record) { } } +// Token 自动刷新状态 +let isRefreshing = false; +let failedQueue: Array<{ + resolve: (value: AxiosResponse) => void; + reject: (reason?: unknown) => void; + config: InternalAxiosRequestConfig; +}> = []; + +function getRefreshToken(): string | null { + try { + const authStorage = localStorage.getItem('auth-storage'); + if (authStorage) { + const parsed = JSON.parse(authStorage); + return parsed?.state?.refreshToken || null; + } + } catch { + // ignore + } + return null; +} + +function processQueue(error: Error | null, token: string | null = null) { + failedQueue.forEach(({ resolve, reject, config }) => { + if (error) { + reject(error); + } else if (token) { + config.headers.Authorization = `Bearer ${token}`; + resolve(request(config)); + } + }); + failedQueue = []; +} + +function updateStoredTokens(access: string, refresh?: string) { + localStorage.setItem('admin_token', access); + try { + const authStorage = localStorage.getItem('auth-storage'); + if (authStorage) { + const parsed = JSON.parse(authStorage); + if (parsed?.state) { + parsed.state.token = access; + if (refresh) { + parsed.state.refreshToken = refresh; + } + localStorage.setItem('auth-storage', JSON.stringify(parsed)); + } + } + } catch { + // ignore + } +} + +function clearAuthAndRedirect() { + localStorage.removeItem('admin_token'); + localStorage.removeItem('admin_info'); + localStorage.removeItem('auth-storage'); + window.location.href = '/login'; +} + // 创建 Axios 实例 const request: AxiosInstance = axios.create({ baseURL: BASE_URL, @@ -95,12 +154,9 @@ request.interceptors.response.use( const data = response.data; // 业务错误处理 if (data.code !== 0) { - // Token 过期 + // Token 过期(业务错误码) if (data.code === 401 || data.code === 1001) { - localStorage.removeItem('admin_token'); - localStorage.removeItem('admin_info'); - localStorage.removeItem('auth-storage'); - window.location.href = '/login'; + clearAuthAndRedirect(); return Promise.reject(new Error(data.message || '登录已过期')); } return Promise.reject(new Error(data.message || '请求失败')); @@ -108,23 +164,64 @@ request.interceptors.response.use( return data; }, (error: AxiosError<{ code: number; message: string }>) => { - // 上报到 Log Center - const apiError = new Error(error.message); - reportToLogCenter(apiError, { - url: error.config?.url, - method: error.config?.method, - status: error.response?.status, - responseData: error.response?.data, - }); - if (error.response) { const { status, data } = error.response; - if (status === 401) { - localStorage.removeItem('admin_token'); - localStorage.removeItem('admin_info'); - localStorage.removeItem('auth-storage'); - window.location.href = '/login'; + const originalConfig = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; + + // 401: 尝试用 refresh token 刷新 + if (status === 401 && originalConfig && !originalConfig._retry) { + const refreshTokenValue = getRefreshToken(); + + if (!refreshTokenValue) { + clearAuthAndRedirect(); + return Promise.reject(new Error(data?.message || '登录已过期')); + } + + if (isRefreshing) { + // 已在刷新中,排队等待 + return new Promise((resolve, reject) => { + failedQueue.push({ resolve, reject, config: originalConfig }); + }); + } + + originalConfig._retry = true; + isRefreshing = true; + + return axios + .post( + `${request.defaults.baseURL || ''}/api/admin/auth/refresh/`, + { refresh: refreshTokenValue }, + { headers: { 'Content-Type': 'application/json' } } + ) + .then((res) => { + const tokenData = res.data.data || res.data; + const newAccess: string = tokenData.access; + const newRefresh: string | undefined = tokenData.refresh; + + updateStoredTokens(newAccess, newRefresh); + isRefreshing = false; + processQueue(null, newAccess); + + originalConfig.headers.Authorization = `Bearer ${newAccess}`; + return request(originalConfig); + }) + .catch((refreshError) => { + isRefreshing = false; + processQueue(refreshError instanceof Error ? refreshError : new Error('刷新令牌失败')); + clearAuthAndRedirect(); + return Promise.reject(new Error('登录已过期,请重新登录')); + }); } + + // 非 401 错误上报到 Log Center + const apiError = new Error(error.message); + reportToLogCenter(apiError, { + url: error.config?.url, + method: error.config?.method, + status: error.response?.status, + responseData: error.response?.data, + }); + return Promise.reject(new Error(data?.message || `请求失败 (${status})`)); } return Promise.reject(new Error('网络错误,请检查网络连接')); -- 2.47.2