Files
lixi f17e6f1215
CI / docs-build (push) Successful in 34s
docs: M3 第一波收口——报告 09~14 与契约草案入档挂导航
- 09 V5+community 骨架(191→206)、10 埋点队列三项(272→286)、
  11 契约草案(13 路径/19 操作)、12 防泄漏三仓落地、
  13 media MinIO 闭环 + auth 契约测试(→226,抓修 1 漂移)、14 收口总表
- backend-modules.md 更新五模块/六容器口径
- 媒体凭据形态已定型(契约冻结输入),剩余待定型点在 T3-04/05

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-08 17:13:49 +08:00

12 KiB
Raw Permalink Blame History

M3 community/media 域契约草案说明(T3-10 起草态)

作者:API 契约工程师 日期:2026-09-08 状态:草案(DRAFT)——非冻结稿。冻结须待 T3-03(媒体凭据)/T3-04(权限与错误语义)/T3-05(Feed 卡片)定型回填,按 M2 迭代式冻结流程升版 v1.3.0 合入 docs/api/openapi.yaml 并同步 api 侧字节级快照。本文与草案文件均不触碰正典 openapi.yaml。 草案文件:openapi-community-draft.yaml(同目录,独立可解析,13 路径 / 19 操作) 依据:iteration-3/01T3-0309 端点定义与 T3-10 规范)、iteration-3/02(表结构、错误码段、media 状态机)、ADR-016021、docs/api/openapi.yaml v1.2.0 通用约定

0. 字段正典基准声明

T3-01 的 Flyway V5 迁移尚未推送 dev(起草时 patbond-api 迁移链仅 V1V4),本草案以 docs/database/patbond_postgresql.sql 的 community schema718875 行)与 media.assets272~331 行)为字段正典。V5 落地后若与 bootstrap 有差异(预期仅两处:剪 generation_job_id 外键为裸列、主键默认值改应用侧 UUIDv7,均不影响契约面),以 V5 为准复核本草案。

1. 端点清单(13 路径 / 19 操作)

# 端点 操作 对应工单 说明
1 POST /api/v1/media/uploads 1 T3-03 登记 asset + 签发预签名 PUT 凭据(201)
2 POST /api/v1/media/uploads/{assetId}/complete 1 T3-03 HEAD 校验后 uploading→ready200,幂等重复确认返回同 asset)
3 POST /api/v1/posts 1 T3-04 创建草稿或直接发布;Idempotency-Key 必带
4 /api/v1/posts/{postId} GET/PATCH/DELETE T3-04 详情 / 编辑与发布(version 乐观锁)/ 软删
5 GET /api/v1/me/posts 1 T3-04 我的帖子(含草稿),(created_at,id) 游标,status 过滤
6 GET /api/v1/feed 1 T3-05 公共 Feed(published_at,id) 游标,谓词=ix_posts_feed
7 /api/v1/posts/{postId}/comments GET/POST T3-07 评论列表(游标)/ 创建(幂等 + replyToUserId
8 DELETE /api/v1/comments/{commentId} 1 T3-07 顶层短路径(pets 域先例),仅评论作者
9 /api/v1/posts/{postId}/like PUT/DELETE T3-06 语义幂等,响应回 {liked, likeCount} 权威态
10 /api/v1/posts/{postId}/bookmark PUT/DELETE T3-06 同构,{bookmarked, bookmarkCount}
11 GET /api/v1/me/bookmarks 1 T3-06 收藏列表,(bookmarks.created_at, post_id) 游标,项复用 FeedCard
12 /api/v1/users/{userId}/follow PUT/DELETE T3-08 语义幂等;自关注 422/42204
13 GET /api/v1/users/{userId}/follow-stats 1 T3-08 计数 + followedByMeADR-018「最小接口 + 数量」口径)

裁剪不出现(与 ADR-018 对齐):话题全部端点(T3-09 条件单未启)、关注/粉丝列表(最小接口仅留 follow/unfollow + 计数,列表需时纯增量补)、作者主页 GET /users/{userId}/postsM3 工单未列)、region/generationJob/visibility=followers|private 字段整体不出现(ADR-010「裁剪字段整体不出现,后续按新增可选字段补入」先例)。

