axios使用异步方式无感刷新token,简单,太简单了

news2024/11/17 0:05:12

文章目录

    • 🍉 废话在前
    • 🍗 接着踩坑
    • 🥩 解决思路
    • 🍓 完整代码

🍉 废话在前

写vue的或帮们无感刷新token相信大家都不陌生了吧,刚好,最近自己的一个项目中就需要用到这个需求,因为之前没有弄过这个,研究了一个上午,终于还是把它拿下了,小小的一个token刷新😏。

在这里插入图片描述

下面接着分析一下踩到的坑以及解决思路

🍗 接着踩坑

我按照之前传统的方式在返回拦截器里面进行token刷新,正常的数据可以返回,但是这个时候会有比较麻烦的地方,就是请求的数据可以在拦截器里面得到,但是不能渲染到界面上(看到这里的时候我是懵的)。

看一下代码


service.interceptors.response.use(
  response => {
    const res = response.data
    //刷新token的时候,可以从这里拦截到新数据,但是没有显示在页面上
    console.log('拦截数据:',res)
    if (loadingInstance) {
      loadingInstance.close();
      NProgress.done();
    }
    switch (res.code) {
      case 200:
        return res
      case 401:
        router.push({
          path: "/login",
        })
        localStorage.clear();
        Notification.error({
          title: '令牌过期',
          message: '当前身份信息超过三天已失效,请您重新登录',
          duration: 0
        });
        break;
      default:
        Message({
          message: res.message || '请求错误',
          type: 'error',
          duration: 5 * 1000
        })
        break;
    }
  },
  error => {
    switch (error.response.status) {
      case 401:
        MessageBox.confirm('身份认证已过期,是否刷新本页继续浏览?', '提示', {
          confirmButtonText: '继续浏览',
          cancelButtonText: '退出登录',
          type: 'warning'
        }).then(() => {
          axios.post('/api/token/refresh/', {
            refresh: localStorage.getItem("retoken")
          }).then(response => {
            let res = response.data || {}
            if (res.code == 200) {
              let token = res.data.result;
              localStorage.setItem("token", token.access);
              localStorage.setItem("retoken", token.refresh);
              error.response.config.baseURL = '';
              error.response.config.headers['Authorization'] = 'Bearer  ' + localStorage.getItem("token")
              window.location.reload();
            } else {
              router.push({
                path: "/login",
              })
              localStorage.clear();
              Notification.error({
                title: '令牌过期',
                message: '当前身份信息超过三天已失效,请您重新登录',
                duration: 0
              });
            }
          }).catch(err => {
            router.push({
              path: "/login",
            })
            localStorage.clear();
            Notification.error({
              title: '认证失败',
              message: '信息认证失败,请重新登录',
              duration: 0
            });
          })
        }).catch(() => {
          router.push({
            path: "/login",

          })
          Message({
            message: '已退出',
            type: 'success',
          })
          localStorage.clear();
        });
        break;
      case 404:
        Notification.error({
          title: '404错误',
          message: '服务器请求错误,请联系管理员或稍后重试。错误状态码:404',
        });
        break
      default:
        Notification.error({
          title: '请求错误',
          message: '服务器请求错误,请联系管理员或稍后重试',
        });
        break;
    }
    if (loadingInstance) {
      loadingInstance.close();
      NProgress.done();
    }
    return Promise.reject(error);

  }
)

懵归懵,好在已经发现这个问题了,剩下的怎么解决呢?

当时想的是和后端配合,让后端直接发一个token过期的时间戳给我,我直接把这个时间戳放到localStrage里面,通过这个localStrage,直接在前端进行判断token的过期时间进行请求拦截,如果当前请求的时间大于了这个localStrage里面的时间,就说明token过期了,我这边就需要重新请求token了,而不要后端去进行token的验证。

信心满满的弄了一下,发现行不通,token过期的时候,后端直接一个错误401,前端就又回到解放前了。而且用时间戳的方式很容易出现bug,且在前端进行token时长的验证,很容易出现问题。因此,我还是觉得再研究一下上面那一段代码。

当然踩到坑不只是这个,还有百度和chatGPT,真的,看了一下没有找到一个可行的,总结一下主要有以下几种

  1. 状态管理vuex
  2. 路由router
  3. 时间戳(和我刚刚那种方式差不多)
  4. 依赖注入inject
  5. 刷新界面(我最开始那种方式,但是刷新的时候会出现页面白屏,且用户如果在页面上有一些自己输入的数据也会被清空,用户体验感不好)

