/** * API 请求拦截器 * 功能: * 1. 为除登录接口外的所有请求添加身份信息 * 2. 统一错误处理 * 3. 请求/响应日志记录 * 4. 自动刷新token */ import axios from 'axios'; import { toast } from 'sonner'; /** * 创建API实例并配置拦截器 */ export const createAPI = ({ baseURL, timeout = 10000, enableLogging = false }) => { // 创建axios实例 const api = axios.create({ baseURL, timeout, headers: { 'Content-Type': 'application/json', }, }); // 获取存储的token和用户信息 const getAuthData = () => { try { const authData = localStorage.getItem('authData'); if (authData) { const { token, user } = JSON.parse(authData); return { token, user_id: user?.id }; } } catch (error) { console.warn('Failed to parse auth data from localStorage:', error); } return { token: null, user_id: null }; }; // 请求拦截器 api.interceptors.request.use( (config) => { // 添加请求日志 if (enableLogging) { console.log(`🚀 API Request: ${config.method?.toUpperCase()} ${config.url}`, { data: config.data, params: config.params, }); } // 为除登录接口外的所有请求添加身份信息 const isLoginRequest = config.url?.includes('/auth/login'); const isRegisterRequest = config.url?.includes('/auth/register'); if (!isLoginRequest && !isRegisterRequest) { const { token, user_id } = getAuthData(); if (token) { config.headers['auth'] = token; // JWT token } if (user_id) { config.headers['user_id'] = user_id; // 用户ID } } // 添加请求时间戳 config.metadata = { startTime: new Date() }; return config; }, (error) => { console.error('❌ Request Error:', error); return Promise.reject(error); } ); // 响应拦截器 api.interceptors.response.use( (response) => { // 添加响应日志 if (enableLogging) { const duration = new Date() - response.config.metadata.startTime; console.log(`✅ API Response: ${response.config.method?.toUpperCase()} ${response.config.url}`, { status: response.status, duration: `${duration}ms`, data: response.data, }); } // 统一处理成功响应格式 if (response.data && typeof response.data === 'object') { // 如果后端返回统一格式 { code, message, data } if ('code' in response.data) { const { code, message, data } = response.data; if (code === 200 || code === 0) { return { ...response, data }; // 返回实际数据 } else { // 业务错误处理 toast.error(message || '请求失败'); return Promise.reject(new Error(message || '请求失败')); } } } return response; }, async (error) => { const originalRequest = error.config; // 添加错误日志 if (enableLogging) { const duration = originalRequest?.metadata?.startTime ? `${new Date() - originalRequest.metadata.startTime}ms` : 'N/A'; console.error(`❌ API Error: ${originalRequest?.method?.toUpperCase()} ${originalRequest?.url}`, { status: error.response?.status, duration, message: error.message, data: error.response?.data, }); } // 处理不同类型的错误 if (error.response) { const { status, data } = error.response; switch (status) { case 401: // 未授权 - token过期或无效 return handleUnauthorizedError(originalRequest); case 403: // 禁止访问 - 权限不足 toast.error('您没有权限访问此资源'); break; case 404: // 资源不存在 toast.error('请求的资源不存在'); break; case 422: // 表单验证错误 if (data?.errors) { Object.values(data.errors).flat().forEach(errorMessage => { toast.error(errorMessage); }); } else { toast.error(data?.message || '请求参数错误'); } break; case 429: // 请求过于频繁 toast.error('请求过于频繁,请稍后再试'); break; case 500: // 服务器内部错误 toast.error('服务器内部错误,请稍后再试'); break; default: // 其他错误 toast.error(data?.message || `请求失败 (${status})`); } } else if (error.request) { // 网络错误 if (error.code === 'ECONNABORTED') { toast.error('请求超时,请检查网络连接'); } else { toast.error('网络连接失败,请检查网络'); } } else { // 其他错误 toast.error('请求配置错误'); } return Promise.reject(error); } ); return api; }; /** * 处理401未授权错误 * 尝试刷新token或跳转到登录页 */ const handleUnauthorizedError = async (originalRequest) => { // 避免重复刷新token if (originalRequest._retry) { // 刷新失败,清除本地存储并跳转到登录页 localStorage.removeItem('authData'); window.location.href = '/login'; return Promise.reject(new Error('登录已过期,请重新登录')); } originalRequest._retry = true; try { // 尝试刷新token const authData = JSON.parse(localStorage.getItem('authData') || '{}'); const refreshToken = authData.refreshToken; if (refreshToken) { const response = await axios.post(`${originalRequest.baseURL}/auth/refresh-token`, { refreshToken }); const { token, user } = response.data.data; // 更新本地存储 localStorage.setItem('authData', JSON.stringify({ token, refreshToken, user })); // 重新发送原始请求 originalRequest.headers['auth'] = token; originalRequest.headers['user_id'] = user.id; return axios(originalRequest); } else { // 没有刷新token,跳转到登录页 localStorage.removeItem('authData'); window.location.href = '/login'; return Promise.reject(new Error('登录已过期,请重新登录')); } } catch (refreshError) { // 刷新token失败 localStorage.removeItem('authData'); window.location.href = '/login'; return Promise.reject(new Error('登录已过期,请重新登录')); } }; /** * 设置认证信息 * @param {string} token JWT token * @param {object} user 用户信息 * @param {string} refreshToken 刷新token */ export const setAuthData = (token, user, refreshToken) => { const authData = { token, refreshToken: refreshToken || '', user }; localStorage.setItem('authData', JSON.stringify(authData)); }; /** * 清除认证信息 */ export const clearAuthData = () => { localStorage.removeItem('authData'); }; /** * 获取当前认证信息 */ export const getAuthData = () => { try { const authData = localStorage.getItem('authData'); return authData ? JSON.parse(authData) : null; } catch (error) { console.warn('Failed to get auth data:', error); return null; } }; /** * 检查是否已登录 */ export const isAuthenticated = () => { const authData = getAuthData(); return !!(authData?.token && authData?.user); }; export default createAPI;