refactor(http): 重构 HTTP 请求模块使用 axios 替代 fetch
- 替换 fetch 为 axios 以提供更好的请求拦截和错误处理能力 - 添加请求拦截器自动处理 Authorization 头和时间戳 - 实现响应拦截器统一处理业务错误码和状态码 - 优化错误提示和认证失败后的跳转逻辑 - 新增 await-to-js 和 axios 依赖
This commit is contained in:
@@ -45,6 +45,8 @@
|
|||||||
"element-plus": "^2.13.6",
|
"element-plus": "^2.13.6",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
"vue-router": "^4.6.4"
|
"vue-router": "^4.6.4",
|
||||||
|
"await-to-js": "^3.0.0",
|
||||||
|
"axios": "^1.13.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,140 @@
|
|||||||
import { getBaseUrl } from './url.js'
|
import axios from 'axios'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { getBaseUrl } from './url.js'
|
||||||
|
|
||||||
async function request(path, options = {}) {
|
// baseURL 由主进程动态分配端口,通过 getBaseUrl() 运行时获取
|
||||||
const { headers = {}, silent = false, ...rest } = options
|
const axiosInstance = axios.create({
|
||||||
const url = `${getBaseUrl()}${path}`
|
baseURL: getBaseUrl(),
|
||||||
|
timeout: 300000,
|
||||||
const config = {
|
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json;charset=utf-8',
|
||||||
...headers,
|
|
||||||
},
|
},
|
||||||
...rest,
|
responseType: 'json',
|
||||||
}
|
})
|
||||||
|
|
||||||
|
// 请求拦截
|
||||||
|
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 {
|
try {
|
||||||
const res = await fetch(url, config)
|
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
|
||||||
|
|
||||||
if (!res.ok) {
|
// 明确的 200 成功,但需要按业务码再判断
|
||||||
const msg = await res.text().catch(() => '')
|
if (code === 0) {
|
||||||
const errMsg = `请求失败: ${res.status}${msg ? ' - ' + msg : ''}`
|
// 业务成功
|
||||||
if (!silent) ElMessage.error(errMsg)
|
return Promise.resolve(res)
|
||||||
return Promise.reject(new Error(errMsg))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 空响应
|
// 特殊业务码处理
|
||||||
const text = await res.text()
|
if (code === 401) {
|
||||||
if (!text) return Promise.resolve(null)
|
// 清除持久化的 Authorization,避免后续使用失效的头部
|
||||||
|
localStorage.removeItem('Authorization')
|
||||||
const data = JSON.parse(text)
|
sessionStorage.removeItem('Token')
|
||||||
return Promise.resolve(data)
|
// 延迟跳转,确保消息显示
|
||||||
} catch (err) {
|
setTimeout(() => {
|
||||||
if (!silent) ElMessage.error(err.message || '网络连接失败')
|
window.location.href = '/#/login'
|
||||||
return Promise.reject(err)
|
}, 500)
|
||||||
|
return Promise.reject(new Error('认证失败,请重新登录'))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default request
|
// 其余非 0 的业务码统一拦截提示,但不在这里显示 ElMessage
|
||||||
|
// 交由业务层使用 await-to-js 处理
|
||||||
|
return Promise.reject(new Error(msg || '请求失败'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非 2xx 按错误分支处理(通常会进入 error 拦截器)
|
||||||
|
return Promise.reject(new Error('请求失败'))
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('请求错误:', error)
|
||||||
|
|
||||||
|
if (error.response) {
|
||||||
|
// 服务器响应错误
|
||||||
|
const status = error.response.status
|
||||||
|
const message = error.response.data?.message || error.response.data?.msg || '请求失败'
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
@@ -1,26 +1,17 @@
|
|||||||
import request from './index.js'
|
import request from './index.js'
|
||||||
|
|
||||||
export function getAction(url, params) {
|
export function getAction(url, params) {
|
||||||
const query = params ? '?' + new URLSearchParams(params).toString() : ''
|
return request({ url, method: 'GET', params })
|
||||||
return request(`${url}${query}`, { method: 'GET' })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postAction(url, data, headers = {}) {
|
export function postAction(url, data, headers = {}) {
|
||||||
return request(url, {
|
return request({ url, method: 'POST', data, headers })
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function putAction(url, data) {
|
export function putAction(url, data) {
|
||||||
return request(url, {
|
return request({ url, method: 'PUT', data })
|
||||||
method: 'PUT',
|
|
||||||
body: JSON.stringify(data),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteAction(url, params) {
|
export function deleteAction(url, params) {
|
||||||
const query = params ? '?' + new URLSearchParams(params).toString() : ''
|
return request({ url, method: 'DELETE', params })
|
||||||
return request(`${url}${query}`, { method: 'DELETE' })
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user