保持登录的实现有很多,都有个自的优缺点,作为前端当然最喜欢后台注入cookie,后台自己管理session时效。这当然只是期望,还是很多用token和refresnToken的机制。
而管理token和刷新token就是一个问题了,比如判断过期时间到了去刷新token之后再请求,同时并发的请求你不能每个都去刷新一次token,只刷新一个,那并发的请求还是老的token。
解决的方法有两种,一种是请求之前处理,把同时并发的请求都挂起,用promise。这个方法不好的就是要新增白名单,那些不需要刷新token的接口要排除:
自己写一个模拟接口,如果是get1的就延迟三秒返回:
const http=require('http');
http.createServer(function(requset,response){
response.setHeader('Access-Control-Allow-Credentials', 'true');
response.setHeader('Access-Control-Allow-Origin', '*');
if(requset.url == '/?0=get1'){
setTimeout(() => {
response.end(`{a: ${requset.url}`);
}, 2000)
}else{
response.end(`{a: ${requset.url}}`);
}
}).listen(3001);
然后:
let recode = []; //收集并发的请求resolve
let isRefreshing = true; //判断是否要刷新,类似过期时间
let isGetToken = false; //判断是否已经有在刷新token了
async function refleshToken(){
return new Promise((resolve) => {
if(!isGetToken){
axios.get('http://192.168.2.132:3001/', {
params: 'reflesh'
}).then(res => {
setTimeout(() => {
recode.forEach(resolve => {
resolve()
});
recode = [];
}, 3000);
console.log(res.data)
})
isGetToken = true;
}
recode.push(resolve)
})
}
axios.interceptors.request.use(async (config) => {
if(isRefreshing && config.params != 'reflesh'){
await refleshToken()
}
return config;
}, (error) => {
console.log(error, "error");
return error;
}
);
function getToken(num){
axios.get('http://192.168.2.132:3001/', {
params: 'get' + num
}).then(res => {
console.log(res.data)
})
}
getToken(1)
getToken(2)
getToken(3)
我这边是直接判断参数有没有refresh,可以弄个白名单数组,判断是否需要。这边是请求之前处理,并发的都挂起,等刷新token的请求成功之后释放之前的请求,可以看见,刷新的请求成功之后三秒才继续请求。
另外一种是响应之后发起,这种不需要什么白名单,但是要重新发起一次请求,相比之前的,会更浪费性能:
模拟接口:
const http=require('http');
http.createServer(function(requset,response){
response.setHeader('Access-Control-Allow-Credentials', 'true');
response.setHeader('Access-Control-Allow-Origin', '*');
console.log(requset.url.indexOf('token'))
if(requset.url.indexOf('token') == -1){
response.end('refresh');
}else{
response.end(`{a: ${requset.url}}`);
}
}).listen(3001);
模拟请求:
let recode = [];
let isGetToken = false;
function refleshToken(){
isGetToken = true;
axios.get('http://192.168.2.132:3001/', {
params: 'refleshtoken'
}).then(res => {
setTimeout(() => {
recode.forEach(({resolve, config}) => {
config.params = config.params + 'token';
resolve(axios(config));
});
recode = [];
isGetToken = false;
}, 3000);
})
}
axios.interceptors.response.use((response) => {
if(response.data == 'refresh' && !isGetToken){
refleshToken();
return new Promise((resolve, reject) => {
recode.push({
resolve: resolve,
config: response.config
});
})
}else if(response.data == 'refresh'){
return new Promise((resolve, reject) => {
recode.push({
resolve: resolve,
config: response.config
})
})
}
return response.data;
}, (error) => {
console.log(error, "error");
return error;
}
);
function getToken(num){
axios.get('http://192.168.2.132:3001/', {
params: 'get' + num
}).then(res => {
console.log(res)
})
}
getToken(1)
getToken(2)
getToken(3)
这边最需要注意的就是这个:
resolve(axios(config));
要resolve重新一个请求才能链式调用。这其实是promise的语法,内部源码:
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
看看下面的方法应该能理解:
function ch1(config){
console.log(config);
return new Promise((resolve, reject) => {
resolve('ch1')
})
}
function ch2(){
console.log('ch2')
}
let chain = [ch1, ch2];
function test(config) {
var promise = Promise.resolve(config);
promise = promise.then(chain.shift(), chain.shift());
return promise;
}
test({a:10}).then((res) => {
console.log(res)
})
promise返回一个promise实现链式调用。
两种方法都有优缺点,本人还是倾向于请求前拦截,响应后重新发起请求还是会比较不理想。另外,如果不需要token的接口很多,也是会麻烦一些,这时候用第二种可能会好一些。