Files
lixi a611acb358
CI / docs-build (push) Successful in 32s
docs: M3 第二波收口——报告 15~20 入档挂导航
- 15~17 社区后端纵切三单(帖子/Feed 作者链路/评论互动关注,226→310)
- 18 契约冻结 v1.3.0(31 路径/43 操作,26 项修正照单全收)
- 19 快照同步与全仓契约矩阵(173 格零漂移,修 allOf 校验盲区,→325)
- 20 收口总表:定型语义汇总(第三波接入依据)与质量事件记录

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-09 11:38:42 +08:00

12 KiB
Raw Permalink Blame History

M3 第二波评论与互动施工报告(T3-06/T3-07/T3-08:单层评论 + 点赞/收藏/关注)

作者:Senior Developer(后端) 日期:2026-09-09 工单:T3-06(点赞/收藏幂等写入与计数)+ T3-07(单层评论)+ T3-08 关注最小接口(随本波合并交付,第二波收尾单) 代码基线:patbond-api 99a3c1f282 测试全绿)→ 交付 7f1dd33310 测试全绿) 结论先行:评论/点赞/收藏/关注全域十一端点落地 patbond-community;三新码定型采纳(40404/40406/4220442205 属 T3-03 已启用不涉本单);幂等并发验收硬项实证——并发 N 次 PUT like 恰计 1、PUT+DELETE 竞态终态列值与关系表恒一致(真并发测试);三计数列全部写侧同事务维护、对账专项通过,T3-05 读侧零改动即时生效;互动门禁定型「只认帖子公开面」;与草案偏差 6 项逐条记录;全套 310 测试全绿(+28),check-secrets --all 通过。


1. 提交清单

按互动 / 评论两个逻辑提交,已推送 origin/dev

提交 内容
19e8cba 点赞/收藏/关注幂等互动 + 同事务计数 + 我的收藏列表 + follow-stats + 14 例测试
7f1dd33 单层评论幂等创建/游标列表/作者软删 + comment_count 维护 + 14 例测试

工单号对照说明:PM 分解(iteration-3/01)中 T3-06 = 点赞/收藏、T3-07 = 评论、T3-08 = 关注(条件单);本波指派文案中的编号与此相反,本报告与提交信息一律按 PM 分解的正典编号。

2. 端点与错误语义定型表(T3-10 冻结输入)

2.1 端点清单(全部在 patbond-community :8084,强制 Bearer 鉴权)

