fix: auto repair bugs #50 #2
Binary file not shown.
@ -1,5 +1,5 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
import type { AxiosInstance, AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
// API 基础配置
|
// 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 实例
|
// 创建 Axios 实例
|
||||||
const request: AxiosInstance = axios.create({
|
const request: AxiosInstance = axios.create({
|
||||||
baseURL: BASE_URL,
|
baseURL: BASE_URL,
|
||||||
@ -95,12 +154,9 @@ request.interceptors.response.use(
|
|||||||
const data = response.data;
|
const data = response.data;
|
||||||
// 业务错误处理
|
// 业务错误处理
|
||||||
if (data.code !== 0) {
|
if (data.code !== 0) {
|
||||||
// Token 过期
|
// Token 过期(业务错误码)
|
||||||
if (data.code === 401 || data.code === 1001) {
|
if (data.code === 401 || data.code === 1001) {
|
||||||
localStorage.removeItem('admin_token');
|
clearAuthAndRedirect();
|
||||||
localStorage.removeItem('admin_info');
|
|
||||||
localStorage.removeItem('auth-storage');
|
|
||||||
window.location.href = '/login';
|
|
||||||
return Promise.reject(new Error(data.message || '登录已过期'));
|
return Promise.reject(new Error(data.message || '登录已过期'));
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(data.message || '请求失败'));
|
return Promise.reject(new Error(data.message || '请求失败'));
|
||||||
@ -108,23 +164,64 @@ request.interceptors.response.use(
|
|||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
(error: AxiosError<{ code: number; message: string }>) => {
|
(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) {
|
if (error.response) {
|
||||||
const { status, data } = error.response;
|
const { status, data } = error.response;
|
||||||
if (status === 401) {
|
const originalConfig = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
|
||||||
localStorage.removeItem('admin_token');
|
|
||||||
localStorage.removeItem('admin_info');
|
// 401: 尝试用 refresh token 刷新
|
||||||
localStorage.removeItem('auth-storage');
|
if (status === 401 && originalConfig && !originalConfig._retry) {
|
||||||
window.location.href = '/login';
|
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(data?.message || `请求失败 (${status})`));
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error('网络错误,请检查网络连接'));
|
return Promise.reject(new Error('网络错误,请检查网络连接'));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user