/** * HTTP请求工具 * 基于Axios封装,支持请求拦截、响应拦截、错误处理等 */ import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios' import { ElMessage, ElMessageBox } from 'element-plus' import { envConfig } from '@/config/env' import { STORAGE_KEYS, ERROR_CODES } from '@/config/constants' import { useAuthStore } from '@/stores/auth' import { useAppStore } from '@/stores/app' import router from '@/router' // 请求配置接口 interface RequestConfig extends AxiosRequestConfig { skipAuth?: boolean skipErrorHandler?: boolean showLoading?: boolean loadingText?: string } // 响应数据接口 interface ResponseData { code: number message: string data: T success: boolean timestamp: number } class RequestService { private instance: AxiosInstance private pendingRequests = new Map() constructor() { // 创建axios实例 this.instance = axios.create({ baseURL: envConfig.apiBaseUrl, timeout: 30000, headers: { 'Content-Type': 'application/json;charset=UTF-8' } }) // 设置请求拦截器 this.setupRequestInterceptor() // 设置响应拦截器 this.setupResponseInterceptor() } /** * 设置请求拦截器 */ private setupRequestInterceptor() { this.instance.interceptors.request.use( (config: any) => { const requestConfig = config as RequestConfig // 生成请求ID用于追踪 const requestId = this.generateRequestId(config) config.metadata = { requestId } // 处理重复请求 this.handleDuplicateRequest(config, requestId) // 添加认证头 if (!requestConfig.skipAuth) { const token = this.getToken() if (token) { config.headers.Authorization = `Bearer ${token}` } } // 显示加载状态 if (requestConfig.showLoading) { const appStore = useAppStore() appStore.setLoading(true, requestConfig.loadingText) } // 调试模式下打印请求信息 if (envConfig.debug) { console.log('🚀 发送请求:', { url: config.url, method: config.method, params: config.params, data: config.data, headers: config.headers }) } return config }, (error) => { console.error('❌ 请求拦截器错误:', error) return Promise.reject(error) } ) } /** * 设置响应拦截器 */ private setupResponseInterceptor() { this.instance.interceptors.response.use( (response: AxiosResponse) => { const config = response.config as RequestConfig const requestId = config.metadata?.requestId // 移除pending请求 if (requestId) { this.pendingRequests.delete(requestId) } // 隐藏加载状态 if (config.showLoading) { const appStore = useAppStore() appStore.setLoading(false) } // 调试模式下打印响应信息 if (envConfig.debug) { console.log('✅ 收到响应:', { url: response.config.url, status: response.status, data: response.data }) } const { data } = response // 处理业务状态码 if (data.code === 200 || data.success) { return data.data } else { return this.handleBusinessError(data, config) } }, (error) => { const config = error.config as RequestConfig const requestId = config?.metadata?.requestId // 移除pending请求 if (requestId) { this.pendingRequests.delete(requestId) } // 隐藏加载状态 if (config?.showLoading) { const appStore = useAppStore() appStore.setLoading(false) } return this.handleRequestError(error, config) } ) } /** * 生成请求ID */ private generateRequestId(config: AxiosRequestConfig): string { const { method, url, params, data } = config return `${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}_${Date.now()}` } /** * 处理重复请求 */ private handleDuplicateRequest(config: AxiosRequestConfig, requestId: string) { const duplicateKey = `${config.method}_${config.url}` // 取消之前的相同请求 if (this.pendingRequests.has(duplicateKey)) { const controller = this.pendingRequests.get(duplicateKey) controller?.abort('请求被取消:发起了新的相同请求') } // 创建新的AbortController const controller = new AbortController() config.signal = controller.signal this.pendingRequests.set(duplicateKey, controller) } /** * 获取Token */ private getToken(): string | null { return localStorage.getItem(STORAGE_KEYS.TOKEN) } /** * 处理业务错误 */ private handleBusinessError(data: ResponseData, config: RequestConfig) { if (config.skipErrorHandler) { return Promise.reject(data) } // 特殊错误码处理 switch (data.code) { case ERROR_CODES.UNAUTHORIZED: this.handleUnauthorized() break case ERROR_CODES.FORBIDDEN: ElMessage.error('权限不足,无法访问该资源') break case ERROR_CODES.NOT_FOUND: ElMessage.error('请求的资源不存在') break default: ElMessage.error(data.message || '请求失败') } return Promise.reject(data) } /** * 处理请求错误 */ private handleRequestError(error: any, config: RequestConfig) { if (config?.skipErrorHandler) { return Promise.reject(error) } let message = '网络错误,请稍后重试' if (error.code === 'ECONNABORTED') { message = '请求超时,请稍后重试' } else if (error.response) { const { status } = error.response switch (status) { case ERROR_CODES.UNAUTHORIZED: this.handleUnauthorized() return Promise.reject(error) case ERROR_CODES.FORBIDDEN: message = '权限不足,无法访问该资源' break case ERROR_CODES.NOT_FOUND: message = '请求的资源不存在' break case ERROR_CODES.INTERNAL_SERVER_ERROR: message = '服务器内部错误' break default: message = `请求失败 (${status})` } } else if (error.request) { message = '网络连接失败,请检查网络设置' } ElMessage.error(message) return Promise.reject(error) } /** * 处理未授权错误 */ private async handleUnauthorized() { const authStore = useAuthStore() try { // 尝试刷新Token await authStore.refreshToken() } catch { // 刷新失败,跳转到登录页 ElMessageBox.alert('登录已过期,请重新登录', '提示', { confirmButtonText: '确定', type: 'warning' }).then(() => { authStore.logout() router.push('/login') }) } } /** * GET请求 */ get(url: string, params?: any, config?: RequestConfig): Promise { return this.instance.get(url, { params, ...config }) } /** * POST请求 */ post(url: string, data?: any, config?: RequestConfig): Promise { return this.instance.post(url, data, config) } /** * PUT请求 */ put(url: string, data?: any, config?: RequestConfig): Promise { return this.instance.put(url, data, config) } /** * DELETE请求 */ delete(url: string, config?: RequestConfig): Promise { return this.instance.delete(url, config) } /** * 上传文件 */ upload(url: string, file: File, config?: RequestConfig): Promise { const formData = new FormData() formData.append('file', file) return this.instance.post(url, formData, { headers: { 'Content-Type': 'multipart/form-data' }, ...config }) } /** * 取消所有请求 */ cancelAllRequests() { this.pendingRequests.forEach((controller) => { controller.abort('用户取消请求') }) this.pendingRequests.clear() } /** * 取消指定请求 */ cancelRequest(requestId: string) { const controller = this.pendingRequests.get(requestId) if (controller) { controller.abort('用户取消请求') this.pendingRequests.delete(requestId) } } } // 创建请求实例 const request = new RequestService() export default request export { type RequestConfig }