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
|