由于项目中需要用户在token失效时无感知刷新token
遇到问题是如何防止多次刷新token,以及刷新token后再次请求之前的接口
参考链接 : https://segmentfault.com/a/1190000020210980
两个接口几乎同时发起和返回,第一个接口会进入刷新token后重试的流程,而第二个接口需要先存起来,然后等刷新token后再重试。同样,如果同时发起三个请求,此时需要缓存后两个接口,等刷新token后再重试。由于接口都是异步的,处理起来会有点麻烦。
当第二个过期的请求进来,token正在刷新,我们先将这个请求存到一个数组队列中,想办法让这个请求处于等待中,一直等到刷新token后再逐个重试清空请求队列。
那么如何做到让这个请求处于等待中呢?为了解决这个问题,我们得借助Promise。将请求存进队列中后,同时返回一个Promise,让这个Promise一直处于Pending状态(即不调用resolve),此时这个请求就会一直等,只要我们不执行resolve,这个请求就会一直在等待。当刷新请求的接口返回来后,我们再调用resolve,逐个重试。
下面以zhouWei-request插件为例
定义请求类request.js
import request from "@/plugins/request";
import base from '@/config/baseUrl';
import {
toSilentAuthorization
} from '@/config/authLogin';
//可以new多个request来支持多个域名请求
let $http = new request({
//接口请求地址
baseUrl: base.baseUrl,
//服务器本地上传文件地址
fileUrl: base.baseUrl,
// 服务器上传图片默认url
defaultUploadUrl: "api/common/v1/upload_image",
//设置请求头(如果使用报错跨域问题,可能是content-type请求类型和后台那边设置的不一致)
header: {
'Content-Type': 'application/json;charset=UTF-8',
// 'project_token': base.projectToken, //项目token(可删除)
}
});
// 添加获取七牛云token的方法
$http.getQnToken = function(callback) {
//该地址需要开发者自行配置(每个后台的接口风格都不一样)
$http.get("api/common/v1/qn_upload").then(data => {
/*
*接口返回参数:
*visitPrefix:访问文件的域名
*token:七牛云上传token
*folderPath:上传的文件夹
*region: 地区 默认为:SCN
*/
callback({
visitPrefix: data.visitPrefix,
token: data.token,
folderPath: data.folderPath
});
});
}
//请求开始拦截器
$http.requestStart = function(options) {
// console.log("请求开始", options);
if (options.load) {
//打开加载动画
}
// 图片上传大小限制
if (options.method == "FILE" && options.maxSize) {
// 文件最大字节: options.maxSize 可以在调用方法的时候加入参数
let maxSize = options.maxSize;
for (let item of options.files) {
if (item.size > maxSize) {
setTimeout(() => {
uni.showToast({
title: "图片过大,请重新上传",
icon: "none"
});
}, 500);
return false;
}
}
}
console.log('-------requestConfig-----');
options.header['user_token'] = store.state.userInfo.token;
return options;
}
//请求结束
$http.requestEnd = function(options) {
//判断当前接口是否需要加载动画
if (options.load) {
// 关闭加载动画
}
}
const code_invalid =401; //token验证失败错误码
//enterpriseList 为token失效后,需要重新登录的接口
const enterpriseList = ["接口1","接口2"]
//所有接口数据处理(此方法需要开发者根据各自的接口返回类型修改,以下只是模板)
$http.dataFactory = async function(res) {
console.log("接口请求数据222", {
url: res.url,
resolve: res.response,
header: res.header,
data: res.data,
method: res.method,
});
return res
};
// 是否正在刷新的标记
let isRefreshing = false
// 重试队列,每一项将是一个待执行的函数形式
let requests = []
async function apiRequest(url, params, opts) {
const res = await $http.request({
url: url,
method: opts['method'] || 'GET', // POST、GET、PUT、DELETE、JSONP,具体说明查看官方文档
data: params,
})
return new Promise((resolve, reject) => {
if (res.response.data.code && res.response.data.code == 401) {
console.log(res.response.data.code, "===无效 无权限=====")
// token失效
//用户添加token校验
//if(!store.state.userInfo.phoneAuth){
//if (res.response.data.code ==401) {
for (let i = 0; i < enterpriseList.length; i++) {
if (res.url.indexOf(enterpriseList[i]) > -1) {
if (!isRefreshing) {
isRefreshing = true
return toSilentAuthorization(1, function() {
// 已经刷新了token,将所有队列中的请求进行重试
console.log(requests,'=------接口数量----');
requests.forEach(cb => cb())
// 重试完了别忘了清空这个队列(掘金评论区同学指点)
isRefreshing = false
requests = []
resolve(apiRequest(url, params, opts))
})
} else {
// 其它延时执行
return requests.push(() => {
resolve(apiRequest(url, params, opts))
})
}
}
//}
//}
}
} else if (res.response.data.code && (res.response.data.code == 200)) {
let httpData = res.response.data;
if (typeof(httpData) == "string") {
httpData = JSON.parse(httpData);
}
// 返回正确的结果(then接受数据)
return resolve(httpData.data);
} else {
// 返回错误的结果(catch接受数据)
return reject({
statusCode: res.response.statusCode,
// errMsg: "【request】数据工厂验证不通过",
errMsg: response.message,
data: res.data
});
}
})
}
// 错误回调
$http.requestError = function(e) {
console.log(e, "========错误回调函数========")
// e.statusCode === 0 是参数效验错误抛出的
if (e.statusCode === 0) {
throw e;
} else {
console.log(e, "=======错误回调========");
uni.showToast({
title: "网络错误,请检查一下网络",
icon: "none"
});
}
}
export default apiRequest;
刷新token,authLogin.js
import{refreshToken} from './apiRequest.js'
import store from '@/store';
function toSilentAuthorization(type,callback){
const _this = this;
apiSilentLogin({
code:'value值',
type:'value值'
}).then(res => {
console.log(res,'======res------');
if(!res) {
callback('')
uni.reLaunch({
url:'回到首页地址'
})
return
}
store.commit('setUserInfo', {...res.info,...userInfo});
console.log(res,'----->getPhone');
callback(res)
}).catch(err=>{
console.log('登录-----',err);
uni.reLaunch({
url:'回到首页地址'
})
}).finally(() => {
})
}
调用接口
apiRequest.js
import apiRequest from './request.js';
const request1 = (params) => apiRequest('接口地址1', params, { method: 'POST' })
const request2 = (params) => apiRequest('接口地址2', params, { method: 'GET' })
const refreshToken = (params) => apiRequest('接口地址3', params, { method: 'POST' })
export {
request1,
request2,
refreshToken
}
页面中调用接口 在vue文件中
首先导入api
import {
request1,
request2,
} from '@/config/apiRequest'
export default{
name:'applyLoan',
created(){
//需要参数调用
/*
request1({参数:value}).then(res => {
console.log(res,'----res')
})
*/
//不需要参数的调用
request1().then(res => {
console.log(res,'----res')
})
}
}