以上方式我先不管行不行,但是麻烦是肯定的,做前端讲究的就一个字:“懒”
不是,应该是 “高效快捷”
所以这些方式就pass掉了

只能想想为什么拦截器里面可以得到数据,为什么页面位置得不到数据了

🥩 解决思路

下面是我的解决思路,有什么不对的地方还请看到的大神指出来一下😁

当用户发起请求的时候,因为刷新token的http状态码是401,这个时候axios的响应拦截器就直接进行错误捕获了,到了这里,因为数据已经返回了,但是因为是错误数据,页面得到的这个数据不可用且当前请求已经结束了,当然这里对状态码401是进行处理了的(应该获取token了)。

采用普通的获取方式来获取token,因为异步的原因,我们获取token的同时页面也在做刷新,token获取的同时,界面也刷新完毕了(但是是没有数据的,不做错误捕获会报错),因此我们在获取token完毕,且用新的token去获取数据时,拦截器里面会有数据,但是界面已经休息了,就不会把拦截器里面的新数据刷新到页面了。

因此这个地方需要对获取token的过程进行一下请求阻塞,把获取token的请求变成同步的。到这里就差不多了。直接把响应拦截器里面的error函数变成同步不就行了吗,async + await可以出来了。

以上是自己当时的想法,简单说来就是 页面刷新需要慢我获取token一步 ,通过这个方式也确实做到了无感刷新🤣

希望以上的能帮助到你,有什么好的思路也欢迎评论区指出。

🍓 完整代码

这个是用了2.13.2版本的element-ui以及nprogress的一个axios代码模块,包含了一个下载文件的模块。

如果需要的话可以根据自己的需求来进行修改

import axios from 'axios'
import {
  Message,
  Loading,
  Notification,
} from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import router from '@/router/index'
const baseURL = '/api'
const service = axios.create({
  baseURL,
  timeout: 6000
})
NProgress.configure({
  showSpinner: false
}) // NProgress Configuration
let loadingInstance = undefined;
service.interceptors.request.use(
  config => {
    NProgress.start()
    loadingInstance = Loading.service({
      lock: true,
      text: '正在加载,请稍候...',
      spinner: 'el-icon-loading',
      background: 'rgba(0, 0, 0, 0.3)'
    })
    config.headers['Authorization'] = 'Bearer  ' + localStorage.getItem("token")
    return config;
  },
  error => {
    return Promise.reject(error)
  }
)

service.interceptors.response.use(
  response => {
    const res = response.data

    if (loadingInstance) {
      loadingInstance.close();
      NProgress.done();
    }
    switch (res.code) {
      case 200:
        return res
      case 401:
        router.push({
          path: "/login",
        })
        localStorage.clear();
        Notification.error({
          title: '认证失效',
          message: '当前身份信息超过三天已失效,请您重新登录',
          duration: 0
        });
        break;
      default:
        Message({
          message: res.message || '请求错误',
          type: 'error',
          duration: 5 * 1000
        })
        break;
    }
  },
  async error => {
    switch (error.response.status) {
      case 401:
        const err401Data = error.response.data || {}
        if (err401Data.code !== "token_not_valid") {
          router.push({
            path: "/login",
          })
          localStorage.clear();
          Notification.error({
            title: '认证失败',
            message: '身份信息认证失败,请您重新登录',
            duration: 0
          });
          return
        }
        try {
          const res = await service.post('/token/refresh/', {
            refresh: localStorage.getItem("retoken")
          })
          if (res.code == 200) {
            let token = res.data.result;
            localStorage.setItem("token", token.access);
            localStorage.setItem("retoken", token.refresh);
            error.response.config.baseURL = ''
            error.response.config.headers['Authorization'] = 'Bearer  ' + localStorage.getItem("token")
            return service(error.response.config)
          } else {
            router.push({
              path: "/login",
            })
            localStorage.clear();
            Notification.error({
              title: '认证失败',
              message: '当前身份认证信息已失效,请您重新登录',
              duration: 0
            });
          }
        } catch (error) {
          router.push({
            path: "/login",
          })
          localStorage.clear();
          Notification.error({
            title: '认证失败',
            message: '身份认证失败,请您重新登录,失败原因:' + error.message,
            duration: 0
          });
        }

        break
      case 404:
        Notification.error({
          title: '404错误',
          message: '服务器请求错误,请联系管理员或稍后重试。错误状态码:404',
        });
        break
      default:
        Notification.error({
          title: '请求错误',
          message: '服务器请求错误,请联系管理员或稍后重试',
        });
        break;
    }
    if (loadingInstance) {
      loadingInstance.close();
      NProgress.done();
    }
    return Promise.reject(error);

  }
)

