import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios' import { useUserStore } from '@/stores/user' const BASE_API = import.meta.env.VITE_APP_BASE_API // API响应结构 interface ApiResponse { code: number message: string data: T } // 加密配置 interface CryptoConfig { enabled: boolean publicKey: string aesKey: string // AES密钥的Base64编码 } // 加密配置缓存 let cryptoConfigCache: CryptoConfig | null = null // 获取加密配置 export async function fetchCryptoConfig(): Promise { if (cryptoConfigCache) { return cryptoConfigCache } try { const response = await axios.get('/api/crypto/config') if (response.data?.code === 200) { cryptoConfigCache = response.data.data return cryptoConfigCache! } } catch (error) { console.error('获取加密配置失败', error) } return { enabled: false, publicKey: '', aesKey: '' } } // 清除加密配置缓存 export function clearCryptoConfigCache() { cryptoConfigCache = null } // 判断是否是AES加密的响应数据(格式:iv.encryptedData) function isAesEncryptedData(data: any): boolean { if (typeof data !== 'string') { return false } const parts = data.split('.') if (parts.length !== 2) { return false } // 检查两部分是否都是有效的Base64,且IV长度正确 try { atob(parts[0]) atob(parts[1]) // IV是12字节,Base64后是16字符 return parts[0].length === 16 && parts[1].length > 10 } catch { return false } } // AES-GCM解密 async function aesDecrypt(encryptedData: string, aesKeyBase64: string): Promise { const parts = encryptedData.split('.') if (parts.length !== 2) { throw new Error('加密数据格式错误') } const iv = Uint8Array.from(atob(parts[0]), c => c.charCodeAt(0)) const data = Uint8Array.from(atob(parts[1]), c => c.charCodeAt(0)) const keyBytes = Uint8Array.from(atob(aesKeyBase64), c => c.charCodeAt(0)) // 导入AES密钥 const aesKey = await crypto.subtle.importKey( 'raw', keyBytes, { name: 'AES-GCM' }, false, ['decrypt'] ) // 解密 const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv }, aesKey, data ) return new TextDecoder().decode(decrypted) } // 解密响应数据 async function decryptResponseData(data: string): Promise { const config = await fetchCryptoConfig() if (!config.aesKey) { return data } try { const decryptedStr = await aesDecrypt(data, config.aesKey) return JSON.parse(decryptedStr) } catch (error) { console.error('响应解密失败', error) // 解密失败可能是密钥过期,清除缓存 cryptoConfigCache = null return data } } //axios.defaults.headers["Content-Type"] = 'application/json;charset=utf-8' //"application/x-www-form-urlencoded;charset=utf-8"; //"application/json;charset=utf-8"; // 创建axios实例 // 后端 API 统一使用 /api 前缀 const service: AxiosInstance = axios.create({ // baseURL:'/api', baseURL: BASE_API, timeout: 30000 }) // 请求拦截器 service.interceptors.request.use( (config) => { const userStore = useUserStore() if (userStore.token) { config.headers['Authorization'] = userStore.token } if (config.data instanceof FormData) { // 文件上传:不设置 JSON 请求头、不序列化,交给浏览器自动处理 multipart return config } config.headers["Content-Type"] = 'application/json;charset=utf-8' config.data = config.data instanceof Object ? JSON.stringify(config.data) : config.data return config }, (error) => { return Promise.reject(error) } ) // 防止重复 logout let isLoggingOut = false // 响应拦截器 service.interceptors.response.use( async (response: AxiosResponse) => { // 如果是 blob 类型响应(文件下载),直接返回 if (response.config.responseType === 'blob') { return response.data } if (response.config.responseType === 'arraybuffer') { // 二进制流不走code校验,直接完整返回ArrayBuffer return response.data } const res = response.data if (res.code !== 200) { // logout 接口返回 401 时不显示错误消息(避免干扰) const isLogoutRequest = response.config.url?.includes('/auth/logout') // 401 未授权,弹出明确提示并跳转登录(防止重复调用) if (res.code === 401 && !isLoggingOut && !isLogoutRequest) { window.$message?.warning('当前用户登录已过期,请重新登录') isLoggingOut = true const userStore = useUserStore() await userStore.logout() isLoggingOut = false return Promise.reject(new Error('登录已过期')) } if (!isLogoutRequest) { window.$message?.error(res.message || '请求失败') } return Promise.reject(new Error(res.message || '请求失败')) } // 检查响应数据是否是AES加密的,自动解密 if (isAesEncryptedData(res.data)) { try { return await decryptResponseData(res.data) } catch (error) { console.error('解密响应失败', error) return res.data } } return res.data }, (error) => { const message = error.response?.data?.message || error.message || '网络错误' window.$message?.error(message) return Promise.reject(error) } ) // 封装请求方法 export function request(config: AxiosRequestConfig): Promise { return service(config) as Promise } export default service