import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' import {getRefreshToken,refreshAccessToken} from "@/api/login.js"; import { baseUrl, acsBaseUrl, noAuthUrls as noAuthUrlList, requestTimeout, acsRequestTimeout, TOKEN_KEY, TOKEN_EXPIRE_KEY, USER_INFO_KEY } from '@/config/env.js' // 检查URL是否需要认证 const urlNeedAuth = (url) => { return !noAuthUrlList.some(noAuthUrl => url.includes(noAuthUrl)) } // 检查token是否过期 const isTokenExpired = () => { const token = localStorage.getItem(TOKEN_KEY) const expire = localStorage.getItem(TOKEN_EXPIRE_KEY) if (!token) return true // 检查过期时间 if (expire) { const expireTime = parseInt(expire) * 1000 // 转换为毫秒 const now = Date.now() return now >= expireTime } // 没有过期时间时,默认认为Token已过期(因为无法验证有效性) return true } // 检查token是否即将过期(5分钟内) const isTokenExpiringSoon = () => { const expire = localStorage.getItem(TOKEN_EXPIRE_KEY) if (!expire) return false const expireTime = parseInt(expire) * 1000 // 转换为毫秒 const now = Date.now() const fiveMinutes = 5 * 60 * 1000 // 5分钟 // 如果已过期,返回false(由isTokenExpired处理) if (now >= expireTime) return false // 如果在5分钟内过期,返回true return (expireTime - now) <= fiveMinutes } // 正在刷新token的标志 let isRefreshing = false // 等待刷新token的请求队列 let refreshSubscribers = [] // 添加请求到队列 const subscribeTokenRefresh = (callback) => { refreshSubscribers.push(callback) } // 刷新token后执行队列中的请求 const onTokenRefreshed = (newToken) => { refreshSubscribers.forEach(callback => callback(newToken)) refreshSubscribers = [] } // 执行token刷新 const doRefreshToken = async () => { try { const domain = window.location.hostname // 获取交换token const refreshTokenRes = await getRefreshToken(domain,{ headers: { Authorization: `Bearer ${localStorage.getItem('token')}` } }) if (refreshTokenRes.data?.code === 200 && refreshTokenRes.data?.data?.refresh_token) { // 使用交换token获取新的access token const newTokenRes = await refreshAccessToken(refreshTokenRes.data.data.refresh_token) if (newTokenRes.data?.code === 200 && newTokenRes.data?.data?.token) { const { token, expire } = newTokenRes.data.data localStorage.setItem(TOKEN_KEY, token) if (expire) { localStorage.setItem(TOKEN_EXPIRE_KEY, expire.toString()) } return token } } // 刷新失败,触发登出逻辑 localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(TOKEN_EXPIRE_KEY) localStorage.removeItem(USER_INFO_KEY) ElMessage.warning('登录过期,请重新登录') router.push('/login') return null } catch (error) { console.error('Token刷新失败:', error) // 刷新失败,触发登出逻辑 localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(TOKEN_EXPIRE_KEY) localStorage.removeItem(USER_INFO_KEY) ElMessage.warning('登录过期,请重新登录') router.push('/login') return null } } class Request { constructor(config = {}) { // 创建 axios 实例 this.instance = axios.create({ baseURL: config.baseURL || '', timeout: config.timeout || 5000, headers: config.headers || {} }) // 请求拦截器 this.instance.interceptors.request.use( (config) => { // 在发送请求之前做些什么 // 例如:添加 token const token = localStorage.getItem(TOKEN_KEY) if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, (error) => { return Promise.reject(error) } ) // 响应拦截器 this.instance.interceptors.response.use( (response) => { // 对响应数据做点什么 return response.data }, (error) => { // 对响应错误做点什么 // if (error.response) { // switch (error.response.status) { // case 401: // // 未授权,可以在这里处理登出逻辑 // break // case 403: // // 禁止访问 // break // case 404: // // 未找到 // break // case 500: // // 服务器错误 // break // } // } return error.response } ) } // GET 请求 get(url, params = {}, config = {}) { return this.instance.get(url, { params, ...config }) } // POST 请求 post(url, data = {}, config = {}) { return this.instance.post(url, data, config) } // PUT 请求 put(url, data = {}, config = {}) { return this.instance.put(url, data, config) } // DELETE 请求 delete(url, config = {}) { return this.instance.delete(url, config) } // PATCH 请求 patch(url, data = {}, config = {}) { return this.instance.patch(url, data, config) } } // 创建默认实例 const request = new Request({ baseURL: baseUrl, timeout: requestTimeout, headers: { 'Content-Type': 'multipart/form-data' } }) export const mainUrl = baseUrl + '/acs' export const baseURL = baseUrl export const http2 = axios.create({ baseURL: baseUrl, timeout: acsRequestTimeout, headers: {}, }); http2.interceptors.request.use(async config => { const token = localStorage.getItem(TOKEN_KEY) // 检查是否需要认证 if (urlNeedAuth(config.url)) { // 检查token是否已过期 if (isTokenExpired()) { if (token) { localStorage.removeItem(TOKEN_KEY) localStorage.removeItem(TOKEN_EXPIRE_KEY) localStorage.removeItem(USER_INFO_KEY) ElMessage.warning('登录过期,请重新登录') } router.push('/login') return Promise.reject(new Error('Token已过期')) } // 检查token是否即将过期,进行无感刷新 if (isTokenExpiringSoon() && !isRefreshing) { isRefreshing = true try { const newToken = await doRefreshToken() if (newToken) { console.log('Token已无感刷新') onTokenRefreshed(newToken) config.headers.Authorization = `Bearer ${newToken}` } else { // 刷新失败,doRefreshToken已处理登出逻辑,直接拒绝请求 return Promise.reject(new Error('Token刷新失败')) } } catch (error) { console.error('Token刷新异常:', error) // 刷新异常,doRefreshToken已处理登出逻辑,直接拒绝请求 return Promise.reject(error) } finally { isRefreshing = false } } else if (isRefreshing) { // 正在刷新,等待刷新完成 return new Promise((resolve, reject) => { subscribeTokenRefresh((newToken) => { if (newToken) { config.headers.Authorization = `Bearer ${newToken}` // 重新发送原始请求 resolve(config) } else { reject(new Error('Token刷新失败')) } }) }) } else { // 正常情况,直接使用token config.headers.Authorization = `Bearer ${token}` } } // 不需要认证的请求,不添加token return config }) http2.interceptors.response.use( response => response, // 正常响应时直接返回 error => { console.log('出现错误', error.response); if (error.response == undefined) { ElMessage.error("服务器错误,请稍后再试"); return Promise.reject(error); } const { status } = error.response; if (status === 401) { localStorage.removeItem(TOKEN_KEY); ElMessage.warning('登陆过期,请重新登陆') router.push('/login') return Promise.reject(); } // 其他错误处理(可选) return Promise.reject(error); } ); export default request