# 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) ```text 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 读写 `community` schema。 - **compose 第五容器**:照 pet 服务块模式(build + .sample 挂载 + 环境变量注入 + 公钥只读挂载);`depends_on` postgres 健康 + 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 仓工作区修改,随波末收口提交)。