在多项目协同开发中,跨项目共享用户状态是常见需求,但细微的状态同步问题可能导致难以复现的异常。本文将完整复现 Vue2 项目中跨项目跳转时的“闪跳 login 再回 home” 问题,并提供基于 Cookie 对比的解决方案。

一、问题背景与现象

本次问题发生在两个关联 Vue2 项目(项目 A、项目 B)中,核心场景与异常表现如下:

1. 项目关联关系

  • 项目 B 是项目 A 的功能延伸模块,需共享项目 A 的登录状态。
  • 用户信息同时存储在localStorage(完整用户信息)和Cookie(身份标识)中。
  • 跳转逻辑:项目 A 通过按钮跳转至项目 B的指定页面(如/projectB/detail)。

2. 异常表现

  • 首次跳转异常:点击跳转后页面闪一下,先跳转到项目 B 的login页面,随后自动切回home页面
  • 二次跳转正常:再次点击跳转按钮,可正常进入项目 B 的目标页面。
  • 场景局限性:仅在切换账号登录后出现,同一账号未清除缓存时重复跳转无异常。

二、问题复现 Demo

1. 项目 A:跳转触发代码

项目 A 通过按钮触发跳转,直接拼接项目 B 的 URL。

<!-- 项目A:跳转按钮组件 -->
<template>
  <button @click="goToProjectB">跳转到项目B详情页</button>
</template>

<script>
export default {
  methods: {
    goToProjectB() {
      // 从localStorage获取当前用户Cookie(项目A登录时已存储)
      const userCookie = localStorage.getItem('userCookie');
      // 跳转至项目B的详情页,使用了域名共享的Cookie
      // 注意:存储cookie时,两个项目中的domain要保持一致
      window.location.href = `http://projectB.com/detail`;
    }
  }
};
</script>

2. 项目 B:问题核心代码

项目 B 的路由守卫和请求拦截逻辑存在 “状态同步延迟”,导致旧用户信息干扰验证。在项目A中退出登录时,项目B的LocalStorage的用户信息并未清除。
(1)路由守卫:未对比 Cookie 直接用旧信息

// 项目B:router/index.js(路由守卫)
import Vue from 'vue';
import Router from 'vue-router';
import { getUserInfo, verifyCookie } from '@/api/user'; // 接口:获取用户信息、验证Cookie

Vue.use(Router);

const router = new Router({
  routes: [
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/Login')
    },
    {
      path: '/home',
      name: 'Home',
      component: () => import('@/views/Home')
    },
    {
      path: '/detail',
      name: 'Detail',
      component: () => import('@/views/Detail'),
      meta: { requiresAuth: true } // 需要登录验证
    }
  ]
});

// 全局前置路由守卫
router.beforeEach(async (to, from, next) => {
  // 判断目标页面是否需要登录
  if (to.meta.requiresAuth) {
    // 1. 从localStorage获取用户信息(问题点:未更新时为旧信息)
    const localUser = JSON.parse(localStorage.getItem('userInfo'));
    
    if (localUser) {
      // 直接使用旧用户信息放行(切换账号后,此信息与Cookie不匹配)
      next();
    } else {
      // 2. 无本地信息时,验证Cookie
      const cookie = document.cookie.replace(/(?:(?:^|.*;\s*)userCookie\s*=\s*([^;]*).*$)|^.*$/, '$1');
      const res = await verifyCookie(cookie); // 接口:用Cookie获取最新用户信息
      
      if (res.success) {
        // 更新localStorage用户信息
        localStorage.setItem('userInfo', JSON.stringify(res.data.user));
        next();
      } else {
        next('/login'); // Cookie无效则跳登录
      }
    }
  } else {
    next();
  }
});

export default router;

(2)请求拦截:Token 不匹配导致拦截跳转

// 项目B:utils/request.js(Axios请求拦截)
import axios from 'axios';
import router from '@/router';

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API,
  timeout: 5000
});

// 请求拦截器:添加Token
service.interceptors.request.use(
  config => {
    // 从localStorage获取旧用户Token(切换账号后与Cookie对应的Token不匹配)
    const userInfo = JSON.parse(localStorage.getItem('userInfo'));
    if (userInfo) {
      config.headers.Authorization = `Bearer ${userInfo.token}`;
    }
    return config;
  },
  error => Promise.reject(error)
);