端点 成功 语义要点
GET /api/v1/posts/{postId}/comments 200 + {items,nextCursor,hasMore} 仅 visible(created_at DESC, id DESC)ix_comments_post_created,keyset 游标;作者与 @ 目标均为 AuthorSummary(批量 + 降级 id-only 同构复用)
POST /api/v1/posts/{postId}/comments 201 + Comment Idempotency-Key 必带(1128trim 后计);content trim 后 12000replyToUserId 可选 @ 回复
DELETE /api/v1/comments/{commentId} 200 + VoidEnvelope 顶层短路径;仅评论作者可删(D3-7:帖主删他人评论首版不做);软删 status→deleted + deleted_at 成对(ck_comments_deleted
PUT /api/v1/posts/{postId}/like 200 + {liked:true, likeCount} 复合主键幂等;重复 PUT 同终态不重复计数
DELETE /api/v1/posts/{postId}/like 200 + {liked:false, likeCount} 取消不存在的点赞不报错不减计数
PUT/DELETE /api/v1/posts/{postId}/bookmark 200 + {bookmarked, bookmarkCount} 与点赞同构
GET /api/v1/me/bookmarks 200 + {items,nextCursor,hasMore} 项 = FeedCard(bookmarks.created_at DESC, post_id DESC)ix_post_bookmarks_user_created;失效帖静默剔除(§4
PUT /api/v1/users/{userId}/follow 200 + {following:true, followerCount} 主键幂等;自关注 422/42204followerCount 为目标粉丝数实时 COUNT
DELETE /api/v1/users/{userId}/follow 200 + {following:false, followerCount} 幂等;自取关也是 200 no-op(行不可能存在,权威 false 即事实;42204 只留给 PUT
GET /api/v1/users/{userId}/follow-stats 200 + {followerCount, followingCount, followedByMe} 实时 COUNT 双向索引;查自己 followedByMe 恒 false

2.2 三新码取舍定型(契约冻结评审输入)

取舍 理由
40404 COMMENT_NOT_FOUND 采纳 评论不可见合并位(不存在/已删/所属帖不可见),与 40401/40403/40405 同一防枚举族
40406 USER_NOT_FOUNDcommunity 侧) 采纳enum 名 TARGET_USER_NOT_FOUND,文案同 40400「用户不存在」) 不复用 40400:该码属 identity 域语义,且四服务的 NoResourceFound 兜底已把 40400 用作「路由不存在」——复用会让「关注目标不存在」与「路径打错」不可区分。触发面:follow PUT/DELETE/stats 的目标、评论 replyToUserId(不存在与注销合并,缺席不泄露成因)
42204 FOLLOW_RULE_VIOLATION 采纳 自关注是业务规则违反非参数格式错(ck_user_follows_self 库层兜底),与 42201/42202 规则违反族同构
42205 MEDIA_UPLOAD_STATE_INVALID 不涉本单 T3-03 已入 ErrorCode 并启用(media 域 complete 语义),列入草案三新码系口径滞后,无需本单动作

2.3 互动门禁定型(本单新增语义,六类路径测试锚定)

互动面 = 帖子公开面:评论(读写删)与点赞/收藏(PUT/DELETE)只对 status='published' AND deleted_at IS NULL 的帖子开放。作者本人的草稿在互动路径上同样 404/40403——草稿不参与社交域(发布前无人可见、计数无意义),且免除「作者特判」后所有不可见情形保持逐字节一致(防枚举断言实测集合大小 = 1)。这较 T3-04 读路径(draft 对作者可见)是收窄而非矛盾:可见性回答「能不能看」,互动门禁回答「能不能社交」。

评论删除的 403/404 边界沿 T3-04 定型原则:403/40301 只发给「可见但无权」(他人对 visible 评论,含帖主),一切不可见合并 404/40404。

2.4 幂等语义定型(ADR-019 两形态并用)

  • 二元互动(PUT/DELETE:复合主键即幂等键,无键管理。ON CONFLICT DO NOTHING / 条件 DELETE 返回实际变更行数,响应恒回权威终态。
  • 评论创建(表内幂等列)Idempotency-Keyclient_request_id,规范化 request_hashcomment.v1\n postId\n replyToUserId\n content(trimmed))落库比对;同键同 hash 返回首条(201,库中恰一行,不重复计数);同键异 hash 409/40905;键按作者隔离(UNIQUE(author_user_id, client_request_id) 天然全局跨帖——同键换帖 = hash 必异 = 40905,符合直觉);重试撞已删首评 404/40404T3-04 §2.4 先例)。V5 comments 表幂等列(client_request_id/request_hash + ck_comments_idempotency)原生就位,无表变更。

3. 计数维护与对账说明(工单验收硬项)

机制:三计数列(like_count/comment_count/bookmark_count)只随关系写的实际变更行数同一事务内增减——insertXxx 冲突返回 0 则不增,deleteXxx 删 0 行则不减;评论删除以 FOR UPDATE 锁定 visible→deleted 迁移,保证 -1 恰一次。ck_posts_counts 非负为库层兜底,从未触发。

并发实证(真多线程集成测试,非串行模拟):

场景 结果
同用户 4 线程并发 PUT like 全部 200;关系表恰 1 行、like_count 恰 1M3 验收标准二)
同用户并发 PUT + DELETE like 两边 200;无论竞态先后,终态恒满足 like_count = COUNT(post_likes)0 行 0 计或 1 行 1 计)
3 线程并发 PUT follow 恰 1 行,follow-stats 计 1

对账专项:混合施加/取消后 like/bookmark 列值 = 关系表 COUNT(逐一断言);评论建 3 删 1 后 comment_count = visible 行数 = 列表长度 = 2;删帖后互动路径一律 404、计数列随帖冻结(帖不可见,列值无消费方;T3-05 读侧只对 published 出卡)。运维级漂移修复口径沿 16 号报告 §4(关系表重算列),不做常驻任务。

两处记录在案的既有行为:① 计数 UPDATE 会触发 trg_posts_updated_at——互动会推动帖子 updated_at(该列语义是「行最后更新」,内容编辑标记是 version,契约消费方勿以 updated_at 判「编辑过」);② follow 无冗余计数列,followerCount/followingCount 恒实时 COUNT(双向索引支撑,草案即此设计)。

4. 我的收藏列表定型

  • 项形态 = FeedCard(草案预设确认),装配复用 FeedService 同一批量路径(媒体/作者/签名 URL 零新代码)。
  • 谓词与公共 Feed 恒等(status='published' AND visibility='public' AND deleted_at IS NULL):被收藏帖软删/hidden/archived 后静默剔除(草案取向定型),剔除在页查询 SQL 内完成——游标键在收藏关系行上(bookmarks.created_at DESC, post_id DESC),剔除不破坏翻页不丢不重。
  • 该谓词同时保证卡片 publishedAt 非空不变式对收藏列表继续成立。

