- 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>
12 KiB
M3 第二波评论与互动施工报告(T3-06/T3-07/T3-08:单层评论 + 点赞/收藏/关注)
作者:Senior Developer(后端) 日期:2026-09-09 工单:T3-06(点赞/收藏幂等写入与计数)+ T3-07(单层评论)+ T3-08 关注最小接口(随本波合并交付,第二波收尾单) 代码基线:patbond-api
99a3c1f(282 测试全绿)→ 交付7f1dd33(310 测试全绿) 结论先行:评论/点赞/收藏/关注全域十一端点落地 patbond-community;三新码定型采纳(40404/40406/42204,42205 属 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 必带(1replyToUserId 可选 @ 回复 |
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/42204;followerCount 为目标粉丝数实时 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_FOUND(community 侧) | 采纳(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-Key落client_request_id,规范化 request_hash(comment.v1\n postId\n replyToUserId\n content(trimmed))落库比对;同键同 hash 返回首条(201,库中恰一行,不重复计数);同键异 hash 409/40905;键按作者隔离(UNIQUE(author_user_id, client_request_id)天然全局跨帖——同键换帖 = hash 必异 = 40905,符合直觉);重试撞已删首评 404/40404(T3-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 恰 1(M3 验收标准二) |
| 同用户并发 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 权威 false(no-op),42204 只在 PUT | DELETE 幂等语义优先:行不可能存在,权威终态即事实 |
| 4 | 草案错误表 42205 列为新码 | 42205 属 T3-03 已启用(media 域),本单零动作 | 冻结时把 42205 从「新增」挪到「既有」口径 |
| 5 | 评论删除 404 例名 commentNotFound、码位 40404 |
采纳;评论幂等重试撞已删首评亦归 40404 | 草案未覆盖该边界;冻结时在 IdempotencyKeyRequiredHeader 描述补一句(与帖子域 40403 平行) |
| 6 | Comment schema 无 updatedAt(M3 无评论编辑) |
确认不带;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 页整走 + 删除项剔除)、计数对账专项。LikeBookmarkIntegrationTest(9):like/bookmark 全生命周期幂等四连(施加/重复施加/取消/重复取消权威终态)、多用户累计与 likedByMe/bookmarkedByMe 视角、8 种不可见组合逐字节一致 40403、真并发双专项(4 线程 PUT 恰计 1;PUT+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'语义仍后置。