342 lines
8.6 KiB
JavaScript
342 lines
8.6 KiB
JavaScript
/**
|
||
* 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 |