Soybean Admin实现Token过期或请求失败跳转登录页的功能

环境

Vue3、soybean admin: “1.0.0”(native-ui: “2.38.0”)、pnpm: “8.5.3”、axios: “1.6.7”

实现

Soybean Admin 使用的 Axios 库,封装的请求方法中没有对请求失败做任何逻辑处理,目前需要实现:请求返回 50020 状态码,表示 token 失效或者过期,跳转登录页面,让用户重新登录

1. 找到补充逻辑的地方

因为这是常用功能,在二次开发各种后台管理模板都是这样,有需求第一步先找文档,Soybean Admin文档-系统请求中说到了, onBackendFail 是后端请求在业务上表示失败时调用的异步函数,例如:处理 token 过期

所以大概率是在 onBackendFail 里面实现功能

// 文件路径:src\service\request\index.ts
export const request = createFlatRequest<App.Service.Response>(
  {
    baseURL: isHttpProxy ? createProxyPattern() : baseURL,
  },
  {
    async onRequest(config) {
      const { headers } = config;

      // set token
      const token = localStg.get('token');
      // const Authorization = token ? `Bearer ${token}` : null;
      const Authorization = token ? `${token}` : null;
      Object.assign(headers, { Authorization });

      return config;
    },
    isBackendSuccess(response) {
      // when the backend response success is "true", it means the request is success
      return response?.data?.success === true;
    },
    async onBackendFail(_response) {
      // when the backend response code is not "0000", it means the request is fail
      // for example: the token is expired, refetch token and retry request
      // 补充token失效逻辑
      console.log("补充token失效逻辑");
    },
    transformBackendResponse(response) {
      return response.data.result;
    },
    onError(error) {
      // when the request is fail, you can show error message

      let message = error.message;

      // show backend error message
      if (error.code === BACKEND_ERROR_CODE) {
        message = error.response?.data?.message || message;
      }

      window.$message?.error(message);
    }
  }
);

打印测试发现,接口请求失败确实会走这个方法。

