| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- 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<ApiResponse>) => {
- 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;
|