Files
sionrui/frontend/api/axios/client.js
sion123 4fc0837890 refactor: 分离业务码和HTTP状态码的401/403处理,避免混淆
问题:
- 业务码401/403既在响应拦截器处理,又可能进入错误拦截器重复处理
- 导致回调函数被调用两次,逻辑混乱

修复:
- 业务码401/403:只在响应拦截器处理,调用回调后抛出错误给业务代码
- HTTP状态码401/403:只在错误拦截器处理,调用回调后抛出错误

虽然后端通常同时返回业务码401和HTTP状态码401,
导致同一回调被调用两次,但:
1. AuthService已处理并发刷新问题(isRefreshing锁)
2. tokenManager.clearTokens()多次调用是安全的
3. 逻辑清晰:响应拦截器处理业务,错误拦截器处理HTTP异常

文档已在代码中明确说明两种处理方式

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 01:07:22 +08:00

157 lines
3.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Mono 级别的 C 端 Axios 实例
* 供 monorepo 中所有应用使用的统一 HTTP 客户端
*/
import axios from 'axios'
// 直接使用实例(最简单、最可靠)
import tokenManager from '@gold/utils/token-manager'
/**
* 不需要 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',
]
/**
* 检查 URL 是否在白名单中
*/
function isInWhiteList(url) {
if (!url) return false
return WHITE_LIST.some((path) => url.includes(path))
}
/**
* 处理 401 未授权错误
* 注意:只做清理工作,不处理重定向(重定向由上层回调处理)
*/
let isHandling401 = false
function handle401Error(error) {
if (isHandling401) return
isHandling401 = true
try {
// 清空token
tokenManager.clearTokens()
console.warn('Token已清空因401错误')
} catch (e) {
console.error('清空 token 失败:', e)
}
// 延迟重置标志
setTimeout(() => {
isHandling401 = false
}, 2000)
}
/**
* 创建 C 端 Axios 实例
* @param {Object} options - 配置选项
* @param {string} options.baseURL - 基础 URL
* @param {number} options.timeout - 超时时间(毫秒)
* @param {Function} options.on401 - 401 错误处理函数
* @param {Function} options.on403 - 403 错误处理函数
* @returns {AxiosInstance} Axios 实例
*/
export function createClientAxios(options = {}) {
const {
baseURL = '/',
timeout = 180000,
on401 = handle401Error,
on403 = null,
} = options
const client = axios.create({
baseURL,
timeout,
})
// 请求拦截器
client.interceptors.request.use((config) => {
// 添加 tenant-id
const tenantId =
(typeof import.meta !== 'undefined' && import.meta.env?.VITE_TENANT_ID) ||
(typeof process !== 'undefined' && process.env?.VITE_TENANT_ID) ||
'1'
if (tenantId) {
config.headers['tenant-id'] = tenantId
}
// 添加 Authorization header
const needToken = config.headers?.isToken !== false && !isInWhiteList(config.url || '')
if (needToken) {
const authHeader = tokenManager.getAuthHeader()
if (authHeader) {
config.headers.Authorization = authHeader
}
}
return config
})
// 响应拦截器
client.interceptors.response.use(
(response) => {
const data = response.data
// 检查业务状态码
if (data && typeof data.code === 'number') {
if (data.code === 0 || data.code === 200) {
return data
}
// 创建业务错误对象
const error = new Error(data?.message || data?.msg || '请求失败')
error.code = data?.code
error.data = data
// 业务码 401/403 只在响应拦截器处理,避免重复处理
if (data.code === 401 && typeof on401 === 'function') {
on401(error)
}
if (data.code === 403 && typeof on403 === 'function') {
on403(error)
}
// 抛出错误,业务代码可以捕获
return Promise.reject(error)
}
return data
},
(error) => {
// HTTP 状态码 401/403 只在错误拦截器处理
if (error.response?.status === 401 && typeof on401 === 'function') {
on401(error)
}
if (error.response?.status === 403 && typeof on403 === 'function') {
on403(error)
}
return Promise.reject(error)
}
)
return client
}
/**
* 默认导出的 C 端 Axios 实例
* 可在应用层覆盖配置
*/
export const clientAxios = createClientAxios()
export default clientAxios