实现前端token无感刷新

实现前端token无感刷新

今天和盆友讨论无感刷新的这个技术,一开始我们讨论的到底是前端做还是后端做,后面公说公有理,婆说婆有理,而且现在老板喜欢提一些奇葩需求,提前准备前端来实现token无感刷新,该文章引用沐夕花开文章

分析需求

无感刷新的场景:

  • 请求前判断Token是否过期,过期则刷新

  • 请求后根据返回状态判断是否过期,过期则刷新

  • 当在做某个操作的时候突然token失效(比如:当你想要提交某个表单信息,突然token失效,而这个表单信息有100个必填项,GG)

处理逻辑

  • 判断token无感刷新

  • 过期则发送刷新token请求,将新token保存,重新发送token过期请求

实现

创建一个flag isRefreshing 来判断是否刷新中

创建一个数组队列retryRequests来保存需要重新发起的请求

判断到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"; //这个是规定当post请求体将以JSON格式来传送
Instance.defaults.headers.post["Accept"] = "application/json";  //这告诉服务器客户端期望响应数据为 JSON 格式。
​
// 定义一个flag 判断是否刷新Token中
let isRefreshing = false;
// 保存需要重新发起请求的队列
let retryRequests = [];
​
// 请求拦截
Instance.interceptors.request.use(async function (config) {
  //这里没有看到大佬的代码 估计应该是保存在vuex中的状态 用来改变状态的方法
  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) {
      //如果code ===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;
          //   刷新Token
          return Store.dispatch("user/refreshToken")
            .then((res) => {
              // 设置刷新后的Token
              config.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(() => {
              // 请求完成后重置flag
              isRefreshing = 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;
​

另外感谢大佬的文章介绍前端无痛刷新Token - 掘金 评论区比代码好看 争吵激烈 哈哈哈哈哈

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值