无感刷新token方案通常有三种,定时器刷新、根据过期时间刷新和后端返回过期状态码刷新。
定时器刷新:鬼才才会用这种方式。
根据过期时间刷新:万一用户设备的时间跟服务器时间不一样就傻眼了。
最直白最有效的方式一定是根据后端返回的状态码去刷新token。
但是这里有一个坑,如果只是简单的在请求拦截中进行逻辑判断,那你就掉进这个坑里了。
假如是在某种条件下,一次性调用多个接口。比如页面初始化时一次性向后端调了3个接口,3个接口返回的都是token过期的状态码,就会去调3次刷新token的接口,3次刷新token接口完事后,再去掉3次原来的接口,这一套下来,9次请求出去了,显然不合适。
实现思路:定义一个队列,以及一个消息状态,当第一个请求回来时,返回的是(424)token过期的状态码,则将这个消息状态设为true,后续所有的424请求,都放在这个队列中,同时,执行刷新token操作,当token刷新完成后,根据这个队列的顺序,再依次将请求发出。
这里以vue+axios为例
// 请求 响应拦截
import axios from 'axios'
import store from '@/store'
import { refreshToken} from '@/utils/auth'
const service = axios.create({
baseURL: '/api',
timeout: 5000
})
let isRefreshing = false
let requests = []
// 请求拦截器
service.interceptors.request.use(
...
)
// 响应拦截器
service.interceptors.response.use(
response => {
...
},
async error => {
const { status, data } = error.response
if (status === 401) {
// token过期
} else if (status === 424) {
// 重新登录时触发
if (!isRefreshing) {
isRefreshing = true
await refreshToken() // 自行定义刷新token的方法
requests.forEach(e => e())
requests = []
isRefreshing = false
error.config.headers.Authorization = `Bearer ${store.state.token.access_token}`
const firstApi = await service(error.config)
return Promise.resolve(firstApi)
} else {
return new Promise(resolve => {
requests.push(() => {
error.config.headers.Authorization = `Bearer ${store.state.token.access_token}`
return resolve(service(error.config))
})
})
}
} else {
// 常规异常时触发
}
}
)
export default service
注意:只能在响应拦截中去处理,不能在请求拦截中处理。理论上在请求第一个的时候发现这个token已过期,后续的请求就可以拦截,减少请求次数,但是后端返回的速度一定比你前处理的要慢