当前位置:   article > 正文

前端无痛刷新Token

无痛刷新

 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5YmN56uv5Lic5Lic,size_20,color_FFFFFF,t_70,g_se,x_16

 

这个需求场景很常见,几乎很多项目都会用上,之前项目也实现过,最近刚好有个项目要实现,重新梳理一番。

需求

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

  1. 请求前判断Token是否过期,过期则刷新
  2. 请求后根据返回状态判断是否过期,过期则刷新

处理逻辑

实现起来也没多大差别,只是判断的位置不一样,核心原理都一样:

  1. 判断Token是否过期
    1. 没过期则正常处理
    2. 过期则发起刷新Token的请求
      1. 拿到新的Token保存
      2. 重新发送Token过期这段时间内发起的请求

重点:

  • 保持Token过期这段时间发起请求状态(不能进入失败回调)
  • 把刷新Token后重新发送请求的响应数据返回到对应的调用者

实现

  1. 创建一个flag isRefreshing 来判断是否刷新中
  2. 创建一个数组队列retryRequests来保存需要重新发起的请求
  3. 判断到Token过期
    1. isRefreshing = false 的情况下 发起刷新Token的请求
      1. 刷新Token后遍历执行队列retryRequests
    2. isRefreshing = true 表示正在刷新Token,返回一个Pending状态的Promise,并把请求信息保存到队列retryRequests
  1. import axios from "axios";
  2. import Store from "@/store";
  3. import Router from "@/router";
  4. import { Message } from "element-ui";
  5. import UserUtil from "@/utils/user";
  6. // 创建实例
  7. const Instance = axios.create();
  8. Instance.defaults.baseURL = "/api";
  9. Instance.defaults.headers.post["Content-Type"] = "application/json";
  10. Instance.defaults.headers.post["Accept"] = "application/json";
  11. // 定义一个flag 判断是否刷新Token中
  12. let isRefreshing = false;
  13. // 保存需要重新发起请求的队列
  14. let retryRequests = [];
  15. // 请求拦截
  16. Instance.interceptors.request.use(async function(config) {
  17. Store.commit("startLoading");
  18. const userInfo = UserUtil.getLocalInfo();
  19. if (userInfo) {
  20. //业务需要把Token信息放在 params 里面,一般来说都是放在 headers里面
  21. config.params = Object.assign(config.params ? config.params : {}, {
  22. appkey: userInfo.AppKey,
  23. token: userInfo.Token
  24. });
  25. }
  26. return config;
  27. });
  28. // 响应拦截
  29. Instance.interceptors.response.use(
  30. async function(response) {
  31. Store.commit("finishLoading");
  32. const res = response.data;
  33. if (res.errcode == 0) {
  34. return Promise.resolve(res);
  35. } else if (
  36. res.errcode == 30001 ||
  37. res.errcode == 40001 ||
  38. res.errcode == 42001 ||
  39. res.errcode == 40014
  40. ) {
  41. // 需要刷新Token 的状态 30001 40001 42001 40014
  42. // 拿到本次请求的配置
  43. let config = response.config;
  44. // 进入登录页面的不做刷新Token 处理
  45. if (Router.currentRoute.path !== "/login") {
  46. if (!isRefreshing) {
  47. // 改变flag状态,表示正在刷新Token中
  48. isRefreshing = true;
  49. // 刷新Token
  50. return Store.dispatch("user/refreshToken")
  51. .then(res => {
  52. // 设置刷新后的Token
  53. config.params.token = res.Token;
  54. config.params.appkey = res.AppKey;
  55. // 遍历执行需要重新发起请求的队列
  56. retryRequests.forEach(cb => cb(res));
  57. // 清空队列
  58. retryRequests = [];
  59. return Instance.request(config);
  60. })
  61. .catch(() => {
  62. retryRequests = [];
  63. Message.error("自动登录失败,请重新登录");
  64. const code = Store.state.user.info.CustomerCode || "";
  65. // 刷新Token 失败 清空缓存的用户信息 并调整到登录页面
  66. Store.dispatch("user/logout");
  67. Router.replace({
  68. path: "/login",
  69. query: { redirect: Router.currentRoute.fullPath, code: code }
  70. });
  71. })
  72. .finally(() => {
  73. // 请求完成后重置flag
  74. isRefreshing = false;
  75. });
  76. } else {
  77. // 正在刷新token,返回一个未执行resolve的promise
  78. // 把promise 的resolve 保存到队列的回调里面,等待刷新Token后调用
  79. // 原调用者会处于等待状态直到 队列重新发起请求,再把响应返回,以达到用户无感知的目的(无痛刷新)
  80. return new Promise(resolve => {
  81. // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
  82. retryRequests.push(info => {
  83. // 将新的Token重新赋值
  84. config.params.token = info.Token;
  85. config.params.appkey = info.AppKey;
  86. resolve(Instance.request(config));
  87. });
  88. });
  89. }
  90. }
  91. return new Promise(() => {});
  92. } else {
  93. return Promise.reject(res);
  94. }
  95. },
  96. function(error) {
  97. let err = {};
  98. if (error.response) {
  99. err.errcode = error.response.status;
  100. err.errmsg = error.response.statusText;
  101. } else {
  102. err.errcode = -1;
  103. err.errmsg = error.message;
  104. }
  105. Store.commit("finishLoading");
  106. return Promise.reject(err);
  107. }
  108. );
  109. export default Instance;

文笔不怎么样,谅

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/947468
推荐阅读
相关标签
  

闽ICP备14008679号