- 09 V5+community 骨架(191→206)、10 埋点队列三项(272→286)、 11 契约草案(13 路径/19 操作)、12 防泄漏三仓落地、 13 media MinIO 闭环 + auth 契约测试(→226,抓修 1 漂移)、14 收口总表 - backend-modules.md 更新五模块/六容器口径 - 媒体凭据形态已定型(契约冻结输入),剩余待定型点在 T3-04/05 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
10 KiB
M3 第一波社区地基施工报告(数据与骨架线:V5 迁移 + patbond-community 骨架)
作者:Senior Developer(后端) 日期:2026-09-08 工单:T3-01(Flyway V5 community schema 迁移)、T3-02(patbond-community 模块骨架与鉴权接入) 代码基线:patbond-api
64c9b72(191 测试全绿)→ 交付3c671fc(206 测试全绿) 结论先行:V5 建 community 全部 8 表,2 条跨 schema 外键(generation_job_id→creation、region_id→platform.regions)按拍板剥离;patbond-community(:8084)挂入构建链、自骨架起 /api/v1/ 即接 RS256 校验并纳入 compose 第五容器;干净 postgres:18 上 V1→V5 全量迁移一次成功,全套 206 测试全绿。**
1. 提交清单
按工单各一逻辑提交,全部已推送 origin/dev:
| 提交 | 内容 |
|---|---|
a97814a |
feat: Flyway V5 community schema 基线 + 迁移验证集成测试(T3-01) |
3c671fc |
feat: 新建 patbond-community 模块骨架(ADR-017,T3-02) |
2. Flyway V5:表清单与裁剪对照(T3-01)
2.1 V5 结构基线(patbond-user/src/main/resources/db/migration/V5__community_baseline.sql)
从目标模型 patbond-doc/docs/database/patbond_postgresql.sql(718~875 行)提取,共建 8 张表:
| # | 表 | 处置 | 与目标模型的差异 |
|---|---|---|---|
| 1 | community.posts |
建 | 剥离 2 条跨 schema FK(见 2.2);列全保留,其余约束/索引无差异 |
| 2 | community.post_media |
建 | 无差异(asset_id → media.assets RESTRICT 保留,media 表 V1 已建;封面部分唯一索引 uq_post_media_cover 照建) |
| 3 | community.comments |
建 | 无差异(刻意单层平铺,reply_to_user_id 支持 @ 回复;幂等列 client_request_id + request_hash 照建) |
| 4 | community.post_likes |
建 | 无差异(PK (post_id, user_id) 天然幂等) |
| 5 | community.post_bookmarks |
建 | 无差异(同上) |
| 6 | community.user_follows |
建 | 无差异(含禁自关注 CHECK) |
| 7 | community.topics |
建 | 无差异(name citext UNIQUE)。表建功能剪:ADR-018 话题剪出 M3 MVP,但结构按目标模型建;无种子数据进生产链(测试断言 topics 为空) |
| 8 | community.post_topics |
建 | 无差异 |
其余保留项:全部 CHECK 约束(ck_posts_publish_state、ck_posts_idempotency、ck_comments_deleted 等)、Feed 部分索引 ix_posts_feed (published_at DESC, id DESC) WHERE status='published' AND visibility='public'、uq_posts_author_idempotency、2 个 updated_at 触发器(posts/comments,复用 V1 的 platform.set_updated_at())。到 identity.users、pet_health.pets、media.assets 的跨 schema FK 全部保留(三个 schema V1/V3 已存在,共库阶段先例)。
2.2 强制裁剪:2 条跨 schema 外键(逐条对照)
按拍板(工单 T3-01,照 V3 剪 4 条 marketplace FK 的先例格式),对应字段保留为裸可空 uuid 列,索引照建,迁移文件头注释逐条标明补回时点:
| # | 原定义 | V5 处置 | 补回时点 |
|---|---|---|---|
| 1 | posts.generation_job_id → creation.generation_jobs(id) ON DELETE SET NULL |
剥离;裸列保留,ix_posts_generation_job 索引保留 |
M4 建 creation schema 的迁移补回 |
| 2 | posts.region_id → platform.regions(id) ON DELETE SET NULL |
剥离;裸列保留,ix_posts_region、ix_posts_region_feed 索引保留 |
M5 地区体系迁移补回(ADR-018 将 region 剪出 M3) |
说明:platform.regions 表 V1 已存在(02 号评估 1.4 节原判「保留」),但 PM 拆解按 ADR-018 范围裁剪把 region 体系整体划入 M5,本工单按拍板剪 FK——列与索引保留,M5 补回约束零成本。
2.3 扩展启用
pg_trgm:02 号评估发现 V1 只建了 pgcrypto 与 citext,而ix_posts_content_trgm(gin,gin_trgm_ops)需要 pg_trgm——V5 文件头CREATE EXTENSION IF NOT EXISTS pg_trgm补齐;postgres:18 官方镜像含 contrib,Testcontainers 与 compose 均实测无障碍。trgm 索引按 02 号建议照建(M3 无搜索需求,但成本极低、剪了偏离目标模型)。citext:V1 已建;V5 以IF NOT EXISTS幂等重申(迁移日志出现一条「already exists, skipping」提示,无害)。
2.4 主键 DEFAULT 的取舍
02 号评估表格建议 V5「去掉主键 DEFAULT gen_random_uuid()」,但核对既有链:V1(users)与 V3(pets)均保留了该 DEFAULT,应用侧显式写入 UUIDv7、DB DEFAULT 仅作兜底。V5 与 V1/V3 同规保留 DEFAULT(工单要求「照 V3 先例写法」优先于评估建议;两者对运行时行为无差异,应用永远显式供 id)。
3. patbond-community 模块骨架(T3-02,ADR-017)
patbond-community/
├── Dockerfile # 同 user/auth/pet 模式(temurin-17-jre,uid 10001,无状态,EXPOSE 8084)
├── pom.xml # 挂入父 pom;依赖对齐 pet(common/web/validation/jdbc/jjwt + 测试侧 user jar + Flyway + Testcontainers)
└── src/
├── main/java/com/patbond/patbond/community/
│ ├── CommunityApplication.java # Spring Boot 入口
│ ├── config/CommunitySecurityProperties.java # patbond.jwt.public-key
│ ├── config/SecurityConfig.java # BearerAuthFilter 注册到 /api/v1/*(order 20)
│ ├── config/JacksonConfig.java # 整数字段拒绝小数(与其余服务同规)
│ ├── security/{BearerAuthFilter,JwtVerifier,RsaPublicKeyLoader}.java # RS256 资源侧校验(user/pet 同款第三份复制)
│ ├── web/GlobalExceptionHandler.java # {code,message,data} 信封契约
│ └── controller/HealthController.java # GET /health 探活(含 SELECT 1 连通检查,在 /api/v1 之外)
├── main/resources/application.yml.sample # .sample 模式,默认端口 8084,DB/公钥经环境变量注入
└── test/java/com/patbond/patbond/community/
├── TestcontainersConfiguration.java # postgres:18 @ServiceConnection;测试 classpath 挂 user jar + Flyway 跑全链 V1..V5
├── CommunityApplicationTests.java # 上下文冒烟
├── controller/HealthControllerTest.java # /health 200 + db=up
├── security/BearerAuthIntegrationTest.java # 无 token/畸形/错签/过期 → 401+40101;有效 token 过滤器放行(未实现路由 404+40400)
└── support/TestJwtKeys.java # 运行时生成 RSA 对,无密钥材料入库
关键取舍:
- 鉴权自骨架起接入(与 pet 骨架期不同):T3-02 验收要求无 token/过期 token 返回 401 + 40100 系,故
BearerAuthFilter/JwtVerifier/RsaPublicKeyLoader随骨架落地(user/pet 同款第三份复制)。02 号评估 P9 建议的「纯 Java 件下沉 common」未进 ADR-016~021 拍板,本单不做,留待后续决策——届时三处复制件切换为共享件、测试全绿即证等价。 - Flyway 归属不拆:community 生产 classpath 无 Flyway;单迁移链(V1..V5)由 patbond-user 启动统一执行。模块只经 JdbcClient 读写
communityschema。 - compose 第五容器:照 pet 服务块模式(build + .sample 挂载 + 环境变量注入 + 公钥只读挂载);
depends_onpostgres 健康 + user 先起(保证 V5 已执行、community schema 就绪)。 - 作者资料取数:按 ADR-017/D3-9 方案 B(user 增 /internal 批量公开资料接口),本单只搭骨架不实现。
4. compose 五容器验证
./mvnw -DskipTests package + docker compose up -d --build 实测(既有 pgdata volume,数据库处于 V4):
- 五容器全部 Up:postgres(healthy)+ auth + user + pet + community。
- 增量迁移零影响:user 启动日志
Migrating schema "public" to version "5 - community baseline"→Successfully applied 1 migration ... now at version v5——在带 M2 数据的既有库上 V5 增量应用成功(纯增量 schema,对 V1~V4 数据零影响的实证)。 - community 探活:
GET :8084/health→{"code":0,...,"data":{"status":"ok","db":"up"}}(pet :8083 同绿)。 - 鉴权实证:
GET :8084/api/v1/posts无 token → HTTP 401 +{"code":40101,"message":"token 无效或过期"},与验收标准一致。 - 验证后
docker compose down(保留 pgdata volume),恢复环境原状。
5. 测试数变化:191 → 206(+15,0 回归)
| 模块 | 基线 | 交付 | 新增内容 |
|---|---|---|---|
| patbond-common | 3 | 3 | — |
| patbond-user | 68 | 76 | CommunityMigrationIntegrationTest 8 例(schema 存在、8 表齐、pg_trgm 扩展与 trgm 索引、2 条裁剪 FK 确不存在且裸列在、保留 FK 抽查、结构抽查、触发器 2 个、topics 无种子) |
| patbond-auth | 31 | 31 | — |
| patbond-pet | 89 | 89 | — |
| patbond-community | — | 7 | 上下文冒烟 1 + /health 探活 1 + 鉴权集成 5(无 token/畸形/错签/过期 → 401+40101、有效 token 放行)+ 迁移链随上下文启动隐式验证 |
| 合计 | 191 | 206 | JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test 一次通过,BUILD SUCCESS |
V1→V2→V3→V4→V5 全量迁移经 Testcontainers 在全新 postgres:18 容器上自动验证通过(user 与 community 两模块的每个 @SpringBootTest 上下文启动即执行全链迁移)。
6. 遗留与下一波衔接
- 2 条裁剪 FK 补回:
generation_job_id随 M4 creation schema 迁移、region_id随 M5 地区体系迁移(V5 文件头注释已标明)。 - P9 共享设施下沉:JWT 校验件已是第三份复制,待拍板后统一下沉 common。
- 作者公开资料 /internal 批量接口(D3-9 方案 B):随 Feed/评论纵切(T3-05 等)在 patbond-user 侧落地。
- media 上传流程(T3-03):归 patbond-user,本模块只做 asset 只读校验,随帖子纵切接入。
- CI:多模块 reactor 自动含 patbond-community,
.gitea/workflows/ci.yml零改动。 - backend-modules.md:已同步加 patbond-community 行与五容器口径(doc 仓工作区修改,随波末收口提交)。