fix: auto repair bugs #50
This commit is contained in:
parent
6b6d4645dd
commit
1e3babee0c
Binary file not shown.
@ -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<string, unknown>) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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<AxiosResponse>((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('网络错误,请检查网络连接'));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user