2. 设计决策记录

  1. 幂等按域(ADR-019:二元互动(like/bookmark/followPUT/DELETE 语义幂等,重复调用返回 200 同一权威终态(非 409)——复合主键即幂等键,无键管理;创建型(发帖/评论)Idempotency-Key 必带(与 pets 域「可选、≤255、不比对请求体」刻意不同:本域 ≤128 对齐表列宽,且比对 request_hash,不符 40905)。差异已在草案头参数描述中显式声明,防止 SDK/客户端按 pets 惯例误用。
  2. 写响应携带权威终态like/bookmark 回 {liked|bookmarked, count}follow 回 {following, followerCount}——iteration-3/02 §7.5 的乐观更新对账契约,客户端回滚=用响应覆盖本地值。
  3. 防枚举 404 沿 pets 先例并分域给码:帖子(40403,合并不存在/软删/hidden/他人 draft)、评论(40404)、asset(40405,合并非本人所有)、用户(40406)。403/40301 只发给「可见但无权」的调用者。
  4. 发布即状态迁移:不设独立 /publish 端点,PATCH {status: published} 是唯一开放迁移(draft→published),与 pets 域「状态流转走 PATCH」惯例一致,少一个端点少一处幂等语义。
  5. PATCH media 整组替换(草案态):部分更新语义下图片增删排序的逐项 diff 契约复杂且易错,草案取「media 字段出现即全量替换」,随 T3-04 实现定型。
  6. AuthorSummary 服务端回退:nickname 为空时由服务端回退 username,required 非空——客户端不做拼装(R10「缺失字段不留本地拼凑」);取数为 community 跨 schema 只读 identityADR-017),契约面不感知取数方式。
  7. 列表信封零新形态:全部列表复用 v1.2.0 cursor 分页正典 {items, nextCursor, hasMore}limit 1~100 缺省 20,游标不透明;每列表排序键与支撑索引在 description 中逐一写死(iteration-3/02 §7.3 对照)。
  8. complete 幂等语义:重复 complete 已 ready 的 asset 返回 200 同 asset(客户端弱网重试友好);failed/deleted 态 422/42205——比「非 uploading 一律拒」多保留一条安全重试路径。

3. 与 bootstrap SQL 的字段对照

3.1 media.assets → MediaAsset / CreateMediaUploadRequest

DB 列 契约字段 说明
id id / assetId UUID 字符串
kind kind 契约 M3 仅 imageDB CHECK 含 video/document,读侧枚举预留)
purpose purpose 白名单草案仅 post_imageTODO-FREEZE #1
mime_type mimeType 白名单草案 jpeg/png/webpTODO-FREEZE #1
byte_size byteSize 创建时声明,complete 实测比对;上限草案 10 MiBTODO-FREEZE #1
sha256 (bytea) sha256 契约为 64 位小写 hex 字符串,可选
width_px / height_px widthPx / heightPx complete 后回填,可空
status status 契约仅露 uploading/ready/faileddeleted 恒 404
ready_at / created_at readyAt / createdAt ISO 8601
bucket / object_key / storage_type / duration_ms / external_url / owner_user_id 不出现 存储内部细节不进契约;owner 由 token 隐含;duration 视频后置

3.2 community.posts → Post / CreatePostRequest / UpdatePostRequest

