You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

6.0 KiB

认证系统重构:集中式状态管理与跨标签页同步

日期: 2026-02-06 类型: 重构 (Refactor) 影响范围: 前端认证流程 Breaking Changes: 无

概述

重构认证系统,引入集中式 AuthContext 管理认证状态,解决以下问题:

  1. 403/401 错误处理不完整(未清除 React Query 缓存)
  2. 跨标签页登录/退出状态不同步
  3. 潜在的登录页面重定向循环
  4. 认证状态管理分散(localStorage、Zustand、React Query)

问题诊断

1. 403/401 处理不完整

  • 位置: client.ts:107-127
  • 问题: 仅清除 localStorage + Zustand,未清除 React Query 缓存
  • 影响: 过期数据残留,可能导致状态不一致

2. 跨标签页状态不同步

  • 问题: 无 storage 事件监听
  • 影响:
    • 标签页 A 登录 → 标签页 B 无感知
    • 标签页 A 退出 → 标签页 B 仍显示已登录

3. 潜在的重定向循环

  • 位置: client.ts:111
  • 问题: 使用 window.location.href = '/login' 硬跳转
  • 影响: 若 LoginPage 触发 403 请求 → 再次跳转 /login → 死循环

4. ProtectedRoute 逻辑复杂

  • 位置: Router.tsx:34-85
  • 问题: 3 个 useEffect,可能产生竞态条件
  • 影响: 维护困难,逻辑难以追踪

5. 退出登录不彻底

  • 位置: useAuth.ts:32-44
  • 问题: 仅清除 React Query,未清除 localStorage token 和 Zustand user
  • 影响: 退出后状态残留

解决方案

架构设计

AuthContext (新增)
├── 统一管理 token/user 状态
├── 监听 storage 事件(跨标签页同步)
├── 提供 login/logout/clearAuth 方法
├── 防重入保护(避免循环跳转)
└── 暴露 useAuth hook

AuthManager (新增)
└── 全局 clearAuth 注册器(供 axios 拦截器使用)

client.ts 拦截器
└── 调用 AuthManager.clearAuth()(清除所有状态)

ProtectedRoute
└── 简化为单一职责:检查 token + 显示加载状态

变更文件

新增文件

  1. client/src/contexts/AuthContext.tsx (145 行)

    • 集中式认证状态管理
    • 跨标签页同步逻辑
    • 防重入保护
  2. client/src/lib/auth-manager.ts (30 行)

    • 全局 clearAuth 注册器
    • 供非 React 上下文使用(如 axios 拦截器)

修改文件

  1. client/src/services/api/client.ts

    • 移除直接操作 localStorage 和 Zustand
    • 使用 clearAuth() 统一清除认证状态
    • 添加延迟跳转,确保状态清除完成
  2. client/src/app/Router.tsx

    • ProtectedRoute 从 50 行简化到 35 行
    • 移除 3 个 useEffect,合并为单一逻辑
    • 使用 useAuth hook 获取认证状态
  3. client/src/pages/LoginPage.tsx

    • 使用 authLogin() 替代手动保存 token + setUser
    • 自动触发跨标签页同步
  4. client/src/hooks/api/useAuth.ts

    • useLogout 使用 AuthContext 统一清除逻辑
    • 确保退出登录彻底清除所有状态
  5. client/src/app/providers/index.tsx

    • 集成 AuthProvider 到应用 Provider 树
    • 位于 QueryProvider 内部(需要 QueryClient)

核心功能

1. 集中式认证管理

// 登录
authLogin(token, user);  // 自动保存到 localStorage + Zustand + 触发 storage 事件

// 退出
logout();  // 清除 localStorage + Zustand + React Query + 触发 storage 事件

// 清除认证(403/401)
clearAuth(reason);  // 带防重入保护

2. 跨标签页同步

// 监听 storage 事件
window.addEventListener('storage', (e) => {
  if (e.key === 'token') {
    if (e.newValue) {
      // 其他标签页登录 → 刷新当前页面
      window.location.reload();
    } else {
      // 其他标签页退出 → 清除本地状态 + 跳转登录
      clearAuth();
      window.location.href = '/login';
    }
  }
});

3. 防重入保护

const clearingRef = useRef(false);

function clearAuth(reason) {
  if (clearingRef.current) {
    console.log('⚠️ 正在清除中,跳过重复调用');
    return;
  }
  clearingRef.current = true;
  // ... 清除逻辑
  setTimeout(() => {
    clearingRef.current = false;
  }, 100);
}

测试验证

手动测试清单

  • 登录成功后,token 和 user 正确保存
  • 刷新页面后,认证状态保持
  • 访问受保护路由时,未登录自动跳转登录页
  • 退出登录后,所有状态清除(localStorage、Zustand、React Query)
  • 403/401 错误触发时,自动清除状态并跳转登录页
  • 跨标签页测试:
    • 标签页 A 登录 → 标签页 B 自动刷新并登录
    • 标签页 A 退出 → 标签页 B 自动跳转登录页
  • 无重定向循环(登录页不会无限跳转)

性能影响

  • 代码体积: +175 行(新增文件),-30 行(简化逻辑)
  • 运行时开销:
    • 新增 1 个 storage 事件监听器(轻量)
    • 简化 ProtectedRoute 逻辑,减少 useEffect 数量
  • 内存占用: 无显著影响

向后兼容性

完全兼容

  • 保持现有 API 接口不变
  • usePhoneLogin、useLogout 等 hooks 签名不变
  • 组件使用方式不变

后续优化建议

  1. Token 刷新机制: 添加 token 过期前自动刷新
  2. 离线检测: 网络断开时暂停 API 请求
  3. 会话超时: 长时间无操作自动退出
  4. 安全增强: 添加 CSRF token 保护

相关文档

作者

  • Claude Sonnet 4.5
  • 协作者:chisdy