作者: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. 定型表间一致性核验(未发现矛盾)
逐对交叉核验四份定型表,两处表面分歧均已由报告自身声明口径,不构成矛盾:
- 15 号(draft 对作者可见)vs 17 号(作者草稿在互动路径 404):17 号 §2.3 显式声明为「收窄而非矛盾」——可见性回答「能不能看」,互动门禁回答「能不能社交」。冻结采两者:getPost 描述保留作者可见 draft,互动六端点补「含作者本人草稿」。
- 15 号(PostMediaItem.url 未配置降级为 null)vs 草案 required:15 号偏差 #6 自身给出两选项并建议「维持 required + 运维前提」,16 号 coverImage 的同规注记同源。冻结采建议项:url 保持 required,描述注明运维前提。
5. 冻结纪律重申
- 本文件即契约:1.3.0 起 community/media 域 13 路径进入冻结面——任何字段/语义变更须显著上报、两端同步;错误码只增不改义、永不复用改号;裁剪字段/端点按纯增量补入(ADR-010/ADR-018 先例)。
- 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 侧同字节复制快照。
- 草案文件处置:
openapi-community-draft.yaml 与 11 号说明保留原地作为过程档案,不再维护;此后一切消费方(SDK/客户端/契约测试)以 docs/api/openapi.yaml v1.3.0 为唯一权威。
- 校验通过项: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,不触碰本次冻结面。