5. 与契约草案(openapi-community-draft.yaml)偏差清单

# 草案 实现定型 理由 / 冻结动作
1 帖子不可见 404/40403(未提作者草稿) 作者本人草稿在全部互动/评论路径同样 404/40403 §2.3 互动面=公开面;冻结时在 comments/like/bookmark 各端点描述补「含作者本人草稿」
2 CreateCommentRequest.replyToUserId 未定校验语义 目标须为存活用户,否则 404/40406(不存在/注销合并) @ 落库有 FK,放任会 500;与 follow 目标同码同语义
3 follow DELETE 响应仅列 200/401/404(未提自取关) 自取关 200 权威 falseno-op42204 只在 PUT DELETE 幂等语义优先:行不可能存在,权威终态即事实
4 草案错误表 42205 列为新码 42205 属 T3-03 已启用(media 域),本单零动作 冻结时把 42205 从「新增」挪到「既有」口径
5 评论删除 404 例名 commentNotFound、码位 40404 采纳;评论幂等重试撞已删首评亦归 40404 草案未覆盖该边界;冻结时在 IdempotencyKeyRequiredHeader 描述补一句(与帖子域 40403 平行)
6 Comment schema 无 updatedAtM3 无评论编辑) 确认不带;replyToUser 为完整 AuthorSummary(含降级 id-only 形态,required 收敛沿 16 号报告偏差 #1 的 [userId] AuthorSummary 收敛口径全域统一,评论侧无新豁免

另三处为草案预设的确认(非偏差):评论列表 DESC 排序 + 正典信封逐字一致(指派文案中的 ASC 备选未采);like/bookmark PUT 重复施加 200 非 409;收藏列表复用 FeedListEnvelope。

6. 测试数变化:282 → 310(+28,0 回归)

模块 基线 交付 新增内容
patbond-common 3 3 ErrorCode 增 3 码(40404/40406/42204),无行为变化
patbond-user 96 96
patbond-auth 39 39
patbond-pet 89 89
patbond-community 55 83 见下
合计 282 310 JAVA_HOME=<你的 JDK17 路径> ./mvnw clean test 一次通过,BUILD SUCCESS<repo>/scripts/check-secrets.sh --all 通过

community 新增 28 例,按工单六类路径 + 三个专项:

  • CommentIntegrationTest(14):全形态创建(trim/昵称/@ 回复摘要)、六类路径(content 空白/超长、幂等键缺失/空白/超长、@ 不存在与注销用户 40406、四种不可见帖逐字节一致 40403、删除的 403/404 四格边界)、幂等矩阵专项(同键重放不重计/异 payload 40905/跨作者同键/撞已删首评 40404)、分页不丢不重(7 评 3 页整走 + 删除项剔除)、计数对账专项。
  • LikeBookmarkIntegrationTest9):like/bookmark 全生命周期幂等四连(施加/重复施加/取消/重复取消权威终态)、多用户累计与 likedByMe/bookmarkedByMe 视角、8 种不可见组合逐字节一致 40403、真并发双专项4 线程 PUT 恰计 1PUT+DELETE 竞态终态一致)、混合操作对账、收藏列表分页 + 静默剔除(软删与 hidden 各一)+ 卡片形态断言、非法分页入参。
  • FollowIntegrationTest(5):follow 生命周期幂等四连、自关注 42204 / 自取关 no-op、不存在/注销目标三端点 40406 + 畸形 UUID 40000、follow-stats 双向计数与三视角 followedByMe、3 线程并发 follow 恰 1 行。

7. 遗留与交接

  • T3-10 冻结回填:§2 定型表(含三新码取舍)+ §5 偏差 6 项即评论/互动域冻结输入。至此第二波后端四单(T3-04/05/06/07)语义全部定型,帖子/Feed/评论/互动四域冻结条件齐备。
  • T3-12~14 Flutter 衔接:乐观更新对账目标即本单权威终态响应({liked,likeCount} 族);回滚基准取响应值而非本地推算。
  • P9 共享设施CommentCursor/BookmarkCursor 是游标件第 5/6 份复制,幂等键规范化亦复制一份——下沉 common 的拍板权重再升。
  • 关注列表端点(关注/粉丝明细)按 ADR-018 裁剪不在 M3,需要时按纯增量补入;visibility='followers' 语义仍后置。