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

迷南。 2024-05-04 08:15 110阅读 0赞

文章目录

    • ? 废话在前
    • ? 接着踩坑
    • ? 解决思路
    • ? 完整代码

? 废话在前

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

在这里插入图片描述

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

? 接着踩坑

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

看一下代码

  1. service.interceptors.response.use(
  2. response => {
  3. const res = response.data
  4. //刷新token的时候,可以从这里拦截到新数据,但是没有显示在页面上
  5. console.log('拦截数据:',res)
  6. if (loadingInstance) {
  7. loadingInstance.close();
  8. NProgress.done();
  9. }
  10. switch (res.code) {
  11. case 200:
  12. return res
  13. case 401:
  14. router.push({
  15. path: "/login",
  16. })
  17. localStorage.clear();
  18. Notification.error({
  19. title: '令牌过期',
  20. message: '当前身份信息超过三天已失效,请您重新登录',
  21. duration: 0
  22. });
  23. break;
  24. default:
  25. Message({
  26. message: res.message || '请求错误',
  27. type: 'error',
  28. duration: 5 * 1000
  29. })
  30. break;
  31. }
  32. },
  33. error => {
  34. switch (error.response.status) {
  35. case 401:
  36. MessageBox.confirm('身份认证已过期,是否刷新本页继续浏览?', '提示', {
  37. confirmButtonText: '继续浏览',
  38. cancelButtonText: '退出登录',
  39. type: 'warning'
  40. }).then(() => {
  41. axios.post('/api/token/refresh/', {
  42. refresh: localStorage.getItem("retoken")
  43. }).then(response => {
  44. let res = response.data || {
  45. }
  46. if (res.code == 200) {
  47. let token = res.data.result;
  48. localStorage.setItem("token", token.access);
  49. localStorage.setItem("retoken", token.refresh);
  50. error.response.config.baseURL = '';
  51. error.response.config.headers['Authorization'] = 'Bearer ' + localStorage.getItem("token")
  52. window.location.reload();
  53. } else {
  54. router.push({
  55. path: "/login",
  56. })
  57. localStorage.clear();
  58. Notification.error({
  59. title: '令牌过期',
  60. message: '当前身份信息超过三天已失效,请您重新登录',
  61. duration: 0
  62. });
  63. }
  64. }).catch(err => {
  65. router.push({
  66. path: "/login",
  67. })
  68. localStorage.clear();
  69. Notification.error({
  70. title: '认证失败',
  71. message: '信息认证失败,请重新登录',
  72. duration: 0
  73. });
  74. })
  75. }).catch(() => {
  76. router.push({
  77. path: "/login",
  78. })
  79. Message({
  80. message: '已退出',
  81. type: 'success',
  82. })
  83. localStorage.clear();
  84. });
  85. break;
  86. case 404:
  87. Notification.error({
  88. title: '404错误',
  89. message: '服务器请求错误,请联系管理员或稍后重试。错误状态码:404',
  90. });
  91. break
  92. default:
  93. Notification.error({
  94. title: '请求错误',
  95. message: '服务器请求错误,请联系管理员或稍后重试',
  96. });
  97. break;
  98. }
  99. if (loadingInstance) {
  100. loadingInstance.close();
  101. NProgress.done();
  102. }
  103. return Promise.reject(error);
  104. }
  105. )

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

