乐趣区

关于golang:深入Go底层原理重写Redis中间件实战吾爱fen享

download:https://www.sisuoit.com/2926….

需要

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

申请前判断 Token 是否过期,过期则刷新
申请后依据返回状态判断是否过期,过期则刷新

解决逻辑

实现起来也没多大差异,只是判断的地位不一样,外围原理都一样:

  • 判断 Token 是否过期
  • 没过期则失常解决
  • 过期则发动刷新 Token 的申请
  • 拿到新的 Token 保留
  • 从新发送 Token 过期这段时间内发动的申请

重点:

放弃 Token 过期这段时间发动申请状态(不能进入失败回调)
把刷新 Token 后从新发送申请的响应数据返回到对应的调用者

实现

创立一个 flag isRefreshing 来判断是否刷新中
创立一个数组队列 retryRequests 来保留须要从新发动的申请
判断到 Token 过期

isRefreshing = false 的状况下 发动刷新 Token 的申请

刷新 Token 后遍历执行队列 retryRequests


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;
        //   刷新 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;
退出移动版