- 09 契约补录 events(关闭 D-1/放行条件②,另含实现与旧规范 5 处出入记录) - 10 Flutter 埋点修复(接线+三偏差+SessionTracker+page_viewed,34→51 测试,12/12 验收) - 11 后端地基(V3/V4 迁移 8 表+种子、4 条跨 schema FK 剥离、patbond-pet 骨架、ADR-013 执行,82→95 测试) - mkdocs build --strict 通过 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9.1 KiB
M2 第一波后端地基施工报告(B 线:V3 迁移 + patbond-pet 骨架 + ADR-013)
作者:Senior Developer(后端) 日期:2026-09-07 工单:T2-01(Flyway V3/V4)、T2-02 前置(patbond-pet 模块骨架)、ADR-013 执行、错误码预置 代码基线:patbond-api
0d81c38(82 测试全绿)→ 交付58576f8(95 测试全绿) 结论先行:V3 建 8 表(health_event_media 按 ADR-010 不建),4 条 marketplace 跨 schema 外键全部剥离;patbond-pet 模块挂入构建链并纳入 compose;health_record_action 已从白名单移除;全套 95 测试在干净 postgres:18 上全绿。
1. 提交清单
按拆分建议分三个提交,全部已推送 origin/dev:
| 提交 | 内容 |
|---|---|
49299fb |
feat: Flyway V3 pet_health 结构基线 + V4 字典种子 + pet 域错误码(T2-01) |
0eae1c9 |
feat: 新建 patbond-pet 模块骨架(ADR-009,T2-02 前置) |
58576f8 |
refactor: 移除 EventDictionary 的 health_record_action(ADR-013) |
流程说明:iteration-2/08 规划中 Flyway 迁移属「短命分支 + PR 合入 dev」的推荐实践;本次第一波经用户拍板直接推 dev,特此注明。
2. Flyway V3/V4:表清单与裁剪对照
2.1 V3 结构基线(patbond-user/src/main/resources/db/migration/V3__pet_health_baseline.sql)
从目标模型 patbond-doc/docs/database/patbond_postgresql.sql(333~554 行)原样提取,共建 8 张表:
| # | 表 | 处置 | 与目标模型的差异 |
|---|---|---|---|
| 1 | pet_health.breeds |
建 | 无差异 |
| 2 | pet_health.pets |
建 | 无差异(avatar_asset_id FK 到 media.assets 保留——media 表 V1 已建,仅上传流程未实现,列 M2 不写入) |
| 3 | pet_health.pet_owners |
建 | 无差异(含 owner/caregiver/viewer 角色约束与 primary owner 部分唯一索引,ADR-015 权限模型的数据基础) |
| 4 | pet_health.pet_weight_records |
建 | 无差异 |
| 5 | pet_health.vaccine_catalog |
建 | 无差异 |
| 6 | pet_health.pet_vaccinations |
建 | 剥离 2 条跨 schema FK(见 2.2);列全保留 |
| 7 | pet_health.health_events |
建 | 剥离 2 条跨 schema FK(见 2.2);列全保留 |
| 8 | pet_health.care_reminders |
建 | 无差异 |
| — | pet_health.health_event_media |
不建 | ADR-010:media/附件剪出 M2;该表 asset_id 为 NOT NULL FK 到 media.assets 且 media 上传流程零代码,与 pet 域业务强耦合无意义。纯增量表,待 media 专项落地时以新版本迁移补建,零成本 |
其余保留项:全部 CHECK 约束、部分唯一索引(uq_pet_vaccination_dose、uq_pet_primary_owner、uq_pets_microchip 等)、4 个 updated_at 触发器(复用 V1 的 platform.set_updated_at(),无需新建函数)。pet_owners.user_id、health_events.created_by_user_id 到 identity.users 的跨 schema FK 保留(与 V1 中 media.assets.owner_user_id 先例一致,共库阶段成立)。
2.2 强制裁剪:4 条 marketplace 跨 schema 外键(逐条对照)
bootstrap SQL 第 1156~1166 行(现实核查已证实行号)以 ALTER TABLE 追加的 4 条约束,V3 全部剥离,对应字段保留为裸可空 uuid 列,索引照建:
| # | 约束名 | 原定义 | V3 处置 |
|---|---|---|---|
| 1 | fk_vaccinations_provider |
pet_vaccinations.provider_id → marketplace.providers(id) ON DELETE SET NULL |
剥离;provider_id uuid 裸列保留,ix_vaccinations_provider 索引保留 |
| 2 | fk_vaccinations_booking |
pet_vaccinations.booking_id → marketplace.bookings(id) ON DELETE SET NULL |
剥离;booking_id uuid 裸列保留,ix_vaccinations_booking 索引保留 |
| 3 | fk_health_events_provider |
health_events.provider_id → marketplace.providers(id) ON DELETE SET NULL |
剥离;裸列 + ix_health_events_provider 保留 |
| 4 | fk_health_events_booking |
health_events.booking_id → marketplace.bookings(id) ON DELETE SET NULL |
剥离;裸列 + ix_health_events_booking 保留 |
迁移文件头部注释已逐条列出并标明「M5 迁移 marketplace schema 时以新版本迁移补回」。集成测试断言这 4 条 FK 确不存在(防照抄回归)。
2.3 V4 字典种子(V4__pet_health_dictionary_seed.sql)
按 02 号评估建议采用「V3 结构 + V4 种子」划分:breeds/vaccine_catalog 是应用 FK 指向的生产参考数据,走正式迁移链而非 db/dev(与开发 fixture 性质不同)。
breeds:28 条(犬 16 + 猫 12,常见品种,含「中华田园犬/猫」兜底项)vaccine_catalog:10 条(犬 6:二/四/五/八联、狂犬、犬窝咳;猫 4:三联、狂犬、白血病、衣原体)- 正典目录内容与量级按 D2-6 由产品侧供稿,届时以后续迁移追加/修订
3. patbond-pet 模块骨架(ADR-009)
patbond-pet/
├── Dockerfile # 同 user/auth 模式(temurin-17-jre,uid 10001,无状态)
├── pom.xml # 挂入父 pom,依赖对齐既有模块(common/web/validation/jdbc + Testcontainers)
└── src/
├── main/java/com/patbond/patbond/pet/
│ ├── PetApplication.java # Spring Boot 入口
│ ├── controller/HealthController.java # GET /health 探活(含 SELECT 1 连通检查)
│ └── web/GlobalExceptionHandler.java # 同一 {code,message,data} 信封契约
├── main/resources/application.yml.sample # .sample 模式,默认端口 8083,DB 经环境变量注入
└── test/java/com/patbond/patbond/pet/
├── TestcontainersConfiguration.java # postgres:18 @ServiceConnection
├── PetApplicationTests.java # 上下文启动冒烟
└── controller/HealthControllerTest.java # /health 200 + db=up 断言
关键取舍:
- Flyway 归属不拆:pet 模块不携带 Flyway。单一迁移链(V1..V4,含 pet_health 基线)仍由 patbond-user 启动时统一执行——共库单
flyway_schema_history,拆链需为新模块配独立 history 表,收益为零。pet 模块只经 JdbcClient 读写pet_healthschema(第二波接口落地时)。 - compose 编排已纳入:既有模式是每服务一个 compose service(build + .sample 挂载 + 环境变量注入),pet 照此加入;
depends_onpostgres 健康 + user 先起(保证迁移已执行、pet_health schema 就绪)。 - 鉴权后置第二波:骨架暂无
/api/v1业务端点,故未接入 JWT 校验;/health刻意放在/api/v1之外(基础设施探针无业务数据)。第二波接口落地时按 user 模块同一约定接入 RS256 本地验签(BearerAuthFilter模式,届时评估下沉 common 或复制)。
4. ADR-013 执行与错误码预置
EventDictionary移除health_record_action白名单项,注释同步改写(引 ADR-013);page_viewed与 v1 auth 漏斗事件保留为完整白名单。既有测试无一引用该事件,零测试改动;新增EventDictionaryTest(3 例)锁定移除后的白名单边界。ErrorCode(patbond-common)按 02 号建议预置 4 个 pets 域错误码,延续既有编号段、不重编号:
| code | 枚举名 | HTTP | 语义 |
|---|---|---|---|
| 40300 | PET_ACCESS_DENIED |
403 | 对可见宠物无相应操作权限(如 viewer 尝试写) |
| 40401 | PET_NOT_FOUND |
404 | 宠物不存在或调用者不可见(防 ID 枚举) |
| 40402 | RECORD_NOT_FOUND |
404 | 宠物下的记录不存在 |
| 40902 | VERSION_CONFLICT |
409 | 乐观锁版本冲突 |
当前无消费方,第二波接口纵切直接使用;契约(openapi.yaml)本波不动,随 T2-09 冻结时一并写入。
5. 测试数变化:82 → 95(+13,0 回归)
| 模块 | 基线 | 交付 | 新增内容 |
|---|---|---|---|
| patbond-common | 3 | 3 | — |
| patbond-user | 48 | 59 | PetHealthMigrationIntegrationTest 8 例(schema 存在、8 表齐、结构抽查、4 条 marketplace FK 确不存在、触发器 4 个、V4 种子非空与抽查);EventDictionaryTest 3 例 |
| patbond-auth | 31 | 31 | — |
| patbond-pet | — | 2 | 上下文冒烟 + /health 探活 |
| 合计 | 82 | 95 | JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test 一次通过,BUILD SUCCESS |
V1→V2→V3→V4 全量迁移经 Testcontainers 在全新 postgres:18 容器上自动验证通过(每个 @SpringBootTest 上下文启动即执行全链迁移)。
6. 遗留与下一波衔接
- T2-02 剩余部分(第二波):pet 模块接入 JWT 资源侧校验(
BearerAuthFilter/JwtVerifier/UuidV7下沉 common 或复制的决策届时定)、当前用户解析注入。 - health_event_media:随 media 专项(对象存储选型拍板后)以新迁移补建。
- 4 条 marketplace FK:M5 迁移 marketplace schema 的版本迁移中补回(V3 文件注释已标明)。
- CI:
.gitea/workflows/ci.yml跑./mvnw -B clean test,多模块 reactor 自动含 patbond-pet,无需改动;push 后 CI 状态由波末闭环核对。 - 正典字典数据:D2-6 产品侧供稿后以后续迁移替换/扩充 V4 种子。