/** * HTTP请求工具 * 基于axios封装的统一请求实例 */ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios' import { ElMessage, ElMessageBox } from 'element-plus' import { envConfig } from '@/config/env' // 请求响应接口 export interface ApiResponse { code: number message: string data: T success: boolean timestamp: number } // 请求配置接口 export interface RequestConfig extends AxiosRequestConfig { // 是否显示loading showLoading?: boolean // 是否显示错误消息 showError?: boolean // 是否需要token needToken?: boolean } // 创建axios实例 const createAxiosInstance = (): AxiosInstance => { const instance = axios.create({ baseURL: envConfig.apiBaseUrl, timeout: 30000, headers: { 'Content-Type': 'application/json;charset=UTF-8' } }) // 请求拦截器 instance.interceptors.request.use( (config: any) => { // 添加token const token = localStorage.getItem('access_token') console.log('🔑 请求拦截器 - Token状态:', { hasToken: !!token, tokenPreview: token ? `${token.substring(0, 20)}...` : 'null', url: config.url, needToken: config.needToken }) if (token && config.needToken !== false) { config.headers.Authorization = `Bearer ${token}` console.log('🔑 已添加Authorization头') } else { console.log('🔑 未添加Authorization头 - 原因:', !token ? '无token' : 'needToken=false') } // 添加请求ID用于追踪 config.headers['X-Request-ID'] = generateRequestId() // 打印请求日志 if (envConfig.debug) { console.log('🚀 Request:', { url: config.url, method: config.method, params: config.params, data: config.data, headers: config.headers }) } return config }, (error: AxiosError) => { console.error('❌ Request Error:', error) return Promise.reject(error) } ) // 响应拦截器 instance.interceptors.response.use( (response: AxiosResponse) => { const { data } = response // 打印响应日志 if (envConfig.debug) { console.log('✅ Response:', { url: response.config.url, status: response.status, data: data }) } // 检查业务状态码 if (data.code === 200 || data.success) { return data } // 处理业务错误 const errorMessage = data.message || '请求失败' // 特殊错误码处理 switch (data.code) { case 401: console.warn('🚫 业务层401错误:', errorMessage) // 只有在非登录接口时才处理401 if (!response.config.url?.includes('/auth/login')) { handleUnauthorized() } else { ElMessage.error(errorMessage) } break case 403: ElMessage.error('没有权限访问该资源') break case 404: ElMessage.error('请求的资源不存在') break case 500: ElMessage.error('服务器内部错误') break default: ElMessage.error(errorMessage) } return Promise.reject(new Error(errorMessage)) }, (error: AxiosError) => { console.error('❌ Response Error:', error) let errorMessage = '网络请求失败' if (error.response) { // 服务器响应错误 const { status, data } = error.response switch (status) { case 400: errorMessage = '请求参数错误' break case 401: errorMessage = '未授权,请重新登录' console.warn('🚫 HTTP层401错误') // 只有在非登录接口时才处理401 if (!error.config?.url?.includes('/auth/login')) { handleUnauthorized() } break case 403: errorMessage = '拒绝访问' break case 404: errorMessage = '请求地址不存在' break case 408: errorMessage = '请求超时' break case 500: errorMessage = '服务器内部错误' break case 502: errorMessage = '网关错误' break case 503: errorMessage = '服务不可用' break case 504: errorMessage = '网关超时' break default: errorMessage = (data as any)?.message || `请求失败 (${status})` } } else if (error.request) { // 网络错误 errorMessage = '网络连接失败,请检查网络' } else { // 其他错误 errorMessage = error.message || '请求配置错误' } ElMessage.error(errorMessage) return Promise.reject(error) } ) return instance } // 处理未授权 const handleUnauthorized = () => { console.warn('🚫 收到401未授权响应') // 检查当前页面是否是登录页,避免在登录页面重复处理 if (window.location.pathname === '/login') { console.log('🚫 当前在登录页面,不处理401错误') return } // 不立即清除认证信息,而是提示用户 ElMessageBox.confirm( '登录状态已过期,请重新登录', '提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' } ).then(() => { // 用户确认后才清除认证信息 localStorage.removeItem('access_token') localStorage.removeItem('refresh_token') localStorage.removeItem('user_info') // 跳转到登录页 window.location.href = '/login' }).catch(() => { // 用户取消,不清除认证信息,让用户继续操作 console.log('🚫 用户取消重新登录') }) } // 生成请求ID const generateRequestId = (): string => { return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}` } // 创建请求实例 export const request = createAxiosInstance() // 导出请求方法 export const http = { get: (url: string, config?: RequestConfig) => request.get>(url, config), post: (url: string, data?: any, config?: RequestConfig) => request.post>(url, data, config), put: (url: string, data?: any, config?: RequestConfig) => request.put>(url, data, config), delete: (url: string, config?: RequestConfig) => request.delete>(url, config), patch: (url: string, data?: any, config?: RequestConfig) => request.patch>(url, data, config) } export default request