rhb-server/mes-ui/rhb-app/utils/https.js

342 lines
8.6 KiB
JavaScript
Raw Normal View History

2025-10-20 11:14:41 +08:00
/**
* 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