1891d9b7b4
CI / docs-build (push) Successful in 1m3s
- iteration-2 报告 01-08(PM 拆解/后端/Flutter 评估/现实核查/UI 规范/埋点规划/证据基线/Git 规划),04、06 已由正式角色复核定稿 - mkdocs 挂「第二迭代」导航,build --strict 通过 - ADR-009 新建 patbond-pet 模块、ADR-010 照片剪出 M2、ADR-011 dev 主干/master 发布、ADR-012 北极星与 H1-H4、ADR-013 废弃 health_record_action、ADR-014 DEBT-1 随 M2、ADR-015 照护人邀请后置 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
136 lines
12 KiB
Markdown
136 lines
12 KiB
Markdown
# Patbond 第二迭代后端技术评估(Dev)
|
||
|
||
- 日期:2026-09-07
|
||
- 评估范围:patbond-api 承接 M2「宠物健康档案」的改动面、建模与 API 草案、迁移规划、遗留项耦合
|
||
- 代码基线:patbond-api `0d81c38`(2026-09-04,工作区干净)
|
||
- 结论先行:**当前基线 82 个测试全绿(50.6s)**;建议 M2 在 patbond-user 内以独立包实现 pet_health 域,建模跟随 patbond-doc 目标模型(体重/疫苗强结构子表 + health_events 单表),共 7 项待拍板。
|
||
|
||
## 1. 现状盘点(实际读码结论)
|
||
|
||
### 1.1 模块与代码结构
|
||
|
||
Maven 三模块:`patbond-common`(错误码/响应契约/内部 DTO)、`patbond-auth`(8081,无库,Feign 调 user)、`patbond-user`(8082,唯一持库服务,Flyway 归属方)。
|
||
|
||
与 M2 直接相关的既有设施,全部可复用:
|
||
|
||
- **鉴权链路**:`patbond-user` 的 `BearerAuthFilter`(`patbond-user/src/main/java/com/patbond/patbond/user/security/BearerAuthFilter.java`)拦截 `/api/v1/*`,RS256 本地验签后把 userId 放进 request attribute `patbond.authenticatedUserId`,controller 用 `@RequestAttribute` 取。宠物接口直接挂在同一过滤器下,零新增鉴权代码。
|
||
- **异常/错误码契约**:`ErrorCode` 枚举(common)+ 每服务一个 `GlobalExceptionHandler`,`{code, message, data}` 信封 + 正确 HTTP 状态。扩展 = 往枚举追加值(不重编号)。
|
||
- **数据访问**:无 JPA,统一 `JdbcClient` + 手写 SQL(见 `UserRepository`),约束下沉数据库(CHECK/部分唯一索引),`updated_at` 由触发器维护。pet 域照此风格即可。
|
||
- **主键**:应用侧生成 UUIDv7(`patbond-user/src/main/java/com/patbond/patbond/user/support/UuidV7.java`)。
|
||
- **埋点挂接点**:`EventDictionary` 已预置 `health_record_action`(props 白名单 `recordType`/`actionType`),M2 后端无需改埋点代码,Flutter 侧触发即可。
|
||
- **测试设施**:Testcontainers(PostgreSQL 18)+ `TestcontainersConfiguration`,集成测试模式成熟,pet 域测试直接套用。
|
||
|
||
### 1.2 数据库现状
|
||
|
||
Flyway 链在 patbond-user:V1(identity/media/platform 基线)+ V2(platform.product_events)。**pet_health schema 尚未创建**(V1 只建了 platform/identity/media 三个 schema)。开发种子在 `db/dev/afterMigrate__dev_seed.sql`,默认不执行。
|
||
|
||
patbond-doc 目标模型(`patbond-doc/docs/database/patbond_postgresql.sql` 333-560 行)已给出完整的 pet_health 设计,共 8 张表:`breeds`、`pets`、`pet_owners`、`pet_weight_records`、`vaccine_catalog`、`pet_vaccinations`、`health_events`、`health_event_media`、`care_reminders`。该模型已经过评审,M2 建模应以它为正典裁剪,而不是另起炉灶。
|
||
|
||
### 1.3 缺口
|
||
|
||
- **media 上传流程未实现**:仓库中没有任何 media 相关代码(无 controller/service),`media.assets` 只有表。目标模型中宠物头像、疫苗证书、健康事件附件全部 FK 到 `media.assets`——附件能力被 media 上传流程阻塞(见待拍板 P3)。
|
||
- `marketplace` schema 未建:目标模型中 `pet_vaccinations.provider_id/booking_id`、`health_events.provider_id/booking_id` 本就未设 FK(预留列),M2 保留可空列即可,无阻塞。
|
||
|
||
## 2. 改动面评估
|
||
|
||
| 改动面 | 内容 | 量级 |
|
||
| --- | --- | --- |
|
||
| Flyway | V3 pet_health 结构基线(从目标模型裁剪)+ V4 字典种子(若拍板引入) | 中 |
|
||
| 新代码 | pet 域 controller/service/repository/DTO(约 5 组资源) | 大(M2 主体) |
|
||
| common | `ErrorCode` 追加 3~4 个值;若拍板新模块则需下沉 `UuidV7`/`BearerAuthFilter` | 小 |
|
||
| 契约 | openapi.yaml 冻结新增 pets 相关 path(实现前先冻结,本评估不动契约) | 中 |
|
||
| 既有代码 | 零改动(鉴权过滤器、异常处理、埋点均直接复用) | — |
|
||
| 依赖 | **无需新增任何依赖**(JdbcClient + Flyway + Testcontainers 足够,不引 JPA) | — |
|
||
|
||
## 3. 领域建模草案
|
||
|
||
### 3.1 模块归属【待拍板 P1】
|
||
|
||
- **方案 A:新建 `patbond-pet` Maven 模块(独立服务)**。符合开发计划 4.1「按迭代增加模块,边界与 schema 对齐」的字面方向。代价:新端口/compose 服务/CI 矩阵;`BearerAuthFilter`、`JwtVerifier`、`GlobalExceptionHandler`、`UuidV7` 需下沉 common 或复制;Flyway 单链归属要拆(共库单 `flyway_schema_history`,需为新模块配独立 history 表),部署与联调面翻倍。
|
||
- **方案 B(推荐):在 patbond-user 内新增独立顶层包 `com.patbond.patbond.user.pethealth`**。零基础设施成本,Flyway 链自然延续(V3+),鉴权/异常/UUIDv7 直接复用。约束:包内不 import user 域内部类(只经 service 接口),SQL 只碰 `pet_health` schema(读 `identity.users` 仅限权限校验 join),保证未来抽成独立模块时是「搬包 + 拆迁移」而非重写。
|
||
- 推荐 B:MVP 单实例共库阶段,「模块边界与 schema 对齐」用包边界 + schema 读写纪律即可兑现,把工程成本留给业务代码。
|
||
|
||
### 3.2 表结构草案(Flyway V3,从目标模型裁剪)
|
||
|
||
按目标模型原样建(列、CHECK、部分唯一索引、`set_updated_at` 触发器全保留),仅做以下裁剪调整:
|
||
|
||
| 表 | M2 处置 | 调整点 |
|
||
| --- | --- | --- |
|
||
| `pets` | 建 | 主键去掉 `DEFAULT gen_random_uuid()`,应用侧 UUIDv7(与 users 做法对齐);`avatar_asset_id` 保留可空列(media 未实现,暂不写入) |
|
||
| `pet_owners` | 建 | 目标模型原样;创建宠物时自动写入 `(pet_id, creator, 'owner', is_primary=true)` |
|
||
| `pet_weight_records` | 建 | 原样 |
|
||
| `breeds` + `vaccine_catalog` | 建(P2 拍板) | 若引入:结构进 V3、种子进 V4 正式迁移(字典是生产数据,不放 db/dev);若不引入:pets 全走 `custom_breed_name`(`ck_pets_breed` 约束允许),疫苗表需把 `vaccine_id` 放宽为自由文本——**偏离目标模型,后续迁移代价大** |
|
||
| `pet_vaccinations` | 建 | `provider_id`/`booking_id`/`certificate_asset_id` 保留可空预留列,M2 不写入 |
|
||
| `health_events` | 建 | `event_type` 枚举沿用目标模型 6 值(medical/feeding/deworming/grooming/measurement/note) |
|
||
| `health_event_media` | **不建,推迟** | 依赖 media 上传流程(P3);纯增量表,后续 V5+ 补零成本 |
|
||
| `care_reminders` | 建(P6 拍板) | 纯 CRUD,无推送 |
|
||
|
||
与 user/auth 的关系:`pet_owners.user_id -> identity.users(id)` 与 `health_events.created_by_user_id -> identity.users(id)` 两个跨 schema FK,共库阶段保留(与 V1 中 `media.assets.owner_user_id` 先例一致)。鉴权只用 JWT 里的 userId,不新增 auth 侧改动、不新增 `/internal` 接口。
|
||
|
||
### 3.3 健康记录类型建模:单表 + type vs 每类型子表【已由目标模型定调,确认即可】
|
||
|
||
- 纯单表(所有记录一张表 + type + jsonb):查询简单,但体重/疫苗的强约束(剂次唯一、状态-日期一致性、数值范围)全丢给应用层。
|
||
- 纯子表(每类型一张表):表爆炸,时间线聚合要 UNION 多表。
|
||
- **推荐(= 目标模型的混合方案)**:`pet_weight_records`、`pet_vaccinations` 独立强结构子表(各自的 CHECK 与部分唯一索引是业务规则本体,如「同系列同剂次未取消唯一」);其余低结构记录统一进 `health_events` + `event_type` 枚举。时间线视图由 health_events 承载,体重/疫苗页各查各表。
|
||
|
||
## 4. API 资源设计草案(供契约冻结参考,本评估不改 openapi.yaml)
|
||
|
||
路径与开发计划 6.2 对齐,全部挂 `BearerAuthFilter` 强制鉴权:
|
||
|
||
| 接口 | 说明 |
|
||
| --- | --- |
|
||
| `GET /api/v1/pets` | 当前用户可见宠物列表(经 pet_owners join);量小,建议一次性返回不分页(契约冻结时定) |
|
||
| `POST /api/v1/pets` | 创建,创建者自动 primary owner,返回 201 |
|
||
| `GET /api/v1/pets/{petId}` | 详情(含调用者自己的 role) |
|
||
| `PATCH /api/v1/pets/{petId}` | 更新,请求体带 `version` 乐观锁,冲突返回 409/40902 |
|
||
| `DELETE /api/v1/pets/{petId}` | 软删(status=deleted),仅 owner;是否进 M2 契约冻结时定 |
|
||
| `GET/POST /api/v1/pets/{petId}/weights` | 体重记录(GET 按 measured_at 倒序,cursor 分页) |
|
||
| `GET/POST /api/v1/pets/{petId}/vaccinations`、`PATCH .../vaccinations/{id}` | 疫苗记录(PATCH 带 version;状态迁移 scheduled→completed/cancelled) |
|
||
| `GET/POST /api/v1/pets/{petId}/health-events` | 健康时间线(cursor 分页:`(occurred_at, id)` 复合游标,与既有索引对齐) |
|
||
| `GET/POST/PATCH /api/v1/pets/{petId}/reminders` | 提醒 CRUD(P6) |
|
||
| `GET /api/v1/pets/{petId}/health-summary` | 服务端聚合:最新体重与趋势、疫苗进度、下次接种、当月花费(P5) |
|
||
| `GET /api/v1/breeds`、`GET /api/v1/vaccines` | 字典只读接口(若 P2 拍板引入;query 参数 species) |
|
||
|
||
权限规则:owner 全权;caregiver 可读写记录、不可改宠物档案与成员;viewer 只读。M2 只实现 owner 路径(P4),但 repository 层权限查询按三档写好。
|
||
|
||
错误码扩展(追加进 `ErrorCode`,延续现有编号段):
|
||
|
||
| code | HTTP | 语义 |
|
||
| --- | --- | --- |
|
||
| 40300 `PET_ACCESS_DENIED` | 403 | 对可见宠物无相应操作权限(如 viewer 尝试写) |
|
||
| 40401 `PET_NOT_FOUND` | 404 | 宠物不存在**或调用者不可见**(防 ID 枚举,见 P7) |
|
||
| 40402 `RECORD_NOT_FOUND` | 404 | 宠物下的记录不存在 |
|
||
| 40902 `VERSION_CONFLICT` | 409 | 乐观锁版本冲突(对应验收标准「并发更新返回明确冲突」) |
|
||
|
||
幂等:开发计划 6.1 的 `Idempotency-Key` 强制名单(帖子/生成任务/预约)不含 pets,M2 写接口不强制幂等键;客户端重试语义靠乐观锁 + 唯一约束兜底。
|
||
|
||
## 5. 待拍板清单
|
||
|
||
| # | 事项 | 选项 | 推荐 |
|
||
| --- | --- | --- | --- |
|
||
| P1 | 模块归属 | 新建 patbond-pet 模块 vs patbond-user 内独立包 | user 内独立包(3.1) |
|
||
| P2 | 品种/疫苗字典 | 引入 breeds + vaccine_catalog(V3 结构 + V4 种子)vs 自由文本 | 引入字典,种子最小集(犬猫核心疫苗),避免偏离目标模型 |
|
||
| P3 | 附件/图片 | 进 M2(需先实现 media 上传流程)vs 推迟 | **推迟出 M2**;media 上传是独立工作量,不该给健康档案当前置;表列已预留 |
|
||
| P4 | 共同照护人 | 邀请/成员管理 API 进 M2 vs 只做 owner 自动归属 | 只做 owner,权限校验按三档 role 实现好,邀请 API 下迭代 |
|
||
| P5 | 健康汇总聚合 | 服务端 `health-summary` 接口 vs 客户端自聚合 | 服务端聚合(计划 M2 验收提到「从事实表聚合生成」,且跨设备一致) |
|
||
| P6 | 提醒 | care_reminders CRUD 进 M2(无推送)vs 推迟 | 进 M2 做纯 CRUD(计划 M2 范围明确包含),推送依赖通知基础设施、明确不做 |
|
||
| P7 | 无权限读取语义 | 403 vs 404 | 不可见宠物一律 404/40401(防枚举);可见但越权操作 403/40300 |
|
||
|
||
## 6. 遗留中低优先项与 M2 的耦合评估
|
||
|
||
- **access token 黑名单**:与 M2 **弱耦合,建议不进本迭代**。宠物权限每次请求实时查 `pet_owners`,撤销照护关系立即生效,不依赖 token 吊销;access token 15 分钟 TTL(ADR-003)对健康档案的敏感级别足够。黑名单需求真正的触发点是「改密/封号即时踢出」,属身份域主题,与 pet 域实现无交集。
|
||
- **/internal 改 mTLS**:与 M2 **无耦合,建议不进本迭代**。M2 不新增任何 `/internal` 接口(按 P1 推荐方案,pet 域与 user 同进程,连内部调用都没有);即使 P1 拍板为独立模块,也应沿用现有静态 service token 方案,mTLS 留给微服务化阶段(与 ADR-002 的节奏一致)。
|
||
|
||
## 7. 构建与测试基线(2026-09-07 实测)
|
||
|
||
命令:`JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw test`(系统默认 JDK 26 不可用于构建,须显式指定)。
|
||
|
||
| 模块 | 测试数 | 结果 |
|
||
| --- | --- | --- |
|
||
| patbond-common | 3 | 通过 |
|
||
| patbond-user | 48 | 通过(含 Testcontainers 集成测试) |
|
||
| patbond-auth | 31 | 通过 |
|
||
| **合计** | **82** | **全绿,BUILD SUCCESS,总耗时 50.6s** |
|
||
|
||
与第一迭代收官基线(82 测试)一致,无回归。此为 M2 开工基线:M2 结束时测试数只增不减,且该命令保持一次通过。
|