- 新建常设「发布记录」页(挂开发文档导航),首条 v0.3.0 M3 社区 - 30 号报告:M2 11/11 + M3 14/14 同环境各连跑 3 轮零 flake、契约偏差 0; 附契约向后兼容结构化比对(removed/changed 均 NONE)与共享代码面回归分析 - 记录首次发布的一次性操作:api main 孤儿历史重建(方案 A)、flutter 快进推法改进 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
16 KiB
30 首次发布门禁:M2+M3 双份 E2E 回归(checklist 第 2 步)
- 执行人:QA(Explore / 回归执行)
- 日期:2026-09-10
- 依据:iteration-3/08 号《Git 工作流规划》§3.3 发布 checklist 第 2 步—— 「compose 全栈起,跑 M2+M3 两份 E2E 烟囱脚本,全场景 PASS,证据入波次报告」
- 环境:
patbond-api(docker compose 六容器)+patbond-flutter(dart 脚本直连) - 测试脚本:
patbond-flutter/test_e2e_m2_manual.dart(M2 收官版,11 场景) 与patbond-flutter/test_e2e_m3_manual.dart(M3 收官版,14 场景),均取flutter@0e87413 - 冻结契约:
patbond-doc/docs/api/openapi.yamlv1.3.0(doc@f848476) - 参照模式:iteration-2/28 与 iteration-3/28 号收官报告(格式与取证标准沿用)
三仓代码零改动:本次只起 compose、跑两份既有脚本、做只读 psql/git 取证。 未修改、未 commit、未 push 任何代码仓;未改
mkdocs.yml。
0. 执行概要
门禁结论
PASS——发布 checklist 第 2 步满足。
| 项目 | 结果 |
|---|---|
| M2 场景通过数 | 11 / 11(44 条断言全绿,exit 0) |
| M3 场景通过数 | 14 / 14(88 条断言全绿,exit 0) |
| 契约偏差数 | 0 个(对照冻结契约 v1.3.0) |
| 失败项 | 0 项 |
| 稳定性 | 同一 compose 环境内 两份脚本各连跑 3 轮,6 次全通过、零 flake |
| 两域共存 | M2→M3→M2→M3 交叉执行,pet_health 与 community 数据同库共存,互不干扰 |
| 容器异常 | 六容器 RestartCount 全 0;四个应用容器日志 ERROR/Exception 计数全 0 |
本单目标(与 M3 收官报告的区别)
M3 版脚本已在 M3 收官(iteration-3/28)跑过 14/14。本单的增量价值在于复跑 M2 版:
M3 期间 pets 域未做功能变更,但两域共享 patbond-common、契约快照文件、CI 流水线与同一
数据库实例——需要实测确认 M3 交付没有回归 M2 的宠物健康档案域。为把「共存」也一并证实,
两份脚本在同一次 compose 生命周期内交叉执行。
脱敏声明
token 一律截断至前 20 字符 + <REDACTED>(脚本内置 redact());MinIO 预签名 URL 的
查询串替换为 <SIGNATURE_REDACTED>;幂等键显示为 <KEY-1>;密码与 .env 内容不出现在
任何输出。
1. 环境记录
1.1 构建与启动(patbond-api 代码零改动)
cd <你的工作区>/patbond-api
JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw -DskipTests package
# BUILD SUCCESS —— Total time: 4.263 s(增量编译,五模块 reactor 全 SUCCESS)
# 产出四个 -exec.jar:auth 36MB / community 51MB / pet 25MB / user 41MB
docker compose up -d --build
# Container patbond-postgres-1 Healthy
# Container patbond-minio-1 Healthy
# Container patbond-user-1 / auth-1 / community-1 / pet-1 Started
1.2 六容器状态与镜像版本
CONTAINER REPOSITORY TAG SIZE STATUS
patbond-postgres-1 postgres 18 162MB Up (healthy)
patbond-minio-1 minio/minio RELEASE.2025-04-22T22-12-26Z 64MB Up (healthy)
patbond-auth-1 patbond-auth latest(本次重建) 141MB Up :8081
patbond-user-1 patbond-user latest(本次重建) 147MB Up :8082
patbond-pet-1 patbond-pet latest(本次重建) 132MB Up :8083
patbond-community-1 patbond-community latest(本次重建) 155MB Up :8084
| 组件 | 版本 |
|---|---|
| PostgreSQL | 18.6 (Debian 18.6-1.pgdg13+2) |
| MinIO | RELEASE.2025-04-22T22-12-26Z |
| 容器内 JRE | Temurin OpenJDK 17.0.20+8 |
| 宿主 Docker | 29.7.2 / Docker Compose 5.5.1 |
| Dart SDK(跑脚本) | 3.12.2 (stable) |
1.3 启动耗时与就绪验证
依赖顺序符合编排:postgres/minio 先 Healthy,四应用容器随后 Started(容器创建到启动 约 3 秒),Spring Boot 自身启动耗时:
Started AuthApplication in 9.752 seconds
Started UserApplication in 13.194 seconds
Started PetApplication in 9.255 seconds
Started CommunityApplication in 11.135 seconds
无 token 探活(预期 401 信封):
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8082/api/v1/me # 401
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8083/api/v1/pets # 401
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8084/api/v1/feed # 401
1.4 脚本执行耗时(第三轮,取权威计时)
| 脚本 | 场景数 | 耗时 | 退出码 | ✓ 断言数 |
失败标记 |
|---|---|---|---|---|---|
test_e2e_m2_manual.dart |
11 | 1149 ms | 0 | 44 | 0 |
test_e2e_m3_manual.dart |
14 | 1662 ms | 0 | 88 | 0 |
1.5 数据残留说明(非缺陷)
pgdata / minio-data 卷延续自 M3 收官那次运行(compose 只重建容器与镜像,未 down -v)。
两份脚本均以时间戳随机账号运行、且 M3 的全量翻页断言按「本轮新增 26 条 vs 全量 117 条」
的相对口径校验,因此残留数据不影响判定,反而额外证明了跨轮数据共存无干扰。
2. M2 版 E2E 逐场景结果(11/11 PASS)
本轮取证账号 e2e_m2_a_1789025850872,petId 01a08a40-1ad6-7a26-962c-a5f6cd3706a1。
| # | 场景 | 结果 | 关键断言实测 |
|---|---|---|---|
| 1 | 注册账号 A → 登录 | ✓ PASS | register 200 / login 200,token eyJhbGciOiJSUzI1NiJ9...<REDACTED> |
| 2 | 建档(含品种)→ 列表/详情读回 | ✓ PASS | 品种目录 16 条;POST /pets 201;myRole=owner、version=0、breedDisplayName=中华田园犬;列表/详情四字段一致 |
| 3 | 记体重 ×2 → cursor 分页 | ✓ PASS | 8.20/8.45kg 各 201;第一页 8.45 在前 + hasMore=true;第二页 8.20 + hasMore=false、nextCursor=null |
| 4 | 疫苗登记(scheduled)→ 标记完成 | ✓ PASS | 疫苗目录 6 条;PATCH 200,version 0→1,administeredOn/nextDueOn 回读一致 |
| 5 | 健康事件(整数分)→ 时间线 | ✓ PASS | amountCents=12500 原样回读;createdByUserId = token subject;时间线 1 条 |
| 6 | 提醒创建 → 标记完成 | ✓ PASS | 创建恒 pending + completedAt=null;PATCH 后 completedAt = 客户端提交时刻 |
| 7 | /summary?tz=Asia/Shanghai 四项聚合 |
✓ PASS | 最新体重 8.45kg;疫苗进度 1/1;下次接种 2027-09-10(source=nextDue);当月花费 12500 分、month=2026-09、tz 回显 |
| 8 | 账号 B 越权访问 A 的宠物四路(防枚举) | ✓ PASS | 详情/体重/疫苗/摘要四路全 404 / 40401,响应体逐字节一致 {"code":40401,"message":"宠物不存在","data":null};B 列表为空 |
| 9 | 账号 A 第二设备重新登录 → 全量读回 | ✓ PASS | 新会话 token 与设备 1 不同(独立 token family);宠物 1 / 体重 2 / 疫苗 1 / 事件 1 / 提醒 1 全量一致 |
| 10 | /api/v1/events v2 事件上报 |
✓ PASS | 4 条 → 202,accepted=4, duplicated=0, rejected=0 |
| 11 | 乐观锁冲突明确性 | ✓ PASS | 第一次 PATCH 200(version 0→1);同过期 version 第二次 409 / 40902;读回确认先写者数据保留 |
3. M3 版 E2E 逐场景结果(14/14 PASS)
本轮取证账号 e2e_m3_a_1789025861236,postId 01a08a40-422a-7d60-a915-f04d7b0061be。
(三轮运行的断言输出逐条一致,仅随机账号名与 UUID 不同;下表的字面值取自取证轮输出。)
| # | 场景 | 结果 | 关键断言实测 |
|---|---|---|---|
| 1 | 注册 A/B 两账号 → 登录 | ✓ PASS | 两账号注册 200 且为两个独立 userId(模拟两客户端) |
| 2 | 两步上传直传 MinIO(/media/uploads → complete) |
✓ PASS | 登记 201 返回 SigV4 预签名凭据、requiredHeaders 恒且仅 {Content-Type};344 字节直传 200;complete 后 uploading→ready(byteSize=344、readyAt 已写);重复 complete 幂等 200 同一 asset |
| 3 | 草稿创建 → 发布(PATCH draft→published) | ✓ PASS | 草稿 status=draft/publishedAt=null、media 挂接 position=0 且服务端置唯一 isCover;发布后 status=published、publishedAt 已写、version 0→1 |
| 4 | 验收① B 拉 /feed A 的帖首位可见 |
✓ PASS | published_at DESC 首位命中;FeedCard 必填齐备;AuthorSummary 不露 bio/username;裁剪生效(无 content 全文/media 整组/version) |
| 5 | 预签名 GET 字节往返 + 桶私有 | ✓ PASS | 344 字节逐字节一致;去签名直访 403 |
| 6 | 验收② 连续 3 次 PUT like → count 恰 1 | ✓ PASS | 三次均 200 权威终态 {liked:true,likeCount:1}(非 409);DELETE → 0;重复 DELETE 幂等 |
| 7 | 收藏 → /me/bookmarks → 取消 |
✓ PASS | 列表项形态 = FeedCard,视角字段为 B;取消后不含 |
| 8 | 评论 ×2 / 作者软删 / 帖主越权删被拒 | ✓ PASS | B 删自己 200;A(帖主)删 B 的 403 / 40301;commentCount 同事务 +1/-1 准确 |
| 9 | 关注幂等 + follow-stats + 自关注拒绝 | ✓ PASS | 重复 PUT 幂等 200;自关注 422 / 42204;自取关 200 no-op;A 查自己 followedByMe 恒 false |
| 10 | 验收③ 25 帖 → 双粒度全量翻页比对 | ✓ PASS | limit=7 → 17 页 117 条;limit=100 → 2 页 117 条;两种页大小逐位一致,零重复零遗漏 |
| 11 | 验收④ 软删一帖 → 出 Feed + 直接 GET 404 | ✓ PASS | 删后全量恰少 1;B 与作者 A 直接 GET 均 404 / 40403 且响应体逐字节一致 |
| 12 | 防枚举:草稿 vs 随机 UUID 响应体一致 | ✓ PASS | 四路全 404 / 40403 一致 {"code":40403,"message":"帖子不存在","data":null};互动面恒为公开面;草稿对作者详情仍可见、不入公共 Feed |
| 13 | v3 社区事件上报 + 白名单/字典兜底 | ✓ PASS | 8 条 → 202 accepted=8;白名单外键剥离后仍 accepted;字典外 post_impression 整条 rejected(unknown_event_name),批次仍 202 |
| 14 | 幂等重放:同键同 hash / 异 hash / 缺头 / 跨作者 | ✓ PASS | 同键同 hash 返回原帖不产生第二帖;异 hash 409 / 40905;缺 Idempotency-Key 400 / 40000;幂等键按作者隔离 |
4. 数据库证据(两域共存,只读 psql)
4.1 schema 与 Flyway 迁移
-- \dn
community | identity | media | pet_health | platform | public
-- select installed_rank, version, description, success from public.flyway_schema_history;
1 | 1 | identity media baseline | t
2 | 2 | create platform product events | t
3 | 3 | pet health baseline | t
4 | 4 | pet health dictionary seed | t
5 | 5 | community baseline | t
V1~V5 全 success=t,M3 的 V5 未触碰 M2 的 V3/V4(不可变迁移纪律保持)。
4.2 两域数据行数(六次脚本运行累计,含既有残留)
pet_health.pets | 8 community.posts | 131
pet_weight_records | 9 community.comments | 10
pet_vaccinations | 7 community.post_likes | 3
health_events | 6 community.user_follows | 3
care_reminders | 6 media.assets | 17
pet_owners | 8 platform.product_events | 61
两域各 8 张表并存于同一实例,交叉执行无外键/唯一键冲突、无死锁。
4.3 埋点落库核对
M2 本轮会话(session_id=1e1a8388-…,共 4 条 = 上报数):
health_record_create_succeeded | android | 1.0.0+e2e | 2
page_viewed | android | 1.0.0+e2e | 1
pet_create_succeeded | android | 1.0.0+e2e | 1
M3 本轮会话(session_id=a28da209-…,8 白名单事件 + 1 条隐私兜底事件):
comment_create_succeeded | 1 post_liked | 2
feed_viewed | 1 post_media_upload_succeeded | 1
page_viewed | 1 post_publish_succeeded | 1
post_favorited | 1 user_followed | 1
隐私红线兜底与字典守门实测:
-- 字典外事件未落库
select count(*) from platform.product_events where event_name='post_impression'; -- 0
-- 混入白名单外 postId 的 post_liked:落库 props 已剥离
select props from platform.product_events where event_id='bc13cbbd-…';
{"source": "feed"} -- 无 postId
5. 契约偏差声明与共享面回归分析
5.1 契约偏差数:0 个
两份脚本共 132 条断言覆盖 HTTP 状态码、业务错误码、信封结构、字段形态、分页语义、 幂等语义、乐观锁语义与防枚举一致性,与冻结契约 v1.3.0 全部一致,无需修复项。
5.2 M2 契约面在 v1.3.0 中零漂移(结构化取证)
M3 期间四模块契约快照从 openapi-v1.2.0.yaml 换名到 openapi-v1.3.0.yaml,
是本次回归最需要盯的共享面。对 doc@511617b(v1.2.0 冻结)与 doc@f848476(v1.3.0 冻结)
做结构化比对(YAML 解析后按键排序序列化对比,非文本 diff):
info.version: 1.2.0 -> 1.3.0
paths:18 -> 31(+13,全部为 community/media 新增;removed: NONE)
v1.2.0 的 18 条 path 定义 —— CHANGED/MISSING: NONE(全部完全一致)
schemas:45 -> 72(+27)
removed schemas: NONE
changed schemas: NONE
即 v1.3.0 相对 v1.2.0 严格增量:M2 的 18 条路径与 45 个 schema 一字未改, M2 版脚本对照 v1.3.0 运行等价于对照 v1.2.0 运行。
5.3 共享代码面回归分析(64c9b72..8089c06,即 M3 全区间)
patbond-pet/src/main/ → 0 个文件变更(pets 域生产代码 M3 期间未被触碰)
patbond-pet/ 变更仅在测试侧:ContractConformanceTest / ContractValidator /
OpenApiContract + 快照文件改名(v1.2.0 → v1.3.0)
patbond-common/ → 仅 ErrorCode.java +9 行,纯新增枚举常量:
POST_ACCESS_DENIED(40301) / POST_NOT_FOUND(40403) / IDEMPOTENCY_PAYLOAD_MISMATCH(40905)
MEDIA_NOT_FOUND(40405) / COMMENT_NOT_FOUND(40404) / TARGET_USER_NOT_FOUND(40406)
MEDIA_NOT_READY(42203) / FOLLOW_RULE_VIOLATION(42204) / MEDIA_UPLOAD_STATE_INVALID(42205)
—— 无任何 `-` 行,M2 错误码(40401/40902/40000…)定义未被修改或删除
三条共享面(契约快照、patbond-common、同一数据库实例)均验证为纯增量,
与实测的 11/11 结果互为印证:M3 交付未破坏 M2 功能,无回归。
6. 失败项
无。 六次脚本运行(M2 ×3、M3 ×3)全部 exit 0,输出中 ✗/FAIL 计数为 0,
无需区分「脚本环境问题」与「真实回归」。
7. 三仓状态(零改动核验)
cd <你的工作区>/patbond-api && git status --short # 空
cd <你的工作区>/patbond-flutter && git status --short # 空
cd <你的工作区>/patbond-doc && git status --short # 仅本报告(未 commit)
| 仓 | HEAD |
|---|---|
| patbond-api | 8089c06 feat: 事件字典 v3 白名单扩充…(T3-20) |
| patbond-flutter | 0e87413 test: M3 E2E 烟囱脚本(T3-21 收官) |
| patbond-doc | 3ebe562 docs: M3 收官——E2E 报告与收官总结入档,验收 PASSED |
(patbond-api/*/target/ 下的重建产物为 gitignore 覆盖项,不产生工作区脏状态。)
8. 环境清理
cd <你的工作区>/patbond-api && docker compose down
数据卷 pgdata / minio-data 按既往做法保留(未加 -v),便于下次复跑与事后取证。
9. 遗留与后续
- 真机挂起项不变:M2 的两项(Android 事件落库真机观察、SessionTracker 30min 手测)与
M3 的真机项仍按方案 A 挂起,本次脚本直连不替代真机验证——见
docs/development/device-verification.md。 - 发布 checklist 后续步骤:本报告只闭合 §3.3 第 2 步;第 3 步(命名统一 + api
main重建)、 第 4~8 步(合并/打标/分支保护/发布说明/hotfix 纪律)待拍板后执行。 - E2E 自动化仍为手动模式:iteration-3/08 §5 的结论(保持「波次收尾手动跑、证据入档」,
自动化做成
workflow_dispatch手动工作流,低优先)未变;本次两份脚本 3 秒内跑完, 手动成本极低,暂无自动化紧迫性。