mi-assessment/uniapp/api/request.js
2026-02-09 14:45:06 +08:00

229 lines
5.1 KiB
JavaScript

/**
* 请求封装模块
* 封装 uni.request 支持 GET/POST/PUT/DELETE
* 实现 JWT Token 自动携带
* 实现 401 状态码拦截和重新认证
* 支持请求重试机制
* 支持请求取消机制
* Requirements: 1.2, 1.4
*/
import { getToken, removeToken, removeUserInfo } from '../utils/storage'
import config from '../config/index'
// 从统一配置获取 API 基础地址
const BASE_URL = config.API_BASE_URL
// 存储进行中的请求任务,用于取消
const pendingRequests = new Map()
/**
* 生成请求唯一标识
* @param {Object} options 请求配置
* @returns {string}
*/
function generateRequestKey(options) {
const { url, method = 'GET', data = {} } = options
return `${method}:${url}:${JSON.stringify(data)}`
}
/**
* 处理401错误 - 清除token
* Property 2: Auth Redirect on Invalid Token
*/
export function handleUnauthorized() {
removeToken()
removeUserInfo()
}
/**
* 延迟函数
* @param {number} ms 延迟毫秒数
*/
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms))
/**
* 取消指定请求
* @param {string} requestKey 请求标识
*/
export function cancelRequest(requestKey) {
if (pendingRequests.has(requestKey)) {
const task = pendingRequests.get(requestKey)
if (task && typeof task.abort === 'function') {
task.abort()
}
pendingRequests.delete(requestKey)
}
}
/**
* 取消所有进行中的请求
*/
export function cancelAllRequests() {
pendingRequests.forEach((task) => {
if (task && typeof task.abort === 'function') {
task.abort()
}
})
pendingRequests.clear()
}
/**
* 取消指定URL前缀的所有请求
* @param {string} urlPrefix URL前缀
*/
export function cancelRequestsByPrefix(urlPrefix) {
pendingRequests.forEach((task, key) => {
if (key.includes(urlPrefix)) {
if (task && typeof task.abort === 'function') {
task.abort()
}
pendingRequests.delete(key)
}
})
}
/**
* 发起请求(带重试和取消机制)
* @param {Object} options 请求配置
* @param {number} retryCount 当前重试次数
* @returns {Promise}
*/
export function request(options, retryCount = 0) {
const {
url,
method = 'GET',
data = {},
header = {},
needAuth = true,
retry = true,
cancelable = true,
cancelPrevious = false
} = options
const requestKey = generateRequestKey(options)
if (cancelPrevious && pendingRequests.has(requestKey)) {
cancelRequest(requestKey)
}
return new Promise((resolve, reject) => {
const token = getToken()
const requestHeader = {
'Content-Type': 'application/json',
...header
}
if (needAuth && token) {
requestHeader['Authorization'] = `Bearer ${token}`
}
const requestTask = uni.request({
url: `${BASE_URL}${url}`,
method,
data,
header: requestHeader,
timeout: config.REQUEST_TIMEOUT,
success: (res) => {
if (cancelable) {
pendingRequests.delete(requestKey)
}
const { statusCode, data: responseData } = res
if (statusCode === 401) {
handleUnauthorized()
reject(new Error('未授权,请重新登录'))
return
}
if (statusCode >= 400) {
const errorMsg = responseData?.message || '请求失败'
reject(new Error(errorMsg))
return
}
if (responseData.success === false) {
reject(new Error(responseData.message || '操作失败'))
return
}
resolve(responseData)
},
fail: async (err) => {
if (cancelable) {
pendingRequests.delete(requestKey)
}
if (err.errMsg && err.errMsg.includes('abort')) {
reject(new Error('请求已取消'))
return
}
console.error('Request failed:', err)
if (retry && retryCount < config.REQUEST_RETRY_COUNT) {
console.log(`请求失败,${config.REQUEST_RETRY_DELAY}ms 后进行第 ${retryCount + 1} 次重试...`)
await delay(config.REQUEST_RETRY_DELAY)
try {
const result = await request(options, retryCount + 1)
resolve(result)
} catch (retryErr) {
reject(retryErr)
}
return
}
reject(new Error('网络连接失败,请检查网络设置'))
}
})
if (cancelable && requestTask) {
pendingRequests.set(requestKey, requestTask)
}
})
}
/**
* GET 请求
*/
export function get(url, data = {}, options = {}) {
return request({ url, method: 'GET', data, ...options })
}
/**
* POST 请求
*/
export function post(url, data = {}, options = {}) {
return request({ url, method: 'POST', data, ...options })
}
/**
* PUT 请求
*/
export function put(url, data = {}, options = {}) {
return request({ url, method: 'PUT', data, ...options })
}
/**
* DELETE 请求
*/
export function del(url, data = {}, options = {}) {
return request({ url, method: 'DELETE', data, ...options })
}
export default {
request,
get,
post,
put,
del,
handleUnauthorized,
cancelRequest,
cancelAllRequests,
cancelRequestsByPrefix
}