rentease-app/utils/request.js

320 lines
6.7 KiB
JavaScript
Raw Normal View History

2026-04-20 06:23:11 +00:00
/**
* 请求拦截器封装
* 统一处理HTTP请求和响应
*/
import { getBaseUrl, getTimeout, getStorageKey, statusCode } from '../config/index.js'
// 请求队列(用于取消重复请求)
const pendingRequests = new Map()
// 是否正在刷新token
let isRefreshing = false
// 等待刷新token的请求队列
let refreshSubscribers = []
/**
* 生成请求唯一标识
*/
const generateRequestKey = (config) => {
const { url, method, data } = config
return `${method}_${url}_${JSON.stringify(data || {})}`
}
/**
* 添加请求到队列
*/
const addPendingRequest = (config) => {
const key = generateRequestKey(config)
if (!pendingRequests.has(key)) {
pendingRequests.set(key, config)
}
}
/**
* 从队列移除请求
*/
const removePendingRequest = (config) => {
const key = generateRequestKey(config)
pendingRequests.delete(key)
}
/**
* 取消重复请求
*/
const cancelPendingRequest = (config) => {
const key = generateRequestKey(config)
if (pendingRequests.has(key)) {
return true
}
return false
}
/**
* 获取请求头
*/
const getHeaders = (needAuth = true) => {
const headers = {
'Content-Type': 'application/json'
}
if (needAuth) {
const token = uni.getStorageSync(getStorageKey('token'))
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
}
return headers
}
/**
* 处理响应错误
* 优先使用后端返回的 message
*/
const handleResponseError = (response) => {
const { statusCode: code, data } = response
// 优先使用后端返回的 message
const message = data?.message
switch (code) {
case 400:
uni.showToast({
title: message || '请求参数错误',
icon: 'none'
})
break
case 401:
// Token过期清除登录状态并跳转登录页
uni.removeStorageSync(getStorageKey('token'))
uni.removeStorageSync(getStorageKey('userInfo'))
uni.showToast({
title: message || '登录已过期,请重新登录',
icon: 'none'
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
})
}, 1500)
break
case 403:
uni.showToast({
title: message || '没有权限执行此操作',
icon: 'none'
})
break
case 404:
uni.showToast({
title: message || '请求的资源不存在',
icon: 'none'
})
break
case 500:
uni.showToast({
title: message || '服务器错误,请稍后重试',
icon: 'none'
})
break
default:
uni.showToast({
title: message || '请求失败',
icon: 'none'
})
}
}
/**
* 统一请求方法
* @param {Object} options - 请求配置
* @param {boolean} needAuth - 是否需要认证
* @returns {Promise}
*/
const request = (options = {}, needAuth = true) => {
const baseUrl = getBaseUrl()
const timeout = getTimeout()
const config = {
url: options.url.startsWith('http') ? options.url : `${baseUrl}${options.url}`,
method: options.method || 'GET',
data: options.data || {},
header: { ...getHeaders(needAuth), ...options.header },
timeout: options.timeout || timeout
}
// 检查重复请求
if (cancelPendingRequest(config)) {
return Promise.reject(new Error('重复请求'))
}
// 添加请求到队列
addPendingRequest(config)
return new Promise((resolve, reject) => {
uni.request({
...config,
success: (response) => {
const { statusCode: code, data } = response
// 请求成功
if (code >= 200 && code < 300) {
// 如果后端返回的数据没有 code 字段,直接视为成功
if (!data.code || data.code === 200) {
resolve(data)
} else {
// 业务逻辑失败
uni.showToast({
title: data.message || '操作失败',
icon: 'none'
})
reject(data)
}
} else {
// HTTP错误
handleResponseError(response)
reject(response)
}
},
fail: (error) => {
uni.showToast({
title: '网络错误,请检查网络连接',
icon: 'none'
})
reject(error)
},
complete: () => {
// 从队列移除请求
removePendingRequest(config)
}
})
})
}
/**
* GET请求
*/
export const get = (url, params = {}, needAuth = true) => {
// 构建查询字符串
const queryString = Object.keys(params)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
.join('&')
const fullUrl = queryString ? `${url}?${queryString}` : url
return request({
url: fullUrl,
method: 'GET'
}, needAuth)
}
/**
* POST请求
*/
export const post = (url, data = {}, needAuth = true) => {
return request({
url,
method: 'POST',
data
}, needAuth)
}
/**
* PUT请求
*/
export const put = (url, data = {}, needAuth = true) => {
return request({
url,
method: 'PUT',
data
}, needAuth)
}
/**
* DELETE请求
*/
export const del = (url, data = {}, needAuth = true) => {
return request({
url,
method: 'DELETE',
data
}, needAuth)
}
/**
* 上传文件
*/
export const upload = (url, filePath, formData = {}, needAuth = true) => {
const baseUrl = getBaseUrl()
const token = uni.getStorageSync(getStorageKey('token'))
return new Promise((resolve, reject) => {
uni.uploadFile({
url: `${baseUrl}${url}`,
filePath,
name: 'file',
formData,
header: needAuth && token ? { 'Authorization': `Bearer ${token}` } : {},
success: (response) => {
const data = JSON.parse(response.data)
if (data.code === 200) {
resolve(data)
} else {
uni.showToast({
title: data.message || '上传失败',
icon: 'none'
})
reject(data)
}
},
fail: (error) => {
uni.showToast({
title: '上传失败',
icon: 'none'
})
reject(error)
}
})
})
}
/**
* 下载文件
*/
export const download = (url, needAuth = true) => {
const baseUrl = getBaseUrl()
const token = uni.getStorageSync(getStorageKey('token'))
return new Promise((resolve, reject) => {
uni.downloadFile({
url: `${baseUrl}${url}`,
header: needAuth && token ? { 'Authorization': `Bearer ${token}` } : {},
success: (response) => {
if (response.statusCode === 200) {
resolve(response)
} else {
reject(response)
}
},
fail: (error) => {
reject(error)
}
})
})
}
export default {
request,
get,
post,
put,
delete: del,
upload,
download
}