request.js
import { rootUrl } from "@/config/app-config.js"
import handleCache from '@/utils/cache/cache.js';
import { showToast } from "@/utils/vant"
import { clearAccountInfo } from '@/utils/clear/clear';
import { filterUnBlackRes } from '@/hooks/black-config/black-config-api';
import { store } from '@/store'; // store实例
import { commonBus } from '@/utils/bus/bus';
let isTokenRefreshing = false; // token是否正在刷新
let requestList = []; //正在等待请求的接口队列
const service = (options) => {
return new Promise((resolve, reject) => {
handleRequest(options, resolve, reject)
})
}
function handleRequest(options, resolve, reject) {
/* 基本请求配置 */
const requestConfig = {
url: `${rootUrl}${options.url}`,
timeout: 60000, // 请求超时时间 毫秒
withCredentials: true, // 异步请求时是否携带cookie
header: {
// 设置后端需要的传参类型
'Content-Type': 'application/json',
Client: 'front', // 所属客户端
// "Authorization": "",
// "X-Requested-With": "XMLHttpRequest",
},
method: options.method.toUpperCase(),
}
/* 根据传参自定义部分请求配置*/
if (options.data || options.params) { //请求的参数
requestConfig.data = options.data || options.params
}
if (options.params && requestConfig.method == 'POST') { //处理post请求且需要接口拼参的情况
requestConfig.header['Content-Type'] = 'application/x-www-form-urlencoded'
}
if (options.headers) {
Object.assign(requestConfig.header, options.headers)
}
if (options.responseType) {
requestConfig.responseType = options.responseType
}
if (options.timeout) {
requestConfig.timeout = options.timeout
}
if (options.apiConfig) {
requestConfig.apiConfig = options.apiConfig
}
// console.log(requestConfig)
/* 处理token相关 */
let tokenInfo = '';
if (!options.url.includes('userLogin/refreshtoken')) { //判断是否为刷新token请求--否
tokenInfo = handleCache.get('accessToken') || '';
} else { //判断是否为刷新token请求--是
tokenInfo = handleCache.get('refreshToken') || '';
}
if (tokenInfo) { //配置请求token
requestConfig.header['Authorization'] = tokenInfo;
}
/* 接口请求成功的处理函数 */
requestConfig.success = (response) => {
// console.log(response)
//和uni.request不同,uni.uploadFile返回结果是字符串对象,需要转一下格式
if (requestConfig.uploadType == 'formdataUpload' && response.data) {
response.data = JSON.parse(response.data)
}
const res = response.data;
if (res.code == 100009 || res.code == 100005) {
// 对通用token令牌过期或验证失败的处理
if (!isTokenRefreshing) {
//判断当前不处于刷新阶段--则进行刷新操作
if (!store.state.accountInfo.refreshToken) {
//判断刷新token也不存在,则直接判定登录失效
clearAccountInfo();
if (requestConfig?.apiConfig?.unMsg !== true) {
showToast('您尚未登录或登录已过期,请登录');
}
return resolve({
code: 900009,
msg: '您尚未登录或登录已过期,请登录',
});
}
isTokenRefreshing = true; // 上锁
const refreshOptions = {
url: `/iam/userLogin/refreshtoken`,
method: 'get',
};
return service(refreshOptions)
.then((_res) => {
if (_res.code == 200000) {
//刷新成功--将新的通用token和刷新token都进行更新;并执行requestList等待队列里的接口请求
// 保存通用token
let accessTokenExpireTime = _res.data.expire_in / 1000;
handleCache.set('accessToken', _res.data.access_token, accessTokenExpireTime);
// 保存刷新token
let refreshTokenExpireTime = _res.data.refresh_expire_in / 1000;
handleCache.set('refreshToken', JSON.stringify(_res.data.refresh_token), refreshTokenExpireTime);
//执行后续推入到requestList队列中的请求,(requestList中存的不是请求参数,而是请求的Promise函数,这里直接拿来执行就好)
requestList.forEach((run) => run());
//将请求队列置空
requestList = [];
//重新执行一下本次未执行成功的请求并返回
return service(options);
} else {
let needReload = false;
if (_res.code == 100010) {
//刷新token也过期了--移除用户登录相关信息
needReload = true; //需要执行页面刷新
clearAccountInfo();
// return Promise.reject(new Error(_res.msg || "Error"));
_res.msg = '您尚未登录或登录已过期,请登录';
}
if (_res?.msg && requestConfig?.apiConfig?.unMsg !== true) {
showToast(_res.msg);
}
if (needReload) {
//需要进行页面刷新
let timer = setTimeout(() => {
commonBus.emit('loginExpire'); //利用事件总线在app.vue里监听此事件,并做对应处理
clearTimeout(timer);
}, 150);
}
return resolve(_res);
}
})
.catch(() => {
showToast('服务器开小差了呢,请稍后再试~');
})
.finally(() => {
isTokenRefreshing = false; //解锁
});
} else {
//判断当前处于token刷新阶段--将后续接口推入等待请求队列,等待刷新token接口请求完毕后执行后续请求队列
return new Promise((resolve) => {
//这里加入的是一个promise的解析函数,将响应的config配置对应解析的请求函数存到requestList中,等到刷新token回调后再执行
requestList.push(() => {
resolve(service(options));
});
});
}
} else {
// 其他情况则返回结果,对应状态code需在具体请求函数里判断
if (res.code !== 200000 && res.code !== 100010 && res?.msg && requestConfig?.apiConfig?.unMsg !== true) {
showToast(res.msg);
}
filterUnBlackRes(response, options.url); //剔除用户拉黑或被拉黑列表中的数据
return resolve(res);
}
}
/* 接口请求失败的处理函数*/
requestConfig.fail = (error) => {
if (handleCache.get('loadWxPayOpenId') != 'yes') {
// showToast(error.message || '服务器开小差了呢,请稍后再试~');
showToast('服务器开小差了呢,请稍后再试~');
}
return Promise.reject(error);
}
if (requestConfig.header['Content-Type'] == 'multipart/form-data') { //formdata为浏览器对象,app不支持;这里做处理
requestConfig.header['Content-Type'] = 'application/x-www-form-urlencoded'
requestConfig.uploadType = 'formdataUpload' //用于区分fomrdata文件上传
requestConfig.filePath = requestConfig.data.path
requestConfig.name = 'file'
// requestConfig.formData = requestConfig.data
delete requestConfig.data
uni.uploadFile(requestConfig)
} else {
uni.request(requestConfig)
}
}
export default service;
demo.js
// demo页
import request from '@/services/config/request';
/**
* 这是一个get请求,使用params传参
* @param {*} params 所有参数
*/
async function getVideoInfo(params) {
return request({
url: `/video/getvideo`,
method: 'get',
params: params,
});
}
/**
* 这是一个get请求,直接在接口里带参
*/
async function getVideoInfoById(id) {
return request({
url: `/video/getvideo/${id}`,
method: 'get',
});
}
/**
* 这是一个post请求,使用data传参
* @param {*} params 所有参数
* @param {String} type 类型
* @param {Number} id 序号
*/
async function addVideoInfo(params) {
return request({
url: `/video/addvideo`,
method: 'post',
data: params,
});
}
export default {
getVideoInfo,
getVideoInfoById,
addVideoInfo,
};
调用
import {getVideoInfo} from "./demo.js"
const params = {
id: xxx,
status: xxx
}
getVideoInfo(params).then(()=>{
}).catch(()=>{
}).finally(()=>{
})