Vben5登录过期无法再次登录问题,http状态码

个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview


前言

最近在做项目前端,使用的https://doc.vben.pro/,在登录过期时出现了无法再次登录的问题,在此记录一下。

项目前面那些直接略过,如果感兴趣直接看官方文档就可以 ,以下会根据解决过程附带部分官网说明。

分析原因

梳理流程

文档前面的都已按照要求配置修改好了,所有的认证接口也都完成了,登录也是没有问题的,就是在accessToken过期的时候无法回到登录页面,就算直接路由到/auth/login也永远在转圈圈。

在本项目中,在登录完成后将accessToken使用pinia加持久化插件存储到localStorage中,

typescript 复制代码
// 项目配置 setup.ts

/**
 * @zh_CN 初始化pinia
 */
export async function initStores(app: App, options: InitStoreOptions) {
  const { createPersistedState } = await import('pinia-plugin-persistedstate');
  pinia = createPinia();
  const { namespace } = options;
  pinia.use(
    createPersistedState({
      // key $appName-$store.id
      key: (storeKey) => `${namespace}-${storeKey}`,
      storage: localStorage,
    }),
  );
  app.use(pinia);
  return pinia;
}

// 认证相关 access.ts
...

  persist: {
    // 持久化
    pick: ['accessToken', 'refreshToken', 'accessCodes'],
      },
  state: (): AccessState => ({
    accessCodes: [],
    accessMenus: [],
    accessRoutes: [],
    accessToken: null,
    isAccessChecked: false,
    loginExpired: false,
    refreshToken: null,
  }),
});

然后在每次请求时使用自定义配置了请求拦截器和响应拦截器的axios中带上accessToken

typescript 复制代码
// axios配置 request.ts
  function formatToken(token: null | string) {
    return token ? `Bearer ${token}` : null;
  }

  // 请求头处理
  client.addRequestInterceptor({
    fulfilled: async (config) => {
      const accessStore = useAccessStore();

      config.headers.Authorization = formatToken(accessStore.accessToken);
      config.headers['Accept-Language'] = preferences.app.locale;
      return config;
    },
  });

  // response数据解构
  client.addResponseInterceptor<HttpResponse>({
    fulfilled: (response) => {
      const { data: responseData, status } = response;

      const { code, data } = responseData;
      if (status >= 200 && status < 400 && code === 0) {
        return data;
      }

      throw Object.assign({}, response, { response });
    },
  });

在此都没问题,因为使用登录和其他接口请求和响应都没问题,问题到底在哪?

后端命名也是正常接收到accessToken了呀,为什么永远在转圈圈,回不去了呢?

那既然这样,只能使出我并没把握的前端debug了。

debug

根据前面的[Vue Router warn]: Unexpected error when starting the router: {code: 401, msg: '账号未登录'}的输出和错误栈,大概可以知道是在bootstrap.ts:33出问题了,那么先在这断一下,结合代码判断,这一步就是设置路由啊,可知一定是初始化路由出错了,导致所有路由都出问题了。

那么在创建路由再加上断点,在export之前操作router都点一下

发现可以到下面的创建路由守卫处,说明前面都还没错,那么就继续分析路由守卫

在继续debug中发现,在到达96行之前就提示错误,开始转圈圈了,那么错误一定是89-96之间的。

最后发现是在fetchUserInfo后出错的,更准确的讲是getUserInfoApi出错的。

typescript 复制代码
async function fetchUserInfo() {
  let userInfo: null | UserInfo = null;
  userInfo = await getUserInfoApi();
  userStore.setUserInfo(userInfo);
  return userInfo;
}

/**
 * 获取用户信息
 */
export async function getUserInfoApi() {
  return requestClient.get<UserInfo>('/user/info');
}

???不是已经正常返回了吗?我也提示账号未登录了啊,那一定是response的问题,继续看axios配置,这里设置三个拦截器,看其中token过期的处理响应拦截器,正常应该走doReAuthenticate重新认证逻辑啊,怎么不行呢?

typescript 复制代码
// response数据解构
  client.addResponseInterceptor<HttpResponse>({
    fulfilled: (response) => {
      const { data: responseData, status } = response;

      const { code, data } = responseData;
      if (status >= 200 && status < 400 && code === 0) {
        return data;
      }

      throw Object.assign({}, response, { response });
    },
  });

  // token过期的处理
  client.addResponseInterceptor(
    authenticateResponseInterceptor({
      client,
      doReAuthenticate,
      doRefreshToken,
      enableRefreshToken: preferences.app.enableRefreshToken,
      formatToken,
    }),
  );

  // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里
  client.addResponseInterceptor(
    errorMessageResponseInterceptor((msg: string, error) => {
      // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg
      // 当前mock接口返回的错误字段是 error 或者 message
      const responseData = error?.response?.data ?? {};
      const errorMessage = responseData?.error ?? responseData?.msg ?? '';
      // 如果没有错误信息,则会根据状态码进行提示
      message.error(errorMessage || msg);
    }),
  );

authenticateResponseInterceptor响应拦截器细看,终于抓到你了,没有进入重新认证的逻辑是因为在

response?.status !== 401true直接抛异常了。

啊啊啊啊,响应码不就是401吗?好吧,还真不是。

根因

根因就是我使用了后端的统一响应处理,将所有异常都以正常的响应返回,也就是说并没有使用http的响应码,所有能被后端处理的异常都会以响应体里的code来表示。

java 复制代码
/**
 * 认证失败
 */
@ExceptionHandler(NotLoginException.class)
public CommonResult<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
    String requestUri = request.getRequestURI();
    log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestUri, e.getMessage());
    return CommonResult.error(UNAUTHORIZED);
}

可是这个项目中使用了http状态码,也是axiosstatus

typescript 复制代码
export interface AxiosResponse<T = any, D = any> {
  data: T;
  status: number;
  statusText: string;
  headers: RawAxiosResponseHeaders | AxiosResponseHeaders;
  config: InternalAxiosRequestConfig<D>;
  request?: any;
}

解决

解决起来也很简单,无非改造前端响应体或改在后端使用http响应,我选择了后者。

像下面这样使用org.springframework.http.ResponseEntity;再包装一下就可以了,

这样本次请求状态码就不是200了,正常走认证逻辑了。

进度

最后提一下进度吧!忘了上次提到的进度是哪里了,那就从指标版本控制之后讲吧。

1、【重要】策略集和规则的版本控制,还没做完

2、【重要】调整了项目结构,优化组件和上下文,新增规则action(发消息、打tag、加名单等)

3、【一般】新增节点、字段逻辑等通用接口,修复一些问题

4、【重要】策略的权重模式,顺序&最坏&投票&权重,在计算权重和动态字段时使用QLExpress

5、【重要】放弃逻辑删除,新增数据接入elasticsearch,增加docker-compose配置

这些是后端的,前端也要开始,有三大挑战:1、条件组合组件;2、公式类输入时联想词;3、LiteFlow编排。

写在最后

拙作艰辛,字句心血,望诸君垂青,多予支持,不胜感激。


个人博客:无奈何杨(wnhyang)

个人语雀:wnhyang

共享语雀:在线知识共享

Github:wnhyang - Overview

相关推荐
ywf12151 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭1 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf7 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特7 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷7 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian8 小时前
前端node常用配置
前端
华洛8 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq8 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A9 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常10 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端