Files
social-app/docs/plans/2026-03-18-auth-global-rewrite-design.md
T
qzl b34697660d feat: 实现 Auth 全局状态机与 401 统一处理机制
- 新增 AuthSessionInvalidated 事件处理 token 失效场景
- ApiInterceptor 新增 authFailureCallback 单飞机制
- AuthBloc 区分 manual logout 与 auto expiry 语义
- 新增 startup recovery fallback 防止启动卡死

feat: 重构 Calendar DayWeek 视图事件布局引擎

- 新增 DayEventLayoutEngine 解耦事件计算与渲染
- 新增 DayTimelineMetrics 统一时间轴常量
- 新增 DayViewScale 支持捏合缩放

feat: 新增 Settings 页面共享 UI 组件

- 新增 BackTitlePageHeader 统一页面 header
- 新增 DetailHeaderActionMenu 统一操作菜单
- 新增 DestructiveActionSheet 统一删除确认
- 新增 AppToggleSwitch 统一开关组件

feat: Chat UI Schema 支持导航操作

- 支持 navigation 类型 action 触发内部路由跳转
- 新增路径验证与参数处理

chore: 更新相关测试覆盖 auth 失效路径
2026-03-18 13:35:25 +08:00

103 lines
3.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Auth 全局模块重写设计(跨端并存、同端互斥)
## 1. 目标
- 彻底消除 Auth 分裂状态:`token` 状态与 `AuthBloc` 状态必须单一真相源。
- 会话策略升级为:
- 同账号允许跨端并存:`mobile + web + desktop`
- 同账号同端互斥:同端新登录会挤下线旧设备
- 保证任何 401 链路在刷新失败后都能统一收敛为“未登录 + 清理本地 + 路由回到登录页”。
- 消除设备差异导致的不一致行为(部分机型“假登录”或“卡死页”)。
## 2. 边界与约束
- 仅重写 `apps/**` 的 Auth 客户端架构与规则,不改后端协议语义。
- 保持现有登录/注册 UI 路由入口,避免用户操作路径变化。
- 认证属于高风险域,重写必须覆盖:
- 启动恢复
- 运行时 token 过期
- 并发 401
- 手动登出与自动过期登出的差异行为
## 3. 核心架构
### 3.1 单一真相源(Single Source of Truth
- `AuthBloc` 成为唯一认证状态源。
- `ApiInterceptor` 只负责协议级拦截与刷新,不直接做路由跳转。
- 401 刷新失败时,`ApiInterceptor -> ApiClient callback -> AuthBloc(AuthSessionInvalidated)`
- 路由守卫只看 `AuthBloc` 状态,不再做隐式 token 判定。
### 3.2 会话状态机
- `AuthInitial`
- `AuthLoading`(启动恢复/会话检查)
- `AuthAuthenticated(user)`
- `AuthUnauthenticated(reason)`
`reason` 取值:
- `signedOut`
- `expired`
- `startupRecoveryFailed`
### 3.3 登出语义分离
- 手动登出:`deleteSession()`
- 尝试调用后端注销
- 最终清本地
- 自动过期:`clearSessionLocalOnly()`
- 仅清本地
- 不调用后端注销接口
### 3.4 并发与抖动控制
- `ApiInterceptor` 继续使用 refresh singleflight。
- 新增 auth failure singleflight:多并发 401 刷新失败,只触发一次全局会话失效事件。
### 3.5 设备差异治理
- 启动时 token 读取异常必须兜底:进入 `AuthUnauthenticated(startupRecoveryFailed)`,避免卡死在 Boot。
- `FlutterSecureStorage` 显式平台配置:
- Android 使用 `encryptedSharedPreferences`
- iOS 指定 keychain accessibility(保证行为稳定)
## 4. 数据流
### 4.1 冷启动
1. `main` 触发 `AuthStarted`
2. `AuthBloc` 读取 refresh token
3. 有 refresh token -> 刷新会话 -> 成功进入 `AuthAuthenticated`
4. 无 token 或异常 -> `AuthUnauthenticated(startupRecoveryFailed)`
### 4.2 运行时 API 请求
1. 请求携带 access token
2. 401 -> 触发 refresh
3. refresh 成功 -> 自动重试原请求
4. refresh 失败 -> 触发一次全局 auth failure
5. `AuthBloc` 收到 `AuthSessionInvalidated(expired)` -> 清本地 -> `AuthUnauthenticated(expired)`
6. Router 根据状态回登录页
## 5. 测试策略
- `AuthBloc`
- 启动读取 refresh token 异常兜底
- session invalidated 事件导致统一未登录
- `ApiInterceptor`
- 并发 401 refresh 失败仅触发一次 auth failure
- `AuthRepository`
- 手动登出 vs 自动过期清理行为差异
## 6. 迁移计划
- 先引入新事件/新回调/新状态原因,不改 UI 交互。
- 再改路由守卫识别未登录原因。
- 最后补齐 `apps/AGENTS.md` Auth 强约束,防止后续回归为“各处乱写”。
## 7. 风险与回滚
- 风险:回调链路接错导致频繁误登出。
- 规避:并发 singleflight + 精确触发条件(仅 401 refresh 失败)。
- 回滚:保留旧事件兼容层,出现异常可快速退回旧路由判定。