Files
patbond-doc/docs/development/iterations/iteration-3/17-comments-interactions-report.md
T
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

117 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/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` 必带(1~128trim 后计);content trim 后 1~2000`replyToUserId` 可选 @ 回复 |
| `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-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/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-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'` 语义仍后置。