Files
lixi 3ebe562ab7
CI / docs-build (push) Successful in 38s
docs: M3 收官——E2E 报告与收官总结入档,验收 PASSED
- 28 E2E 烟囱:14/14 场景、契约偏差 0、M3 四条验收标准逐条取证
  (两次不同 limit 全量翻页有序 id 逐位相等、删除前后差集验证、psql 库层互证)
- 29 收官总结:终态对照开工基线(测试 191/272→334/502、契约 18→31 路径冻结、
  ADR 001~021、四容器→六容器 + MinIO)、遗留清单与 M4 方向输入
- iteration-3/index.md 进展看板补建;feature-checklist 新增 M3 第 10~12 节
- 另登记 2 项 E2E 观察项:widthPx/heightPx 恒 null、eventVersion 口径未定型

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-10 15:13:18 +08:00

21 KiB
Raw Permalink Blame History

功能完成清单

目的:直观呈现哪些功能已完成且有自动化测试、哪些部分完成、哪些尚未开始,方便针对性验证与回归。 维护约定:每波工单合入后由执行人更新本清单;状态以 dev 分支 + 门禁全绿为准。 最后更新:2026-09-10M3 收官:patbond-api 8089c06 334 测试、patbond-flutter 0e87413 502 测试,均门禁全绿;E2E 烟囱 14/14 契约偏差 0;M2 条目见第 79 节,M3 见第 1012 节)

图例: 已完成且已测试 🟡 部分完成/有已知限制 未开始

1. 后端基础设施

