Files
patbond-doc/docs/development/iterations/iteration-3/02-backend-technical-assessment.md
T
lixi d2867826d3
CI / docs-build (push) Successful in 55s
docs: M3 开工分析 8 份报告入档 + ADR-016~021 拍板决策
- iteration-3 报告 01-08(PM 拆解/后端/Flutter/RC 首个 CERTIFIED/UI/埋点/证据基线/Git)
- ADR-016 自托管 MinIO 起步预留迁云(用户确认现无云存储)、ADR-017 patbond-community
  :8084 + media 归 user + 作者信息跨 schema 只读、ADR-018 范围裁剪(话题剪出/单层评论)、
  ADR-019 幂等按域(PUT/DELETE + request_hash)、ADR-020 聚合 feed_viewed/字典 v3/
  北极星不变/队列三项升第一波、ADR-021 Git 修订(main 更正/PR 情形触发/首发布重建 main/
  防泄漏 grep 先行)
- mkdocs 挂「第三迭代」导航,build --strict 通过

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 16:02:53 +08:00

26 KiB
Raw Blame History

Patbond 第三迭代后端技术评估(Dev)

  • 日期:2026-09-08
  • 评估范围:patbond-api 承接 M3「社区」的改动面、模块划分、Flyway V5+ 规划、Feed/互动机制草案、对象存储选型专题、遗留项耦合
  • 代码基线:patbond-api dev@64c9b72(工作区干净)
  • 结论先行:当前基线 191 个测试全绿(1 分 05 秒);建议新建 patbond-community 模块(:8084)承载社区域、media 上传流程放 patbond-user;对象存储推荐腾讯云 COS + S3 兼容 API + 预签名直传(本地/测试用 MinIO 容器跑同一套代码);Flyway V5 社区基线须剪 1 条跨 schema FKposts → creation.generation_jobsM4 补回)并补 pg_trgm 扩展;共 9 项待拍板。

1. 现状盘点(实际读码结论)

1.1 模块与可复用惯例

Maven 四模块:patbond-common(错误码/响应信封/内部 DTO)、patbond-auth8081,无库)、patbond-user8082唯一 Flyway 迁移链持有者V1~V4)、patbond-pet8083,与 user 共库,ADR-009 定型的「新模块 + 共库 + 单迁移链」形态)。M2 沉淀的设施对 M3 全部直接可套用:

  • 鉴权pet 模块的 BearerAuthFilter/JwtVerifier/RsaPublicKeyLoaderpatbond-pet/src/main/java/com/patbond/patbond/pet/security/)是从 user 复制的第二份,RS256 本地验签、userId 进 request attribute。community 若再复制就是第三份——见待拍板 P9。
  • 游标分页CursorPage<T>{items, nextCursor, hasMore} 信封,契约 §3.5 定为全 API 分页正典)+ EventCursor/WeightCursorbase64url("epochMicros:id") 不透明游标,keyset 谓词 (sortKey, id) < (cursor),同 key 平局用 id 决胜,保证不丢不重)。社区各列表照此模式各配一个游标类型即可。
  • 幂等IdempotencyKeys.deriveId()(键派生主键 + ON CONFLICT (id) DO NOTHING,免键表免 TTL)。注意:这是 M2 因 V3 表内没有幂等列而设计的方案;目标模型的 community.posts/comments 表自带 idempotency_key + request_hash 列与唯一约束,两种机制取一,见 P5。
  • 乐观锁version 列 + 40902 VERSION_CONFLICTupdated_at 由 V1 的 platform.set_updated_at() 触发器维护,version 自增留在 repository UPDATE 语句里显式可见。
  • 防枚举 404:不可见资源一律 404PET_NOT_FOUND 先例),可见但越权 403。
  • 契约锁v1.2.0 冻结(18 个 path),ContractConformanceTest + 字节级快照 patbond-pet/src/test/resources/contract/openapi-v1.2.0.yaml。M3 新增 path 走 M2 验证过的「草案 → 实现回填 → 拍板冻结 v1.3.0 → 快照锁」流程。
  • 测试Testcontainers postgres:18pet 模块生产 classpath 无 Flyway测试 classpath 挂 user 的 jar + Flyway 跑全链 V1~V4patbond-pet/src/test/java/com/patbond/patbond/pet/TestcontainersConfiguration.java 注释明确此机制)——community 模块测试照抄即可拿到 V5+。
  • CIGitea Actions 单 job ./mvnw -B clean test,新模块进 reactor 自动纳入门禁,CI 零改动。

