282 lines
8.8 KiB
JavaScript
282 lines
8.8 KiB
JavaScript
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 |