Files
patbond-doc/docs/development/iterations/iteration-1/17-flutter-login-report.md
T
lixi 273064c10d docs: 第三波交付收口——认证契约与两端实现报告入档
- 新增 API 契约:docs/api/openapi.yaml(T6a 正式化)与契约说明页(契约先行原则)
- 入档报告 16(后端 JWT 会话,37→73 测试)与 17(Flutter 登录纵切,7→30 测试)
- 进展看板更新至第三波完成,第四波为联调 E2E → CI → 编排 → 埋点
- 门禁:mkdocs build --strict 通过
2026-09-04 12:18:29 +08:00

7.4 KiB
Raw Blame History

17 · Flutter 登录纵切实现报告

作者:Frontend Developer 日期:2026-09-04 依据:12-ui-design-qa-and-assembly.md(组装稿)、ADR-003/ADR-004、开发计划 §4.2、接口契约冻结稿 提交:patbond-flutter dev 分支 8d890c0(门禁全绿后提交,未 push


1. 交付总览

登录纵切完整落地:网络层(dio)+ 认证会话(安全存储)+ Splash / 登录 / 注册三页 + 主壳真实退出登录,另完成 FIX-1 / FIX-2 / m2 三项顺带修复。门禁三连全绿:dart format --output=none --set-exit-if-changed lib test0 changed)、flutter analyzeNo issues)、flutter test30 passed,其中新增 23 个)。

契约偏差:零。所有路径、请求/响应字段名、错误码与冻结稿逐字一致。额外附带两个契约外请求头(服务端可忽略):注册请求带 Idempotency-Key(每次提交生成 UUID,token 刷新后的自动重放沿用同一个键),所有请求带 X-Device-Id(首启生成、安全存储持久化的设备 UUID)。

2. 分层与文件

按开发计划 §4.2 的 Page → Repository → API Client 分层(登录表单状态照组装稿放页面 state,不引入独立 Controller 层):