// 文件下载通用方式
export const requestFile = axios.create({
  baseURL,
  timeout: 0, //关闭超时时间
});

requestFile.interceptors.request.use((config) => {
  config.headers['Authorization'] = 'Bearer  ' + localStorage.getItem("token") //携带的请求头
  config.responseType = 'blob';
  loadingInstance = Loading.service({
    lock: true,
    text: '正在下载,请稍候...',
    spinner: 'el-icon-loading',
    background: 'rgba(0, 0, 0, 0.3)'
  })
  return config;
});

requestFile.interceptors.response.use(
  (response) => {
    let res = response.data;
    if (loadingInstance) {
      loadingInstance.close()
    }
    // const contentType = response.headers['content-type'];//获取返回的数据类型
    let blob = new Blob([res], {
      type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" //文件下载以及上传类型为 .xlsx
    });
    let url = window.URL.createObjectURL(blob);
    // 创建一个链接元素
    let link = document.createElement('a');
    link.href = url;
    link.download = '产品列表.xlsx'; // 自定义文件名
    link.click();


  },
  (err) => {
    Message({
      message: '操作失败,请联系管理员',
      type: 'error',
    })
    if (loadingInstance) {

      loadingInstance.close()
    }
  }
);


export default service;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/805783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Fluentbit

Fluent Bit(常简称为Fluent-Bit 或 Fluentbit)是一个开源的、轻量级的日志数据收集器(log collector)和 转发器(log forwarder),旨在高效地收集、处理和转发日志数据。它是Fluentd项目的一个子项…

山东农业大学图书馆藏书《乡村振兴战略下传统村落文化旅游设计》

山东农业大学图书馆藏书《乡村振兴战略下传统村落文化旅游设计》

数字化时代,企业研发效能跃升之道丨IDCF

本文节选自新书《数字化时代研发效能跃升方法与实践》 作者:冬哥 研发效能是近年的热词,企业言必谈效能,但究竟什么是研发效能,落地具体应该如何进行,相信每个人都会有无数的问题浮现。 什么是效能? 效能…

Element-plus侧边栏踩坑

问题描述 el-menu直接嵌套el-menu-item菜单&#xff0c;折叠时不会出现文字显示和小箭头无法隐藏的问题&#xff0c;但是实际开发需求中难免需要把el-menu-item封装为组件 解决 vue3项目中嵌套两层template <template><template v-for"item in list" :k…

内网隧道代理技术(十三)之内网代理介绍

前言 什么?你问我内网隧道代理技术怎么突然就第十三篇了,第十二篇呢?这个,因为某些不可抗拒力量,第十二篇博客无法发表,如果想要查阅,请加内网渗透qq群:838076210 内网代理介绍 内网代理介绍 内网资产扫描这种场景一般是进行内网渗透才需要的代理技术,如果你不打内…

公共字段的填充

方式1&#xff0c;通过mybatis-plus提供的MetaObjectHandler进行填充 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;import com.sky.context.BaseContext; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import o…

【ROS第一讲】一、创建工作空间

【ROS第一讲】一、创建工作空间 一、工作空间1.src&#xff1a;2.build&#xff1a;3.devel&#xff1a;4.install: 二、创建工作空间1.工作空间的编译2.配置环境变量&#xff1a; 三、创建功能包 一、工作空间 1.src&#xff1a; 放置所有功能包源码的空间 2.build&#xf…

Unity XML3——XML序列化

一、XML 序列化 ​ 序列化&#xff1a;把对象转化为可传输的字节序列过程称为序列化&#xff0c;就是把想要存储的内容转换为字节序列用于存储或传递 ​ 反序列化&#xff1a;把字节序列还原为对象的过程称为反序列化&#xff0c;就是把存储或收到的字节序列信息解析读取出来…

再见 MyBatis-Plus !

一、Mybatis-Flex是什么&#xff1f; Mybatis-Flex 是一个优雅的 Mybatis 增强框架&#xff0c;它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库&#xff0c;其内置的 QueryWrapper^亮点 帮助我们极大的减少了 SQL 编写的工作的同时…

odoo16-domain

