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

需要

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

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

解决逻辑

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

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

重点:

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

实现

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

isRefreshing = false 的状况下 发动刷新Token的申请刷新Token后遍历执行队列retryRequestsisRefreshing = 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;