- 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>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# 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/01(T3-03~09 端点定义与 T3-10 规范)、iteration-3/02(表结构、错误码段、media 状态机)、ADR-016~021、`docs/api/openapi.yaml` v1.2.0 通用约定
|
||||
|
||||
## 0. 字段正典基准声明
|
||||
|
||||
**T3-01 的 Flyway V5 迁移尚未推送 dev**(起草时 patbond-api 迁移链仅 V1~V4),本草案以 `docs/database/patbond_postgresql.sql` 的 community schema(718~875 行)与 media.assets(272~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→ready(200,幂等重复确认返回同 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 | 计数 + followedByMe(ADR-018「最小接口 + 数量」口径) |
|
||||
|
||||
裁剪不出现(与 ADR-018 对齐):话题全部端点(T3-09 条件单未启)、关注/粉丝**列表**(最小接口仅留 follow/unfollow + 计数,列表需时纯增量补)、作者主页 `GET /users/{userId}/posts`(M3 工单未列)、`region`/`generationJob`/`visibility=followers|private` 字段整体不出现(ADR-010「裁剪字段整体不出现,后续按新增可选字段补入」先例)。
|
||||
|
||||
## 2. 设计决策记录
|
||||
|
||||
1. **幂等按域(ADR-019)**:二元互动(like/bookmark/follow)PUT/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 只读 identity(ADR-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 仅 `image`(DB CHECK 含 video/document,读侧枚举预留) |
|
||||
| purpose | purpose | 白名单草案仅 `post_image`(TODO-FREEZE #1) |
|
||||
| mime_type | mimeType | 白名单草案 jpeg/png/webp(TODO-FREEZE #1) |
|
||||
| byte_size | byteSize | 创建时声明,complete 实测比对;上限草案 10 MiB(TODO-FREEZE #1) |
|
||||
| sha256 (bytea) | sha256 | 契约为 64 位小写 hex 字符串,可选 |
|
||||
| width_px / height_px | widthPx / heightPx | complete 后回填,可空 |
|
||||
| status | status | 契约仅露 uploading/ready/failed;deleted 恒 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 同宽(1~120 / 1~10000) |
|
||||
| status | status | 契约露 draft/published;hidden/archived 不开放(D3-7),草案对作者也不露 |
|
||||
| visibility | visibility | M3 恒 `public`(ADR-018;DB 三值保留) |
|
||||
| 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 | `createMediaUpload`、`MediaUploadCredentials`、`MediaAsset.url`、`CreateMediaUploadRequest` 三字段 | 预签名 PUT + TTL 10 分钟 + 10 MiB + jpeg/png/webp + purpose 仅 post_image |
|
||||
| 2 | **Feed 卡片字段**(PM 列②):contentPreview 截断规则、coverImage 选取规则、是否需 mediaCount 外的图列表 | T3-05 | `getFeed`、`FeedCard`;收藏列表「已删帖静默剔除 vs 占位」联动 | 200 字符截断 + isCover→position 0 + 仅封面一图 + mediaCount |
|
||||
| 3 | **作者公开资料形态**(PM 列③,D3-9 方案 B 预设字段) | T3-05 | `AuthorSummary` | userId + nickname(服务端回退 username)+ avatarUrl 可空;bio/username 露出与注销墓碑待定 |
|
||||
| 4 | **权限矩阵与错误语义边界**(补充,冻结条件之一) | T3-04 | `getPost`、`UpdatePostRequest.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 | 同键不同 payload(request_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 缺失/超长、非法状态迁移)、40101(token)、40401(petId 引用不可见宠物,沿 pets 域语义)、40902(version 冲突)、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 头「通用约定」中显式成文。
|
||||
Reference in New Issue
Block a user