当时想的是和后端配合,让后端直接发一个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代码模块,包含了一个下载文件的模块。

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

  1. import axios from 'axios'
  2. import {
  3. Message,
  4. Loading,
  5. Notification,
  6. } from 'element-ui'
  7. import NProgress from 'nprogress' // progress bar
  8. import 'nprogress/nprogress.css' // progress bar style
  9. import router from '@/router/index'
  10. const baseURL = '/api'
  11. const service = axios.create({
  12. baseURL,
  13. timeout: 6000
  14. })
  15. NProgress.configure({
  16. showSpinner: false
  17. }) // NProgress Configuration
  18. let loadingInstance = undefined;
  19. service.interceptors.request.use(
  20. config => {
  21. NProgress.start()
  22. loadingInstance = Loading.service({
  23. lock: true,
  24. text: '正在加载,请稍候...',
  25. spinner: 'el-icon-loading',
  26. background: 'rgba(0, 0, 0, 0.3)'
  27. })
  28. config.headers['Authorization'] = 'Bearer ' + localStorage.getItem("token")
  29. return config;
  30. },
  31. error => {
  32. return Promise.reject(error)
  33. }
  34. )
  35. service.interceptors.response.use(
  36. response => {
  37. const res = response.data
  38. if (loadingInstance) {
  39. loadingInstance.close();
  40. NProgress.done();
  41. }
  42. switch (res.code) {
  43. case 200:
  44. return res
  45. case 401:
  46. router.push({
  47. path: "/login",
  48. })
  49. localStorage.clear();
  50. Notification.error({
  51. title: '认证失效',
  52. message: '当前身份信息超过三天已失效,请您重新登录',
  53. duration: 0
  54. });
  55. break;
  56. default:
  57. Message({
  58. message: res.message || '请求错误',
  59. type: 'error',
  60. duration: 5 * 1000
  61. })
  62. break;
  63. }
  64. },
  65. async error => {
  66. switch (error.response.status) {
  67. case 401:
  68. const err401Data = error.response.data || {
  69. }
  70. if (err401Data.code !== "token_not_valid") {
  71. router.push({
  72. path: "/login",
  73. })
  74. localStorage.clear();
  75. Notification.error({
  76. title: '认证失败',
  77. message: '身份信息认证失败,请您重新登录',
  78. duration: 0
  79. });
  80. return
  81. }
  82. try {
  83. const res = await service.post('/token/refresh/', {
  84. refresh: localStorage.getItem("retoken")
  85. })
  86. if (res.code == 200) {
  87. let token = res.data.result;
  88. localStorage.setItem("token", token.access);
  89. localStorage.setItem("retoken", token.refresh);
  90. error.response.config.baseURL = ''
  91. error.response.config.headers['Authorization'] = 'Bearer ' + localStorage.getItem("token")
  92. return service(error.response.config)
  93. } else {
  94. router.push({
  95. path: "/login",
  96. })
  97. localStorage.clear();
  98. Notification.error({
  99. title: '认证失败',
  100. message: '当前身份认证信息已失效,请您重新登录',
  101. duration: 0
  102. });
  103. }
  104. } catch (error) {
  105. router.push({
  106. path: "/login",
  107. })
  108. localStorage.clear();
  109. Notification.error({
  110. title: '认证失败',
  111. message: '身份认证失败,请您重新登录,失败原因:' + error.message,
  112. duration: 0
  113. });
  114. }
  115. break
  116. case 404:
  117. Notification.error({
  118. title: '404错误',
  119. message: '服务器请求错误,请联系管理员或稍后重试。错误状态码:404',
  120. });
  121. break
  122. default:
  123. Notification.error({
  124. title: '请求错误',
  125. message: '服务器请求错误,请联系管理员或稍后重试',
  126. });
  127. break;
  128. }
  129. if (loadingInstance) {
  130. loadingInstance.close();
  131. NProgress.done();
  132. }
  133. return Promise.reject(error);
  134. }
  135. )
  136. // 文件下载通用方式
  137. export const requestFile = axios.create({
  138. baseURL,
  139. timeout: 0, //关闭超时时间
  140. });
  141. requestFile.interceptors.request.use((config) => {
  142. config.headers['Authorization'] = 'Bearer ' + localStorage.getItem("token") //携带的请求头
  143. config.responseType = 'blob';
  144. loadingInstance = Loading.service({
  145. lock: true,
  146. text: '正在下载,请稍候...',
  147. spinner: 'el-icon-loading',
  148. background: 'rgba(0, 0, 0, 0.3)'
  149. })
  150. return config;
  151. });
  152. requestFile.interceptors.response.use(
  153. (response) => {
  154. let res = response.data;
  155. if (loadingInstance) {
  156. loadingInstance.close()
  157. }
  158. // const contentType = response.headers['content-type'];//获取返回的数据类型
  159. let blob = new Blob([res], {
  160. type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" //文件下载以及上传类型为 .xlsx
  161. });
  162. let url = window.URL.createObjectURL(blob);
  163. // 创建一个链接元素
  164. let link = document.createElement('a');
  165. link.href = url;
  166. link.download = '产品列表.xlsx'; // 自定义文件名
  167. link.click();
  168. },
  169. (err) => {
  170. Message({
  171. message: '操作失败,请联系管理员',
  172. type: 'error',
  173. })
  174. if (loadingInstance) {
  175. loadingInstance.close()
  176. }
  177. }
  178. );
  179. export default service;

发表评论

表情:
评论列表 (有 0 条评论,110人围观)

还没有评论,来说两句吧...

相关阅读

    相关 axios简单使用

    > 学习笔记 一、简介 1. axios 是一个基于 Promise 的 HTTP 库,可以用在浏览器和node.js中。 2. 本质:发送请求,获得相对应的响应结