import axios, {type AxiosRequestConfig, type AxiosResponse} from "axios"; import type {AxiosInstance} from "axios"; import type {NotificationArgsProps} from "antd"; // 全局变量声明 window.requests = []; window.tokenRefreshing = false; // 请求去重映射表 const pendingMap = new Map(); // 配置常量 const REQUEST_TIMEOUT = 10000; // 请求超时时间 const LOGIN_PATH = '/login'; // 登录页面路径 /** * 添加请求到待处理映射表,实现请求去重 * @param config - Axios 请求配置 */ function addPending(config: AxiosRequestConfig): void { const pendingKey = getPendingKey(config); if (!config.signal) { const controller = new AbortController(); config.signal = controller.signal; // 只在不存在时添加,避免重复请求 if (!pendingMap.has(pendingKey)) { pendingMap.set(pendingKey, controller); } } } /** * 移除待处理的请求,取消重复请求 * @param config - Axios 请求配置 */ function removePending(config: AxiosRequestConfig): void { const pendingKey = getPendingKey(config); if (pendingMap.has(pendingKey)) { const controller = pendingMap.get(pendingKey); controller?.abort(); pendingMap.delete(pendingKey); } } /** * 生成每个请求的唯一标识键 * @param config - Axios 请求配置 * @returns 请求的唯一标识字符串 */ function getPendingKey(config: AxiosRequestConfig): string { let { data } = config; const { url, method, params, headers } = config; // 处理响应中返回的字符串格式 data if (typeof data === 'string') { try { data = JSON.parse(data); } catch (e) { // 如果解析失败,保持原值 console.warn('Failed to parse request data:', e); } } // 安全获取 header 值 const headersObj = headers as Record || {}; const language = headersObj['User-Language'] || headersObj['i18nextLng'] || ''; const auth = headersObj['Authorization'] || ''; return [ url || '', method || '', language, auth, JSON.stringify(params || {}), JSON.stringify(data || {}), ].join('&'); } /** * 处理网络错误(HTTP 状态码错误) * @param errStatus - HTTP 状态码 */ const handleNetworkError = async (errStatus?: number): Promise => { if (!errStatus) { window.$message?.error("无法连接到服务器!"); return; } // 401 未授权 - 需要特殊处理,清除认证信息并跳转登录 if (errStatus === 401) { window.$message?.error('您未登录,或者登录已经超时,请先登录!'); // 清除本地存储的认证信息 localStorage.removeItem('token'); localStorage.removeItem('refresh_token'); localStorage.removeItem('auth-storage'); // 避免在登录页重复跳转 if (window.location.pathname !== LOGIN_PATH) { window.location.href = LOGIN_PATH; } return; } // HTTP 状态码错误消息映射表 const errorMessages: Record = { 302: '接口重定向了!', 400: '参数不正确!', 403: '您没有权限操作!', 404: '请求错误,未找到该资源!', 408: '请求超时!', 409: '系统已存在相同数据!', 500: '服务器内部错误!', 501: '服务未实现!', 502: '网关错误!', 503: '服务不可用!', 504: '服务暂时无法访问,请稍后再试!', 505: 'HTTP版本不受支持!', }; const messageString = errorMessages[errStatus] || `连接错误 (状态码: ${errStatus})`; window.$message?.error(messageString); }; /** * 处理业务错误(接口返回的业务逻辑错误) * @param data - 接口返回的数据结构 */ const handleBusinessError = async (data: API.ResponseStructure): Promise => { const { msg = '', showType = 0, description = '', placement = 'topRight' } = data; // 通知配置 const notificationProps: NotificationArgsProps = { message: msg, description: description, placement: placement }; // 根据 showType 展示不同类型的提示 // 0-2: Message 提示; 3-5: Notification 通知; 99: 静默处理 switch (showType) { case 99: // 静默处理,不显示任何提示 break; case 0: // Message 成功提示 if (msg) window.$message?.success(msg); break; case 1: // Message 警告提示 if (msg) window.$message?.warning(msg); break; case 2: // Message 错误提示 if (msg) window.$message?.error(msg); break; case 3: // Notification 成功通知 if (msg) window.$notification?.success(notificationProps); break; case 4: // Notification 警告通知 if (msg) window.$notification?.warning(notificationProps); break; case 5: // Notification 错误通知 if (msg) window.$notification?.error(notificationProps); break; default: // 默认使用 Message 错误提示 if (msg) window.$message?.error(msg); } }; /** * 创建并配置 Axios 实例 * @param axiosConfig - Axios 请求配置 * @returns Promise> */ function createAxios>( axiosConfig: AxiosRequestConfig ): Promise> { // 创建 axios 实例 const instance: AxiosInstance = axios.create({ baseURL: import.meta.env.VITE_BASE_URL || '', timeout: REQUEST_TIMEOUT, responseType: 'json', // 允许携带凭证 withCredentials: false, }); // ==================== 请求拦截器 ==================== instance.interceptors.request.use( (config) => { // 请求去重:移除重复的待处理请求 removePending(config); // 将当前请求添加到待处理映射表 addPending(config); // 确保 headers 存在 config.headers = config.headers || {}; // 自动附加国际化语言到请求头 const currentLanguage = localStorage.getItem('i18nextLng'); if (currentLanguage) { config.headers['User-Language'] = currentLanguage; } // 自动附加 Token 到请求头(排除登录接口) const token = localStorage.getItem('token'); if (token) { config.headers['Authorization'] = `Bearer ${token}`; } return config; }, (error) => { // 请求错误处理 console.error('Request Error:', error); return Promise.reject(error); } ); // ==================== 响应拦截器 ==================== instance.interceptors.response.use( async (response) => { // 请求完成后,从待处理映射表中移除 removePending(response.config); const { data } = response; // 业务成功,直接返回 if (data?.success) { return Promise.resolve(response); } // 业务失败,统一处理业务错误 await handleBusinessError(data); return Promise.reject(response); }, async (err) => { // 请求被取消(去重或手动取消) if (axios.isCancel(err)) { console.warn('请求已取消:', err.message); return Promise.reject(err); } // 有响应但状态码错误(4xx, 5xx) if (err.response) { removePending(err.response.config); await handleNetworkError(err.response.status); return Promise.reject(err.response); } // 网络错误或超时(无响应) if (err.code === 'ECONNABORTED') { window.$message?.error('请求超时,请稍后重试!'); } else if (err.message?.includes('Network Error')) { window.$message?.error('网络连接失败,请检查网络!'); } else { window.$message?.error(`网络错误: ${err.message || '未知错误'}`); } return Promise.reject(err); } ); // 执行请求并返回 return instance(axiosConfig) as Promise>; } export default createAxios;