2025-11-13 01:06:28 +08:00
|
|
|
|
import axios from 'axios'
|
2025-11-25 00:58:51 +08:00
|
|
|
|
import tokenManager from '@gold/utils/token-manager'
|
2025-11-13 01:06:28 +08:00
|
|
|
|
|
2026-01-18 15:27:43 +08:00
|
|
|
|
// Token白名单
|
2025-11-13 01:06:28 +08:00
|
|
|
|
const WHITE_LIST = [
|
|
|
|
|
|
'/auth/login',
|
|
|
|
|
|
'/auth/send-sms-code',
|
|
|
|
|
|
'/auth/sms-login',
|
|
|
|
|
|
'/auth/validate-sms-code',
|
|
|
|
|
|
'/auth/register',
|
|
|
|
|
|
'/auth/reset-password',
|
|
|
|
|
|
'/auth/refresh-token',
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
function isInWhiteList(url) {
|
2026-01-18 02:55:02 +08:00
|
|
|
|
return !!(url && WHITE_LIST.some(path => url.includes(path)))
|
2025-11-13 01:06:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 15:27:43 +08:00
|
|
|
|
// Token刷新状态管理
|
2025-12-21 22:24:16 +08:00
|
|
|
|
let isRefreshing = false
|
|
|
|
|
|
let refreshSubscribers = []
|
|
|
|
|
|
|
|
|
|
|
|
function subscribeTokenRefresh(callback) {
|
2026-01-18 15:27:43 +08:00
|
|
|
|
isRefreshing ? refreshSubscribers.push(callback) : callback()
|
2025-12-21 22:24:16 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function onRefreshed() {
|
|
|
|
|
|
refreshSubscribers.forEach(callback => callback())
|
|
|
|
|
|
refreshSubscribers = []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
/**
|
2026-01-18 15:27:43 +08:00
|
|
|
|
* 创建Axios实例
|
2025-11-13 01:06:28 +08:00
|
|
|
|
* @param {Object} options - 配置选项
|
2026-01-18 15:27:43 +08:00
|
|
|
|
* @param {string} options.baseURL - 基础URL
|
|
|
|
|
|
* @param {number} options.timeout - 超时时间
|
|
|
|
|
|
* @param {Function} options.on401 - 401错误处理函数
|
|
|
|
|
|
* @param {Function} options.on403 - 403错误处理函数
|
|
|
|
|
|
* @param {Function} options.refreshTokenFn - Token刷新函数
|
|
|
|
|
|
* @returns {AxiosInstance} Axios实例
|
2025-11-13 01:06:28 +08:00
|
|
|
|
*/
|
2026-01-18 15:27:43 +08:00
|
|
|
|
export default function createClientAxios(options = {}) {
|
2025-11-13 01:06:28 +08:00
|
|
|
|
const {
|
|
|
|
|
|
baseURL = '/',
|
|
|
|
|
|
timeout = 180000,
|
2026-01-18 15:27:43 +08:00
|
|
|
|
on401 = null,
|
2025-11-13 01:06:28 +08:00
|
|
|
|
on403 = null,
|
2025-12-21 22:24:16 +08:00
|
|
|
|
refreshTokenFn = null,
|
2025-11-13 01:06:28 +08:00
|
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
|
|
const client = axios.create({
|
|
|
|
|
|
baseURL,
|
|
|
|
|
|
timeout,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 请求拦截器
|
|
|
|
|
|
client.interceptors.request.use((config) => {
|
|
|
|
|
|
const needToken = config.headers?.isToken !== false && !isInWhiteList(config.url || '')
|
2025-12-21 22:24:16 +08:00
|
|
|
|
|
2026-01-18 02:55:02 +08:00
|
|
|
|
if (!needToken) {
|
|
|
|
|
|
return config
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const currentToken = tokenManager.getAccessToken()
|
|
|
|
|
|
if (!currentToken) {
|
|
|
|
|
|
return config
|
|
|
|
|
|
}
|
2026-01-18 15:37:31 +08:00
|
|
|
|
|
2026-01-18 15:27:43 +08:00
|
|
|
|
const isTokenExpired = tokenManager.isExpired(30 * 1000)
|
2026-01-18 02:55:02 +08:00
|
|
|
|
|
|
|
|
|
|
if (!isTokenExpired) {
|
|
|
|
|
|
const authHeader = tokenManager.getAuthHeader()
|
|
|
|
|
|
if (authHeader) {
|
|
|
|
|
|
config.headers.Authorization = authHeader
|
|
|
|
|
|
}
|
|
|
|
|
|
return config
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isRefreshing) {
|
|
|
|
|
|
isRefreshing = true
|
2025-12-21 22:24:16 +08:00
|
|
|
|
|
2026-01-18 02:55:02 +08:00
|
|
|
|
if (!refreshTokenFn || typeof refreshTokenFn !== 'function') {
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
onRefreshed()
|
2025-12-21 22:24:16 +08:00
|
|
|
|
return config
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 15:27:43 +08:00
|
|
|
|
refreshTokenFn().finally(() => {
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
onRefreshed()
|
|
|
|
|
|
})
|
2026-01-18 02:55:02 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-18 15:27:43 +08:00
|
|
|
|
return new Promise((resolve, reject) => {
|
2026-01-18 02:55:02 +08:00
|
|
|
|
subscribeTokenRefresh(() => {
|
2026-01-18 15:27:43 +08:00
|
|
|
|
const token = tokenManager.getAccessToken()
|
|
|
|
|
|
if (!token) {
|
|
|
|
|
|
return reject({
|
|
|
|
|
|
message: 'Token已过期,需要重新登录',
|
|
|
|
|
|
code: 401,
|
|
|
|
|
|
})
|
2025-12-21 22:24:16 +08:00
|
|
|
|
}
|
2026-01-18 15:27:43 +08:00
|
|
|
|
|
|
|
|
|
|
config.headers.Authorization = tokenManager.getAuthHeader()
|
2026-01-18 02:55:02 +08:00
|
|
|
|
resolve(config)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2025-11-13 01:06:28 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 响应拦截器
|
|
|
|
|
|
client.interceptors.response.use(
|
|
|
|
|
|
(response) => {
|
|
|
|
|
|
const data = response.data
|
2025-11-25 01:07:22 +08:00
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
if (data && typeof data.code === 'number') {
|
|
|
|
|
|
if (data.code === 0 || data.code === 200) {
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
2025-11-25 01:00:20 +08:00
|
|
|
|
|
|
|
|
|
|
const error = new Error(data?.message || data?.msg || '请求失败')
|
|
|
|
|
|
error.code = data?.code
|
|
|
|
|
|
error.data = data
|
|
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
if (data.code === 403 && typeof on403 === 'function') {
|
2026-01-18 15:27:43 +08:00
|
|
|
|
on403(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
if (data.code === 401 && typeof on401 === 'function') {
|
|
|
|
|
|
refreshTokenFn && refreshTokenFn()
|
2025-11-13 01:06:28 +08:00
|
|
|
|
}
|
|
|
|
|
|
return Promise.reject(error)
|
|
|
|
|
|
}
|
2025-11-25 01:07:22 +08:00
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
return data
|
|
|
|
|
|
},
|
|
|
|
|
|
(error) => {
|
2026-01-18 02:55:02 +08:00
|
|
|
|
const status = error.response?.status
|
2026-01-18 15:27:43 +08:00
|
|
|
|
const code = error.code
|
2025-11-25 01:00:20 +08:00
|
|
|
|
|
2026-01-18 15:27:43 +08:00
|
|
|
|
if ((status === 401 || code === 401) && typeof on401 === 'function') {
|
2026-01-18 02:55:02 +08:00
|
|
|
|
on401(error)
|
2026-01-18 15:27:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
else if ((status === 403 || code === 403) && typeof on403 === 'function') {
|
|
|
|
|
|
on403(error)
|
2025-11-13 01:06:28 +08:00
|
|
|
|
}
|
2025-11-25 01:00:20 +08:00
|
|
|
|
|
2025-11-13 01:06:28 +08:00
|
|
|
|
return Promise.reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
return client
|
2026-01-18 15:27:43 +08:00
|
|
|
|
}
|