实现思路

这里用的请求框架是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
    })
}

上面的所有代码我都是根据自己情况进行了一些封装处理后使用的,大家可以根据自己手上的具体项目情况来做适合自己的封装,不一定要和上面一模一样,主要的相关代码就上面这些了,具体还有疑问的地方可留言询问,我看到会回复的。