素材巴巴 > 程序开发 >

前端无痛刷新Token

程序开发 2023-09-06 18:52:07

 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5YmN56uv5Lic5Lic,size_20,color_FFFFFF,t_70,g_se,x_16

 

这个需求场景很常见,几乎很多项目都会用上,之前项目也实现过,最近刚好有个项目要实现,重新梳理一番。

需求

对于需要前端实现无痛刷新Token,无非就两种:

  1. 请求前判断Token是否过期,过期则刷新
  2. 请求后根据返回状态判断是否过期,过期则刷新

处理逻辑

实现起来也没多大差别,只是判断的位置不一样,核心原理都一样:

  1. 判断Token是否过期
    1. 没过期则正常处理
    2. 过期则发起刷新Token的请求
      1. 拿到新的Token保存
      2. 重新发送Token过期这段时间内发起的请求

重点:

实现

  1. 创建一个flag isRefreshing 来判断是否刷新中
  2. 创建一个数组队列retryRequests来保存需要重新发起的请求
  3. 判断到Token过期
    1. isRefreshing = false 的情况下 发起刷新Token的请求
      1. 刷新Token后遍历执行队列retryRequests
    2. isRefreshing = true 表示正在刷新Token,返回一个Pending状态的Promise,并把请求信息保存到队列retryRequests
import axios from "axios";
 import Store from "@/store";
 import Router from "@/router";
 import { Message } from "element-ui";
 import UserUtil from "@/utils/user";// 创建实例
 const Instance = axios.create();
 Instance.defaults.baseURL = "/api";
 Instance.defaults.headers.post["Content-Type"] = "application/json";
 Instance.defaults.headers.post["Accept"] = "application/json";// 定义一个flag 判断是否刷新Token中
 let isRefreshing = false;
 // 保存需要重新发起请求的队列
 let retryRequests = [];// 请求拦截
 Instance.interceptors.request.use(async function(config) {Store.commit("startLoading");const userInfo = UserUtil.getLocalInfo();if (userInfo) {//业务需要把Token信息放在 params 里面,一般来说都是放在 headers里面config.params = Object.assign(config.params ? config.params : {}, {appkey: userInfo.AppKey,token: userInfo.Token});}return config;
 });// 响应拦截
 Instance.interceptors.response.use(async function(response) {Store.commit("finishLoading");const res = response.data;if (res.errcode == 0) {return Promise.resolve(res);} else if (res.errcode == 30001 ||res.errcode == 40001 ||res.errcode == 42001 ||res.errcode == 40014) {// 需要刷新Token 的状态 30001 40001 42001 40014// 拿到本次请求的配置let config = response.config;//   进入登录页面的不做刷新Token 处理if (Router.currentRoute.path !== "/login") {if (!isRefreshing) {// 改变flag状态,表示正在刷新Token中isRefreshing = true;//   刷新Tokenreturn Store.dispatch("user/refreshToken").then(res => {// 设置刷新后的Tokenconfig.params.token = res.Token;config.params.appkey = res.AppKey;//   遍历执行需要重新发起请求的队列retryRequests.forEach(cb => cb(res));//   清空队列retryRequests = [];return Instance.request(config);}).catch(() => {retryRequests = [];Message.error("自动登录失败,请重新登录");const code = Store.state.user.info.CustomerCode || "";// 刷新Token 失败 清空缓存的用户信息 并调整到登录页面Store.dispatch("user/logout");Router.replace({path: "/login",query: { redirect: Router.currentRoute.fullPath, code: code }});}).finally(() => {// 请求完成后重置flagisRefreshing = false;});} else {// 正在刷新token,返回一个未执行resolve的promise// 把promise 的resolve 保存到队列的回调里面,等待刷新Token后调用// 原调用者会处于等待状态直到 队列重新发起请求,再把响应返回,以达到用户无感知的目的(无痛刷新)return new Promise(resolve => {// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行retryRequests.push(info => {// 将新的Token重新赋值config.params.token = info.Token;config.params.appkey = info.AppKey;resolve(Instance.request(config));});});}}return new Promise(() => {});} else {return Promise.reject(res);}},function(error) {let err = {};if (error.response) {err.errcode = error.response.status;err.errmsg = error.response.statusText;} else {err.errcode = -1;err.errmsg = error.message;}Store.commit("finishLoading");return Promise.reject(err);}
 );export default Instance;

文笔不怎么样,谅


标签:

上一篇: Springboot连接arangodb数据库 下一篇:
素材巴巴 Copyright © 2013-2021 http://www.sucaibaba.com/. Some Rights Reserved. 备案号:备案中。