1.2 media 域现状

  • media.assets 自 V1 就有且设计完备——storage_typeobject/external)、bucket + object_key(部分唯一索引 uq_media_object)、mime_type/byte_size/sha256/width_px/height_px/duration_ms、状态机 uploading → ready/failed/deletedCHECK 强制 ready 必有 ready_at)、ix_media_uploading_created 部分索引(明显是给「清理超时未完成上传」预留的)。表结构零改动即可承载 M3 上传流程
  • 代码:仍是零(无 controller/service/repository,与 iteration-2/02 §1.3 评估时一致)。
  • 消费方pet_health 三处可空 FK 已预留(pets.avatar_asset_idpet_vaccinations.certificate_asset_id,均 M2 未写入);health_event_media 表被 V3 明确剪出(注释:纯增量表,随 media 工作以后续迁移补建);社区侧 post_media.asset_idNOT NULL RESTRICT——社区图片对 media 是硬依赖,绕不过去。
  • 供应商:未定(第一迭代 D4 遗留,ADR-010 引为剪出 M2 的理由)。这是 M3 头号拍板项,专题见 §5。

1.3 目标模型社区表通读(patbond_postgresql.sql 718~875 行)

8 张表 + 2 个 updated_at 触发器(posts/comments)。要点:

关键设计 M3 承接注记
posts 状态机 draft/published/hidden/archived + deleted_at 软删;visibility public/followers/private;冗余计数 like_count/comment_count/bookmark_countCHECK ≥0);idempotency_key + request_hash(uq 约束按 author 域隔离);version 乐观锁;published_at CHECK 与 status 联动 Feed 部分索引 ix_posts_feed (published_at DESC, id DESC) WHERE status='published' AND visibility='public' 与游标排序键严格对齐
post_media PK (post_id, position)asset_id NOT NULL → media.assets RESTRICT,封面部分唯一索引 硬依赖 media 流程
comments 刻意平铺一层reply_to_user_id 支持 @ 回复,无 parent_comment_id 无递归);status visible/hidden/deleted 与 deleted_at CHECK 联动;client_request_id + request_hash 幂等列 与 M2「结构定调照目标模型」纪律一致,不要自行加嵌套
post_likes / post_bookmarks PK (post_id, user_id),无附加列 天然主键幂等,ON CONFLICT DO NOTHING 即可,无需 Idempotency-Key
user_follows PK (follower, followee) + 禁自关注 CHECK 同上
topics name citext UNIQUEcitext 扩展 V1 已建),status active/hidden 话题来源见 P8
post_topics 纯关联表

1.4 跨 schema FK 排查(照 M2 剪 marketplace FK 的经验逐条过)

FK 目标 schema 是否已迁移 处置
posts.author_user_id、comments/likes/bookmarks/follows → identity.users V1 有 保留
posts.region_id → platform.regions V1 有 保留
posts.pet_id → pet_health.pets V3 有 保留
post_media.asset_id → media.assets V1 有 保留
posts.generation_job_id → creation.generation_jobs creation schema 属 M4,未迁移 必剪:V5 保留裸可空 uuid 列,FK 由 M4 建 creation schema 的迁移补回(与 M2 剪 4 条 marketplace FK、目标模型 1156~1166 行 M5 补回同一先例)

另一个非 FK 的迁移前置:ix_posts_content_trgmgin, gin_trgm_ops)需要 pg_trgm 扩展,V1 只建了 pgcrypto 与 citext——V5 需 CREATE EXTENSION IF NOT EXISTS pg_trgmpostgres:18 官方镜像含 contribTestcontainers 与 compose 均无障碍)。M3 范围没有搜索需求,该索引理论上可裁;但扩展 + 索引成本极低、剪了就偏离目标模型,建议照建(P7)。

