import axios from 'axios' import { message } from 'ant-design-vue' import { getAuthHeader, clearAllTokens } from '@gold/utils/token-manager' import { useUserStore } from '@/stores/user' /** * 不需要 token 的接口白名单 * 支持完整路径匹配或路径包含匹配 */ const WHITE_LIST = [ '/auth/login', // 密码登录 '/auth/send-sms-code', // 发送验证码 '/auth/sms-login', // 短信登录 '/auth/validate-sms-code', // 验证验证码 '/auth/register', // 注册(如果有) '/auth/reset-password', // 重置密码 '/auth/refresh-token', // 刷新token(可选,根据后端要求) ] /** * 检查 URL 是否在白名单中 * @param {string} url - 请求 URL * @returns {boolean} */ function isInWhiteList(url) { if (!url) return false return WHITE_LIST.some((path) => url.includes(path)) } // 创建 axios 实例 const http = axios.create({ baseURL: '/', timeout: 180000, // 3分钟 }) // 请求拦截:自动注入 Token http.interceptors.request.use((config) => { // 检查是否需要 token(不在白名单中且未显式设置 isToken = false) const needToken = config.headers?.isToken !== false && !isInWhiteList(config.url || '') config.headers['tenant-id'] = import.meta.env.VITE_TENANT_ID if (needToken) { // 使用统一的 token 管理器获取 header const authHeader = getAuthHeader() if (authHeader) { config.headers.Authorization = authHeader } } // 允许跨域第三方域名,使用绝对地址时保持原样 return config }) http.interceptors.response.use( (resp) => { const data = resp.data // 检查响应数据中的 code 字段 if (data && typeof data.code === 'number' && (data.code === 0 || data.code === 200)) { return data } else { // code 不为 0 时,检查是否为401 if (data && typeof data.code === 'number' && data.code === 401) { handle401Error() } // code 不为 0 时,抛出错误 const error = new Error(data?.message || data?.msg || '请求失败') error.code = data?.code error.data = data return Promise.reject(error) } }, (error) => { // 处理 HTTP 状态码 401 if (error.response && error.response.status === 401) { handle401Error() } // 统一错误处理:输出关键信息,便于排查 403 等问题 return Promise.reject(error) } ) /** * 处理 401 未授权错误 * 清空 token 并退出登录 * * 注意:使用防抖机制避免多个请求同时401时重复处理 */ function handle401Error() { // 避免重复处理(防止多个请求同时401导致多次调用) if (handle401Error.processed) { return } handle401Error.processed = true // 1. 清空所有 token try { clearAllTokens() // 统一使用 token-manager 的清空函数 } catch (e) { console.error('清空 token 失败:', e) } // 2. 退出登录状态(清空用户信息) try { const userStore = useUserStore() // logout() 会清空用户信息和本地存储 userStore.logout() } catch (e) { console.error('退出登录失败:', e) } // 3. 提示用户(延迟显示,避免在清空过程中显示) setTimeout(() => { message.warning('登录已过期,请重新登录', 3) }, 100) // 4. 延迟重置标志,避免短时间内重复处理 setTimeout(() => { handle401Error.processed = false }, 2000) } export default http