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 存token
if (res.headers.authorization) {
const token = res.headers.authorization.replace("Bearer ", "");
setToken(token);
instance.defaults.headers.Authorization = `Bearer ${token}`;
}
// 如果有刷新token,存刷新token
if (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) {
// 获取新的token
res.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;
};