DB 列 契约字段 说明
id / author_user_id id / author(AuthorSummary) 作者展开为公开摘要,不露裸 authorUserId(含在 author.userId
pet_id petId 可空
category category 写侧 enum [general, help]ai_creation M4 预留只读)
title / content title / content 长度约束与 ck_posts_title/content 同宽(1120 / 110000
status status 契约露 draft/publishedhidden/archived 不开放(D3-7),草案对作者也不露
visibility visibility M3 恒 publicADR-018DB 三值保留)
like/comment/bookmark_count 同名 camelCase int64
idempotency_key / request_hash Idempotency-Key 头 不进 body;≤128 对齐列宽
published_at / created_at / updated_at / version 同名 camelCase version 进 PATCH 请求体(必带)
deleted_at 不出现 软删即 404
generation_job_id / region_id / location_text_snapshot 不出现 M4/M5 裁剪(V5 剪外键,ADR-018
(关联)post_likes/post_bookmarks 行 likedByMe / bookmarkedByMe 批量查询组装,required

3.3 community.post_media → PostMediaItem / PostMediaAttachRequest

DB 列 契约字段 说明
asset_id / position / is_cover / caption assetId / position / isCover / caption position 0~8(≤9 图,D3-4);isCover 至多一(uq_post_media_cover),全 false 服务端取 position 0
url / widthPx / heightPx 响应侧由 asset 展开,免客户端二次请求

3.4 community.comments → Comment / CreateCommentRequest

DB 列 契约字段 说明
id / post_id id / postId
author_user_id / reply_to_user_id author / replyToUser(均 AuthorSummary 请求侧 replyToUserId 裸 UUID
content content 1~2000 同宽
client_request_id / request_hash Idempotency-Key 头 落 client_request_id 列
status / deleted_at / updated_at 不出现 deleted/hidden 过滤在列表外;契约无评论编辑,不露 updatedAt

3.5 post_likes / post_bookmarks / user_follows

关系行不作为资源暴露,仅以 likedByMe/bookmarkedByMe/following/followedByMe 布尔态与计数出现;复合主键 = PUT/DELETE 幂等的实现本体(ON CONFLICT DO NOTHING + 同事务计数增减)。ck_user_follows_self → 422/42204。

4. TODO-FREEZE 清单(PM 三处 + 补充一处)

草案 YAML 内共 10 处 # TODO-FREEZE 标注,归并为 4 个待定型点:

# 待定型点 等待 草案内位置 草案预设
1 媒体凭据形态PM 列①):uploadUrl 签名形态、requiredHeaders 键集、TTL、读取侧 URL(公共读稳定 URL vs 签名读);连带 purpose/mime 白名单与大小上限数值 T3-03 createMediaUploadMediaUploadCredentialsMediaAsset.urlCreateMediaUploadRequest 三字段 预签名 PUT + TTL 10 分钟 + 10 MiB + jpeg/png/webp + purpose 仅 post_image
2 Feed 卡片字段PM 列②):contentPreview 截断规则、coverImage 选取规则、是否需 mediaCount 外的图列表 T3-05 getFeedFeedCard;收藏列表「已删帖静默剔除 vs 占位」联动 200 字符截断 + isCover→position 0 + 仅封面一图 + mediaCount
3 作者公开资料形态(PM 列③,D3-9 方案 B 预设字段) T3-05 AuthorSummary userId + nickname(服务端回退 username+ avatarUrl 可空;bio/username 露出与注销墓碑待定
4 权限矩阵与错误语义边界(补充,冻结条件之一) T3-04 getPostUpdatePostRequest.media 整组替换语义、作者视角 hidden 露出 403/404 边界按 §2-3 草案;media 整组替换

收敛期限沿 PM 要求:第二波中期。冻结时逐项回填、删除标注、升版 v1.3.0、同步 api 侧字节级快照。

5. 错误码段草案(新增 9 码,延续既有分段不重编号)

业务码 HTTP 稳定名 场景
40301 403 POST_ACCESS_DENIED 可见但无权操作(改删他人帖/评论)
40403 404 POST_NOT_FOUND 不存在/软删/hidden/不可见,防枚举合并
40404 404 COMMENT_NOT_FOUND 评论不存在/已删/所属帖不可见
40405 404 MEDIA_NOT_FOUND asset 不存在或非本人所有
40406 404 USER_NOT_FOUND 关注目标用户不存在/已注销(草案新增02 号报告未列)
40905 409 IDEMPOTENCY_PAYLOAD_MISMATCH 同键不同 payloadrequest_hash 不符)
42203 422 MEDIA_NOT_READY 引用非 ready 的 asset
42204 422 FOLLOW_RULE_VIOLATION 自关注(草案新增
42205 422 MEDIA_UPLOAD_STATE_INVALID complete 时 asset 非 uploading(幂等 ready 除外)(草案新增

复用既有码:40000(参数校验,含 mime/大小白名单拒绝、游标非法、limit 越界、Idempotency-Key 缺失/超长、非法状态迁移)、40101token)、40401petId 引用不可见宠物,沿 pets 域语义)、40902version 冲突)、50000/50300。与 iteration-3/02 §6 六码草案的差异:新增 40406/42204/42205 三码(关注与 complete 状态机在 02 号报告端点表中有行为但无码位),冻结评审时定夺。

6. 冻结前必办事项(交接给冻结时点)

  1. T3-01 V5 推送后与 bootstrap 复核一遍字段对照(§0)。
  2. 四个 TODO-FREEZE 点逐项回填(§4),删除全部标注。
  3. 错误码段三枚草案新增码(40406/42204/42205)评审定夺。
  4. 合入正典 openapi.yaml:升版 1.3.0、错误码表并入 info 头、servers 增 :8084、tags 并入;mkdocs build --strict + api 侧字节级快照同步。
  5. Idempotency-Key「必带 + 比对 hash + ≤128」与 pets 域差异在正典 info 头「通用约定」中显式成文。