由于項目中需要用戶在token失效時無感知刷新token
遇到問題是如何防止多次刷新token,以及刷新token后再次請求之前的接口
參考鏈接 : https://segmentfault.com/a/1190000020210980
兩個接口幾乎同時發(fā)起和返回,第一個接口會進(jìn)入刷新token后重試的流程,而第二個接口需要先存起來,然后等刷新token后再重試。同樣,如果同時發(fā)起三個請求,此時需要緩存后兩個接口,等刷新token后再重試。由于接口都是異步的,處理起來會有點麻煩。
當(dāng)?shù)诙€過期的請求進(jìn)來,token正在刷新,我們先將這個請求存到一個數(shù)組隊列中,想辦法讓這個請求處于等待中,一直等到刷新token后再逐個重試清空請求隊列。
那么如何做到讓這個請求處于等待中呢?為了解決這個問題,我們得借助Promise。將請求存進(jìn)隊列中后,同時返回一個Promise,讓這個Promise一直處于Pending狀態(tài)(即不調(diào)用resolve),此時這個請求就會一直等,只要我們不執(zhí)行resolve,這個請求就會一直在等待。當(dāng)刷新請求的接口返回來后,我們再調(diào)用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,
//服務(wù)器本地上傳文件地址
fileUrl: base.baseUrl,
// 服務(wù)器上傳圖片默認(rèn)url
defaultUploadUrl: "api/common/v1/upload_image",
//設(shè)置請求頭(如果使用報錯跨域問題,可能是content-type請求類型和后臺那邊設(shè)置的不一致)
header: {
'Content-Type': 'application/json;charset=UTF-8',
// 'project_token': base.projectToken, //項目token(可刪除)
}
});
// 添加獲取七牛云token的方法
$http.getQnToken = function(callback) {
//該地址需要開發(fā)者自行配置(每個后臺的接口風(fēng)格都不一樣)
$http.get("api/common/v1/qn_upload").then(data => {
/*
*接口返回參數(shù):
*visitPrefix:訪問文件的域名
*token:七牛云上傳token
*folderPath:上傳的文件夾
*region: 地區(qū) 默認(rèn)為: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) {
// 文件最大字節(jié): options.maxSize 可以在調(diào)用方法的時候加入?yún)?shù)
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;
}
//請求結(jié)束
$http.requestEnd = function(options) {
//判斷當(dāng)前接口是否需要加載動畫
if (options.load) {
// 關(guān)閉加載動畫
}
}
const code_invalid =401; //token驗證失敗錯誤碼
//enterpriseList 為token失效后,需要重新登錄的接口
const enterpriseList = ["接口1","接口2"]
//所有接口數(shù)據(jù)處理(此方法需要開發(fā)者根據(jù)各自的接口返回類型修改,以下只是模板)
$http.dataFactory = async function(res) {
console.log("接口請求數(shù)據(jù)222", {
url: res.url,
resolve: res.response,
header: res.header,
data: res.data,
method: res.method,
});
return res
};
// 是否正在刷新的標(biāo)記
let isRefreshing = false
// 重試隊列,每一項將是一個待執(zhí)行的函數(shù)形式
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, "===無效 無權(quán)限=====")
// 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() {
// 已經(jīng)刷新了token,將所有隊列中的請求進(jìn)行重試
console.log(requests,'=------接口數(shù)量----');
requests.forEach(cb => cb())
// 重試完了別忘了清空這個隊列(掘金評論區(qū)同學(xué)指點)
isRefreshing = false
requests = []
resolve(apiRequest(url, params, opts))
})
} else {
// 其它延時執(zhí)行
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);
}
// 返回正確的結(jié)果(then接受數(shù)據(jù))
return resolve(httpData.data);
} else {
// 返回錯誤的結(jié)果(catch接受數(shù)據(jù))
return reject({
statusCode: res.response.statusCode,
// errMsg: "【request】數(shù)據(jù)工廠驗證不通過",
errMsg: response.message,
data: res.data
});
}
})
}
// 錯誤回調(diào)
$http.requestError = function(e) {
console.log(e, "========錯誤回調(diào)函數(shù)========")
// e.statusCode === 0 是參數(shù)效驗錯誤拋出的
if (e.statusCode === 0) {
throw e;
} else {
console.log(e, "=======錯誤回調(diào)========");
uni.showToast({
title: "網(wǎng)絡(luò)錯誤,請檢查一下網(wǎng)絡(luò)",
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(() => {
})
}
調(diào)用接口
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
}
頁面中調(diào)用接口 在vue文件中
首先導(dǎo)入api
import {
request1,
request2,
} from '@/config/apiRequest'
export default{
name:'applyLoan',
created(){
//需要參數(shù)調(diào)用
/*
request1({參數(shù):value}).then(res => {
console.log(res,'----res')
})
*/
//不需要參數(shù)的調(diào)用
request1().then(res => {
console.log(res,'----res')
})
}
}