- 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>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
# M3 第二波帖子生命周期施工报告(T3-04:草稿/编辑/发布/删除)
|
||||
|
||||
> 作者:Senior Developer(后端)
|
||||
> 日期:2026-09-09
|
||||
> 工单:T3-04(帖子生命周期,第二波关键路径首单)
|
||||
> 代码基线:patbond-api `263cd88`(226 测试全绿)→ 交付 `101ac0f`(251 测试全绿)
|
||||
> 结论先行:**帖子域五端点(创建/详情/编辑与发布/软删/我的列表)全落地 patbond-community;权限与错误语义定型(T3-10 冻结输入之一):403/40301 只发给「可见但无权」,一切不可见合并 404/40403 防枚举,hidden/archived 对作者同样 404;创建型幂等按 ADR-019 落 `uq_posts_author_idempotency` + 规范化 request_hash 实证;asset 校验取「同库只读 media.assets」(ADR-017 同一先例);与契约草案偏差 6 项逐条记录;全套 251 测试全绿(+25)。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 提交清单
|
||||
|
||||
按工单一逻辑提交,已推送 `origin/dev`:
|
||||
|
||||
| 提交 | 内容 |
|
||||
| --- | --- |
|
||||
| `101ac0f` | T3-04:帖子生命周期五端点 + 幂等/乐观锁/可见性矩阵集成测试(含 §5 两处附带修正) |
|
||||
|
||||
## 2. 端点与错误/权限语义定型表(T3-10 契约冻结输入)
|
||||
|
||||
### 2.1 端点清单(全部在 patbond-community :8084,强制 Bearer 鉴权)
|
||||
|
||||
| 端点 | 成功 | 语义要点 |
|
||||
| --- | --- | --- |
|
||||
| `POST /api/v1/posts` | 201 + Post | 创建草稿或直接发布(`status: published` 时服务端写 publishedAt);`Idempotency-Key` 必带;纯文字帖合法(D3-4,图片不必填) |
|
||||
| `GET /api/v1/posts/{postId}` | 200 + Post | published 对全部登录用户开放;draft 仅作者;响应含 likedByMe/bookmarkedByMe |
|
||||
| `PATCH /api/v1/posts/{postId}` | 200 + Post(新 version) | 部分更新 + version 乐观锁,仅作者;发布 = `status: published` 状态迁移,无独立端点;media 出现即整组替换 |
|
||||
| `DELETE /api/v1/posts/{postId}` | 200 + VoidEnvelope | 软删 `deleted_at`,仅作者;删除后一切读路径 404 |
|
||||
| `GET /api/v1/me/posts` | 200 + `{items,nextCursor,hasMore}` | 作者视角含草稿;`(created_at DESC, id DESC)` 走 `ix_posts_author_created`,keyset 游标;`status` 过滤可选(draft\|published) |
|
||||
|
||||
### 2.2 权限矩阵定型(有测试逐格锚定)
|
||||
|
||||
| 帖子状态 \ 调用者 | 作者读 | 他人读 | 作者写(PATCH/DELETE) | 他人写 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| draft | 200 | **404/40403** | 200 | **404/40403**(不可见,非 403) |
|
||||
| published | 200 | 200 | 200 | **403/40301** |
|
||||
| hidden / archived(运营态,D3-7) | **404/40403(作者同样)** | 404/40403 | 404/40403 | 404/40403 |
|
||||
| 软删 / 不存在 | 404/40403 | 404/40403 | 404/40403 | 404/40403 |
|
||||
|
||||
定型原则:**403/40301 只发给对资源「可见」的调用者**(不泄露新信息);一切不可见情形(不存在/软删/hidden/archived/他人 draft)响应逐字节一致(防枚举)。hidden 对作者也不露——M3 无任何端点能产生或解除 hidden,契约 status 枚举保持 `[draft, published]` 两值,不为运营态开读侧口子(草案预设「不露」的定型,读侧同样适用)。
|
||||
|
||||
### 2.3 错误码定型(本单启用 4 码,均按草案取值,无新码位)
|
||||
|
||||
| 业务码 | HTTP | 稳定名 | 本单触发场景(全部有测试) |
|
||||
| --- | --- | --- | --- |
|
||||
| 40301 | 403 | POST_ACCESS_DENIED | 非作者改/删他人**已发布**帖 |
|
||||
| 40403 | 404 | POST_NOT_FOUND | §2.2 全部不可见情形合并 |
|
||||
| 40905 | 409 | IDEMPOTENCY_PAYLOAD_MISMATCH | 同 Idempotency-Key 不同规范化 payload |
|
||||
| 42203 | 422 | MEDIA_NOT_READY | 引用本人 uploading/failed 态 asset |
|
||||
|
||||
复用既有码(行为实证):40000(content 缺失/超长、category=ai_creation、status 非法值或非法迁移、Idempotency-Key 缺失/空白/超 128、position 不连续/重复、isCover 多于一、assetId 重复、空白 title、limit 越界、游标非法、非 UUID 路径参数、version 缺失)、40101(无/坏 token)、40401(petId 引用不可见宠物,沿 pets 域防枚举语义:他人宠物与不存在同响应)、40405(asset 不存在/非本人/deleted 合并,T3-03 已入 ErrorCode)、40902(version 过期)。
|
||||
|
||||
### 2.4 发布与幂等语义定型
|
||||
|
||||
- **发布**:`PATCH {status: "published"}` 是唯一开放迁移(draft→published),publishedAt 恰写一次,`ck_posts_publish_state` 库层兜底;**对已发布帖重复提交 `status: published` 为幂等 no-op(200,version 照常 +1),不是 400**——同态提交不是迁移,弱网重试友好;`published→draft` 与 hidden/archived 目标值被请求枚举拒为 400/40000。发布时内容非空由构造保证(content 全程必填 1~10000,无「空草稿」可发布)。
|
||||
- **创建型幂等(ADR-019 实证)**:`Idempotency-Key` 必带(1~128,trim 后计),落 `uq_posts_author_idempotency`,`ON CONFLICT DO NOTHING` + 回读比对 request_hash——同键同 hash 返回首帖(同样 201,库中恰一行);同键异 hash 409/40905;**键按作者隔离**(跨用户同键各自成帖,有测试);并发同键重试由唯一约束收敛,输家回读赢家行。
|
||||
- **request_hash 规范化定型**:hash 对象是**规范化后的创建命令**(title/content/caption trim、category/status 缺省展开、media position/isCover 解析完成后的规范串 SHA-256,32 字节合 `ck_posts_idempotency`),非请求原始字节——语义相同、仅格式不同(空白、缺省写全)的重试仍命中首帖(有测试)。
|
||||
- **幂等重试撞已删首帖**(草案未覆盖的边界,本单定型):同键同 hash 但首帖已被删 → 404/40403(重试询问的资源已消亡,沿防枚举合并;不复活、不另建)。
|
||||
|
||||
### 2.5 软删语义定型(D3-7)
|
||||
|
||||
`deleted_at` 是全域唯一删除判定基准(一切读路径过滤)。已发布帖软删时 status 同步归档为 `archived` 以满足 `ck_posts_publish_state`(published 行不得带 deleted_at),草稿保持原 status——归档后的 status 值纯属内部记账,对外恒 404。重复删除与「删不存在的帖」同响应 404/40403。删除不提供恢复端点(M3 无回收站)。
|
||||
|
||||
### 2.6 media 挂接定型
|
||||
|
||||
- position:**全给或全不给**——全给须恰为 0..n-1 不重复;全不给按数组序。混合 400/40000。
|
||||
- isCover:至多一个 true(`uq_post_media_cover` 库层兜底);全 false 时**服务端把 position 0 行落库置 is_cover=true**(比草案「展示层取 position 0」更强:库内恒有唯一封面行,T3-05 取封面免特判)。
|
||||
- PATCH media **整组替换**(草案预设定型):字段出现即删旧插新;`[]` 清空为纯文字帖;缺席不动。
|
||||
- ≤9 图(D3-4);caption trim 后 ≤300;同帖 assetId 不重复(`UNIQUE (post_id, asset_id)` 兜底)。
|
||||
|
||||
## 3. asset 校验取舍说明(13 号报告联调协议的引用侧落地)
|
||||
|
||||
**定型:同库只读 `media.assets`(`MediaAssetGateway`,JdbcClient 单查询),不调 user 内部接口。**理由:
|
||||
|
||||
1. ADR-017 同一先例——作者公开信息即为「community 跨 schema 只读 identity」,asset 校验同构;拆库时两者一起切内部批量接口,同一演进逻辑。
|
||||
2. `post_media.asset_id → media.assets` 的外键本就要求同库,网络接口不消除该耦合,只添故障面与延迟。
|
||||
3. 13 号报告交接明言本模块「只做 asset 只读校验」;media 状态机的一切**写**操作仍归 patbond-user,本模块零写入。
|
||||
|
||||
校验语义(每项有测试):不存在 / 非本人 / `status='deleted'` → 404/40405(防枚举合并,与 media 域自身语义一致);本人所有但 uploading/failed → 422/42203。
|
||||
|
||||
**读取侧 URL**:`media[].url` 为预签名 GET(T3-03 定型「私有桶 + 签名读」,TTL 同 `download-ttl` 配置),由 community 侧 `MediaUrlSigner` **本地 SigV4 计算**生成——预签名不联网,本服务不与对象存储建立任何连接。配置与 user 共用同组环境变量(`PATBOND_MINIO_PUBLIC_ENDPOINT/ACCESS_KEY/SECRET_KEY`,compose 已为 community 服务注入,无需 depends_on minio);未配置时服务照常启动、`url` 为 null(与 JWT 公钥未配置同一降级先例)。
|
||||
|
||||
## 4. 与契约草案(openapi-community-draft.yaml)偏差清单
|
||||
|
||||
| # | 草案 | 实现定型 | 理由 / 冻结动作 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `Post.author` 为 AuthorSummary(required) | **占位 `authorId`(裸 UUID 字符串)** | 工单口径:作者公开资料随 T3-05 的 /internal 批量接口落地;**冻结前须由 T3-05 回填 AuthorSummary**,本单不预造假数据(R10) |
|
||||
| 2 | `Post.status` 对作者是否露 hidden 留白(草案预设不露) | 定型**不露**:hidden/archived 对作者读写一律 404/40403 | M3 无端点能产生 hidden,枚举保持两值;运营台账属 M4+ |
|
||||
| 3 | 「其余迁移 400/40000」 | **published→published 为幂等 no-op(200)**,非 400 | 同态提交不是迁移;弱网重发 PATCH 不应报错。400 保留给真非法目标值(draft/hidden/archived) |
|
||||
| 4 | 幂等重试语义未覆盖「首帖已删」 | 同键同 hash 撞已删首帖 → **404/40403** | §2.4;冻结时在 `IdempotencyKeyRequiredHeader` 描述补一句 |
|
||||
| 5 | 「request_hash(请求体规范化 SHA-256)」未定规范化细则 | 规范化 = trim + 缺省展开 + media 解析后的规范串(§2.4) | 冻结时把「语义等价即命中」写入头参数描述 |
|
||||
| 6 | `PostMediaItem.url` required | 保持事实 required(生产恒配置),但**对象存储未配置时为 null** | 降级路径与 user 模块同规;冻结时在 url 描述注明「未配置降级」或维持 required + 运维前提,建议后者 |
|
||||
|
||||
另两处为**草案预设的确认**(非偏差):PATCH media 整组替换成立(草案决策 5 删除「草案态」标注即可);isCover 全 false 取 position 0 成立(实现落库置真,见 §2.6)。
|
||||
|
||||
## 5. 测试数变化:226 → 251(+25,0 回归)
|
||||
|
||||
| 模块 | 基线 | 交付 | 新增内容 |
|
||||
| --- | --- | --- | --- |
|
||||
| patbond-common | 3 | 3 | ErrorCode 增 4 码(40301/40403/40905/42203),无行为变化 |
|
||||
| patbond-user | 88 | 88 | — |
|
||||
| patbond-auth | 39 | 39 | — |
|
||||
| patbond-pet | 89 | 89 | — |
|
||||
| patbond-community | 7 | 32 | 帖子域 25 例(postgres:18 Testcontainers 真库,V1..V5 全链) |
|
||||
| **合计** | **226** | **251** | `JAVA_HOME=<你的 JDK17 路径> ./mvnw clean test` 一次通过,BUILD SUCCESS |
|
||||
|
||||
新增 25 例按工单六类路径 + 专项覆盖:
|
||||
|
||||
- `PostLifecycleIntegrationTest`(14):全形态创建(草稿/直接发布/挂宠物)、六类路径——成功/参数错(content 缺失、ai_creation、hidden、空白 title、幂等键缺失/空白/超长)/不存在(随机 UUID、畸形 UUID)/无权限(403/404 边界四格)/并发冲突(**真双线程并发 PATCH,恰一个 200 一个 40902**,外加串行过期 version)/幂等重试(见下);**草稿可见性矩阵**(作者/他人 × draft/published/hidden/软删逐格);软删墓碑落库实证(archived + deleted_at);我的列表 keyset 翻页不丢不重 + status 过滤 + 三种非法入参;likedByMe/bookmarkedByMe 视角实证。
|
||||
- `PostIdempotencyIntegrationTest`(5,幂等专项):同键同 hash(库中恰一行)/同键异 hash 40905/**跨用户同键**各自成帖/语义等价异格式仍命中/重试撞已删首帖 404。
|
||||
- `PostMediaAttachIntegrationTest`(6):数组序 + 封面缺省、显式 position/isCover、他人与不存在 asset 合并 40405、uploading/failed 42203、四种形态违规 40000、PATCH 整组替换(换图/缺席不动/清空)。预签名 GET URL 形态在位断言(指向 public-endpoint、含 X-Amz-Signature)——签名是本地计算,测试注入假凭证即可,**无需 MinIO 容器**。
|
||||
|
||||
契约一致性测试按工单暂不加,冻结后统一入 patbond-community 契约矩阵(T3-10/T3-11)。
|
||||
|
||||
附带修正两处:
|
||||
|
||||
1. 骨架期 `BearerAuthIntegrationTest.validTokenPassesTheFilter` 的探针路径由 `/api/v1/posts`(现已是真实路由)改为未映射路径,测试意图不变。
|
||||
2. `check-secrets.sh --all`(CI 兜底门禁)的 KEY-ASSIGN 规则会把配置类 setter 的「字段 = 同名形参」自赋值误报为凭证字面量——patbond-user `MediaProperties` 两处属基线既有误报,新增 `CommunityMediaProperties` 同形态再中两处。按脚本处置指引第 2 条最小化解:四处 setter 形参改名 `value`(行为零变化,`@ConfigurationProperties` 绑定按 setter 名不按形参名),不单方面改三仓同构的规则表;是否给规则加自赋值豁免留待三仓同步时定。修正后 `--all` 全仓通过。
|
||||
|
||||
## 6. 遗留与交接
|
||||
|
||||
- **T3-10 冻结回填**:§2 四张定型表 + §4 偏差 6 项即帖子域冻结输入;偏差 #1(AuthorSummary)等 T3-05 落地后一并回填。
|
||||
- **T3-05/06/07 衔接**:可见性谓词(`status='published' AND deleted_at IS NULL`)与「不可见一律 40403」语义直接复用;`MediaUrlSigner`/`MediaAssetGateway` 即 Feed 封面签名与校验的现成件;likedByMe 批量查询模式已在列表路径验证。
|
||||
- **P9 共享设施**:UuidV7/游标/幂等件已是第三份复制,下沉 common 的拍板仍悬置。
|
||||
- 405(方法不匹配路由)目前落通用 500——全部四个服务同现状,属横切收口项,不在本单发明新语义。
|
||||
@@ -0,0 +1,118 @@
|
||||
# M3 第二波公共 Feed 与作者公开资料链路施工报告(T3-05 / D3-9 方案 B)
|
||||
|
||||
> 作者:Senior Developer(后端)
|
||||
> 日期:2026-09-09
|
||||
> 工单:T3-05(公共 Feed 游标分页与帖子卡片聚合)+ D3-9 方案 B 落地(作者公开资料链路)
|
||||
> 代码基线:patbond-api `101ac0f`(251 测试全绿)→ 交付 `99a3c1f`(282 测试全绿)
|
||||
> 结论先行:**契约冻结(T3-10)的最后两个待定型点就位——FeedCard 与 AuthorSummary 均已按实现定型(§2/§3 两张定型表即冻结输入);user 侧 `/internal/users/profiles` 批量公开资料接口落地(≤50/次,昵称回退归属侧完成,注销静默缺席);community 侧 Feign 批量取 + 60s 进程内缓存 + 头像本地解析签名;user 服务不可达时 Feed/详情照常 200、作者摘要退为仅 userId(降级有专项测试,绝不 5xx);T3-04 偏差①(authorId 占位)闭环;计数取 posts 冗余列(§4 取舍);与草案偏差 7 项逐条记录;全套 282 测试全绿(+31),`check-secrets --all` 通过。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 提交清单
|
||||
|
||||
按 user 侧 / community 侧两个逻辑提交,已推送 `origin/dev`:
|
||||
|
||||
| 提交 | 内容 |
|
||||
| --- | --- |
|
||||
| `40bac85` | user 域 `/internal/users/profiles` 批量公开资料接口 + 8 例端点测试 |
|
||||
| `99a3c1f` | 公共 Feed 游标分页 + FeedCard/AuthorSummary 定型 + Feign 链路/缓存/降级 + 23 例测试 + compose 注入 |
|
||||
|
||||
## 2. FeedCard 定型表(T3-10 冻结输入之一)
|
||||
|
||||
`GET /api/v1/feed`(强制 Bearer 鉴权;`limit` 1~100 缺省 20,`cursor` 可选)。谓词恒为 `status='published' AND visibility='public' AND deleted_at IS NULL`,恰合 `ix_posts_feed` 部分索引;复合游标 `(published_at DESC, id DESC)`,keyset 翻页(`(published_at, id) < (cursor)`),禁 OFFSET;信封 `{items, nextCursor, hasMore}`(`hasMore=false` 时 `nextCursor` 恒 null)。游标编码与我的列表同构:base64url("epochMicros:id"),timestamptz 微秒精度无损往返。
|
||||
|
||||
| 字段 | 类型 | 定型语义 |
|
||||
| --- | --- | --- |
|
||||
| `id` | uuid | 帖子 id |
|
||||
| `author` | AuthorSummary | §3;降级时退为 id-only 形态 |
|
||||
| `category` | enum | general \| help \| ai_creation |
|
||||
| `title` | string?(nullable) | 原样透传,无标题为 null |
|
||||
| `contentPreview` | string | **正文前 200 个 Unicode 码点,码点边界截断(emoji 等增补面字符绝不劈开),不追加省略号**;短于 200 码点原样透传。全文恒走详情端点 |
|
||||
| `coverImage` | PostMediaItem?(nullable) | **库中唯一 `is_cover` 行**(T3-04 §2.6 保证有图必有唯一封面行,读侧零特判);纯文字帖为 null;`url` 为现签预签名 GET,对象存储未配置时 null(沿 T3-04 偏差 #6 同规) |
|
||||
| `mediaCount` | int | 帖子图片总数(0~9),卡片角标用 |
|
||||
| `likeCount` / `commentCount` / `bookmarkCount` | int64 | 取自 posts 冗余列(§4) |
|
||||
| `likedByMe` / `bookmarkedByMe` | bool | 当前用户视角,页查询内联 EXISTS 主键探针(无 N+1,无二次往返) |
|
||||
| `publishedAt` | date-time | 恒非空(谓词只放行 published) |
|
||||
|
||||
较 Post 裁剪掉的字段:`content` 全文、`petId`、`visibility`、`version`、`media` 整组、时间戳对(created/updated)。卡片不带 coverImage 之外的图列表(草案 TODO 就此定型:只有封面 + 计数)。
|
||||
|
||||
## 3. AuthorSummary 定型表(T3-10 冻结输入之二,D3-9 方案 B)
|
||||
|
||||
嵌入位置:FeedCard.author、Post.author(详情/我的列表/写响应——**T3-04 偏差①闭环,`authorId` 裸字段已删除**)、后续 T3-07 评论作者同构复用。
|
||||
|
||||
| 字段 | 类型 | 定型语义 |
|
||||
| --- | --- | --- |
|
||||
| `userId` | uuid(required,唯一必选字段) | 恒等于 posts.author_user_id,任何情形都在 |
|
||||
| `nickname` | string?(**nullable,较草案放宽**) | 正常路径恒非空:**nickname→username 回退在 user 侧 SQL 完成**(COALESCE,消费侧与客户端都不做拼装);null 当且仅当降级/墓碑(见下) |
|
||||
| `avatarUrl` | string?(nullable) | ready 头像 asset 的现签预签名 GET;无头像 / asset 非 ready / 对象存储未配置 / 降级 → null,客户端出占位 |
|
||||
|
||||
**链路形态(三段)**:
|
||||
1. **user 侧** `GET /internal/users/profiles?ids=…`:一次最多 50 个(超限/空/非法 UUID 均 400/40000,重复 id 去重),仅回 `userId/nickname/avatarAssetId` 三字段;不存在与已注销(deleted_at)用户**静默缺席**——缺席不泄露成因。既有 InternalAuthFilter(X-Internal-Token 共享密钥)直接覆盖,无新安全面。
|
||||
2. **community 侧 Feign**(ADR-002 静态直连,`patbond.user-service.url`):按缓存未命中 id 去重分批(≤50/批,一页 20 卡通常恰一批、热缓存零批);`avatarAssetId` 经 **media.assets 同库只读**(ADR-017 既有豁免,与帖图校验同构)解析为 bucket/object_key(仅 `status='ready'` 计入),URL 由 `MediaUrlSigner` 每次响应现签——**缓存里永远不存会过期的 URL**。
|
||||
3. **缓存**:进程内 ConcurrentHashMap,TTL 60s(`patbond.author-profile.cache-ttl` 可配),条目为 (nickname, bucket, objectKey);超 1 万条时顺手清理过期项。昵称/头像变更最迟一分钟全站可见。
|
||||
|
||||
**注销用户墓碑形态**(草案 TODO 定型):/internal 缺席 → 消费侧渲染 id-only AuthorSummary(`{userId, nickname: null, avatarUrl: null}`),与降级同形——客户端只需要一种占位逻辑。不补 bio、不露 username(回退后的展示名不标注来源)。
|
||||
|
||||
## 4. 计数策略取舍
|
||||
|
||||
**定型:三计数读 posts 冗余列(V5 的 like_count/comment_count/bookmark_count),不实时 COUNT(*)。**
|
||||
|
||||
1. V5 结构本就为此建列(含 `ck_posts_counts` 非负兜底),T3-06(点赞/收藏)与 T3-07(评论)的工单已明确写侧**同事务**维护关系行 + 冗余列——同库同事务,读侧不存在滞后窗口,只有普通的并发读写序问题。
|
||||
2. 实时 COUNT 是每页 20 帖 × 3 计数的聚合扫描,随互动量线性劣化;冗余列是页查询顺读,代价 O(页)。
|
||||
3. 当前基线互动写侧未落地,列值恒 0——卡片计数透传列值的正确性已用 SQL 置值实证(FeedCardIntegrationTest),T3-06/07 落地后无需回改读侧。
|
||||
|
||||
一致性兜底记录:若未来出现列与关系表漂移(如运维手改),修复口径为以关系表 COUNT 重算列(一条 UPDATE … FROM 聚合),属运维手册项,不做常驻对账任务。`likedByMe/bookmarkedByMe` 不走冗余列,恒查关系表主键,天然精确。
|
||||
|
||||
## 5. 降级语义定型(有专项测试逐条锚定)
|
||||
|
||||
| 情形 | 行为 |
|
||||
| --- | --- |
|
||||
| user 服务连接拒绝 / 超时(Feign connect 1s / read 2s 兜底)/ 回 4xx/5xx | 一条 WARN 日志,该批 id 不解析;**Feed/详情照常 200**,未解析作者退为 id-only AuthorSummary;分批场景失败前已成功的批次照常生效 |
|
||||
| 失败结果 | **不写缓存**(无负缓存)——下一请求自动重试,恢复即回满摘要(有测试:降级→恢复两连请求) |
|
||||
| /internal 回包缺席某 id(不存在/注销) | 同上 id-only 形态,不缓存缺席 |
|
||||
| 头像 asset 非 ready / 已删 / 对象存储未配置 | 仅 `avatarUrl: null`,昵称照常 |
|
||||
| 缓存命中 | 零下游调用(有调用计数测试) |
|
||||
|
||||
设计要点:降级判定在 `AuthorProfileGateway` 单点收口(catch 一切 RuntimeException),Feign 层不配 ErrorDecoder——对这条链路,下游业务错误与网络故障同义(都是"拿不到资料"),没有需要透传的错误语义。
|
||||
|
||||
## 6. 与契约草案(openapi-community-draft.yaml)偏差清单
|
||||
|
||||
| # | 草案 | 实现定型 | 理由 / 冻结动作 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | `AuthorSummary` required `[userId, nickname]` | **required 收为 `[userId]`,`nickname` nullable** | 降级与墓碑形态需要合法的 id-only 摘要;正常路径 nickname 恒非空的语义写入字段描述 |
|
||||
| 2 | contentPreview「200 字符 + 完整边界截断」 | **200 Unicode 码点,码点边界截断,不加省略号** | 「字符」口径歧义(UTF-16 单元会劈开 emoji);码点是最小不破字形单位,词边界截断对中文无意义。冻结时把码点口径写死 |
|
||||
| 3 | FeedCard TODO「是否带 mediaCount 之外的图列表」 | **只带 coverImage + mediaCount** | 卡片是列表形态,整组图属详情;封面行库层唯一(T3-04),读侧零歧义 |
|
||||
| 4 | AuthorSummary TODO(bio/username/墓碑) | **不补 bio;不露 username;墓碑 = /internal 静默缺席 → id-only** | 最小泄露面(D3-9 候选 C 的否决理由同源);bio 字段库里尚不存在 |
|
||||
| 5 | 封面「isCover 优先→position 0 兜底」(读侧规则) | 读侧**只认 is_cover 行**,无兜底分支 | 兜底已在写侧完成(T3-04 §2.6 落库置真),读侧兜底是死代码;冻结时封面描述改为「唯一 is_cover 行」 |
|
||||
| 6 | 「likedByMe/bookmarkedByMe 批量查询」 | 页查询**内联 EXISTS 主键探针**(单 SQL,非独立批量查询) | 语义与性能目标一致(无 N+1、无二次往返),实现形态更简;契约无感知,仅记录 |
|
||||
| 7 | `Post.author` 占位 `authorId`(T3-04 偏差①) | **已回填 AuthorSummary,`authorId` 字段删除** | 本单交付;冻结时 Post.author 按 §3 收编,T3-04 偏差①销项 |
|
||||
|
||||
另两处为草案预设的确认(非偏差):Feed 谓词/游标/信封与草案逐字一致;`PageLimitParam`(1~100 缺省 20,越界 400/40000)与实现一致。**`/internal/users/profiles` 不入公网 openapi.yaml**(服务间接口,非客户端契约),形态以本报告 §3 为准。
|
||||
|
||||
## 7. 测试数变化:251 → 282(+31,0 回归)
|
||||
|
||||
| 模块 | 基线 | 交付 | 新增内容 |
|
||||
| --- | --- | --- | --- |
|
||||
| patbond-common | 3 | 3 | — |
|
||||
| patbond-user | 88 | 96 | `InternalProfileEndpointTest` 8 例:无/错密钥 401、昵称回退、头像指针透传、注销与不存在静默缺席、ids 缺失/空白/非法 UUID/超 50 各 400、恰 50 放行、重复 id 去重 |
|
||||
| patbond-auth | 39 | 39 | — |
|
||||
| patbond-pet | 89 | 89 | — |
|
||||
| patbond-community | 32 | 55 | 见下 |
|
||||
| **合计** | **251** | **282** | `JAVA_HOME=<你的 JDK17 路径> ./mvnw clean test` 一次通过,BUILD SUCCESS;`<repo>/scripts/check-secrets.sh --all` 通过 |
|
||||
|
||||
community 新增 23 例,按工单六类路径 + 两个专项:
|
||||
|
||||
- `FeedPaginationIntegrationTest`(8,分页专项):空 Feed / 单页无游标 / **翻页不丢不重**(7 帖 3 页整走)/ **published_at 同刻并列按 id 破序**(SQL 置同刻实证)/ **翻页间隙增删不移位不重复**(页间新发布不挤入下页、下页候选被删除干净消失)/ 可见性谓词(draft/hidden/软删/followers 可见性一律不出 Feed)/ 非法游标两形态 400 / limit 越界 400。Feed 是全局态,每例先清 posts 表保证断言确定性。
|
||||
- `FeedCardIntegrationTest`(5,卡片定型):纯文字帖全字段形态(含「不带 content/version」的裁剪断言)/ **200 码点截断(199 汉字 + emoji 恰好 200,增补面字符不劈)**/ 短文原样透传 / 封面取 is_cover 行 + mediaCount + 签名 URL 在位 / 计数透传冗余列 + likedByMe 关系表实证。
|
||||
- `AuthorProfileIntegrationTest`(7,作者链路):详情回填昵称(偏差①闭环)/ username 回退 / ready 头像签名 URL + uploading 头像 null / **缓存命中零下游调用**(调用计数)/ 详情降级 id-only / **Feed 降级整页照常 200** / **失败不入缓存、恢复即回满摘要**。
|
||||
- `AuthorProfileClientWireTest`(3,Feign 线路):真实 Feign 客户端打在测试内 JDK HttpServer 上——X-Internal-Token 拦截器在位 + ids 批量成单请求 + 信封解码 / avatarAssetId 经 media.assets 解析并签名 / 下游 500 降级为空结果。
|
||||
|
||||
**测试替身取舍说明(工单许可项)**:作者链路测试未起 user+community 双服务同 JVM(AuthE2e 先例成本高),采用**读同一真库的 DB-backed stub** 顶替 Feign 代理(与真端点跑同一条 SQL,含昵称回退),Feign 传输层另由线路测试用真实客户端 + 真 HTTP 服务器覆盖,`/internal` 端点自身在 user 模块测全——三层拼起来无未测缝隙。为让 stub 可置换,`@FeignClient` 显式 `primary = false`(生产唯一候选,行为无差)。
|
||||
|
||||
## 8. 遗留与交接
|
||||
|
||||
- **T3-10 冻结回填**:§2/§3 两张定型表 + §6 偏差 7 项即 Feed/作者域冻结输入;至此 T3-03 凭据形态、T3-04 权限/错误语义、T3-05 卡片字段三项冻结条件齐备,可开冻结单。
|
||||
- **T3-06/07 衔接**:互动写侧同事务维护三计数列即可,读侧零改动;评论作者摘要直接复用 `AuthorProfileGateway.summarize`(批量 + 缓存现成)。
|
||||
- **compose**:community 服务已注入 `PATBOND_USER_SERVICE_URL` / `PATBOND_INTERNAL_TOKEN`(与 auth/user 同一密钥),容器内直连 user 服务。
|
||||
- **技术债记录**:/internal 仍为共享密钥(mTLS 债项在 01 号报告已记,Feign 面扩大后权重再升);进程内缓存是单实例视角,多副本部署时各副本独立 60s 窗口(可接受,无一致性要求);游标/UuidV7 等共享件已是第四份复制,P9 下沉拍板仍悬置。
|
||||
- **头像上传口子**:链路已通但 `user_avatar` purpose 尚无上传入口(media 域 M3 只开 post_image),全库头像数据为空时 `avatarUrl` 恒 null——前端占位即可,purpose 扩展随 P6 拍板另立工单;identity.users 的 nickname 字段已存在(V1),无需表变更,设置昵称的公开端点亦属后续工单。
|
||||
@@ -0,0 +1,116 @@
|
||||
# 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` 必带(1~128,trim 后计);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/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'` 语义仍后置。
|
||||
@@ -0,0 +1,121 @@
|
||||
# M3 契约冻结报告(T3-10:community/media 域合入正典 v1.3.0)
|
||||
|
||||
> 作者:API 契约工程师
|
||||
> 日期:2026-09-09
|
||||
> 工单:T3-10(契约冻结,第二波收口)
|
||||
> 输入:草案 `openapi-community-draft.yaml` + 11 号草案说明;定型表 13(媒体凭据)/ 15(帖子生命周期)/ 16(FeedCard/AuthorSummary)/ 17(评论/互动/关注)
|
||||
> 结论先行:**community/media 域按四份定型表照单全收合入 `docs/api/openapi.yaml`,1.2.0 → 1.3.0:新增 13 路径 / 19 操作 / 27 schemas / 4 参数 / 7 响应组件 / 9 错误码,正典总量 31 路径 / 43 操作 / 72 schemas。草案→冻结修正 26 项逐条对照见 §3;四份定型表间未发现矛盾(两处表面分歧均已由报告自身声明口径,见 §4);草案 10 处 TODO-FREEZE 全部回填删除;YAML 解析、$ref 全解析、operationId 唯一性、`mkdocs build --strict` 全部通过。api 侧字节级快照同步为本冻结的硬依赖,由后续 api 侧工单执行(§5)。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 冻结版本与总量
|
||||
|
||||
| 项 | 1.2.0 | 1.3.0 | 增量 |
|
||||
| --- | --- | --- | --- |
|
||||
| 路径 | 18 | 31 | +13 |
|
||||
| 操作 | 24 | 43 | +19 |
|
||||
| schemas | 45 | 72 | +27 |
|
||||
| parameters | 4 | 8 | +4(PostIdParam/AssetIdParam/UserIdParam/IdempotencyKeyRequiredHeader) |
|
||||
| responses | 6 | 13 | +7(PostNotFound/CommentNotFound/MediaNotFound/UserNotFound/PostAccessDenied/IdempotencyPayloadMismatch/MediaNotReady) |
|
||||
| 错误码 | 19 | 28 | +9(40301/40403/40404/40405/40406/40905/42203/42204/42205) |
|
||||
| servers | 3 | 4 | +:8084 patbond-community |
|
||||
| tags | 6 | 12 | +media/posts/feed/comments/interactions/follows |
|
||||
|
||||
info 头同步动作:更新履历补 1.3.0 段;错误码表按码位序并入 9 码;新增「Community / Media 域约定」段(幂等域差异、媒体两步上传与签名读语义、防枚举码族、互动面=公开面、ADR-018 裁剪与 `/internal` 不入契约)——11 号报告 §6-5 要求的「Idempotency-Key 必带 + 比对 hash + ≤128 与 pets 域差异在 info 头显式成文」已落。
|
||||
|
||||
## 2. 冻结端点总表(13 路径 / 19 操作)
|
||||
|
||||
| # | 端点 | 操作 | 服务 | 成功 | 错误面(HTTP/业务码) |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| 1 | `/api/v1/media/uploads` | POST | user :8082 | 201 凭据 | 400/40000、401/40101 |
|
||||
| 2 | `/api/v1/media/uploads/{assetId}/complete` | POST | user :8082 | 200 asset | 400/40000、401、404/40405、422/42205 |
|
||||
| 3 | `/api/v1/posts` | POST | community :8084 | 201 Post | 400、401、404/40401+40405、409/40905、422/42203 |
|
||||
| 4 | `/api/v1/posts/{postId}` | GET / PATCH / DELETE | community | 200 | GET:401、404/40403;PATCH:400、401、403/40301、404/40403+40401+40405、409/40902、422/42203;DELETE:401、403、404 |
|
||||
| 5 | `/api/v1/me/posts` | GET | community | 200 分页 Post | 400、401 |
|
||||
| 6 | `/api/v1/feed` | GET | community | 200 分页 FeedCard | 400、401 |
|
||||
| 7 | `/api/v1/posts/{postId}/comments` | GET / POST | community | 200 / 201 | GET:400、401、404/40403;POST:400、401、404/40403+40406、409/40905 |
|
||||
| 8 | `/api/v1/comments/{commentId}` | DELETE | community | 200 Void | 401、403/40301、404/40404 |
|
||||
| 9 | `/api/v1/posts/{postId}/like` | PUT / DELETE | community | 200 LikeState | 401、404/40403 |
|
||||
| 10 | `/api/v1/posts/{postId}/bookmark` | PUT / DELETE | community | 200 BookmarkState | 401、404/40403 |
|
||||
| 11 | `/api/v1/me/bookmarks` | GET | community | 200 分页 FeedCard | 400、401 |
|
||||
| 12 | `/api/v1/users/{userId}/follow` | PUT / DELETE | community | 200 FollowState | 401、404/40406;PUT 另有 422/42204 |
|
||||
| 13 | `/api/v1/users/{userId}/follow-stats` | GET | community | 200 FollowStats | 401、404/40406 |
|
||||
|
||||
全部端点强制 Bearer 鉴权。裁剪不出现(ADR-018):话题端点、关注/粉丝列表、作者主页帖子列表、`region`/`generationJob`/`visibility=followers|private`;`/internal/users/profiles` 为服务间接口,**不入公网契约**(形态以 16 号报告 §3 为准)。
|
||||
|
||||
## 3. 草案 → 冻结修正项对照(26 项,照单全收)
|
||||
|
||||
### 3.1 媒体域(依据:13 号报告 §3/§4)
|
||||
|
||||
| # | 草案 | 冻结 | 依据 |
|
||||
| --- | --- | --- | --- |
|
||||
| M1 | 读取侧 URL 形态留白(公共读 vs 签名读) | **私有桶 + 预签名 GET**(TTL 默认 1 小时,配置项);MediaAsset.url / PostMediaItem.url / AuthorSummary.avatarUrl 描述统一注明「时效性、每次响应现签、客户端不得持久化、过期即重取」 | 13 号偏差 #1 + 用户拍板 |
|
||||
| M2 | complete「校验失败置 failed」一刀切 | 对象不存在 → 422/42205 **保持 uploading 可重试**;对象存在但大小/类型不符 → 置 failed 终态 422/42205 | 13 号偏差 #2 |
|
||||
| M3 | 「有 sha256 则一并核」 | sha256 **照收照存,M3 不核验**(字段描述改写;后续经存储侧 checksum 补齐不改契约形态) | 13 号偏差 #3 |
|
||||
| M4 | complete 未声明 400 | 补 400/ValidationError(非 UUID assetId) | 13 号偏差 #4 |
|
||||
| M5 | purpose 白名单待定 | 定 `post_image` 一项;P6 扩展为向后兼容枚举追加 | 13 号偏差 #5 |
|
||||
| M6 | mime 白名单与 HEIC 待定 | 定 jpeg/png/webp,**不收 HEIC** | 13 号偏差 #6 |
|
||||
| M7 | byteSize 上限草案 10 MiB | 定 10485760(配置项) | 13 号偏差 #7 |
|
||||
| M8 | requiredHeaders「键集草案态」 | 定型为恒且仅 `{"Content-Type": <mimeType>}` 一键,**并入 required**;expiresAt TTL 10 分钟维持 | 13 号 §3 定型表 |
|
||||
|
||||
### 3.2 帖子域(依据:15 号报告 §2/§4)
|
||||
|
||||
| # | 草案 | 冻结 | 依据 |
|
||||
| --- | --- | --- | --- |
|
||||
| P1 | Post.author 占位争议(T3-04 曾落 authorId 裸字段) | **Post.author = AuthorSummary**(T3-05 回填闭环,authorId 不出现) | 15 号偏差 #1 + 16 号偏差 #7(同一事项两端) |
|
||||
| P2 | status 对作者是否露 hidden 留白 | **不露**:hidden/archived 对作者读写一律 404/40403,枚举保持 `[draft, published]`,权限矩阵写入 getPost 描述 | 15 号偏差 #2 |
|
||||
| P3 | 「其余迁移 400/40000」 | **published→published 为幂等 no-op(200,version 照常 +1)**;400 只留给 draft/hidden/archived 目标值 | 15 号偏差 #3 |
|
||||
| P4 | 幂等重试撞已删首帖未覆盖 | 同键同 hash 撞已删首帖 → 404/40403,写入 IdempotencyKeyRequiredHeader 描述 | 15 号偏差 #4 |
|
||||
| P5 | request_hash 规范化细则未定 | 「hash 对象是规范化后的创建命令(trim、缺省展开),语义等价即命中」写入头参数描述与 info 头 | 15 号偏差 #5 |
|
||||
| P6 | PostMediaItem.url required 与降级冲突 | **维持 required + 运维前提**(生产恒配置),描述注明现签与 TTL | 15 号偏差 #6(报告建议后者) |
|
||||
| P7 | PATCH media 整组替换「草案态」 | 定型确认,删标注:字段出现即删旧插新、`[]` 清空、缺席不动 | 15 号 §2.6(草案预设确认) |
|
||||
| P8 | isCover 全 false「展示层取 position 0」 | 改为**写侧落库置真**:库内恒有唯一封面行;PostMediaAttachRequest/PostMediaItem 描述同步 | 15 号 §2.6 |
|
||||
| P9 | —(草案未列) | createPost/updatePost 404 显式声明 40401(petId)与 40405(asset)双例;Post.updatedAt 注明「互动计数亦推动该值,判编辑以 version 为准」 | 15 号 §2.3 复用码行为 + 17 号 §3 记录在案行为 |
|
||||
|
||||
### 3.3 Feed / 作者域(依据:16 号报告 §2/§3/§6)
|
||||
|
||||
| # | 草案 | 冻结 | 依据 |
|
||||
| --- | --- | --- | --- |
|
||||
| F1 | AuthorSummary required `[userId, nickname]` | **required 收为 `[userId]`**,nickname nullable(null 仅降级/墓碑;正常路径恒非空语义写入描述) | 16 号偏差 #1 + 用户拍板 |
|
||||
| F2 | contentPreview「200 字符 + 完整边界截断」 | **200 Unicode 码点、码点边界截断(增补面字符不劈)、不加省略号** | 16 号偏差 #2 |
|
||||
| F3 | FeedCard 是否带图列表待定 | **只带 coverImage + mediaCount**(0~9);裁剪面(无 content 全文/petId/visibility/version/media 整组/created/updated)写入 schema 描述 | 16 号偏差 #3 |
|
||||
| F4 | bio/username/墓碑待定 | **不补 bio、不露 username**;墓碑 = id-only 形态(`{userId, nickname: null, avatarUrl: null}`),与降级同形 | 16 号偏差 #4 |
|
||||
| F5 | 封面「isCover 优先→position 0 兜底」 | 读侧**只认唯一 is_cover 行**(兜底已在写侧完成),FeedCard.coverImage 描述改写 | 16 号偏差 #5 |
|
||||
| F6 | likedByMe/bookmarkedByMe「批量查询」 | 实现为内联 EXISTS——契约无感知,仅在此记录,条文不动 | 16 号偏差 #6 |
|
||||
| F7 | avatarUrl 示例为公共读稳定 URL 形态 | 示例删除,描述改为预签名 GET 语义(与 M1 同源) | 16 号 §3 + 13 号偏差 #1 |
|
||||
|
||||
### 3.4 评论 / 互动 / 关注域(依据:17 号报告 §2/§5)
|
||||
|
||||
| # | 草案 | 冻结 | 依据 |
|
||||
| --- | --- | --- | --- |
|
||||
| C1 | 帖子不可见 404(未提作者草稿) | **互动面 = 帖子公开面**:作者本人草稿在评论(读写)与 like/bookmark 全部路径同样 404/40403——comments GET/POST、like/bookmark PUT/DELETE 六处描述逐一补「含作者本人草稿」,并入 info 头与 40403 错误表行 | 17 号偏差 #1 |
|
||||
| C2 | replyToUserId 校验语义未定 | 目标须为存活用户,不存在/注销合并 **404/40406**(createComment 404 双例:40403/40406) | 17 号偏差 #2 |
|
||||
| C3 | unfollow 未提自取关 | **自取关 200 幂等 no-op(following 恒 false)**;42204 只在 PUT,双端描述与错误表行写明 | 17 号偏差 #3 + 用户拍板 |
|
||||
| C4 | 42205 列为「草案新增」 | 42205 属 T3-03 已启用码,口径修正;对 1.3.0 契约错误码表仍是本次新收录(1.2.0 表中无此码) | 17 号偏差 #4 |
|
||||
| C5 | 幂等重试撞已删首评未覆盖 | 404/40404,与帖子域 40403 平行写入 IdempotencyKeyRequiredHeader 描述 | 17 号偏差 #5 |
|
||||
| C6 | Comment 形态确认 | 无 updatedAt(M3 无评论编辑,schema 描述注明);replyToUser 为完整 AuthorSummary(required 收敛沿 `[userId]`,含降级 id-only 形态) | 17 号偏差 #6 |
|
||||
| C7 | 评论删除权限 | **仅评论作者可删——帖主不可删他人评论(D3-7 首版不做)**在 deleteComment 描述显式写明;对可见评论的非作者(含帖主)403/40301 | 17 号 §2.1 + 用户拍板 |
|
||||
|
||||
### 3.5 错误码收录裁定(用户拍板全收)
|
||||
|
||||
新收录 9 码:40301 / 40403 / 40404 / 40405 / 40406 / 40905 / 42203 / 42204 / 42205(42205 在实现侧属 T3-03 既有,但 1.2.0 契约表无此码,故按实际入 1.3.0 表)。**40400 不复用**:该码已承担四服务 NoResourceFound「路由级资源不存在」兜底语义,关注/回复目标缺失独立取 40406,理由成文进错误码表行。复用既有码(40000/40101/40401/40902/50000/50300)不新增行、语义不动。
|
||||
|
||||
## 4. 定型表间一致性核验(未发现矛盾)
|
||||
|
||||
逐对交叉核验四份定型表,两处表面分歧均已由报告自身声明口径,不构成矛盾:
|
||||
|
||||
1. **15 号(draft 对作者可见)vs 17 号(作者草稿在互动路径 404)**:17 号 §2.3 显式声明为「收窄而非矛盾」——可见性回答「能不能看」,互动门禁回答「能不能社交」。冻结采两者:getPost 描述保留作者可见 draft,互动六端点补「含作者本人草稿」。
|
||||
2. **15 号(PostMediaItem.url 未配置降级为 null)vs 草案 required**:15 号偏差 #6 自身给出两选项并建议「维持 required + 运维前提」,16 号 coverImage 的同规注记同源。冻结采建议项:url 保持 required,描述注明运维前提。
|
||||
|
||||
## 5. 冻结纪律重申
|
||||
|
||||
1. **本文件即契约**:1.3.0 起 community/media 域 13 路径进入冻结面——任何字段/语义变更须显著上报、两端同步;错误码只增不改义、永不复用改号;裁剪字段/端点按纯增量补入(ADR-010/ADR-018 先例)。
|
||||
2. **api 侧字节级快照同步是本冻结的硬依赖**:patbond-api 现有契约一致性测试持有 v1.2.0 字节级快照(至少 patbond-pet 与 patbond-auth 的 `src/test/resources/contract/` 两处复制,13 号报告 §8 亦要求 T3-10 冻结时同步),**本仓升版 1.3.0 后,api 侧快照未同步前其快照守卫测试将保持红灯(CI 红)**——这是防漂移门禁按设计生效,不是事故。快照同步(连同 community 域契约矩阵测试 T3-11 的入场)由**后续 api 侧工单**执行,本报告仅冻结契约本体并注明该依赖顺序:先本仓合入推送,再 api 侧同字节复制快照。
|
||||
3. **草案文件处置**:`openapi-community-draft.yaml` 与 11 号说明保留原地作为过程档案,不再维护;此后一切消费方(SDK/客户端/契约测试)以 `docs/api/openapi.yaml` v1.3.0 为唯一权威。
|
||||
4. **校验通过项**:YAML 解析、283 处 $ref 全解析、43 个 operationId 无重复、全操作 security 声明齐、草案 10 处 TODO-FREEZE 归零、`mkdocs build --strict` 通过。
|
||||
|
||||
## 6. 遗留与交接
|
||||
|
||||
- api 侧:快照同步 + community 契约矩阵测试(见 §5-2,后续工单)。
|
||||
- 本仓:本报告(18 号)随波末统一挂导航入档;mkdocs.yml 本次不动。
|
||||
- 头像上传口子(purpose 扩 user_avatar)与设置昵称端点随 P6 拍板另立工单,届时按「枚举追加 + 新端点」纯增量升 1.4.x,不触碰本次冻结面。
|
||||
@@ -0,0 +1,82 @@
|
||||
# M3 契约同步报告(api 侧:v1.3.0 字节级快照同步 + community/media 契约矩阵入场)
|
||||
|
||||
> 作者:Senior Developer(后端)
|
||||
> 日期:2026-09-09
|
||||
> 工单:契约冻结 v1.3.0 的 api 侧收尾(18 号冻结报告 §5-2 注明的硬依赖工单)
|
||||
> 输入:doc 仓 `docs/api/openapi.yaml` v1.3.0(main@f848476,31 路径 / 43 操作 / 72 schemas);patbond-api dev@7f1dd33(310 测试基线)
|
||||
> 结论先行:**正典 v1.3.0 已字节级复制为四个模块的 `openapi-v1.3.0.yaml` 快照(md5 与正典逐一比对一致),pet/auth 守卫期望同步升版;community 域 17 操作 64 单元格、media 域 2 操作 8 单元格的契约一致性测试全响应矩阵入场,均零豁免;实现与冻结契约零漂移(64+8 格无一漂移报告);发现并修复框架级校验盲区一处(ContractValidator 不支持 v1.3.0 引入的 `nullable + allOf: [$ref]` 模式,会静默跳过 coverImage/replyToUser 内部校验);mutation 自证两轮通过(普通路径 + allOf 定向路径注毒均红、还原即绿);全套 325 测试全绿(310 + 15),`check-secrets.sh --all` 通过。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 快照同步(字节级)
|
||||
|
||||
| 位置 | 旧 | 新 | 处置 |
|
||||
| --- | --- | --- | --- |
|
||||
| `patbond-pet/src/test/resources/contract/` | openapi-v1.2.0.yaml | openapi-v1.3.0.yaml | 替换(删旧) |
|
||||
| `patbond-auth/src/test/resources/contract/` | openapi-v1.2.0.yaml | openapi-v1.3.0.yaml | 替换(删旧) |
|
||||
| `patbond-community/src/test/resources/contract/` | —(新建) | openapi-v1.3.0.yaml | 新增 |
|
||||
| `patbond-user/src/test/resources/contract/` | —(新建) | openapi-v1.3.0.yaml | 新增 |
|
||||
|
||||
- 四份快照 md5 与 doc 仓正典(main@f848476)逐一比对一致(`5b550fabf8e94b715ac1161798cb2738`),满足「字节级复制」纪律。
|
||||
- **旧 v1.2.0 快照删除而非保留**:每个模块的 `OpenApiContract.RESOURCE` 常量只认一份快照文件,守卫测试锁 `info.version`,保留旧文件只是死重——历史版本由 git 历史与 doc 仓承载。
|
||||
- pet/auth 守卫期望同步升版:`1.2.0/18 路径/24 操作/45 schemas` → `1.3.0/31/43/72`;各域 `operationsTagged` 断言不变仍绿(pets 域 18 操作、auth 域 6 操作在 v1.3.0 中零变化,即 v1.2.0 冻结面未被 1.3.0 触碰的实证)。
|
||||
- 契约框架(OpenApiContract + ContractValidator)按既有的模块内复制纪律扩为四份同构副本(pet/auth/community/user),同步纪律注释已改为「四模块各复制一份、各自更新期望」。
|
||||
|
||||
## 2. 覆盖矩阵规模(本单新增 19 操作 / 72 单元格,零豁免)
|
||||
|
||||
| 域 | 模块 | 测试类 | 操作 | (操作, 状态码) 单元格 | 豁免 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| community(posts/feed/comments/interactions/follows) | patbond-community | CommunityContractConformanceTest | 17 | 64 | **0** |
|
||||
| media(两步上传,属 user 模块) | patbond-user | MediaContractConformanceTest | 2 | 8 | **0** |
|
||||
| pets/dictionaries/health-records(既有) | patbond-pet | ContractConformanceTest | 18 | 82 | 1(沿用) |
|
||||
| auth/user/analytics(既有) | patbond-auth | AuthContractConformanceTest | 6 | 19 | 0 |
|
||||
| **合计(v1.3.0 全部 43 操作)** | 4 模块 | 4 类 | **43** | **173** | **1** |
|
||||
|
||||
- 机制与 pet 侧 T2-09 完全同构:真实起服务发请求(community 走 MockMvc + postgres:18 Testcontainer 全迁移链;media 走真实 MinIO Testcontainer,直传为真实 HTTP PUT)→ 严格校验器逐字段比对(未声明字段即报漂移)→ 末位全矩阵门禁断言每个声明单元格都被真实响应触发过。
|
||||
- community 域覆盖要点:错误码全谱 40000/40101/40301/40401/40403/40404/40405/40406/40902/40905/42203/42204 各至少一格实证;双业务码单元格(POST /posts 404 的 40401/40405、POST comments 404 的 40403/40406)两种业务码分别触发;分页信封 hasMore/nextCursor 两态、coverImage 与 replyToUser 的 null/非空两分支、防枚举合并语义(幽灵 id 与他人 draft 同响应)均在矩阵内。
|
||||
- media 域覆盖要点:201 凭据形态、直传后 complete 200(含幂等重复确认)、400(mime 白名单外 + 畸形 assetId)、401、404 防枚举合并(他人 asset 与幽灵 asset 同答 40405)、422/42205(直传前确认)。
|
||||
|
||||
### 豁免格清单
|
||||
|
||||
**本单新增矩阵零豁免**——community 域的 409 均为幂等键/乐观锁冲突、422 均为业务规则拒绝,media 域 422 为状态机拒绝,单线程 MockMvc 均可确定性触发。全仓唯一豁免格仍为 pet 侧沿用的 `PATCH /api/v1/care-reminders/{reminderId} 409`(并发条件更新守卫落空,单线程无法确定性构造,行为语义由并发一致性设计文档背书)。
|
||||
|
||||
## 3. 发现并修复的漂移清单
|
||||
|
||||
### 3.1 实现 ↔ 冻结契约:零漂移
|
||||
|
||||
新增 72 单元格全部一次通过严格校验,无字段名/类型/必填/nullable/枚举/格式漂移,无需修实现;未发现语义级冲突。这与第二波「先定型表、后冻结照单全收」的流程预期一致——契约本就是按已定型实现冻结的,本单是对「冻结稿与实现零偏差」声明的全矩阵实证。
|
||||
|
||||
### 3.2 框架级校验盲区一处(发现并修复)
|
||||
|
||||
- **问题**:v1.3.0 为表达「可空的 $ref」引入 `nullable: true + allOf: [$ref]` 模式(`FeedCard.coverImage`、`Comment.replyToUser`),而既有 ContractValidator 明文只支持无 allOf 子集——遇到该模式会解析出 `type=null` 而**静默跳过内部校验**:coverImage/replyToUser 里新增泄漏字段或类型漂移将无法被察觉,属校验盲区而非误报。
|
||||
- **修复**:四份 ContractValidator 副本同步加入单分支 allOf 展平合并(分支键先入、同级键——如外层 nullable——胜出;冻结契约只用单分支 allOf,浅合并即精确),并以定向 mutation 证明该路径生效(见 §4)。
|
||||
|
||||
## 4. mutation 自证(注毒应红、还原应绿)
|
||||
|
||||
| 轮次 | 注毒点 | 预期 | 实测 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1a | community 快照 `PostMediaItem.required` 注入假必填字段 | 红 | 5 测试失败,`$.data.media[0].fakeContractField: 契约必填字段缺失` |
|
||||
| 1b | user 快照 `MediaAsset.required` 注入假必填字段 | 红 | 2 测试失败,`$.data.fakeContractField: 契约必填字段缺失` |
|
||||
| 2 | community 快照 `coverImage` 的 allOf 同级注入 `required: [fakeAllOfField]`(定向打 allOf 合并路径) | 红 | `GET /api/v1/feed 200` 漂移:`$.data.items[*].coverImage.fakeAllOfField: 契约必填字段缺失` |
|
||||
| 还原 | 四快照 cp 回正典并 md5 复核 | 绿 | 全套 325 测试全绿 |
|
||||
|
||||
第 2 轮专为 §3.2 的修复自证:假必填字段被报告在 **coverImage 内部**,证明 allOf 合并后校验器确实下钻到了此前静默跳过的分支。
|
||||
|
||||
## 5. 测试数变化
|
||||
|
||||
| 模块 | 基线 | 现在 | 增量 |
|
||||
| --- | --- | --- | --- |
|
||||
| patbond-common | 3 | 3 | — |
|
||||
| patbond-user | 96 | 100 | +4(MediaContractConformanceTest) |
|
||||
| patbond-auth | 39 | 39 | —(守卫期望升版,数量不变) |
|
||||
| patbond-pet | 89 | 89 | —(守卫期望升版,数量不变) |
|
||||
| patbond-community | 83 | 94 | +11(CommunityContractConformanceTest) |
|
||||
| **合计** | **310** | **325** | **+15** |
|
||||
|
||||
`JAVA_HOME=/usr/lib/jvm/java-17-openjdk ./mvnw clean test` 全绿;`scripts/check-secrets.sh --all` 通过(快照与测试无敏感信息,MinIO 凭据沿用 dummy 占位值先例)。
|
||||
|
||||
## 6. 遗留与交接
|
||||
|
||||
- 契约同步纪律自此为**四处复制**:doc 仓正典升版 → 四模块同字节复制新快照 + 各守卫期望更新,任一处忘记同步 CI 即红(守卫锁 `info.version` 与三项计数)。
|
||||
- 契约测试框架仍为模块内四份同构副本(与 BearerAuthFilter 同纪律);若第三迭代后副本继续增多,可评估抽入 patbond-common 的 test-jar,此次不动。
|
||||
- 本报告(19 号)随波末统一挂导航入档;mkdocs.yml 本次不动;doc 仓 openapi.yaml 本单未触碰。
|
||||
@@ -0,0 +1,38 @@
|
||||
# 20 M3 第二波收口:社区后端纵切与契约冻结 v1.3.0
|
||||
|
||||
**执行日期**:2026-09-08 ~ 2026-09-09
|
||||
**交付**:community 域全部业务接口 + /internal 作者资料链路 + 契约冻结 v1.3.0 + 全仓契约矩阵扩展
|
||||
|
||||
---
|
||||
|
||||
## 0. 概要
|
||||
|
||||
| 工单 | 交付 | 提交(api dev) | 测试 |
|
||||
|------|------|------|------|
|
||||
| T3-04 帖子生命周期 | 5 端点、幂等专项、防枚举 40403、MediaAssetGateway | 101ac0f | 226→251 |
|
||||
| T3-05 Feed + 作者链路 | FeedCard/AuthorSummary 定型、/internal 批量 + Feign 降级 | 40bac85 / 99a3c1f | →282 |
|
||||
| T3-06/07/08 评论互动关注 | 11 端点、真并发幂等、计数同事务、三新码定型 | 19e8cba / 7f1dd33 | →310 |
|
||||
| 契约冻结 | openapi v1.3.0(31 路径/43 操作/72 schema),26 项修正照单全收 | doc main@f848476 | — |
|
||||
| 快照同步 + 矩阵扩展 | 四模块快照 v1.3.0、community 64 格 + media 8 格、修 allOf 校验盲区 | 0569585 | →325 |
|
||||
|
||||
**波末状态**:patbond-api **325 测试**全绿(CI 直查 success)、契约矩阵 43/43 操作 173 格唯一豁免(care-reminders 并发 409)、实现-契约零漂移。
|
||||
|
||||
## 1. 定型的关键语义(第三波 Flutter 接入依据)
|
||||
|
||||
- **媒体**:两步上传(创建→预签名 PUT 直传→confirm ready);读取一律预签名 GET(1h TTL,URL 会过期客户端不得持久化);post_image/jpeg/png/webp/10 MiB
|
||||
- **帖子**:发布走 PATCH draft→published;防枚举 404/40403(hidden 对作者亦不露、互动面=帖子公开面含本人草稿);Idempotency-Key 必带 + request_hash(40905 同键异 hash)
|
||||
- **Feed**:(published_at,id) 游标;FeedCard 含 contentPreview(200 码点)/coverImage/三计数/likedByMe/bookmarkedByMe/AuthorSummary;user 服务故障时作者退 id-only、Feed 照常 200
|
||||
- **互动**:PUT/DELETE 语义幂等,响应权威终态 {liked,likeCount};自取关 200 no-op;42204 仅自关注
|
||||
- **评论**:单层;仅评论作者可删(拍板);40404/40406 新码
|
||||
- 错误码 v1.3.0 新增 9 码:40301/40403/40404/40405/40406/40905/42203/42204/42205
|
||||
|
||||
## 2. 质量事件
|
||||
|
||||
- auth 契约测试(第一波)与本波矩阵扩展累计抓修 2 处真实漂移(events reason NON_NULL、校验器 allOf 盲区),契约测试机制持续兑现
|
||||
- T3-04 agent 曾在等待测试构建时中断,SendMessage 续跑无损交付
|
||||
- 快照同步 agent 报告 Monitor 出现过与 Gitea API 直查矛盾的假 success 事件(含时间戳晚于实时时钟的不可能事件),其未采信、以 API 多次直查为准——多源核验纪律有效
|
||||
|
||||
## 3. 遗留与下波
|
||||
|
||||
- uploading 超时清理定时任务(方案在 13 号)、429 Retry-After 分支(待后端限流)
|
||||
- **第三波(Flutter 社区接入)**:T3-12 community feature 数据层(契约 v1.3.0 已冻结可开工)→ T3-13 媒体上传客户端 → T3-14 Feed 替换 → T3-15/16 详情互动(乐观更新 ToggleSync)→ T3-17 发布页;字典 v3 埋点白名单与挂接随页面滚动
|
||||
Reference in New Issue
Block a user