The front end painlessly refreshes the Token

This requirement scenario is very common, and it is used in almost any project. It has been implemented in previous projects, and it just happened that a project was implemented recently.

demand

For the need for the front-end to achieve painless refresh Token, there are no more than two:

  1. Check whether the Token expires before the request. If the Token expires, refresh the Token
  2. After the request is received, check whether the request expires according to the returned status. If the request expires, refresh

Processing logic

The implementation is not much different, but the position of judgment is different, the core principle is the same:

  1. judgeTokenIs late
    1. If not expired, normal processing
    2. Expiration triggers a refreshTokenThe request of
      1. Get a newTokensave
      2. To resendTokenExpired Indicates the request initiated during this period

Key points:

  • keepTokenExpired time to initiate a request status (cannot enter a failed callback)
  • The refreshTokenAfter resending the request, the response data is returned to the corresponding caller

implementation

  1. Create a FlagisRefreshingTo determine whether it is refreshing
  2. Create an array queueretryRequestsTo save requests that need to be reinitiated
  3. Judgment toTokenoverdue
    1. isRefreshing = falseIn case a refresh is initiatedTokenThe request of
      1. The refreshTokenAfter traversing the execution queueretryRequests
    2. isRefreshing = trueIndicates refreshingToken, returns aPendingThe state of thePromiseAnd saves the request information to the queueretryRequestsIn the
import axios from "axios";
import Store from "@/store";
import Router from "@/router";
import { Message } from "element-ui";
import UserUtil from "@/utils/user";

// Create an instance
const Instance = axios.create();
Instance.defaults.baseURL = "/api";
Instance.defaults.headers.post["Content-Type"] = "application/json";
Instance.defaults.headers.post["Accept"] = "application/json";

// Define a flag to determine whether to refresh the Token
let isRefreshing = false;
// Save the queue that needs to be reinitiated
let retryRequests = [];

// Request interception
Instance.interceptors.request.use(async function(config) {
  Store.commit("startLoading");
  const userInfo = UserUtil.getLocalInfo();
  if (userInfo) {
    // Business needs to place Token information in params, usually in headers
    config.params = Object.assign(config.params ? config.params : {}, {
      appkey: userInfo.AppKey,
      token: userInfo.Token
    });
  }
  return config;
});

// Response interception
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
    ) {
    // The Token status needs to be refreshed. 30001 40001 42001 40014
    // Get the configuration for this request
      let config = response.config;
    // The login page does not refresh the Token
      if(Router.currentRoute.path ! = ="/login") {
        if(! isRefreshing) {// Changing the flag state indicates that the Token is being refreshed
          isRefreshing = true;
        / / refresh Token
          return Store.dispatch("user/relogin")
            .then(res= > {
            // Set the refreshed Token
              config.params.token = res.Token;
              config.params.appkey = res.AppKey;
            // Iterate over the queue where the request needs to be reinitiated
              retryRequests.forEach(cb= > cb(res));
            // Clear the queue
              retryRequests = [];
              return Instance.request(config);
            })
            .catch(() = > {
              retryRequests = [];
              Message.error("Automatic login failed. Please log in again.");
                const code = Store.state.user.info.CustomerCode || "";
                // Failed to refresh the Token Clear the cached user information and adjust it to the login page
                Store.dispatch("user/logout");
                Router.replace({
                  path: "/login".query: { redirect: Router.currentRoute.fullPath, code: code }
                });
            })
            .finally(() = > {
                // Reset the flag when the request is complete
              isRefreshing = false;
            });
        } else {
          // Refreshing tokens returns an unperformed resolve promise
          // Save the promise's resolve to the queue's callback, waiting to be invoked after the Token is refreshed
          // The original caller will wait until the queue reinitiates the request, and then return the response, so that the user is unaware of the purpose (painless refresh)
          return new Promise(resolve= > {
            // Queue resolve, save it as a function, and execute it directly after token refresh
            retryRequests.push(info= > {
                // Reassign the new Tokenconfig.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;

Copy the code

The writing is not very good, forgive me