249 lines
6.8 KiB
TypeScript
249 lines
6.8 KiB
TypeScript
/**
|
|
* HTTP请求工具
|
|
* 基于axios封装的统一请求实例
|
|
*/
|
|
|
|
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { envConfig } from '@/config/env'
|
|
|
|
// 请求响应接口
|
|
export interface ApiResponse<T = any> {
|
|
code: number
|
|
message: string
|
|
data: T
|
|
success: boolean
|
|
timestamp: number
|
|
}
|
|
|
|
// 请求配置接口
|
|
export interface RequestConfig extends AxiosRequestConfig {
|
|
// 是否显示loading
|
|
showLoading?: boolean
|
|
// 是否显示错误消息
|
|
showError?: boolean
|
|
// 是否需要token
|
|
needToken?: boolean
|
|
}
|
|
|
|
// 创建axios实例
|
|
const createAxiosInstance = (): AxiosInstance => {
|
|
const instance = axios.create({
|
|
baseURL: envConfig.apiBaseUrl,
|
|
timeout: 30000,
|
|
headers: {
|
|
'Content-Type': 'application/json;charset=UTF-8'
|
|
}
|
|
})
|
|
|
|
// 请求拦截器
|
|
instance.interceptors.request.use(
|
|
(config: any) => {
|
|
// 添加token
|
|
const token = localStorage.getItem('access_token')
|
|
console.log('🔑 请求拦截器 - Token状态:', {
|
|
hasToken: !!token,
|
|
tokenPreview: token ? `${token.substring(0, 20)}...` : 'null',
|
|
url: config.url,
|
|
needToken: config.needToken
|
|
})
|
|
|
|
if (token && config.needToken !== false) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
console.log('🔑 已添加Authorization头')
|
|
} else {
|
|
console.log('🔑 未添加Authorization头 - 原因:', !token ? '无token' : 'needToken=false')
|
|
}
|
|
|
|
// 添加请求ID用于追踪
|
|
config.headers['X-Request-ID'] = generateRequestId()
|
|
|
|
// 打印请求日志
|
|
if (envConfig.debug) {
|
|
console.log('🚀 Request:', {
|
|
url: config.url,
|
|
method: config.method,
|
|
params: config.params,
|
|
data: config.data,
|
|
headers: config.headers
|
|
})
|
|
}
|
|
|
|
return config
|
|
},
|
|
(error: AxiosError) => {
|
|
console.error('❌ Request Error:', error)
|
|
return Promise.reject(error)
|
|
}
|
|
)
|
|
|
|
// 响应拦截器
|
|
instance.interceptors.response.use(
|
|
(response: AxiosResponse<ApiResponse>) => {
|
|
const { data } = response
|
|
|
|
// 打印响应日志
|
|
if (envConfig.debug) {
|
|
console.log('✅ Response:', {
|
|
url: response.config.url,
|
|
status: response.status,
|
|
data: data
|
|
})
|
|
}
|
|
|
|
// 检查业务状态码
|
|
if (data.code === 200 || data.success) {
|
|
return data
|
|
}
|
|
|
|
// 处理业务错误
|
|
const errorMessage = data.message || '请求失败'
|
|
|
|
// 特殊错误码处理
|
|
switch (data.code) {
|
|
case 401:
|
|
console.warn('🚫 业务层401错误:', errorMessage)
|
|
// 只有在非登录接口时才处理401
|
|
if (!response.config.url?.includes('/auth/login')) {
|
|
handleUnauthorized()
|
|
} else {
|
|
ElMessage.error(errorMessage)
|
|
}
|
|
break
|
|
case 403:
|
|
ElMessage.error('没有权限访问该资源')
|
|
break
|
|
case 404:
|
|
ElMessage.error('请求的资源不存在')
|
|
break
|
|
case 500:
|
|
ElMessage.error('服务器内部错误')
|
|
break
|
|
default:
|
|
ElMessage.error(errorMessage)
|
|
}
|
|
|
|
return Promise.reject(new Error(errorMessage))
|
|
},
|
|
(error: AxiosError) => {
|
|
console.error('❌ Response Error:', error)
|
|
|
|
let errorMessage = '网络请求失败'
|
|
|
|
if (error.response) {
|
|
// 服务器响应错误
|
|
const { status, data } = error.response
|
|
|
|
switch (status) {
|
|
case 400:
|
|
errorMessage = '请求参数错误'
|
|
break
|
|
case 401:
|
|
errorMessage = '未授权,请重新登录'
|
|
console.warn('🚫 HTTP层401错误')
|
|
// 只有在非登录接口时才处理401
|
|
if (!error.config?.url?.includes('/auth/login')) {
|
|
handleUnauthorized()
|
|
}
|
|
break
|
|
case 403:
|
|
errorMessage = '拒绝访问'
|
|
break
|
|
case 404:
|
|
errorMessage = '请求地址不存在'
|
|
break
|
|
case 408:
|
|
errorMessage = '请求超时'
|
|
break
|
|
case 500:
|
|
errorMessage = '服务器内部错误'
|
|
break
|
|
case 502:
|
|
errorMessage = '网关错误'
|
|
break
|
|
case 503:
|
|
errorMessage = '服务不可用'
|
|
break
|
|
case 504:
|
|
errorMessage = '网关超时'
|
|
break
|
|
default:
|
|
errorMessage = (data as any)?.message || `请求失败 (${status})`
|
|
}
|
|
} else if (error.request) {
|
|
// 网络错误
|
|
errorMessage = '网络连接失败,请检查网络'
|
|
} else {
|
|
// 其他错误
|
|
errorMessage = error.message || '请求配置错误'
|
|
}
|
|
|
|
ElMessage.error(errorMessage)
|
|
return Promise.reject(error)
|
|
}
|
|
)
|
|
|
|
return instance
|
|
}
|
|
|
|
// 处理未授权
|
|
const handleUnauthorized = () => {
|
|
console.warn('🚫 收到401未授权响应')
|
|
|
|
// 检查当前页面是否是登录页,避免在登录页面重复处理
|
|
if (window.location.pathname === '/login') {
|
|
console.log('🚫 当前在登录页面,不处理401错误')
|
|
return
|
|
}
|
|
|
|
// 不立即清除认证信息,而是提示用户
|
|
ElMessageBox.confirm(
|
|
'登录状态已过期,请重新登录',
|
|
'提示',
|
|
{
|
|
confirmButtonText: '重新登录',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}
|
|
).then(() => {
|
|
// 用户确认后才清除认证信息
|
|
localStorage.removeItem('access_token')
|
|
localStorage.removeItem('refresh_token')
|
|
localStorage.removeItem('user_info')
|
|
|
|
// 跳转到登录页
|
|
window.location.href = '/login'
|
|
}).catch(() => {
|
|
// 用户取消,不清除认证信息,让用户继续操作
|
|
console.log('🚫 用户取消重新登录')
|
|
})
|
|
}
|
|
|
|
// 生成请求ID
|
|
const generateRequestId = (): string => {
|
|
return `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`
|
|
}
|
|
|
|
// 创建请求实例
|
|
export const request = createAxiosInstance()
|
|
|
|
// 导出请求方法
|
|
export const http = {
|
|
get: <T = any>(url: string, config?: RequestConfig) =>
|
|
request.get<any, ApiResponse<T>>(url, config),
|
|
|
|
post: <T = any>(url: string, data?: any, config?: RequestConfig) =>
|
|
request.post<any, ApiResponse<T>>(url, data, config),
|
|
|
|
put: <T = any>(url: string, data?: any, config?: RequestConfig) =>
|
|
request.put<any, ApiResponse<T>>(url, data, config),
|
|
|
|
delete: <T = any>(url: string, config?: RequestConfig) =>
|
|
request.delete<any, ApiResponse<T>>(url, config),
|
|
|
|
patch: <T = any>(url: string, data?: any, config?: RequestConfig) =>
|
|
request.patch<any, ApiResponse<T>>(url, data, config)
|
|
}
|
|
|
|
export default request
|