import axios from 'axios' import { ElMessage } from 'element-plus' import { authStore } from '@/utils/auth' const api = axios.create({ baseURL: '/api/v1', timeout: 30000, headers: { 'Content-Type': 'application/json' } }) // 请求拦截器:自动带 Authorization header api.interceptors.request.use( config => { const token = authStore.getAccessToken() if (token) { config.headers = config.headers || {} config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error) ) // 标记正在刷新 token,防止并发 401 触发多次刷新 let isRefreshing = false let pendingQueue = [] function flushQueue(newToken) { pendingQueue.forEach(cb => cb(newToken)) pendingQueue = [] } function queueRefresh(cb) { pendingQueue.push(cb) } // 响应拦截器:401 时尝试 refresh token;refresh 失败就跳登录 api.interceptors.response.use( response => { return response.data }, async error => { const originalRequest = error.config || {} const status = error.response?.status const detail = error.response?.data?.detail // 401 且不是 login/refresh 本身:尝试刷新 token if (status === 401 && !originalRequest._retry && !originalRequest.url?.includes('/auth/')) { const refreshToken = authStore.getRefreshToken() if (!refreshToken) { authStore.clear() redirectToLogin() return Promise.reject(error) } if (isRefreshing) { // 已有刷新请求在进行,等它完成后再重试 return new Promise(resolve => { queueRefresh(newToken => { if (newToken) { originalRequest._retry = true originalRequest.headers.Authorization = `Bearer ${newToken}` resolve(api(originalRequest)) } else { redirectToLogin() resolve(Promise.reject(error)) } }) }) } originalRequest._retry = true isRefreshing = true try { const formBody = new URLSearchParams() formBody.append('refresh_token', refreshToken) const resp = await axios.post('/api/v1/auth/refresh', formBody, { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }) const newToken = resp.data?.access_token if (newToken) { authStore.setAccessToken(newToken) if (resp.data?.refresh_token) { authStore.setRefreshToken(resp.data.refresh_token) } isRefreshing = false flushQueue(newToken) originalRequest.headers.Authorization = `Bearer ${newToken}` return api(originalRequest) } else { throw new Error('refresh 响应缺少 access_token') } } catch (refreshErr) { isRefreshing = false flushQueue(null) authStore.clear() redirectToLogin() return Promise.reject(refreshErr) } } // 非 401 或 refresh 后仍失败的:弹 toast(保持原行为,避免 view 重复弹) console.error('API Error:', error) ElMessage.error(typeof detail === 'string' ? detail : '请求失败') return Promise.reject(error) } ) function redirectToLogin() { if (typeof window !== 'undefined' && window.location.pathname !== '/login') { const next = encodeURIComponent(window.location.pathname + window.location.search) window.location.href = `/login?next=${next}` } } export default api