你还会发现打印了两次,不着急,等功能完成后再解决(参考博客

确定补充逻辑的地方后,下一步:

补充上状态码的判断,将 redirectLogin 单独出来

async onBackendFail(_response) {
  // when the backend response code is not "0000", it means the request is fail
  // for example: the token is expired, refetch token and retry request
  // 补充token失效逻辑
  if (_response.data.code === TOKEN_EXPIRED_CODE) {
   	await redirectLogin();  // token失效跳转到登录页
  }
},
2. 写redirectLogin方法

首先我们看 onBackendFail 是在哪里执行的,写 redirectLogin 方法之前有没有需要注意的地方。createFlatRequest 调用的是 createCommonRequest 方法

看看 createCommonRequest 方法

// 文件路径:packages\axios\src\index.ts
function createCommonRequest<ResponseData = any>(
  axiosConfig?: CreateAxiosDefaults,
  options?: Partial<RequestOption<ResponseData>>
) {
  const opts = createDefaultOptions<ResponseData>(options);

  const axiosConf = createAxiosConfig(axiosConfig);
  const instance = axios.create(axiosConf);

  const cancelTokenSourceMap = new Map<string, CancelTokenSource>();

  // config axios retry
  const retryOptions = createRetryOptions(axiosConf);
  axiosRetry(instance, retryOptions);

  instance.interceptors.request.use();  // ...

  instance.interceptors.response.use(
    async response => {
      if (opts.isBackendSuccess(response)) {
        return Promise.resolve(response);
      }

      const fail = await opts.onBackendFail(response, instance);  // 就是这里!!!
      if (fail) {
        return fail;
      }

      // ...
    },
    async (error: AxiosError<ResponseData>) => {}
  );

  function cancelRequest(requestId: string) {} // ...

  function cancelAllRequest() {} // ...

  return {
    instance,
    opts,
    cancelRequest,
    cancelAllRequest
  };
}

看起来很简单,没什么好注意的,只是注意要返回一个 Promise 对象,setTimeout 模拟一下异步就行了。

准备开始写方法,参考现有的跳转登录页功能,比如系统自带的【退出登录】功能,去看看有没有能抄的:

文件路径:src\layouts\modules\global-header\components\user-avatar.vue

<script setup lang="ts">
import { useAuthStore } from '@/store/modules/auth';
import { useRouterPush } from '@/hooks/common/router';

const authStore = useAuthStore();
const { routerPushByKey, toLogin } = useRouterPush();

function loginOrRegister() {
  toLogin();
}

function logout() {
  window.$dialog?.info({
    title: $t('common.tip'),
    content: $t('common.logoutConfirm'),
    positiveText: $t('common.confirm'),
    negativeText: $t('common.cancel'),
    onPositiveClick: () => {
      authStore.resetStore();  // 确认退出登录,会清空token, 然后跳转到登录页
    }
  });
}
</script>

<template></template>
<style scoped></style>
3. 遇到问题

照上面【退出登录】组件代码,补充 redirectLogin 方法,代码如下:

import { useAuthStore } from '@/store/modules/auth';

const authStore = useAuthStore();

export const request = createFlatRequest<App.Service.Response>(){}; // ...

// 跳转登录页面
function redirectLogin() {
  return new Promise<void>((resolve, reject) => {
    setTimeout(() => {
      authStore.resetStore();  // 参考退出登录的逻辑
      resolve();
    }, 500);
  })
}

但控制台报错:pinia.js?v=f2cc29a3:1340 Uncaught Error: [🍍]: “getActivePinia()” was called but there was no active Pinia. Are you trying to use a store before calling “app.use(pinia)”? See https://pinia.vuejs.org/core-concepts/outside-component-usage.html for help.

getActivePinia

解决了好久还是不知道什么问题~

但从菠萝报错信息来看,是 store 的问题,那就不使用 authStore.resetStore 这个方法了,直接跳转登录页也是一样的

4. 另辟蹊径

那就直接跳转登录页了,也不清空 token,反正登录就会重新赋值的,最重要的是解决问题。

代码如下

import { useRouterPush } from '@/hooks/common/router';

const { toLogin } = useRouterPush();

// 跳转登录页面
function redirectLogin() {
  return new Promise<void>((resolve, reject) => {
    setTimeout(() => {
      toLogin(); // 跳转到登录页
      resolve();
    }, 1200);
  })
}

没有跳转到登录页,并且控制台警告+报错

漂黄+报错

漂黄: [Vue warn]: inject() can only be used inside setup() or functional components.
漂红:Uncaught ReferenceError: Cannot access ‘globalRouter’ before initialization

第一个警告好解决,看 useRouterPush 源码,需要将 inSetup 参数设置成 false

// 文件路径:src\hooks\common\router.ts
import { useRouter } from 'vue-router';
import type { RouteLocationRaw } from 'vue-router';
import type { RouteKey } from '@elegant-router/types';
import { router as globalRouter } from '@/router';

/**
 * Router push
 *
 * Jump to the specified route, it can replace function router.push
 *
 * @param inSetup Whether is in vue script setup
 */
export function useRouterPush(inSetup = true) { ... }

设置完了还是报错:Uncaught ReferenceError: Cannot access ‘globalRouter’ before initialization

没有耐心了,报错全都是执行时机的问题,陷进屎坑了。

库好多方法都封装的很好,无可挑剔,很难排查执行时机问题。

用最原始的方法跳转登录页,不使用 Soybean Admin 提供的 authStore.resetStore、toLogin 方法。

5. 原始方法

网上搜索了:“Vue3 axios实现token失效跳转登录页面”,参考了网友的实现,决定使用最丑陋的route.push方式,开始导入router对象:

import { router } from '@/router';

...

// 跳转登录页面
function redirectLogin() {
  return new Promise<void>((resolve, reject) => {
    setTimeout(() => {
      console.log(router); // 先测试
      resolve();
    }, 1200);
  })
}

测试发现 router 能成功打印,没有报错。

突然就想把之前的 useAuthStore、useRouterPush 移到 redirectLogin 方法里面去执行,竟然不报错,可以成功跳转到登录页???!!!

6. 完善方法

千辛万苦,实现 token 过期或失效时跳转登录页的功能,最终的代码如下:

async onBackendFail(_response) {
 // when the backend response code is not "0000", it means the request is fail
  // for example: the token is expired, refetch token and retry request
  // 补充token失效逻辑
  if (_response.data.code === TOKEN_EXPIRED_CODE) {
    await redirectLogin();  // token失效
  }
}

function redirectLogin() {
 return new Promise<void>((resolve, reject) => {
   setTimeout(() => {
     const authStore = useAuthStore();
     authStore.resetStore();  // 参考退出登录的逻辑
     resolve();
   }, 500);
 })
}

简单得要死!

最后分享一下,不需要后端配合,自测的方法:

自测Token失效
欢迎评论指出问题!


总结

记录了这次Soybean Admin基础上实现Token过期跳转登录页功能的整个过程。

因为代码放错地方,导致出现了很多奇奇怪怪的问题,也算是踩坑过程了,主要还是因为不熟悉。

最后希望本博客能帮助到大家!

  • 12
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值