// 响应拦截器:处理Token无效
service.interceptors.response.use(
  response => response.data,
  error => {
    // Token验证失败(401):跳登录页
    if (error.response?.status === 401) {
      router.push('/login');
      // 登录页逻辑:检测到Cookie有效,自动拉取新用户信息后跳home
      localStorage.removeItem('userInfo'); // 清除旧信息
    }
    return Promise.reject(error);
  }
);

export default service;

三、问题根源分析

通过复现代码可定位核心矛盾:localStorage的旧用户信息与最新Cookie不同步,具体流程如下:

  • 切换账号后状态残留:用户在项目 A 切换账号登录,项目 A 更新了localStorage和Cookie,但项目 B 的localStorage仍保留上一用户的旧信息。
  • 路由守卫误判:跳转至项目 B 时,路由守卫检测到localStorage有旧用户信息,直接放行,未验证Cookie是否匹配。
  • 请求拦截触发异常:项目 B 的接口请求携带旧用户 Token,与Cookie对应的新 Token 不匹配,触发 401 拦截,跳转到login页。
  • 登录页二次修正:login页检测到Cookie有效,调用接口拉取新用户信息更新localStorage,随后自动跳回home页。

四、解决方案:Cookie 对比同步用户信息

核心思路:每次路由守卫验证时,对比localStorage存储的旧 Cookie 与当前浏览器的新 Cookie,不一致则强制更新用户信息,确保状态同步。
(1)优化后的项目B的路由守卫

// 项目B:router/index.js(优化后路由守卫)
router.beforeEach(async (to, from, next) => {
  if (to.meta.requiresAuth) {
    // 1. 获取两个关键信息:当前Cookie、localStorage存储的旧Cookie
    const currentCookie = document.cookie.replace(/(?:(?:^|.*;\s*)userCookie\s*=\s*([^;]*).*$)|^.*$/, '$1');
    const localUser = JSON.parse(localStorage.getItem('userInfo'));
    const oldCookie = localUser ? localUser.cookie : ''; // 存储用户信息时同步保存Cookie
    
    // 2. 关键判断:Cookie不一致 或 无本地用户信息 → 强制更新
    if (!localUser || currentCookie !== oldCookie) {
      const res = await verifyCookie(currentCookie);
      if (res.success) {
        // 存储用户信息时,同步保存当前Cookie(用于下次对比)
        const newUserInfo = { ...res.data.user, cookie: currentCookie };
        localStorage.setItem('userInfo', JSON.stringify(newUserInfo));
        next();
      } else {
        next('/login');
      }
    } else {
      // Cookie一致 → 直接放行
      next();
    }
  } else {
    next();
  }
});

(2)配套优化:用户信息存储格式,确保存储用户信息时同步保存当前 Cookie,便于后续对比

// 项目B:登录/验证Cookie成功后,存储用户信息的格式
const newUserInfo = {
  id: res.data.user.id,
  name: res.data.user.name,
  token: res.data.user.token,
  cookie: currentCookie // 关键:同步保存当前Cookie
};
localStorage.setItem('userInfo', JSON.stringify(newUserInfo));

五、优化后验证效果

优化后,跨项目跳转流程恢复正常,具体表现:

  • 首次跳转:路由守卫对比currentCookie与oldCookie,发现不一致,调用接口拉取新用户信息,更新localStorage后正常进入目标页面。
  • 二次跳转:Cookie 无变化,直接放行,无需重复请求接口。
  • 切换账号:项目 A 切换账号后,项目 B 通过 Cookie 对比感知变化,自动更新用户信息,无闪跳异常。

六、总结与扩展建议

1. 问题核心启示

  • 多项目共享状态时,localStorage等本地存储存在 “状态残留” 风险,需通过与Cookie(或后端接口)的对比确保时效性。
  • 路由守卫的验证逻辑不能仅依赖本地存储,需结合权威数据源(如Cookie、后端接口)双重校验。

2. 扩展优化方向

  • Cookie 安全性:敏感场景下,建议使用HttpOnly Cookie(需后端配合),避免前端直接操作 Cookie 带来的安全风险。
  • 状态同步方案:复杂场景可引入单点登录(SSO)系统,统一管理多项目的登录状态,减少跨项目状态同步成本。
  • 异常监控:在路由守卫和请求拦截中添加日志(如console.log或埋点),便于快速定位类似的状态同步问题。
Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