import axios, { type AxiosInstance, type AxiosResponse, type InternalAxiosRequestConfig } from 'axios'; import { ElMessage } from 'element-plus'; import { useAuthStore } from '@/stores/auth'; import { useServerStore } from '@/stores/server'; import router from '@/router'; import type { ApiResponse } from '@media-manager/shared'; // 创建 axios 实例 const request: AxiosInstance = axios.create({ timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // 将 localhost 规范为 127.0.0.1,避免 Windows 上 IPv6(::1) 导致 ECONNREFUSED function normalizeBaseUrlForRequest(url: string): string { try { const u = new URL(url); if (u.hostname === 'localhost' || u.hostname === '::1') { u.hostname = '127.0.0.1'; } return u.origin.replace(/\/$/, ''); } catch { return url; } } function isLocalBackend(url: string): boolean { try { const u = new URL(url); const port = u.port || (u.protocol === 'https:' ? '443' : '80'); return (u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '::1') && port === '3000'; } catch { return false; } } // 请求拦截器 request.interceptors.request.use( (config: InternalAxiosRequestConfig) => { const serverStore = useServerStore(); const authStore = useAuthStore(); // 设置基础 URL:显式传入的 baseURL 优先,否则用 store const rawBaseUrl = config.baseURL ?? serverStore.currentServer?.url; if (rawBaseUrl) { // 开发环境下本地 3000 端口走 Vite 代理,避免 CORS 和重复 Access-Control-Allow-Origin 头 const isDevLocal = import.meta.env.DEV && isLocalBackend(rawBaseUrl); config.baseURL = isDevLocal ? '' : normalizeBaseUrlForRequest(rawBaseUrl); } // 添加认证 token if (authStore.accessToken) { config.headers.Authorization = `Bearer ${authStore.accessToken}`; } return config; }, (error) => { return Promise.reject(error); } ); // 是否正在刷新 token let isRefreshing = false; // 等待刷新的请求队列 let refreshSubscribers: ((token: string) => void)[] = []; // 通知所有等待的请求 function onRefreshed(token: string) { refreshSubscribers.forEach(callback => callback(token)); refreshSubscribers = []; } // 添加到等待队列 function addRefreshSubscriber(callback: (token: string) => void) { refreshSubscribers.push(callback); } // 响应拦截器 request.interceptors.response.use( (response: AxiosResponse) => { const data = response.data; if (data.success) { return data.data as any; } // 业务错误 ElMessage.error(data.message || '请求失败'); return Promise.reject(new Error(data.message)); }, async (error) => { const originalRequest = error.config; // 排除不需要刷新 token 的请求 const isAuthRequest = originalRequest.url?.includes('/api/auth/refresh') || originalRequest.url?.includes('/api/auth/login') || originalRequest.url?.includes('/api/auth/register'); // Token 过期,尝试刷新(排除认证相关请求) if (error.response?.status === 401 && !originalRequest._retry && !isAuthRequest) { originalRequest._retry = true; // 如果已经在刷新中,将请求加入队列等待 if (isRefreshing) { return new Promise((resolve) => { addRefreshSubscriber((token: string) => { originalRequest.headers.Authorization = `Bearer ${token}`; resolve(request(originalRequest)); }); }); } isRefreshing = true; const authStore = useAuthStore(); try { const refreshed = await authStore.refreshAccessToken(); if (refreshed) { const newToken = authStore.accessToken!; onRefreshed(newToken); originalRequest.headers.Authorization = `Bearer ${newToken}`; return request(originalRequest); } } catch { // 刷新失败 } finally { isRefreshing = false; } // 刷新失败,清除等待队列并跳转登录 refreshSubscribers = []; authStore.clearTokens(); ElMessage.error('登录已过期,请重新登录'); router.push('/login'); return Promise.reject(error); } // 认证请求失败(login/register 等) if (isAuthRequest) { // 获取错误消息 const message = error.response?.data?.error?.message || error.response?.data?.message || error.message || '认证失败'; // 显示错误消息 ElMessage.error(message); // refresh 请求失败才清除 token 并跳转 if (originalRequest.url?.includes('/api/auth/refresh')) { const authStore = useAuthStore(); authStore.clearTokens(); router.push('/login'); } return Promise.reject(error); } // 其他错误 const message = error.response?.data?.error?.message || error.response?.data?.message || error.message || '网络错误'; ElMessage.error(message); return Promise.reject(error); } ); export default request;