rhb-server/mes-ui/rhb-app/utils/https.js
2025-10-20 11:14:41 +08:00

342 lines
8.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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