2. 改动面评估

改动面 内容 量级
新模块 patbond-community:8084):feed/posts/comments/likes/bookmarks/follows/topics 约 7 组资源 大(M3 主体)
media 上传流程(预签名签发 + complete 确认 + 清理任务),归 patbond-userP2
Flyway V5 社区基线(剪 1 FK + pg_trgm);V6 health_event_media 补建(若 P6 拍板)
common ErrorCode 追加约 5 值;若 P9 拍板则下沉 security/support 共享件 小~中
依赖 community 模块无新依赖;media 需引对象存储 SDK(推荐 AWS SDK v2 S3 客户端,见 §5
契约 v1.2.0 → v1.3.0,新增约 15 个 path(草案见 §6,本评估不动 openapi.yaml
compose 新增 community 服务(照 pet 服务块抄);若 P3 选 MinIO 另加一个有状态服务
既有代码 零改动(auth/user/pet 业务代码不动)

3. 模块划分建议

3.1 社区域归属【待拍板 P1】

  • 方案 A(推荐):新建 patbond-community Maven 模块(:8084。ADR-009 已为「按域新建模块 + 共库 + user 单迁移链」拍过板并在 M2 全程验证(pet 模块 89 个测试、compose 联调、CI 均无摩擦);社区与宠物档案是平行业务域,没有理由破坏既定形态。成本在 M2 已一次性摊销:Testcontainers 跑全链、compose 服务块、CI 自动纳入都是抄作业。
  • 方案 B:并入 patbond-pet 或 patbond-user 内包。省一个服务进程,但与 ADR-009 的裁定方向相逆,且社区是后续体量最大的域,混入他模块日后必拆。
  • 推荐 A。唯一实质增量是第 4 个 JVM 进程的内存占用,单机 compose 下可接受(各服务未设堆上限的话部署时统一加 -Xmx 即可,属部署细节)。

3.2 media 归属【待拍板 P2】

  • 方案 A(推荐):上传流程放 patbond-user。理由:media.assets 在 V1 就与 identity 同批建(owner_user_id 指向 users,天然身份域相邻);user 是迁移链持有者与基础域服务,media 是横切基础能力(社区图片、宠物头像、疫苗证书、M4 生成输入输出全要用),放任何单一业务模块都会造成反向依赖;user 已有最全的安全设施与集成测试基建。community/pet 对 media.assets 做只读 SQL 校验(asset 存在、owner 匹配、status='ready'),沿用「共库阶段跨 schema 只读」的既有纪律(pet 读 identity.users 先例)。
  • 方案 B:独立 patbond-media 模块。边界最干净,但双人团队第 5 个服务的运维/联调成本,对一个「两个接口 + 一个清理任务」的域不成比例;将来真需要(如加图片处理流水线)再从 user 拆出,代价是搬包级别。
  • 方案 C:放 community。M3 内最省事,但 M4generation_jobs 的 input/output asset)和宠物头像会反向依赖社区模块,方向错误。
  • 推荐 A。

3.3 共享设施下沉【待拍板 P9】

BearerAuthFilter/JwtVerifier/RsaPublicKeyLoader/UuidV7/CursorPage/游标编解码在 user 和 pet 已是两份复制,community + media 落地后将是三到四份。建议 M3 第一波把这组下沉到 patbond-common(或 common 内独立包),community 从第一行代码就用共享件;user/pet 的存量复制件可顺带切换(纯搬移,测试全绿即证等价),也可不动留待日后。反方观点:common 目前刻意保持零 Spring Web 依赖,下沉 filter 会引入 servlet 依赖——可用「common 只收 JwtVerifier/UuidV7/游标编解码等纯 Java 件,filter 仍每模块一份薄壳」的折中。推荐折中方案。

4. Flyway V5+ 规划

迁移链继续由 patbond-user 持有(community 生产 classpath 无 Flyway,测试经 test classpath 复用 user 链,照 pet 先例)。

版本 内容 调整点(相对目标模型原样)
V5 社区基线 community schema + 8 表 + 索引 + posts/comments 两个 updated_at 触发器(复用 platform.set_updated_at() ① 剪 fk posts.generation_job_id → creation.generation_jobs(裸可空 uuid,M4 补回,迁移文件头注释写明——照 V3 剪 marketplace FK 的文档格式);② 文件头先 CREATE EXTENSION IF NOT EXISTS pg_trgm;③ 主键 DEFAULT gen_random_uuid() 去掉,应用侧 UUIDv7users/pets 同规)
V6 health_event_media 补建(若 P6 拍板进) 照目标模型 521~532 行原样建表 无需调整(asset_id → media.assets 已可建 FK);V3 注释承诺的「随 media 工作补建」在此兑现
V7 预留 topics 运营种子(若 P8 拍板预置制) 生产字典数据进正式链,不进 db/dev(V4 先例)

风险面:V5 无破坏性变更(纯增量 schema),对既有 V1~V4 数据零影响;PetHealthMigrationIntegrationTest 模式可复制一个 CommunityMigrationIntegrationTest 验证约束与索引。

5. 对象存储选型专题【待拍板 P3/P4,M3 头号拍板项】

5.1 环境事实

  • 部署形态:单机 docker compose,应用容器无状态、文件明确走对象存储(ADR-007 原文),敏感值环境变量注入。
  • 服务器在腾讯云(docs/development/ci-runner-setup.md 与 api 仓 CI 注释实证:runner 位于腾讯云、用内网镜像源)。
  • 双人团队,运维预算有限(ADR-007 立论基础)。
  • 社区场景的流量特征:图片下行读远大于上行写(Feed 刷图),且轻量云主机的公网出口带宽通常是个位数 Mbps——这是选型的决定性约束。

5.2 候选对比

维度 A:自托管 MinIOcompose 内) B:腾讯云 COS(推荐) C:本地卷过渡
现金成本 0 MVP 体量下每月几元量级(存储 + 流量按量),有免费额度 0
图片下行带宽 全部吃服务器公网出口——Feed 刷图直接顶死个位数 Mbps,是硬伤 走 COS 公网/CDN,服务器带宽零占用 同 A,且更差(经应用容器)
运维 多一个有状态服务:volume、备份、版本升级都要自己做 零运维,备份/多副本由云侧兜底 违反 ADR-007「状态不落容器本地」纪律
预签名直传 支持,但「直传」仍落到同一台服务器,带宽上毫无收益 支持,客户端直连 COS,真正卸载 不适用
供应商锁定 无(S3 API :COS 提供 S3 兼容端点,代码层用 S3 协议即无锁定
与测试体系 与 Testcontainers 同体系 测试不打真云——见 5.3 的 MinIO 替身方案
  • 推荐 B:腾讯云 COS。决定性理由是带宽:社区 Feed 的图片读流量放在自己服务器上,MVP 刚有点用户就会先死在出口带宽而不是 CPU;COS 同厂商内网上行、公网/CDN 下行,把最贵的资源(带宽)externalize,月成本在 MVP 体量下可忽略。ADR-007「重运维在需要时外包给云托管」的成本逻辑对对象存储同样成立,且比数据库更该先外包(无状态、无迁移锁定)。
  • 方案 A 不是被否定而是被降级:MinIO 转为本地开发与集成测试的替身(见下),生产不跑。
  • 方案 C 违反已拍板的 ADR-007,列出仅为完整性,不推荐。

5.3 落地方式:S3 协议统一,环境三态零分叉

代码统一用 AWS SDK for Java v2 的 S3 客户端endpoint/credentials/bucket 全部 PATBOND_S3_* 环境变量注入,符合既有配置纪律):

  • 生产:指向 COS 的 S3 兼容端点;
  • 本地 compose:可选加 MinIO 服务块(profile 隔离),开发者无云账号也能全流程联调;
  • 集成测试:Testcontainers 起 MinIO 容器(与 postgres:18 同模式),上传流程测试全自动、不打真云、不进 CI 密钥。

如此供应商锁定压到最低:将来换任何 S3 兼容存储只改环境变量。

5.4 上传流程草案【待拍板 P4:预签名直传 vs 服务端中转】

推荐预签名直传,流程:

  1. POST /api/v1/media/uploads:客户端声明 {kind, purpose, mimeType, byteSize, sha256?} → 服务端校验白名单(mime/大小上限)→ 写 media.assets 行(应用侧 UUIDv7status='uploading'bucket + object_key 服务端生成,key 形如 {purpose}/{yyyy/MM}/{assetId} 不含用户输入)→ 返回 {assetId, uploadUrl(预签名 PUT,短 TTL 约 10 分钟), headers}
  2. 客户端向 uploadUrl 直传字节流(不经应用服务器)。
  3. POST /api/v1/media/uploads/{assetId}/complete:服务端对对象 HEAD 校验存在性与 byte_size(有 sha256 则一并核)→ status='ready', ready_at=now()。失败置 failed
  4. 业务引用时机:post_media/头像等只允许挂 status='ready' 且 owner 匹配的 asset,否则 422(新错误码,见 §6)。
  5. 清理:定时任务(照 SessionCleanupJob 模式)用 ix_media_uploading_created 扫超时(如 >24h)的 uploading 行,删对象 + 行置 failed——该索引 V1 就是为此预留的,全链路闭环。

服务端中转(multipart 上传给应用、应用转存)唯一优势是校验在字节流上同步做,但上传流量两次过应用容器、占用连接与堆,在 COS 方案下毫无必要;即便将来切 MinIO 同机部署它也只是不更差。推荐直传。

5.5 与 M2 剪出项的衔接

  • health_event_media 补建:media 流程落地后表即可建(V6,见 §4),健康事件附件接口是否随 M3 接线见 P6。
  • pets.avatar_asset_id / certificate_asset_id:列早已就位,接线只是 pet 模块 PATCH 校验 + 契约增字段,量级 S;范围见 P6。

6. API 资源设计草案(供 v1.3.0 契约草案参考,本评估不动 openapi.yaml

全部挂 Bearer 鉴权;列表全部 CursorPage 信封。

接口 说明
POST /api/v1/media/uploadsPOST /api/v1/media/uploads/{assetId}/complete §5.4 上传流程(user 模块)
GET /api/v1/feed 公共 Feed(published_at, id) 游标,谓词与 ix_posts_feed 部分索引对齐
GET /api/v1/feed?scope=following 关注流(若 P7 拍板进):同排序键,author 限定关注集合
POST /api/v1/posts 创建(Idempotency-Key 必带——开发计划 6.1 强制名单含帖子);status 可 draft 或 published
GET /api/v1/posts/{postId} 详情:published 对可见者开放;draft/hidden 仅作者可见,他人 404 防枚举;响应含 likedByMe/bookmarkedByMe
PATCH /api/v1/posts/{postId} 编辑/发布草稿(status 迁移)/隐藏,请求体带 version,冲突 40902
DELETE /api/v1/posts/{postId} 软删(deleted_at),仅作者
GET/POST /api/v1/posts/{postId}/commentsDELETE /api/v1/comments/{commentId} 评论平铺一层 + replyToUserIdPOST 带 Idempotency-Key(落 client_request_id 列);游标 (created_at, id)
PUT/DELETE /api/v1/posts/{postId}/like 点赞/取消:天然幂等(§7.1),响应回 {liked, likeCount} 权威态
PUT/DELETE /api/v1/posts/{postId}/bookmark 收藏/取消:同上
GET /api/v1/me/bookmarks 收藏列表:游标 (bookmarks.created_at, post_id),与 ix_post_bookmarks_user_created 对齐
PUT/DELETE /api/v1/users/{userId}/follow、`GET /api/v1/users/{userId}/followers following`
GET /api/v1/users/{userId}/posts 作者主页:游标 (created_at, id),与 ix_posts_author_created 对齐;本人可带 status 过滤(含 draft
GET /api/v1/topicsGET /api/v1/topics/{topicId}/posts 话题与话题下帖子

错误码扩展草案(延续现有分段,不重编号):

code HTTP 语义
40301 POST_ACCESS_DENIED 403 帖子/评论可见但无权操作(如改他人帖)
40403 POST_NOT_FOUND 404 帖子不存在、已删或不可见(防枚举合并)
40404 COMMENT_NOT_FOUND 404 评论不存在或已删
40405 MEDIA_NOT_FOUND 404 asset 不存在或非本人所有(防枚举)
40905 IDEMPOTENCY_PAYLOAD_MISMATCH 409 同 Idempotency-Key 不同 payloadrequest_hash 不符)
42203 MEDIA_NOT_READY 422 引用了非 ready 状态的 asset

7. 互动与 Feed 机制草案

7.1 点赞/收藏幂等(验收标准「重复点赞不重复计数」的实现本体)

无需 Idempotency-Key——post_likes/post_bookmarks 主键 (post_id, user_id) 就是幂等键:

-- 同一事务内:
INSERT INTO community.post_likes (post_id, user_id) VALUES (?, ?) ON CONFLICT DO NOTHING;
-- 仅当上句 rowsAffected = 1 才执行:
UPDATE community.posts SET like_count = like_count + 1 WHERE id = ?;

取消侧对称(DELETE 影响行数为 1 才 -1ck_posts_counts CHECK ≥0 兜底)。重复 PUT/DELETE 返回 200 同一权威态而非 409——对客户端乐观更新最友好。计数列即目标模型的冗余列,读侧零 join。

7.2 帖子/评论幂等【待拍板 P5】

  • 方案 A(推荐):用目标模型表内幂等列INSERT ... ON CONFLICT (author_user_id, idempotency_key) DO NOTHING,冲突时按 key 读回已建资源返回;request_hash(请求体规范化 SHA-256)不符则 40905——比 M2 的键派生主键多一层「key 复用但 payload 变了」的误用检测。列是目标模型自带的,不用白不用。
  • 方案 B:沿用 M2 IdempotencyKeys 键派生主键。惯例统一,但 posts 的幂等列与唯一约束就闲置了,且丢掉 payload 校验。
  • 推荐 A;两方案客户端语义相同(重试返回同一资源 id),不影响契约。

7.3 Feed 游标分页(多排序键)

「多排序键」= 每个列表各有固定排序键,游标携带本列表的排序键值 + id 决胜,端点间互不通用(游标不透明,客户端只回传):

列表 排序键 支撑索引(目标模型已备)
公共 Feed / 关注流 (published_at DESC, id DESC) ix_posts_feed(部分索引,谓词同查询过滤)
作者主页 (created_at DESC, id DESC) ix_posts_author_created
评论 (created_at DESC, id DESC) ix_comments_post_created
我的收藏 (bookmarks.created_at DESC, post_id DESC) ix_post_bookmarks_user_created
粉丝/关注列表 (follows.created_at DESC, user_id) ix_user_follows_followee

编码沿用 pet 惯例 base64url("epochMicros:id");实现上建议把 EventCursor 的模式提炼成一个通用编解码件(P9 下沉候选)。禁 OFFSET 由开发计划 6.1 明文规定。

7.4 删除/隐藏内容出 Feed(验收标准「不可继续出现在公共 Feed」)

  • 读侧过滤即机制本体:所有公共查询恒带 status='published' AND deleted_at IS NULL AND visibility='public'——与 ix_posts_feed 部分索引谓词一致,过滤免费。删除/隐藏是行状态翻转,无需任何 Feed 重建(无物化 FeedMVP 拉模型)。
  • keyset 分页天然免疫中途删除:不像 OFFSET 会页移丢行,游标翻页时被删行只是不再命中谓词,不丢不重(验收标准「分页不丢失、不重复」由排序键唯一性 + keyset 谓词共同保证)。
  • 已删/隐藏帖详情对非作者 404(40403,防枚举);作者访问自己的 hidden/draft 正常返回(编辑场景)。
  • 评论区随帖子状态整体不可见;单条评论删除置 status='deleted',列表过滤 status='visible'

7.5 客户端乐观更新回滚需要的后端保证

  1. 写响应携带权威终态like/bookmark 响应必回 {liked, likeCount}(收藏同构),客户端以响应对账而非自行猜测计数——回滚 = 用响应值覆盖本地乐观值。
  2. 重复请求收敛:重复 PUT like 返回 200 同态(非 409);带同 Idempotency-Key 重发帖返回同一 post id——客户端重试永不产生第二份资源,乐观插入的临时项可按 id 对账替换。
  3. 失败语义可辨:40403(帖子已没了→客户端剔除该卡片)、40902(版本冲突→拉最新重演)、42203(图未 ready→回滚发布态提示重传)、40905(幂等 key 误用→视为 bug 上报)各自可编程区分,{code,message,data} 信封已保证。
  4. 无部分成功:计数与关系行同事务(§7.1),客户端看到的 likeCount 与 liked 永远一致,不需要处理「计了数但没点上赞」的中间态。

8. M2 遗留与 M3 的耦合评估

  • auth 域契约测试补齐(M2 遗留 §4-2):与 M3 社区代码无耦合,但 M3 要把契约升 v1.3.0 并重打快照,正是补齐 auth path 覆盖的顺手时机(ContractConformanceTest 机制照搬,量级 S)。建议进 M3 第一波,不做也不阻塞任何社区工单。
  • access token 黑名单(承自 M1):与 M3 弱耦合,维持不进。社区写操作的授权是「作者本人」逐请求校验(同 pet 逐请求查 pet_owners 的结构),不依赖 token 吊销;15 分钟 TTL(ADR-003)对社区场景敏感度同样够用。M3 未引入新的触发点(封号踢出属治理域,不在 M3 范围)。结论与 iteration-2/02 §6 一致,无需翻案。
  • /internal 改 mTLSM3 不新增 internal 接口(media 校验走共库只读,不走服务间调用),无耦合。

9. 构建与测试基线(2026-09-08 实测)

命令:JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test(系统默认 JDK 不可用于构建,须显式指定,与前两轮一致)。

模块 测试数 结果
patbond-common 3 通过
patbond-user 68 通过(含 Testcontainers 全链迁移测试)
patbond-auth 31 通过
patbond-pet 89 通过(含契约一致性 11 项)
合计 191 全绿,BUILD SUCCESS,总耗时 1 分 05 秒

与 M2 收官基线(dev@64c9b72,191 测试)一致,无回归。此为 M3 开工基线:M3 结束时该命令一次通过且测试数只增不减。

10. 待拍板清单

# 事项 选项 推荐
P1 社区域归属 新建 patbond-community 模块 vs 并入既有模块 新模块 :8084ADR-009 形态已验证,§3.1
P2 media 归属 patbond-user 内 vs 独立 patbond-media vs community 内 user 内(横切基础能力 + V1 schema 同源,§3.2
P3 对象存储供应商(头号拍板,宜出 ADR 腾讯云 COS vs 自托管 MinIO vs 本地卷 COS(带宽决定论,§5.2);MinIO 降级为本地/测试替身
P4 上传流程 预签名直传 vs 服务端中转 直传(§5.4
P5 帖子/评论幂等机制 目标模型表内幂等列 + request_hash vs M2 键派生主键 表内幂等列(§7.2
P6 media 接线范围 仅社区图片 vs 社区 + 宠物头像 vs 全量(含证书/事件附件 + V6 建 health_event_media 社区图片 + 宠物头像进 M3(头像量级 S、补 M2 占位方案);证书/事件附件接口推迟,V6 表是否随建看排期余量
P7 关注流与 visibility following feed + followers 可见性全做 vs M3 只做 public/private、关注关系先落库 关注关系 + following feed 进(M3 范围明文含关注);visibility='followers' 语义推迟(Feed 权限矩阵复杂度的主要来源,砍它不砍表)
P8 话题来源 发帖时自动 get-or-create vs 运营预置种子(V7+ 只读 自动创建(citext 唯一约束天然去重,MVP 免运营流程);status='hidden' 留给治理
P9 共享设施下沉 纯 Java 件(JwtVerifier/UuidV7/游标编解码)下沉 common vs 继续每模块复制 下沉纯 Java 件,filter 留薄壳(§3.3