token无感刷新+长token过期处理+当短token过期之后,需要刷新token,但是进来了很多请求,不需要每个请求都去刷新token处理
所用技术栈:Vue3+TS+Vite+arco-design
1.模拟页面,进行登录和数据请求
<template><div class="refresh"><a-button type="outline" status="success" @click="toLogin">登录</a-button><a-button type="outline" status="warning" @click="getInfo">请求受保护的接口</a-button></div>
</template><script setup lang="ts">
import { login, reqProtected } from "@/API/getData.js";
const toLogin = async () => {const res = await login()
}const getInfo = async () => {const res = await reqProtected()
}
</script>
<style scoped lang="less">
.refresh{margin-top: 20px;display: flex;.arco-btn{margin-right: 20px;}
}
</style>
2.token储存方法封装
const TOKEN_KEY = "TOKEN";
const REFRESH_TOKEN_KEY = "REFRESH_TOKEN";const getToken = () => {return localStorage.getItem(TOKEN_KEY);
};const setToken = (token) => {return localStorage.setItem(TOKEN_KEY, token);
};const getRefreshToken = () => {return localStorage.getItem(REFRESH_TOKEN_KEY);
};const setRefreshToken = (token) => {localStorage.setItem(REFRESH_TOKEN_KEY, token);
};export {getToken,setToken,getRefreshToken,setRefreshToken
}
3.接口封装:request
import axios from "axios";
import { getToken, setToken, setRefreshToken } from "@/utils/token.js";
import { refreshToken, isRefreshRequest } from "./getData.js";
import router from "@/router";
import { Message } from "@arco-design/web-vue";const baseURL = "http://localhost:9527";const instance = axios.create({// 基础地址baseURL,// 默认超时的时间timeout: 5000,
});// 请求拦截
instance.interceptors.request.use((config) => {// 拿到请求头return config;},(err) => {// 打印错误值return Promise.reject(err);}
);// 响应拦截
instance.interceptors.response.use(async (res) => {// 如果有token 存tokenif (res.headers.authorization) {const token = res.headers.authorization.replace("Bearer ", "");setToken(token);instance.defaults.headers.Authorization = `Bearer ${token}`;}// 如果有刷新token,存刷新tokenif (res.headers.refreshtoken) {const refreshtoken = res.headers.refreshtoken.replace("Bearer ", "");setRefreshToken(refreshtoken);}console.log(res.data.data);if (res.data.code === 401 && !isRefreshRequest(res.config)) {// 等待token刷新成功const isSuccess = await refreshToken();// 当长token过期了,需要判断是否获取到了token的信息if (isSuccess) {// 获取新的tokenres.config.headers.Authorization = `Bearer ${getToken()}`;// 获取请求配置,再次请求数据const resp = await instance.request(res.config);return resp;} else {Message.warning({content: "Token认证过期,请重新登录",});router.push({name: "home",});}}return res.data;
});// 整体导出
export default instance;
4.请求接口封装
import request from "./request.js";
import { getRefreshToken } from "../utils/token.js";
// 登录获取token
export const login = () => request.post("/login");// 获取需要token认证的接口
export const reqProtected = () => request.post("/protected");// 假如token过期了,然后需要再次刷新token,但是这个时候同时进来了多个请求,其实只需要刷新一次token
let promise = null;
// 刷新token 这里需要等待刷新token完成,否则会执行多次
export const refreshToken = () => {if (promise) {return promise;}promise = new Promise(async (resolve) => {const res = await request.get("/refresh_token", {headers: {Authorization: `Bearer ${getRefreshToken()}`,},__isRefreshToken: true, //增加刷新token的辨识});resolve(res.code === 0)});console.log(promise,' console.log(promise);');// 完成刷新token之后,需要把promise置空,因为后续还会进行判断执行promise.finally(() => {promise = null;});// 这里为什么要return promise ,因为你再外部调用的时候,需要去执行他:await refreshToken()// 然后才会执行到调用接口,并返回resolve结果return promise;
};// 判断是否为刷新token
export const isRefreshRequest = (config) => {return !!config.__isRefreshToken;
};