refactor(http): 重构 HTTP 请求模块使用 axios 替代 fetch

- 替换 fetch 为 axios 以提供更好的请求拦截和错误处理能力
- 添加请求拦截器自动处理 Authorization 头和时间戳
- 实现响应拦截器统一处理业务错误码和状态码
- 优化错误提示和认证失败后的跳转逻辑
- 新增 await-to-js 和 axios 依赖
This commit is contained in:
houakang
2026-04-10 10:05:54 +08:00
parent 22fe6e069c
commit d7d089cc2a
3 changed files with 137 additions and 42 deletions

View File

@@ -1,38 +1,140 @@
import { getBaseUrl } from './url.js'
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { getBaseUrl } from './url.js'
async function request(path, options = {}) {
const { headers = {}, silent = false, ...rest } = options
const url = `${getBaseUrl()}${path}`
// baseURL 由主进程动态分配端口,通过 getBaseUrl() 运行时获取
const axiosInstance = axios.create({
baseURL: getBaseUrl(),
timeout: 300000,
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
responseType: 'json',
})
const config = {
headers: {
'Content-Type': 'application/json',
...headers,
},
...rest,
// 请求拦截
axiosInstance.interceptors.request.use((config) => {
config.headers = config.headers || {}
let Authorization = localStorage.getItem('Authorization')
// 优先使用本地持久化的 Authorization 头(完整值)
config.headers.Authorization = Authorization || ''
if ('get' === config?.method?.toLowerCase()) {
if (config.params) {
config.params.timestamp = new Date().getTime()
}
}
// 移除敏感信息日志
// console.log(config, 'axios request.use config')
return config
})
axiosInstance.interceptors.response.use(
(response) => {
// 移除敏感信息日志
// console.log(response, 'response response')
// 若请求为二进制下载blob直接透传响应交由调用方自行处理
try {
const isBlob = response?.config?.responseType === 'blob'
if (isBlob) {
// 仍然尝试持久化可能返回的 Authorization
const respHeaders = response?.headers || {}
const newAuthorization = respHeaders['authorization'] || respHeaders['Authorization']
if (newAuthorization && typeof newAuthorization === 'string') {
localStorage.setItem('Authorization', newAuthorization)
}
return response
}
} catch (e) {
console.log(e)
// 忽略检查失败
}
// 如果响应头里带有 Authorization则使用 useStorage 持久化到 localStorage
// 以便后续请求自动携带该请求头
try {
const respHeaders = response?.headers || {}
const newAuthorization = respHeaders['authorization'] || respHeaders['Authorization']
if (newAuthorization && typeof newAuthorization === 'string') {
localStorage.setItem('Authorization', newAuthorization)
}
} catch (e) {
// 忽略持久化失败,避免影响主流程
console.warn('持久化 Authorization 失败:', e)
}
if (response.status === 200) {
const res = response.data || {}
const code = res.code
const msg = res.message || res.msg
try {
const res = await fetch(url, config)
// 明确的 200 成功,但需要按业务码再判断
if (code === 0) {
// 业务成功
return Promise.resolve(res)
}
if (!res.ok) {
const msg = await res.text().catch(() => '')
const errMsg = `请求失败: ${res.status}${msg ? ' - ' + msg : ''}`
if (!silent) ElMessage.error(errMsg)
return Promise.reject(new Error(errMsg))
// 特殊业务码处理
if (code === 401) {
// 清除持久化的 Authorization避免后续使用失效的头部
localStorage.removeItem('Authorization')
sessionStorage.removeItem('Token')
// 延迟跳转,确保消息显示
setTimeout(() => {
window.location.href = '/#/login'
}, 500)
return Promise.reject(new Error('认证失败,请重新登录'))
}
// 其余非 0 的业务码统一拦截提示,但不在这里显示 ElMessage
// 交由业务层使用 await-to-js 处理
return Promise.reject(new Error(msg || '请求失败'))
}
// 空响应
const text = await res.text()
if (!text) return Promise.resolve(null)
// 非 2xx 按错误分支处理(通常会进入 error 拦截器)
return Promise.reject(new Error('请求失败'))
},
(error) => {
console.error('请求错误:', error)
const data = JSON.parse(text)
return Promise.resolve(data)
} catch (err) {
if (!silent) ElMessage.error(err.message || '网络连接失败')
return Promise.reject(err)
}
}
if (error.response) {
// 服务器响应错误
const status = error.response.status
const message = error.response.data?.message || error.response.data?.msg || '请求失败'
export default request
switch (status) {
case 400:
ElMessage.error(`无效的请求参数:${message}`)
break
case 401:
// 清除持久化的 Authorization避免后续使用失效的头部
localStorage.removeItem('Authorization')
ElMessage.error('未授权访问或登录已过期,请重新登录')
break
case 403:
ElMessage.error('访问被拒绝')
break
case 404:
ElMessage.error('资源未找到')
break
case 500:
ElMessage.error('服务器内部错误')
break
case 502:
case 503:
case 504:
ElMessage.error('服务暂时不可用,请稍后重试')
break
default:
ElMessage.error(`请求失败: ${message}`)
}
} else if (error.request) {
// 网络错误
ElMessage.error('网络连接失败,请检查网络连接')
} else {
// 其他错误
ElMessage.error('请求发送失败')
}
return Promise.reject(error)
},
)
export default axiosInstance

View File

@@ -1,26 +1,17 @@
import request from './index.js'
export function getAction(url, params) {
const query = params ? '?' + new URLSearchParams(params).toString() : ''
return request(`${url}${query}`, { method: 'GET' })
return request({ url, method: 'GET', params })
}
export function postAction(url, data, headers = {}) {
return request(url, {
method: 'POST',
body: JSON.stringify(data),
headers,
})
return request({ url, method: 'POST', data, headers })
}
export function putAction(url, data) {
return request(url, {
method: 'PUT',
body: JSON.stringify(data),
})
return request({ url, method: 'PUT', data })
}
export function deleteAction(url, params) {
const query = params ? '?' + new URLSearchParams(params).toString() : ''
return request(`${url}${query}`, { method: 'DELETE' })
return request({ url, method: 'DELETE', params })
}