文件 职责
网络 lib/core/network/api_client.dart dio 封装;base URL 经 --dart-define=PATBOND_API_BASE_URL 注入(默认 http://127.0.0.1:8081);validateStatus 全放行,错误信封统一解析;AuthInterceptor 附加 Bearer;鉴权请求遇 HTTP 401 / code 40101 → 单飞刷新后重放一次,重放仍失败清会话抛 SessionExpiredException
网络 lib/core/network/token_refresher.dart 单飞(single-flight)刷新:并发 401 只发一次 POST /auth/refresh仅 40102 / HTTP 401 清会话,网络失败与 5xx 一律保留 token
网络 lib/core/network/api_exception.dartapi_envelope.dart 类型化异常(ApiNetworkException / ApiBusinessException / ApiRateLimitException / SessionExpiredException+ 错误码常量 + 信封解析
认证 lib/features/auth/session_manager.dart token 内存副本 + flutter_secure_storage 持久化(TokenStore 抽象,测试注入内存实现);认证状态机 unknown/authenticated/unauthenticatedtoken 不进 SharedPreferences
认证 lib/features/auth/auth_repository.dart AuthRepository 抽象 + ApiAuthRepositorylogin / register / logout / restoreSession / melogout 服务端失败也保证本地清除
页面 lib/features/auth/splash_page.dartlogin_page.dartregister_page.dart 照组装稿逐项实现(见 §3
lib/app/app.dart 认证状态机驱动 Splash ↔ 登录 ↔ 主壳,AnimatedSwitcher 300ms fade;测试注入口(sessionManager / authRepository 可注入)
导航 lib/core/navigation/fade_route.dart PageRouteBuilder + FadeTransition 300ms(登录 → 注册 push 用)

3. 页面与状态覆盖

Splash(组装稿 §7):checking / failed 双态;BrandMark 与登录页同构保证过渡对位;spinner 等待 >300ms 才出现(占位保高度不跳动);最短停留 500ms;refresh 超时 5s;错误态「重试」+「改用账号登录」逃生口(清凭证进登录页);网络失败不清 refresh token,仅服务端 401/40102 才清

登录页(组装稿 §5):垂直居中、无 Spacer;两字段仅非空校验(去首尾空格),Focus 包裹失焦校验 + 提交总校验;提交中整表单锁定(字段禁用、注册链接置 null、按钮 loading);错误三层映射——字段级 errorTextonChanged 即清)、40100 → 横幅「用户名或密码错误」+ SemanticsService.sendAnnouncement 播报、HTTP 429 → 横幅「尝试次数过多,请稍后再试」、网络 → SnackBar「网络异常,请检查网络后重试」+ 重试 action;成功后 finishAutofillContext(),状态机 300ms fade 进主壳;协议行与预留区一律不渲染(ADR-004)。

注册页(组装稿 §6):透明返回栏顶部左对齐;四字段(用户名/手机号/密码/确认密码)失焦校验 + 提交总校验,文案照 04 规范 §3.2;密码 helperText 走主题 mutedFIX-2);密码变更时确认密码已有值则重校验一致性;40900 → 用户名字段「该用户名已被使用」、40901 → 手机号字段「该手机号已注册,可直接登录」;注册成功即建立会话直接进首页(popUntil 首路由,不回登录页)。

主壳/个人中心ProfilePage 的「切换账号或退出登录」接入真实 logout(POST /auth/logout Bearer + refreshToken,随后清会话,状态机自动回登录页)。

4. 顺带修复

  • FIX-1:首页促销卡渐变改 [primaryStrong, primary](深端在左承载白字,AA 达标;brandGradient 本身未动)。
  • FIX-2inputDecorationThemehelperStyle: TextStyle(color: muted, fontSize: 12)
  • m2README 验证命令补 --output=none

5. 测试(30 通过 = 既有 7 + 新增 23)

文件 数量 覆盖
test/core/network/token_refresher_test.dart 5 并发单飞(仅 1 次请求 + token 轮换)、40102 清会话抛 SessionExpired、网络失败保留 token、单飞复位可重刷、无本地 refresh 直接判失效
test/features/auth/auth_repository_test.dart 10 登录成功存会话(含请求体逐字段断言)、40100 业务异常、注册 Idempotency-Key + 40900、40101 刷新后重放一次携带新 token、重放仍 401 清会话、登出网络失败也清本地、会话恢复两分支、5xx → 系统错误、429 → 限流异常(全部 mock dio 假 adapter
test/features/auth/login_page_test.dart 4 初始 / loading(字段禁用+链接置灰)/ 字段错误(输入即清)/ 横幅错误(40100 文案 + 输入即清)四态
test/features/auth/register_page_test.dart 4 渲染(含预留区不渲染断言)、空表单拦截、四字段格式文案逐项、合法提交调接口

既有 widget_test.dart 改为注入已认证会话 + 假仓库后 pump App,断言不变仍通过。任务描述中的「现有 13 个测试」与实际不符——工单开工时仓库为 7 个测试(上次提交信息「6 个 widget 测试」+ 1 个导航冒烟),7 个全部保持通过。

6. 遗留问题与备忘

  1. SemanticsService.announce 已废弃Flutter 3.44 标记 deprecated,横幅播报改用替代 API sendAnnouncement(View.of(context), ...),行为等价,组装稿 §4 后续修订时可同步文案。
  2. 会话过期的 Splash 最短停留refresh 被服务端判 40102 时清会话即切登录页,该罕见分支可能早于 500ms 最短停留(正常成功/失败/无 token 三路均严格遵守);fade 过渡下无闪烁,判定可接受。
  3. access token 过期时间未做本地预判:当前依赖 401/40101 被动刷新(契约行为完备);accessTokenExpiresAt 已持久化,后续可加过期前主动刷新优化首个请求延迟。
  4. 未与真实后端联调:后端按同一冻结契约并行实现中,本报告所有验证基于 mock dio;联调烟囱测试建议列入下一波工单。
  5. DEBT-1TagPill 对比度)按 12 号报告裁决仍另开工单,本次未动。

Frontend Developer · 2026-09-04 · 门禁:format 0 changed / analyze 0 issues / test 30 passed