功能 状态 自动化测试 说明
Spring Boot 3 / JDK 17 基线(ADR-001 全量门禁 Boot 3.5.16 + Spring Cloud 2025.0.3
移除 NacosFeign 静态地址(ADR-002 AuthApplicationTests 干净检出可启动、可测试
Flyway V1 baselineplatform/identity/media UserPersistenceIntegrationTest.flywayBaselineAppliedOnCleanPostgres16 干净 postgres:18 全量执行;dev 种子默认不加载
Testcontainers postgres:18ADR-006/008 所有 user 模块集成测试 + auth E2E 不依赖本机数据库
统一响应信封 {code,message,data} + 稳定错误码 ApiResponseTest、各 Controller 测试 错误码表见 docs/api/openapi.yaml
跨服务错误码透传(不折叠) ApiErrorDecoderTest + E2E 真实链路 第三波修复两处存量缺陷(ErrorDecoder 未进 Feign 子上下文、JDK HttpURLConnection 读不到 401 错误体),此前真实调用中折叠为 503

2. 用户与凭证(patbond-user

功能 状态 自动化测试 说明
用户注册落库(UUIDv7、bcrypt、软删不可见) UserPersistenceIntegrationTestUserControllerTest 原生 JDBC 读回验证持久性
用户名唯一(citext 大小写不敏感)→ 40900 UserControllerTest.duplicateUsernameCheckIsCaseInsensitive 依赖 DB 约束 + 冲突翻译
手机号唯一 → 40901E.164 校验(DTO 与 DB CHECK 对齐) UserControllerTestdatabaseRejectsNonE164PhoneEvenIfValidationWereBypassed
密码校验(含防账号探测的哑 hash 比对) UserControllerTest.verifyPassword*
登录失败限制(窗口计数→锁定→423/42300) LoginLockoutIntegrationTest3 例)+ E2E 按用户名维度,5 次/15 分钟锁 15 分钟,全部配置项;成功登录重置窗口
GET /api/v1/meBearerRS256 公钥本地验签) MeEndpointTest(5 例:正常/缺失/过期/伪造/垃圾) 响应恰好 {userId, username, phone, createdAt}

3. 认证与会话(ADR-003

功能 状态 自动化测试 说明
POST /api/v1/auth/register(冻结契约 6 字段响应) AuthControllerTest + AuthE2eIntegrationTest.fullAuthVerticalFlow 时间字段 ISO 8601 带时区
POST /api/v1/auth/login(多设备并行会话) 同上 + logoutOnOneDeviceKeepsOtherDevicesLoggedIn
Access tokenJWT RS25615 分钟(配置项) JwtSignerTest5 例) 私钥仅 auth,公钥仅 user;密钥环境变量注入,仓库零密钥材料
Refresh 会话:SHA-256 摘要落 auth_sessions SessionLifecycleIntegrationTest.createSessionStoresSha256DigestNotPlaintext 明文不落库(逐字节断言)
POST /api/v1/auth/refresh:刷新即轮换 + 轮换链 refreshRotatesTokenAndChainsSessions + E2E 旧行 revoked/rotated/replaced_by 三字段断言
旧 refresh 重用 → 40102 + 撤销整个 token family reuseOfRotatedTokenRevokesWholeFamily + E2E 并发轮换同样按重用处理
refresh 过期/未知 → 40102 expiredRefreshTokenIsRejectedunknownRefreshTokenIsRejected
POST /api/v1/auth/logout:仅撤当前会话,幂等 SessionLifecycleIntegrationTest(含跨账号撤销不掉用例)+ E2E 需有效 access token40101 兜底)
会话记录设备信息(X-Device-Id / UA / IP registerForwardsDeviceIdHeaderToTheSessionRecord + 会话落库断言 前端每请求携带 X-Device-Id,为多设备会话列表备数据
access 过期/伪造 → 40101 MeEndpointTestJwtSignerTest、E2E
/internal/** 服务间鉴权(X-Internal-Token InternalAuthFilterTest3 例)+ E2E 无凭证/错误凭证 401;未配置 fail-closed
access token 主动吊销(黑名单) 退出后已签发 access 在剩余 ≤15 分钟内仍有效(jti/sid 已入库备用),见报告 16 §9.1
auth_sessions 过期行清理任务 SessionCleanupIntegrationTest patbond-api@6528a06@Scheduled 定时删除死亡超过保留期(默认 30d,即重用检测窗口)的行,间隔/保留期均配置项

4. API 契约与文档

功能 状态 说明
OpenAPI 3 正式契约(5 公开端点+错误码表+会话/锁定策略) docs/api/openapi.yaml;与冻结稿字段零偏差;新增 42300 已显著标注
后端三波迭代报告 docs/development/iterations/iteration-1/10、16 等)
README 运行手册(密钥生成、环境变量表、新端点) patbond-api/Readme.md

5. 客户端(patbond-flutter

功能 状态 说明
珊瑚橙主题迁移 + 认证基础组件(ADR-005) 第二波已交付(dart format / analyze / test 全绿)
dio API client + 信封解包 + 错误码映射(含 42300) 第三波并行交付(patbond-flutter@8d890c0+da25804,报告 17),基于 mock 验证
Splash/登录/注册页 + secure storage + 登录态恢复 + 真实退出 同上,登录页四态/注册校验 widget 测试锁定
401 单飞刷新拦截器 TokenRefresher 单元测试(刷新单飞、40102 清会话)
与真实后端联调(烟囱测试) 🟡 2026-09-04 手动联调通过(compose 后端 + 本地 Flutter,注册/登录链路无报错);自动化 integration_test 留第四波

跨端核对发现(前端线跟进,后端已按 openapi.yaml 核对全部通过)

  1. UserProfile.fromJsonphone 按非空 String 强转——已修复patbond-flutter@845e92f,phone 改为可空,30 测试全绿)。
  2. 注册请求携带的 Idempotency-Key 后端暂未实现幂等语义(开发计划仅要求帖子/预约类写接口支持);「刷新重放沿用同键」的设计正确,待后端实现后自动受益。
  3. 前端每请求携带的 X-Device-Id 后端已接入 auth_sessions.device_idpatbond-api@8bdaf53)。

6. 工程化

功能 状态 说明
后端集成测试门禁(本地) JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test82 测试(含埋点 +7
跨服务真实 HTTP E2E AuthE2eIntegrationTest(同 JVM 双服务 + 真实 postgres:18
docker compose 最小编排(postgres:18 + 两无状态服务容器) patbond-api@ab0265c./deploy/init-secrets.shmvnw -DskipTests packagedocker compose up -d --build;完整冒烟实测通过(register→me→refresh→旧 token 重用 40102→internal 401→logout);用法见 patbond-api/Readme.md
可执行镜像构建(repackage exec jar、非 root 运行) 同上;顺带修复无 starter-parent 时 package 产物不可执行
信封严格化(success 派生字段不再上线) patbond-api@8a79971,信封恰为 {code, message, data}
CI 载体(自动执行门禁) 三仓全覆盖:apimvnw 82 测试,#6 全绿 3m18s)、flutterformat/analyze/testSDK 走 flutter-io.cn + toolcache 缓存)、docmkdocs --strict);全部零 GitHub 依赖

针对性测试速查

# 全量门禁
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test

# 只跑会话生命周期 / 锁定 / me 鉴权(user 模块)
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -pl patbond-user -am test \
  -Dtest='SessionLifecycleIntegrationTest,LoginLockoutIntegrationTest,MeEndpointTest' \
  -Dsurefire.failIfNoSpecifiedTests=false

# 只跑跨服务 E2E 纵切(auth 模块;-am 必带,避免 ~/.m2 旧 common 快照)
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -pl patbond-auth -am test \
  -Dtest='AuthE2eIntegrationTest' -Dsurefire.failIfNoSpecifiedTests=false

手动冒烟(两服务本地起好后,密钥与内部 token 配置见 patbond-api/Readme.md):

# 注册 → 拿令牌对
curl -s -X POST http://127.0.0.1:8081/api/v1/auth/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"demo_user","phone":"+8613800138000","password":"secret123"}'

# me(换成上一步返回的 accessToken
curl -s http://127.0.0.1:8082/api/v1/me -H "Authorization: Bearer <accessToken>"

# 刷新(旧 refreshToken 随即失效;再用旧值应得 40102)
curl -s -X POST http://127.0.0.1:8081/api/v1/auth/refresh \
  -H 'Content-Type: application/json' -d '{"refreshToken":"<refreshToken>"}'

# 退出(撤销当前会话)
curl -s -X POST http://127.0.0.1:8081/api/v1/auth/logout \
  -H "Authorization: Bearer <accessToken>" \
  -H 'Content-Type: application/json' -d '{"refreshToken":"<refreshToken>"}'

# /internal 无凭证应 401
curl -s -i http://127.0.0.1:8082/internal/users/by-username/demo_user | head -1

M2 宠物健康档案(第二迭代)

7. 宠物域后端(patbond-pet:8083

功能 状态 自动化测试 说明
Flyway V3 pet_health 8 表 + V4 字典种子(28 品种/10 疫苗) 迁移验证 8 例(干净 postgres:18 全量 V1..V4 4 条 marketplace 跨 schema FK 剥离标注 M5 补回,有测试断言 FK 不存在
patbond-pet 独立模块(ADR-009)挂 pom + compose 骨架测试 + compose 实测 /health 探活;迁移链仍归 patbond-user 单链
宠物 CRUD + breeds 目录(T2-03 23 例(六类路径 + 三角色矩阵) 创建者自动 primary ownerPATCH version 乐观锁 40902;芯片号唯一 40903
PetAccessService 三档权限闸口(READ/WRITE/MANAGEADR-015 三角色矩阵 + caregiver 写正向用例 防枚举:无关系/不存在/已软删一律 404/40401 响应逐字一致(有测试断言)
体重记录 + cursor 分页(T2-04 8 例(分页不丢不重/同刻跨页专项) {items,nextCursor,hasMore} 信封为全 API 分页正典;weight_kg (0,500]
疫苗目录 + 疫苗记录 + 状态机(T2-05) 12 例 scheduled→completed/cancelled;剂次唯一 40904;规则违反 42201;cancel 释放占位可重建
健康事件六类 + 时间线分页 + 顶层 PATCHT2-06 11 例 amountCents 整数分非负;禁 float 静默截断;记录级防枚举 40402
照护提醒四类 + 状态流转(T2-07) 10 例 pending→completed/dismissedcompleted 必带 completedAt42202);仅数据接口不推送
档案摘要四聚合(T2-08 12 例(空数据/双时区跨月/cancelled 不计/多宠隔离/零写入红线) 实时聚合不持久化展示串;tz 参数(IANA)缺省 UTC;无记录 null 语义
写接口幂等(Idempotency-Key 可选头,四个 POST 幂等重试用例 键派生确定性主键 + ON CONFLICT,零迁移
契约一致性测试(v1.2.0 字节级快照) 全响应矩阵 + mutation 自证 + 版本守卫 契约未声明字段即报漂移;升版须同步快照否则 CI 红;已抓修 1 项漂移(sex 必填)
照片/附件(头像、疫苗证书、事件附件) ADR-010 剪出 M2,待对象存储选型;health_event_media 表未建(纯增量后补零成本)
照护人邀请/绑定流程 ADR-015 后置;权限校验已用测试数据覆盖三角色
auth 域契约测试补齐 机制可直接复用(报告 20 §建议),另立工单

8. 宠物域客户端(patbond-flutter

功能 状态 说明
pets 数据层(契约 18 操作 DTO/Client/Repository 全覆盖,T2-11 8 新错误码类型化异常;三服务分端口直连共享 TokenRefresher 单飞;DTO 映射 62 例测试
宠物列表/详情/建档/编辑页真实数据(T2-12) 四态齐备有 widget 测试;40902 自动取新 version 重提;40903 字段级报错;品种目录 + 自定义互斥;demo 数据消亡
体重录入 + 历史列表(cursor 分页,T2-13 契约区间前端校验 + 后端兜底;加载更多/翻页失败保留重试
疫苗登记/列表 + 完成/取消流转 + 厂商批号补录(T2-13/14) 状态-日期规则双重前端拦截 + 42201/40904 兜底;按系列分组三态 TagPill
摘要接数(最新体重/疫苗进度/下一针/月度花费)替换 demo 展示串 null → 空态而非 0/0(测试锁定);月度花费透传设备时区 tz
健康事件时间线(六类、按月分组、元/分换算)+ 录入/编辑(T2-14) 金额换算单测锁定;40902 自动重提
照护提醒列表/创建/完成/忽略(T2-14) 逾期红标双通道;档案页「健康提醒」卡真实数据驱动(demo 硬编码移除)
跨设备读取验收(M2 验收标准) compose 实测:同账号新会话全量可见;第二账号四路访问均 40401
DEBT-1 TagPill 对比度债偿还(ADR-014 深变体映射四组全达 WCAG AA;既有调用零参数回归
单宠直进/切换器、归档入口、sterilizedOn 编辑 🟡 三项交互细节待拍板(报告 23 §8

9. 埋点体系(M2 演进)

功能 状态 说明
M1 遗留清偿:生产接线/eventId v7/SessionTracker/page_viewed 第一波交付(报告 10,12/12 验收);生产事件流自 M1 以来首次非零
events 契约补录(v1.1.0)+ 上传端口纠正 + 毒丸批次防护 4xx 永久拒绝不重试;离开前台冲刷(低活跃用户事件不再滞留)
分段持久化队列(shared_preferences500 条 at-least-once 冷启动恢复离线积压;损坏段容错;按段拼批 ≤50
事件字典 v2 白名单(pet 域 3 + health_record 域 7 后端白名单 + 边界测试(api@64c9b72);page_viewed 正稿核对零修正
客户端挂接:pet 域 3 事件 + health_record 域 6 事件 + pet_form 等页名 强类型封装(pet_analytics/health_record_analytics);deleted 留待删除端点
Android 真机落库验证 + SessionTracker 30min 手测 🟡 桌面端全链路已通(platform 枚举拒绝属契约内);真机验证按方案 A 挂起待设备
队列完善:30s 定时冲刷、退避、anonymousId 持久化 M3 第一波交付iteration-3/10flutter@4d40c38);429 Retry-After 精细分支仍待后端限流

M3 社区(第三迭代)

10. 社区域后端(patbond-community :8084 + media in user

功能 状态 自动化测试 说明
Flyway V5 community 8 表 + pg_trgm 扩展 迁移验证 8 例(干净库 V1→V5 剪 2 条跨 schema FKposts.generation_job_idM4 补回)、posts.region_idM5 补回),裸列与索引保留
patbond-community 独立模块(ADR-017)挂 pom + compose 骨架 7 例(含鉴权 5 骨架期即接 RS256 校验(无 token/畸形/错签/过期均 401+40101);只读写 community schema
media 两步上传闭环ADR-016/017 12 例(MinIO Testcontainer 全链路 + 六类失败) 创建 upload 签预签名 PUT10min)→ confirm ready → 私有桶预签名 GET1h);post_image / jpeg·png·webp / 10 MiBObjectStorage 适配层隔离供应商
帖子生命周期 5 端点(草稿/编辑/发布/软删/详情/我的列表) 25 例(含真双线程并发 PATCH 恰一胜) 发布走 PATCH draft→publishedIdempotency-Key 必带 + request_hash40905);防枚举 404/40403 逐字节一致(hidden 对作者亦不露)
公共 Feed 游标分页 + FeedCard 分页专项 8 + 卡片定型 5 (published_at,id) 游标对齐 ix_posts_feedcontentPreview 200 码点截断;三计数走冗余列写侧同事务维护
作者公开资料链路(D3-9 方案 B 内部端点 8 + 作者链路 7 + Feign 线路 3 user 增 /internal/users/profiles(≤50 批量,不入公网契约)→ community Feign + 60s 缓存;user 故障时作者退 id-only、Feed 照常 200
单层评论(列表/创建/删除) 11 例 仅评论作者可删(用户拍板,帖主不可删他人评论);40404/40406
点赞/收藏 PUT+DELETE 幂等 + 我的收藏 真并发(4 线程 PUT 恰计 1 行 1 计数)+ 对账专项 响应回权威终态 {liked,likeCount};互动面 = 帖子公开面(作者本人草稿亦 404)
关注 PUT/DELETE + follow-stats 3 线程并发 follow 恰 1 行 自关注 42204(仅 PUT);自取关 200 幂等 no-op
契约冻结 v1.3.0 + 快照矩阵 173 格 community 64 格 + media 8 格 + mutation 自证 43/43 操作零漂移;四模块字节级快照,升版须同步否则 CI 红;修 nullable+allOf 校验盲区
uploading 超时未确认 asset 清理任务 方案在 iteration-3/13,定时任务另排
429 限流 承自 iteration-2/09 出入清单;连带客户端 Retry-After 分支挂起
话题 / 关注列表 / 作者主页 ADR-018 裁剪出 M3topics 表已建)

11. 社区域客户端(patbond-flutter

功能 状态 说明
community 数据层(契约 19 操作全覆盖,T3-12) 9 新错误码类型化;未知枚举抛 FormatException 暴露漂移;CursorPage 上移 core 供两域复用
ToggleSync 乐观更新状态机 乐观翻转 + 快照回滚 + 单飞合并最终意图 + 代次守卫 + 服务端权威终态收敛;竞态时序 controller 级单测
MediaUploader 六态编排(T3-13 选图→压缩→预签名直传→confirm;29 项专项(降质阶梯/凭据过期重取/多图并发保序/孤儿防护)
首页 Feed 真实数据 + 四态 + 尾部三态(T3-14) 下拉刷新 + 触底游标翻页;PostCard 三形态/PostMediaGrid/LikeButton/FeedSkeleton 入 coreSignedNetworkImage 剥离签名参数做缓存 key
帖子详情整页替换 + 评论区(T3-15) 真九宫格 + InteractiveViewer 大图;仅本人评论渲染删除入口;40403 返回 Feed 并刷新;关注双态钮
互动接线 + 三层视觉抑制(T3-16) 240ms 弹性激活 / 失败零动画跳变 + SnackBar / 对账静默替换;Feed 与详情共享 ToggleSync 同帧一致;减弱动态降级
发布页 PostComposePageT3-17 两步发布 createPost(draft)→PATCH published(「发布失败但草稿已保存」为事实);gating 双保险;失败三语义(40905/42203/网络同键重放);草稿两路径 + 进页恢复
社区 demo 数据消亡 AppState.posts/publishPost/updatePost 及持久化整体退役;create 页仅余 M4 的 AI 生成模拟
完整草稿列表 / 自动保存 🟡 最小实现(进页恢复最新一条);完整管理留待(报告 26 §7)
大图「下滑关闭」手势 🟡 InteractiveViewer 手势冲突,待 photo_view 复评
widthPx/heightPx 真实宽高比 🟡 服务端恒 nullE2E 观察项 1),单图帖一律回落 4:3

12. 埋点体系(M3 演进)

功能 状态 说明
队列三项加固(30s 定时冲刷 / 指数退避 / anonymousId 持久化) 13 号规范四触发点补齐;退避 30s→5min 只挡定时冲刷;anonymousId 跨冷启动稳定(A/B 前置 #4)
事件字典 v3 白名单(community 域 19 + experiment_exposed EventDictionary 22→427 个被否决事件锁死 unknown(含逐卡曝光 post_impression
客户端挂接 21 事件 feed 2(聚合 feed_viewed:≥50% 可见 ≥500ms、段内去重、离开结算)+ 互动 8 + 媒体三段 3 + 发布漏斗 5 + page_viewed 页名增量
逐卡 Feed 曝光 ADR-020 否决(量级测算 7~14 个月击穿分区阈值 + 无背压);留 backlog 待 M4+ 服务端下发日志
Android 真机验证(M2 两项 + M3 四项) 🟡 步骤全部备齐在真机验证清单;桌面 platform=linux 整批 400 属契约内,落库只能真机验
eventVersion 口径定型 契约描述可两读、服务端不校验、客户端硬编码 1(E2E 观察项 2)