因为token定期过期,这时请求返回token过期,就需要拿着refreshToken去请求新的token,但是因为ajax是异步请求,所以会存在多个接口重复刷新token,引发报错。
解决办法:刷新一次token,其余请求拦截放入缓存中,等token刷新成功后在发起请求。
我是uiapp项目,以unapp为例,其余项目同理
1.封装请求函数,你的就是你自己的封装好的请求函数,在提示token异常需要重新刷新token时执行checkStatus函数,只执行第一个刷新token的请求,其余拦截,并将请求成功后的回调请求缓存
const http = (params) => {
//返回promise 对象
return new Promise((resolve, reject) => {
uni.request({
// 服务器url+参数中携带的接口具体地址
url: Host + params.url,
// 请求参数
data: params.data,
// 设置后端需要的常用的格式就好,特殊情况调用的时候单独设置
header: params.header || {
"Content-Type": "application/json;charset=utf-8",
"api-version": params.apiVersion ||'1.0',
"Authorization": uni.getStorageSync("accessToken")
},
method: params.method && params.method.toUpperCase() || 'POST',
dataType: params.dataType,//返回的数据格式,默认为JSON,特殊格式可以在调用的时候传入参数
responseType: params.responseType,//响应的数据类型
success: res => {
// 接口访问正常返回数据
if (res.statusCode == 200&&res.data.code === 200) {
//1. 操作成功返回数据
resolve(res.data)
} else {
//2. 操作不成功返回数据,以toast方式弹出响应信息,如后端未格式化非操作成功异常信息,则可以统一定义异常提示
if(res.data.code === 401){ //token异常
//checkStatus 是一个promise,必须resolve返回请求数据,不然请求接口无法获取返回结果
checkStatus(params).then(res=>{
resolve(res)
})
}else{
resolve(res.data)
if(res.data.msg){
uni.showToast({
icon: "none",
title: res.data.msg,
duration:3000
})
}
}
}
},
fail: function (e) {
if(e.errMsg === "request:fail timeout"){
uni.showToast({
icon: "none",
title: "网络连接超时,请稍后重试"
})
}
reject(e);
setTimeout(() =>{
uni.hideLoading();
}, 1500)
}
})
})
}
2.checkStatus函数,发送第一个刷新token请求,其余请求拦截,将刷新token后的回调请求存入缓存,写一个开关控制发送请求。
let isRefreshing = true;
function checkStatus(params) {
// 刷新token的函数,这需要添加一个开关,防止重复请求
if(isRefreshing){
referToken()
}
isRefreshing = false;
// 将token刷新成功后的回调请求缓存
const retryOriginalRequest = new Promise((resolve) => {
addSubscriber(()=> {
resolve(http(params))
})
});
return retryOriginalRequest;
}
3.token刷新函数
function referToken(){
const params={
"data":{
"refreshToken":uni.getStorageSync("refreshToken")
}
}
if(uni.getStorageSync("refreshToken")){
http({
url:`/oauth2/refreshToken`,
data:aes.encrypt(JSON.stringify(params)),
header:{
"Content-Type": "application/json;charset=utf-8",
"api-version": params.apiVersion ||'1.0'
}
}).then(res=>{
if(res.code===200){ uni.setStorageSync("refreshToken",res.data.refreshToken)
uni.setStorageSync("accessToken",res.data.accessToken)
//执行缓存中的请求
onAccessTokenFetched()
//延迟几秒再将刷新token的开关放开,不然偶尔还是会重复提交刷新token的请求
setTimeout(()=>{
isRefreshing = true
},3000)
}else{
//跳转至登录页面
}
})
// })
}
}
4.缓存数组
// 缓存
let subscribers = [];
function onAccessTokenFetched() {
subscribers.forEach((callback)=>{
callback()
})
subscribers = [];
}
function addSubscriber(callback) {
subscribers.push(callback)
}
5.注意事项
第一点:将第一个刷新token发送请求,其余刷新token拦截,并将刷新token后的回调请求存入缓存中时,执行该函数是一个promise,返回的数据通过该resolve,返回给调用接口。
checkStatus(params).then(res=>{
resolve(res)
})
第二点:当刷新token接口成功后,开放可刷新token的开关延迟几秒放开,不然也可能造成刷新请求重复提交
setTimeout(()=>{
sRefreshing = true
},3000)