/** * HTTP工具类 - Vue2版本 * 基于uni-app的request封装 */ import { config } from './config.js' import userUtil from './UserUtil.js' // 基础配置 const baseURL = config.baseURL // 是否正在刷新 Token 的标记 let isRefreshing = false let requestQueue = [] // 添加拦截器 const httpInterceptor = { // 拦截前触发 invoke(options) { // 1. 非 http 开头需拼接地址 if (!options.url.startsWith('http')) { options.url = baseURL + options.url } // 2. 请求超时, 默认 60s options.timeout = config.timeout // 3. 添加小程序端请求头标识 options.header = { ...config.headers, ...options.header, } // 4. 添加 token 请求头标识 // 4. 添加 token 请求头标识 const userStore = userUtil.getLoginInfo() //const storage_token = uni.getStorageSync('storage_loginInfo')?.accessToken if (userStore) { const token = userStore?.accessToken options.header.Authorization = token } // else { // uni.showToast({ // icon: 'none', // duration: 3000, // title: '登录信息已过期,请重新登录!', // }) // uni.reLaunch({ // url: '/packageUser/pages/login' // }) // } }, } // 注册拦截器 uni.addInterceptor('request', httpInterceptor) uni.addInterceptor('uploadFile', httpInterceptor) /** * 请求函数 * @param {Object} options - 请求配置 * @returns {Promise} Promise对象 * 1. 返回 Promise 对象 * 2. 获取数据成功 * 2.1 提取核心数据 res.data * 2.2 添加类型,支持泛型 * 3. 获取数据失败 * 3.1 401错误 -> 清理用户信息,跳转到登录页 * 3.2 其他错误 -> 根据后端错误信息轻提示 * 3.3 网络错误 -> 提示用户换网络 */ function ajaxError(data) { uni.showToast({ title: data.msg || '请求出错,请重试', duration: 3000, icon: 'none', complete() { if (data.code === config.responseCode.TOKEN_EXPIRED || data.code === config.responseCode.TOKEN_INVALID || data.code === config.responseCode.TOKEN_MISSING) { // 清理用户信息 uni.removeStorageSync(config.storageKeys.LOGIN_INFO) uni.reLaunch({ url: '/packageUser/pages/login' }) } } }) } // 请求函数 export const http = (options) => { // 1. 返回 Promise 对象 return new Promise((resolve, reject) => { // 显示loading uni.showLoading({ title: '加载中...', mask: true }) uni.request({ ...options, // 响应成功 success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { if (res.data?.code == 0) { setTimeout(() => uni.hideLoading(), 2000); resolve(res.data.data) } else if (res.data?.code === config.responseCode.UNAUTHORIZED || res.data ?.code === config.responseCode.TOKEN_EXPIRED) { setTimeout(() => uni.hideLoading(), 2000); // Token 无效或过期 const originalRequest = options // 保存原始请求配置 const retryOriginalRequest = new Promise((resolveRetry, rejectRetry) => { // 将当前请求的配置、成功回调和失败回调都加入队列 requestQueue.push({ config: originalRequest, resolve: resolveRetry, reject: rejectRetry }); }); if (!isRefreshing) { // 如果没有在刷新,则开始刷新 isRefreshing = true doRefreshToken() .then(newTokenData => { userUtil.setLoginInfo(newTokenData); // 2. 重试队列中的所有请求 requestQueue.forEach(({ config, resolve, reject }) => { if (!config.header) config.header = {}; config.header.Authorization = newTokenData .accessToken; // 重新发起请求,并将结果传递给队列中请求的 Promise http(config).then(resolve).catch(reject); }); // 清空队列 requestQueue = [] // 3. 重试当前这个失败的请求 originalRequest.header.Authorization = newTokenData .accessToken http(originalRequest).then(resolve).catch(reject) }) .catch(error => { // 刷新 Token 失败! // 1. 清空队列,并通知所有请求失败 requestQueue.forEach(({ reject }) => reject(error)) requestQueue = [] // 2. 清除用户信息,强制跳转到登录页 userUtil.clearAll() uni.showToast({ icon: 'none', duration: 3000, title: '登录已过期,请重新登录', }) // 使用 reLaunch 确保用户无法返回 uni.reLaunch({ url: '/packageUser/pages/login' }) }) .finally(() => { // 无论成功失败,都释放刷新锁 isRefreshing = false }) } // 如果正在刷新,则将当前请求加入队列 retryOriginalRequest.then(resolve).catch(reject); } else { const msg = res.data?.msg uni.showToast({ icon: 'none', duration: 3000, title: msg || '请求错误', }); setTimeout(() => uni.hideLoading(), 2000); reject(new Error(msg || '请求错误')); } } else { const msg = res.data?.msg uni.showToast({ icon: 'none', duration: 3000, title: msg || '请求错误', }); setTimeout(() => uni.hideLoading(), 2000); reject(new Error(msg || '请求错误')); } }, // 响应失败(网络错误、超时等) fail(err) { uni.showToast({ icon: 'none', duration: 3000, title: '网络错误,换个网络试试', }); setTimeout(() => uni.hideLoading(), 2000); reject(err); // reject 网络错误 } }) }) } // 封装常用请求方法 export const request = { // GET请求 get(url, data = {}, options = {}) { return http({ url, method: 'GET', data, ...options }) }, // POST请求 post(url, data = {}, options = {}) { return http({ url, method: 'POST', data, ...options }) }, // PUT请求 put(url, data = {}, options = {}) { return http({ url, method: 'PUT', data, ...options }) }, // DELETE请求 delete(url, data = {}, options = {}) { return http({ url, method: 'DELETE', data, ...options }) }, // 文件上传 upload(url, filePath, name = 'file', formData = {}, options = {}) { return new Promise((resolve, reject) => { // 显示loading uni.showLoading({ title: '上传中...', mask: true }) uni.uploadFile({ url: url.startsWith('http') ? url : baseURL + url, filePath, name, formData, header: { ...config.headers, 'Authorization': uni.getStorageSync(config.storageKeys.LOGIN_INFO) ?.accessToken || '' }, success(res) { try { const data = JSON.parse(res.data) if (data.code === 0) { resolve(data.data) } else { uni.showToast({ title: data.msg || '上传失败', icon: 'none' }) reject(data) } } catch (e) { reject(e) } }, fail(err) { uni.showToast({ title: '上传失败', icon: 'none' }) reject(err) }, // 上传完成(无论成功还是失败) complete() { // 隐藏loading uni.hideLoading() } }) }) } } const doRefreshToken = () => { const loginInfo = userUtil.getLoginInfo() if (!loginInfo || !loginInfo.refreshToken) { // 如果连 refreshToken 都没有,直接拒绝,跳转登录 return Promise.reject(new Error('No refresh token available.')) } // 发起刷新 token 的请求 // 注意:这个请求本身不能被拦截器添加过期的 token,所以我们要手动构造一个不带 token 或使用 refresh token 的请求 // 假设你的刷新接口是 POST /system/auth/refresh-token,并且 refreshToken 通过 query 参数传递 const refreshUrl = `${baseURL}/system/auth/refresh-token?refreshToken=${loginInfo.refreshToken}` return new Promise((resolve, reject) => { uni.request({ url: refreshUrl, method: 'POST', success: (res) => { // 假设你的后端返回格式和普通请求一致,即 { code: 0, data: {...}, msg: '...' } if (res.statusCode >= 200 && res.statusCode < 300 && res.data?.code === 0) { // 刷新成功,res.data.data 是新的登录信息对象 resolve(res.data.data) } else { // 刷新失败,返回错误信息 reject(new Error(res.data?.msg || 'Failed to refresh token.')) } }, fail: (err) => { // 网络请求失败 reject(new Error('Network error while refreshing token.')) } }) }) } export default request