2025-11-13 01:06:28 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 应用层 HTTP 客户端
|
|
|
|
|
|
* 使用 mono 级别的 axios 实例,添加应用特定的 401 处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2025-11-12 22:45:29 +08:00
|
|
|
|
import { message } from 'ant-design-vue'
|
2025-11-13 01:06:28 +08:00
|
|
|
|
import { clearAllTokens, getRefreshToken } from '@gold/utils/token-manager'
|
2025-11-12 22:45:29 +08:00
|
|
|
|
import { useUserStore } from '@/stores/user'
|
2025-11-13 01:06:28 +08:00
|
|
|
|
import { createClientAxios } from '@gold/api/axios/client'
|
|
|
|
|
|
import { refreshToken } from '@/api/auth'
|
2025-11-10 00:59:40 +08:00
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
// 刷新 token 的状态管理
|
|
|
|
|
|
let isRefreshing = false
|
|
|
|
|
|
let refreshPromise = null
|
2025-11-10 00:59:40 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-13 01:06:28 +08:00
|
|
|
|
* 处理 403 禁止访问错误(应用层特定逻辑)
|
|
|
|
|
|
* 先尝试刷新 token,如果失败或没有 refresh token 才提示用户
|
2025-11-10 00:59:40 +08:00
|
|
|
|
*/
|
2025-11-13 01:06:28 +08:00
|
|
|
|
async function handleApp403Error() {
|
|
|
|
|
|
// 避免重复处理
|
|
|
|
|
|
if (handleApp403Error.processed) {
|
|
|
|
|
|
return
|
2025-11-10 00:59:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
handleApp403Error.processed = true
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查是否有 refresh token
|
|
|
|
|
|
const refreshTokenValue = getRefreshToken()
|
|
|
|
|
|
|
|
|
|
|
|
if (refreshTokenValue) {
|
|
|
|
|
|
// 如果有 refresh token,尝试刷新
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 如果正在刷新,等待刷新完成
|
|
|
|
|
|
if (isRefreshing && refreshPromise) {
|
|
|
|
|
|
await refreshPromise
|
|
|
|
|
|
handleApp403Error.processed = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开始刷新 token
|
|
|
|
|
|
isRefreshing = true
|
|
|
|
|
|
refreshPromise = refreshToken()
|
|
|
|
|
|
|
|
|
|
|
|
await refreshPromise
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新成功,重置状态
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
refreshPromise = null
|
|
|
|
|
|
handleApp403Error.processed = false
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新成功,不提示用户(静默刷新)
|
|
|
|
|
|
return
|
|
|
|
|
|
} catch (refreshError) {
|
|
|
|
|
|
// 刷新失败,清除状态
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
refreshPromise = null
|
|
|
|
|
|
console.error('刷新 token 失败:', refreshError)
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新失败才提示用户
|
|
|
|
|
|
message.warning('登录状态已过期,请重新登录', 3)
|
|
|
|
|
|
handleApp403Error.processed = false
|
|
|
|
|
|
return
|
2025-11-12 22:45:29 +08:00
|
|
|
|
}
|
2025-11-13 01:06:28 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 没有 refresh token,提示用户
|
|
|
|
|
|
message.warning('登录状态已过期,请重新登录', 3)
|
|
|
|
|
|
handleApp403Error.processed = false
|
2025-11-12 22:45:29 +08:00
|
|
|
|
}
|
2025-11-13 01:06:28 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('处理 403 错误失败:', e)
|
|
|
|
|
|
handleApp403Error.processed = false
|
2025-11-10 00:59:40 +08:00
|
|
|
|
}
|
2025-11-13 01:06:28 +08:00
|
|
|
|
}
|
2025-11-10 00:59:40 +08:00
|
|
|
|
|
2025-11-12 22:45:29 +08:00
|
|
|
|
/**
|
2025-11-13 01:06:28 +08:00
|
|
|
|
* 处理 401 未授权错误(应用层特定逻辑)
|
|
|
|
|
|
* 先尝试刷新 token,如果失败或没有 refresh token 才退出登录
|
2025-11-12 22:45:29 +08:00
|
|
|
|
*/
|
2025-11-13 01:06:28 +08:00
|
|
|
|
async function handleApp401Error() {
|
|
|
|
|
|
// 避免重复处理
|
|
|
|
|
|
if (handleApp401Error.processed) {
|
2025-11-12 22:45:29 +08:00
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
handleApp401Error.processed = true
|
2025-11-12 22:45:29 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
2025-11-13 01:06:28 +08:00
|
|
|
|
// 检查是否有 refresh token
|
|
|
|
|
|
const refreshTokenValue = getRefreshToken()
|
|
|
|
|
|
|
|
|
|
|
|
if (refreshTokenValue) {
|
|
|
|
|
|
// 如果有 refresh token,尝试刷新
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 如果正在刷新,等待刷新完成
|
|
|
|
|
|
if (isRefreshing && refreshPromise) {
|
|
|
|
|
|
await refreshPromise
|
|
|
|
|
|
handleApp401Error.processed = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 开始刷新 token
|
|
|
|
|
|
isRefreshing = true
|
|
|
|
|
|
refreshPromise = refreshToken()
|
|
|
|
|
|
|
|
|
|
|
|
await refreshPromise
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新成功,重置状态
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
refreshPromise = null
|
|
|
|
|
|
handleApp401Error.processed = false
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新成功,不提示用户(静默刷新)
|
|
|
|
|
|
return
|
|
|
|
|
|
} catch (refreshError) {
|
|
|
|
|
|
// 刷新失败,清除状态
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
refreshPromise = null
|
|
|
|
|
|
console.error('刷新 token 失败:', refreshError)
|
|
|
|
|
|
|
|
|
|
|
|
// 继续执行退出登录逻辑
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 没有 refresh token 或刷新失败,执行退出登录
|
|
|
|
|
|
try {
|
|
|
|
|
|
clearAllTokens()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('清空 token 失败:', e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
userStore.logout()
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error('退出登录失败:', e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
message.warning('登录已过期,请重新登录', 3)
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
|
2025-11-12 22:45:29 +08:00
|
|
|
|
} catch (e) {
|
2025-11-13 01:06:28 +08:00
|
|
|
|
console.error('处理 401 错误失败:', e)
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
handleApp401Error.processed = false
|
|
|
|
|
|
}, 2000)
|
2025-11-12 22:45:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 创建应用层 HTTP 客户端
|
|
|
|
|
|
* 基于 mono 级别的 axios 实例,添加应用特定的错误处理
|
|
|
|
|
|
*/
|
|
|
|
|
|
const http = createClientAxios({
|
|
|
|
|
|
baseURL: '/',
|
|
|
|
|
|
timeout: 180000,
|
|
|
|
|
|
on401: handleApp401Error,
|
|
|
|
|
|
on403: handleApp403Error,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 注意:403 处理已在 createClientAxios 的响应拦截器中通过 on403 回调处理
|
|
|
|
|
|
|
2025-11-10 00:59:40 +08:00
|
|
|
|
export default http
|
|
|
|
|
|
|
|
|
|
|
|
|