需求背景

按以前的做法,前端在token过期后会跳回登录页,要求重新登录,这样会打断用户操作,所以提出需求:要无感刷新token

代码设计背景

1.后端:每个用户的token存入user表里这个用户的token字段,每次登录即更新token字段;
2.前端:利用axios发请求
3.前后端共用:前端把token存在headers的acces_token中,每个请求后端都进行校验token是否过期(实现方式看2020-11-04 node端做用户token校验(jsonwebtoken)),token过期返回特定code供前端在拦截器里做判断。

设计思路

在返回拦截器判断token是否过期,如果过期,重新发送登录请求以刷新token,然后重新发起token过期时发送的请求。
具体实现思路可查看【axios如何利用promise无痛刷新token

返回拦截器代码

最终实现目标:
1.拦截器收到token过期的code,请求登录接口,成功后再请求过期时发送的请求
2.防止多次刷新token
3.解决多个请求的重新发送

  1. const axios = Axios.create(axios_config); // 创建axios
  2. const REFRESH_TOKEN_CODE = 996; // 和后端统一定义996是token过期的code
  3. // 响应拦截器
  4. let isRefreshing = false; // 是否正在刷新token
  5. let requests = []; // 等待token刷新后重新发送的请求队列
  6. axios.interceptors.response.use(
  7. async res => {
  8. // 返回成功有code的情况,返回 res.data.data
  9. if (res.data && res.data.code) {
  10. if (res.data.code === 200) {
  11. return res.data.data != undefined ? res.data.data : {}; // code 200 也可能没有 data
  12. } else {
  13. // 自动刷新token逻辑
  14. const config = res.config; // 本次失败请求的信息
  15. if (res.data.code === REFRESH_TOKEN_CODE) {
  16. if (!isRefreshing) {
  17. isRefreshing = true;
  18. try {
  19. const userInfo = JSON.parse(sessionStorage.getItem('userInfo')); // 从缓存中读取用户信息,用于登录
  20. const params = {
  21. ... // 登录请求的参数
  22. };
  23. const loginRes = await login(params); // 登录请求
  24. sessionStorage.setItem('userInfo', JSON.stringify(loginRes)); // 再存入缓存
  25. config.headers['access_token'] = loginRes.token;
  26. // 已经刷新了token,将所有队列中的请求进行重试
  27. requests.forEach(cb => cb(loginRes.token));
  28. requests = []; // 重试完了别忘了清空这个队列
  29. return axios(config); // 第一个重新发送的请求,其他请求都在else中
  30. } catch (e) {
  31. console.log(e);
  32. vm.$router.push({ path: '/login' });
  33. } finally {
  34. isRefreshing = false;
  35. }
  36. } else {
  37. // 正在刷新token,返回一个未执行resolve的promise
  38. return new Promise(resolve => {
  39. // 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
  40. requests.push(token => {
  41. config.headers['access_token'] = token;
  42. resolve(axios(config));
  43. });
  44. });
  45. }
  46. return;
  47. }
  48. vm.$message.closeAll();
  49. vm.$message.error(res.data.msg);
  50. // if (LOGOUT_RES_CODE.includes(res.data.code)) {
  51. // vm.$router.push({ path: '/login' });
  52. // }
  53. return Promise.reject({
  54. code: res.data.code,
  55. msg: res.data.msg || '',
  56. });
  57. }
  58. }
  59. return res.data;
  60. },
  61. error => {
  62. ... // 省略部分代码
  63. return Promise.reject(error);
  64. }
  65. );