实现token过期后拦截请求,刷新token后再重新请求

功能描述

登录系统后会有新的token和refreshToken,token会在一定时间后失效,这时就需要拦截当前的所有请求,先通过refreshToken刷新token,如果刷新成功,将获取到新的token和refreshToken,再将拦截的请求重新使用新的token请求一遍;如果刷新失败说明该token已完全过期就需要退出到登录页面重新登录。
axios.js

import request from "xxx-axios"; //自己封装的axios
import store from "@/store";
import md5 from "js-md5";
import { refreshToken } from "@/api/user";
import i18n from "@/i18n";
import { EN_US, ZH_CN } from "@/constant";

const appId = "b285bbc244c64bec";
let shouldRefresh = false;
let requestConfig = [];

//所有请求都需携带以下header的内容
export function getHeaders(params = {}) {
  const { locale } = i18n.global;
  const timestamp = new Date().getTime(); //时间戳
  const nonce = parseInt((Math.random() * 9 + 1) * 10000); //5位随机数
  let prefix = "";
  //将请求接口url携带的参数拼接起来作为前缀
  Object.keys(params).forEach((key) => (prefix += key + "=" + (params[key] + "")?.trim())); 
  return {
    appId,
    authorization: store?.state.user.token,
    timestamp,
    nonce,
    sign: md5(`${prefix + appId + store.state.user.token + timestamp + nonce}`), //通过MD5生成签名
    locale
  };
}
request.config({
  timeout: 30000,
  baseURL: process.env.VUE_APP_BASE_URL // "/api"
});

request.instance.interceptors.request.use(
  async function (config) {
    config.headers = getHeaders(getParams(config) || {});
    return config;
  },
  function (error) {
    return Promise.reject(error);
  }
);

//关键实现
["get", "post", "put", "delete"].forEach((name) => {
  let fn = request[name];
  const refreshTokenUrl = "/oauth/token/refresh";
  request[name] = (url, ...args) => {
    const refresh = () => {
      return new Promise((resolve, reject) => {
        requestConfig.push((cancel = true) => {
          if (!cancel) {
            reject("取消");
            return;
          }
          fn.call(null, url, ...args)
            .then(resolve)
            .catch(reject);
        });
      });
    };

    if (shouldRefresh && url !== refreshTokenUrl) {
      return refresh();
    }

    return new Promise((resolve, reject) => {
      fn.call(null, url, ...args)
        .then((res) => {
          if (!shouldRefresh || url === refreshTokenUrl) {
            return resolve(res);
          }
          requestConfig.push((cancel = true) => {
            if (!cancel) {
              reject("取消");
              return;
            }
            fn.call(null, url, ...args)
              .then(resolve)
              .catch(reject);
          });
        })
        .catch(reject);
    });
  };
});
function refreshCookie() {
  if (!shouldRefresh) {
    shouldRefresh = true;
    if (store.state.user.refreshToken && store.state.user.token) {
      refreshToken(store.state.user.refreshToken).then((res) => {
        shouldRefresh = false;
        if (res.success) {
          store.commit("user/userLogin", { ...res.data });  //更新token和refreshToken
          requestConfig.forEach((fn) => fn()); //接口依次请求
        } else {
          //刷新也失效
          store.dispatch("user/logout"); //登出
          requestConfig.forEach((fn) => fn(false)); //接口依次取消
        }
        requestConfig = [];
      });
    } else {
      //没有refreshToken和token直接登出
      store.dispatch("user/logout");
      requestConfig.forEach((fn) => fn(false));
      requestConfig = [];
      shouldRefresh = false;
    }
  }
}

request.instance.interceptors.response.use(
  function (response) {
    const { t } = i18n.global;
    if (response.status !== 200) {
      toast.error(t(`tooltip.networkError`));
    } else {
      // 下载文件用
      if (
        (response.request && response.request.responseType === "blob") ||
        response.data.code === undefined
      ) {
        if (response.data?.type != "application/octet-stream") {
          refreshCookie();
        }
        return response;
      }
      // code 数字转换
      response.data.code = Number(response.data.code) || response.data.code;
      //15101 13101 都是后端返回token需要刷新的code
      if (response.data.code === 15101 || response.data.code === 13101) {
        refreshCookie();
      } else if (response.data.success !== undefined && !response.data.success) {
        if (response.config.url != "/oauth/logout")
         toast.error({ content: response.data.message });
        checkError(response); 
        return Promise.reject(response.data);
      }
      return response.data;
    }
  },
  function (error) {
    return Promise.reject(error);
  }
);

function getParams(config) {
  let params = {};
  let arrStr = config.url?.split("?") || [];
  // if (config.method === "get") { //??todo
  params = {
    ...(config.params || {})
  };
  // }
  if (arrStr.length > 1) {
    arrStr[1].split("&").map((m) => {
      let temp = m.split("=");
      if (temp.length === 2) {
        params[temp[0]] = temp[1];
      }
    });
  }
  return paramsSort(params);
}

//字典排序
function paramsSort(params) {
  let result = {};
  let arr = Object.keys(params);
  if (arr.length) {
    arr.sort();
    arr.forEach((key) => (result[key] = params[key]));
  }
  return result;
}

//检查接口返回的message语言是否是当前系统的语言
function checkError(response) {
  const { locale } = i18n.global;
  let result = false;
  if (response.data?.message) {
    try {
      let regZh = new RegExp("[\\u4E00-\\u9FFF]+", "g");
      result =
        (locale == ZH_CN && !regZh.test(response.data?.message)) ||
        (locale == EN_US && regZh.test(response.data?.message));
      result
        ? console.error("语言环境错误:", {
            url: response?.config?.url,
            locale,
            message: response?.data?.message,
            code: response?.data?.code
          })
        : null;
    } catch (e) {
      console.log(e);
    }
  }
}

export default request;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值