rentease-app/utils/request.js

328 lines
6.9 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.

/**
* 请求拦截器封装
* 统一处理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
}
/**
* 处理响应错误
* 根据后端统一响应格式处理错误
* 后端格式: { code: 400/401/403/404/500, message: 'xxx', data: null }
*/
const handleResponseError = (response) => {
const { statusCode: httpCode, data } = response
// 优先使用后端返回的 message
const message = data?.message || '请求失败'
switch (httpCode) {
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: httpCode, data } = response
// HTTP 请求成功 (2xx)
if (httpCode >= 200 && httpCode < 300) {
// 根据后端统一响应格式处理
// 后端格式: { code: 200, message: 'xxx', data: xxx }
// 业务逻辑成功
if (data.code === 200) {
resolve(data)
} else {
// 业务逻辑失败(如参数错误等)
// 后端格式: { code: 400/403/404/500, message: 'xxx', data: null }
const errorMessage = data.message || '操作失败'
uni.showToast({
title: errorMessage,
icon: 'none'
})
reject(data)
}
} else {
// HTTP 错误4xx, 5xx
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 {
const errorMessage = data.message || '上传失败'
uni.showToast({
title: errorMessage,
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
}