实现思路
这里用的请求框架是axios,所以我在这里主要是在请求拦截器里实现token刷新逻辑处理的。先大概说一下整个思路:
我们这里是在token即将过期的时候进行token刷新,而不是已经过期了才去刷新,这里即将过期的时间设置的是10分钟(这里有一个特别的点,就是我这里和后台协商,如果过期了20分钟内也让刷新token,超过过期20分钟,则判断为已过期,退出登录。所以这里请根据自身需求进行修改即可),这个时间在下面代码判断中可以根据自己项目情况自行调整。
一般一个页面同时会有很多个请求,所以我们需要建一个数组先缓存起来这些请求;除此之外,还要建一个全局标志,绑在window上,用来判断当前是否正在刷新token;待token刷新完成后,再去执行数组中缓存的所有请求,此时,数组中的请求都是带着刷新后的最新的token值作为headers去请求的。
下面直接上相关代码:
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import { getToken, setToken, getExpires, setExpires, getUserInfo, setUserInfo } from '@/utils/auth'
import { refreshToken } from "@/api/refreshToken"
// 是否正在刷新的标志
window.isRefreshing = false;
// 存储请求的数组
let cacheRequestArr = [];
// 将所有的请求都push到数组中,其实数组是[function(token){}, function(token){},...]
function cacheRequestArrHandle(cb) {
cacheRequestArr.push(cb);
}
// 数组中的请求得到新的token之后自执行,用新的token去重新发起请求
function afreshRequest(token) {
cacheRequestArr.map(cb => cb(token));
cacheRequestArr = [];
}
// 判断token是否即将过期
function isTokenExpired() {
let curTime = new Date().getTime();
let expiresTime = Number(getExpires()) - curTime;
// 还差10分钟即将过期或者已经过期了,但过期时间在20分钟内
if ((expiresTime >= 0 && expiresTime < 600000) || (expiresTime < 0 && Math.abs(expiresTime) <= 1200000)) {
return true
}
return false;
}
// 创建一个 axios 接口
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
timeout: 5000 // 请求超时时间设置
})
// 请求拦截器
service.interceptors.request.use(
config => {
// console.log(config);
// 在请求发送之前做一些处理
if (store.getters.token) {
// 让每个请求携带token-- ['X-Token']为自定义key 请根据实际情况自行修改
config.headers['Authorization'] = getToken();
// 判断token是否即将过期,且不是请求刷新token的接口
if (isTokenExpired() && config.url !== '/admin/base/refresh') {
// 所有的请求来了,先判断是否正在刷新token,
// 如果不是,将刷新token标志置为true并请求刷新token.
// 如果是,则先将请求缓存到数组中
// 等到刷新完token后再次重新请求之前缓存的请求接口即可
if (!window.isRefreshing) {
// 标志改为true,表示正在刷新
window.isRefreshing = true;
let userInfo = getUserInfo() ? JSON.parse(getUserInfo()) : '';
const data = {
uuid: userInfo.uuid,
expiresAt: Number(getExpires())
}
refreshToken(data).then(res => {
if (res.data.code == 200) {
// 更新cookie里的值
setToken(res.data.data.token, new Date(res.data.data.expiresAt));
setExpires(res.data.data.expiresAt, new Date(res.data.data.expiresAt));
setUserInfo(JSON.parse(getUserInfo()), new Date(res.data.data.expiresAt))
// 更新 store里的值
store.commit('SET_TOKEN', res.data.data.token);
store.commit('SET_EXPIRESAT', res.data.data.expiresAt);
// 将刷新的token替代老的token
config.headers['Authorization'] = getToken();
// 刷新token完成后重新请求之前的请求
afreshRequest(getToken())
} else {
Message({
message: res.data.msg,
type: 'error',
})
store.dispatch('resetUserCookies').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
}
}).catch(err => {
console.log('refreshToken err =>' + err);
store.dispatch('resetUserCookies').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
}).finally(() => {
window.isRefreshing = false;
})
// 下面这段代码一定要写,不然第一个请求的接口带过去的token还是原来的,要将第一个请求也缓存起来
let retry = new Promise((resolve) => {
cacheRequestArrHandle((token) => {
config.headers['Authorization'] = token; // token为刷新完成后传入的token
// 将请求挂起
resolve(config)
})
})
return retry;
} else {
let retry = new Promise((resolve) => {
cacheRequestArrHandle((token) => {
config.headers['Authorization'] = token; // token为刷新完成后传入的token
// 将请求挂起
resolve(config)
})
})
return retry;
}
} else {
return config
}
}
return config
},
error => {
// 请求错误处理
Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data;
if (res.code == 401 || res.code == 402) {
Message({
message: res.msg,
type: 'error',
duration: 5 * 1000
})
store.dispatch('resetUserCookies').then(() => {
location.reload() // 为了重新实例化vue-router对象 避免bug
})
return Promise.reject('error')
}
return response
},
error => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
// return Promise.reject(error)
}
)
export default service
上面代码主要是请求拦截器和响应拦截器核心代码,里面添加了刷新token的逻辑代码,上面的有些代码可根据自己情况进行自定义修改,主要核心逻辑就如上面所示。
下面是上面代码里面用到的一些相关导入代码:
下面这部分代码是操作cookie相关代码
import Cookies from 'js-cookie'
// 将token写入cookie
const TokenKey = 'Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token, expires) {
return Cookies.set(TokenKey, token, { expires: expires })
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
// 将expiresAt过期时间写入cookie
const expiresAtKey = 'ExpiresAt'
export function getExpires() {
return Cookies.get(expiresAtKey)
}
export function setExpires(expiresAt, expires) {
return Cookies.set(expiresAtKey, expiresAt, { expires: expires })
}
export function removeExpires() {
return Cookies.remove(expiresAtKey)
}
// 将userInfo用户信息写入cookie
const userInfoKey = 'UserInfo'
export function getUserInfo() {
return Cookies.get(userInfoKey)
}
export function setUserInfo(userInfo, expires) {
// cookie存储对象时,必须转化为JSON字符串,否则读取不到对象里的属性值
return Cookies.set(userInfoKey, JSON.stringify(userInfo), { expires: expires })
}
export function removeUserInfo() {
return Cookies.remove(userInfoKey)
}
下面这部分代码是刷新token时请求后台用到接口(这个可根据自己项目情况写)
import request from '@/utils/request'
export function refreshToken(data) {
return request({
url: '/admin/base/refresh',
method: 'post',
data
})
}
上面的所有代码我都是根据自己情况进行了一些封装处理后使用的,大家可以根据自己手上的具体项目情况来做适合自己的封装,不一定要和上面一模一样,主要的相关代码就上面这些了,具体还有疑问的地方可留言询问,我看到会回复的。