odoo16-domain 参考:https://blog.csdn.net/u013250491/article/details/86699928 domain的使用注意以下几点: 是在py文件中使用还是在xml中使用,py文件是在后端使用可以利用orm, 而xml是在前端渲染,使用的是js,没有办法使用orm如果在xml中使用,domain的格式建议为[[]], 二维…

LeetCode32.Longest-Valid-Parentheses<最长有效括号>

题目&#xff1a; 思路&#xff1a; 遍历括号.遇到右括号然后前一个是左括号 那就res2,然后重定位 i 的值 并且长度减少2; 但是问题在于无法判断最长的括号.只能得到string内的全部括号长度. 错误代码: 写过一题类似的,那题是找括号数.记得是使用的栈,但是死活写不出来. 看完…

【Visual Studio Code】加载saved_model.pb时报错缺失‘cudart64_110.dll‘等

如果报错Could not load dynamic library cudart64_110.dll; dlerror: cudart64_110.dll not found&#xff0c; 将对应的cudart64_110.dll复制到C:\Windows\System32下即可 如果VScode仍继续报错&#xff0c;重新启动软件即解决问题。 同理&#xff0c;若仍有相同报错 Cou…

ios私钥证书的创建方法

ios私钥证书是苹果公司为ios开发者打包app&#xff0c;推出的一种数字证书&#xff0c;只有同一个苹果开发者账号生成的ios私钥证书打的包&#xff0c;才能上架同一个开发者账号的app store。因此不要指望别人给你共享私钥证书和描述文件&#xff0c;因为别人的证书和描述文件打…

Ubuntu Server版 之 apache系列 安装、重启、开启,版本查看

安装之前首先要检测是否安装过 apt list --installed | grep tool tool&#xff1a;要检测的名称&#xff0c;如mysql、apache 、ngnix 等 安装 apache sudo apt install apache2 安装apache 默认是开启的 可以通过浏览器 检测一下 service apache stop # apache 停止服务…

道本科技||全面建立国有企业合规管理体系

为全面深化国有企业法治建设&#xff0c;不断加强合规管理&#xff0c;防控合规风险&#xff0c;保障企业稳健发展&#xff0c;近日&#xff0c;市国资委印发《常州市市属国有企业合规管理办法&#xff08;试行&#xff09;》&#xff08;以下简称《办法》&#xff09;&#xf…

包装设计软件 CubeDesigner[CubeMaster] 10.0 Crack

CubeDesigner 是一款包装设计软件&#xff0c;旨在支持包装专业人员创建最佳的包装设计和托盘图案。借助 CubeDesigner&#xff0c;您可以轻松确定优化的船箱尺寸、布置、托盘配置和卡车装载。CubeDesigner提供不同级别的服务&#xff0c;以满足不同用户的需求。CubeDesigner f…

Go语法入门 + 项目实战

&#x1f442; Take me Hand Acoustic - Ccile Corbel - 单曲 - 网易云音乐 第3个小项目有问题&#xff0c;不能在Windows下跑&#xff0c;懒得去搜Linux上怎么跑了&#xff0c;已经落下进度了.... 目录 &#x1f633;前言 &#x1f349;Go两小时 &#x1f511;小项目实战 …

深度学习:BatchNorm、LayerNorm、InstanceNorm、GroupNorm和SwitchableNorm的理解

深度学习&#xff1a;BatchNorm、LayerNorm、InstanceNorm、GroupNorm和SwitchableNorm的理解 深度学习中的NormBatchNormLayerNormInstanceNormGroupNormSwitchableNorm 附录 深度学习中的Norm 在深度学习中会经常遇到BatchNorm、LayerNorm、InstanceNorm和GroupNorm&#xf…

queue

文章目录 定义分类链式队列静态队列循环队列静态队列为什么必须是循环队列&#xff1f;循环队列需要几个参数&#xff1f;循环队列入队伪代码循环队列出队伪代码判断循环队列是否为空判断循环队列是否已满 循环队列的代码实现队列的应用 定义 一种可以实现“先进先出”的存储结…

postcss-pxtorem适配插件动态配置rootValue(根据文件路径名称,动态改变vue.config里配置的值)

项目背景&#xff1a;一个项目里有两个分辨率的设计稿(1920和2400)&#xff0c;不能拆开来打包 参考&#xff1a; 是参考vant插件&#xff1a;移动端Vant组件库rem适配下大小异常的解决方案&#xff1a;https://github.com/youzan/vant/issues/1181 说明&#xff1a; 因为vue.c…