f17e6f1215
CI / docs-build (push) Successful in 34s
- 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>
1380 lines
48 KiB
YAML
1380 lines
48 KiB
YAML
openapi: 3.0.3
|
||
info:
|
||
title: Patbond API — Community / Media 域契约草案(T3-10 起草态,非冻结稿)
|
||
version: 1.3.0-draft.1
|
||
description: |
|
||
M3 社区迭代 community + media 域契约**草案**(T3-10 第一波起草态)。
|
||
本文件独立可解析,仅供评审与实现对照;**不是** `docs/api/openapi.yaml` 的一部分,
|
||
冻结合入须待 T3-03/04/05 定型回填后按 M2 迭代式冻结流程升版 v1.3.0。
|
||
|
||
## 沿用正典约定(openapi.yaml v1.2.0 零偏差)
|
||
- 统一前缀 `/api/v1`;JSON 一律 camelCase;ID 为 UUID 字符串。
|
||
- 时间字段 ISO 8601 带时区偏移(timestamptz)。
|
||
- 统一信封 `{"code": 0, "message": "success", "data": …}`;错误同时带正确 HTTP
|
||
状态码与稳定业务码,业务码永不复用或改号。
|
||
- cursor 分页正典:`data: {items, nextCursor, hasMore}`;`limit` 1~100 缺省 20;
|
||
游标不透明,客户端不得解析;禁 OFFSET。
|
||
- 创建操作返回 201(pets 域先例);全部端点强制 Bearer 鉴权,无匿名端点。
|
||
|
||
## 本域新增约定(草案)
|
||
- **幂等按域(ADR-019)**:二元互动(点赞/收藏/关注)用 PUT/DELETE 语义幂等,
|
||
重复调用返回同一权威终态(200,非 409),无键管理;创建型写入(发帖/评论/
|
||
媒体登记)`Idempotency-Key` 头**必带**,落表内幂等列并比对 request_hash
|
||
(请求体规范化 SHA-256),同键不同 payload 返回 409/40905。
|
||
与 pets 域「可选键、不比对请求体」并存,pets 不回改。
|
||
- **媒体两步上传(ADR-016)**:创建 upload(登记 asset + 签发 MinIO 预签名 PUT
|
||
凭据)→ 客户端直传 → complete 确认(服务端 HEAD 校验后 uploading→ready)。
|
||
业务引用只接受本人所有且 `status='ready'` 的 asset,否则 422/42203。
|
||
- **防枚举 404**:帖子不存在 / 已软删 / hidden / 他人 draft 或 private 响应完全一致
|
||
(40403);评论、asset、用户同理(40404/40405/40406)。
|
||
- **写响应携带权威终态**:like/bookmark/follow 响应回 `{…State, …Count}`,
|
||
客户端乐观更新以响应对账回滚。
|
||
|
||
## community/media 域错误码段(草案,待 T3-04 定型冻结)
|
||
| 业务码 | 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:目标用户不存在或已注销(关注/计数端点) |
|
||
| 40905 | 409 | IDEMPOTENCY_PAYLOAD_MISMATCH:同 Idempotency-Key 不同 payload(request_hash 不符) |
|
||
| 42203 | 422 | MEDIA_NOT_READY:引用了非 ready 状态或超挂接窗口的 asset |
|
||
| 42204 | 422 | FOLLOW_RULE_VIOLATION:自关注(库层 ck_user_follows_self 兜底) |
|
||
| 42205 | 422 | MEDIA_UPLOAD_STATE_INVALID:complete 时 asset 非 uploading 态(幂等重复 ready 除外) |
|
||
复用既有码:40000 参数校验、40101 token、40902 VERSION_CONFLICT、50000/50300。
|
||
|
||
servers:
|
||
- url: http://127.0.0.1:8082
|
||
description: patbond-user(media 上传流程,ADR-017)
|
||
- url: http://127.0.0.1:8084
|
||
description: patbond-community(社区域全部端点,ADR-017)
|
||
|
||
security:
|
||
- bearerAuth: []
|
||
|
||
tags:
|
||
- name: media
|
||
description: 媒体上传两步流程(patbond-user,ADR-016 MinIO 预签名直传)
|
||
- name: posts
|
||
description: 帖子生命周期:草稿/编辑/发布/删除/详情/我的帖子(patbond-community)
|
||
- name: feed
|
||
description: 公共 Feed 游标分页(patbond-community)
|
||
- name: comments
|
||
description: 单层平铺评论 + @ 回复(patbond-community,ADR-018)
|
||
- name: interactions
|
||
description: 点赞/收藏 PUT+DELETE 幂等与收藏列表(patbond-community,ADR-019)
|
||
- name: follows
|
||
description: 关注最小数据接口:follow/unfollow + 计数(patbond-community,ADR-018)
|
||
|
||
paths:
|
||
/api/v1/media/uploads:
|
||
post:
|
||
tags: [media]
|
||
summary: 创建上传(登记 asset 并签发预签名直传凭据)
|
||
description: |
|
||
两步上传第一步:校验 mime 白名单与大小上限 → 写 `media.assets` 行
|
||
(status=uploading,bucket/objectKey 服务端生成、不含用户输入)→
|
||
返回预签名 PUT 凭据(短 TTL)。客户端凭凭据直传 MinIO,不经应用服务器。
|
||
M3 仅 `kind=image`(ADR-018 视频后置)。
|
||
# TODO-FREEZE: 等待 T3-03 —— purpose 白名单、mime 白名单、单文件大小上限数值定型
|
||
operationId: createMediaUpload
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CreateMediaUploadRequest'
|
||
responses:
|
||
'201':
|
||
description: asset 已登记(uploading),返回直传凭据
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MediaUploadEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
|
||
/api/v1/media/uploads/{assetId}/complete:
|
||
post:
|
||
tags: [media]
|
||
summary: 确认上传完成(uploading → ready)
|
||
description: |
|
||
两步上传第二步:服务端对对象 HEAD 校验存在性与 byteSize(有 sha256 则一并核)
|
||
→ status=ready、写 readyAt,返回可引用的 asset。校验失败置 failed。
|
||
对已 ready 的 asset 重复 complete 幂等返回同一 asset(200 语义下仍走 201?——
|
||
草案取 200 返回既有 ready asset);对 failed/deleted 态返回 422/42205。
|
||
operationId: completeMediaUpload
|
||
parameters:
|
||
- $ref: '#/components/parameters/AssetIdParam'
|
||
responses:
|
||
'200':
|
||
description: 确认成功(或幂等重复确认),asset 为 ready
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/MediaAssetEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/MediaNotFound'
|
||
'422':
|
||
description: asset 非 uploading 态,或对象校验失败已置 failed(code 42205)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
stateInvalid:
|
||
value: { code: 42205, message: 上传状态不允许确认, data: null }
|
||
|
||
/api/v1/posts:
|
||
post:
|
||
tags: [posts]
|
||
summary: 创建帖子(草稿或直接发布)
|
||
description: |
|
||
`Idempotency-Key` **必带**(开发计划 6.1 强制名单含帖子):落
|
||
`uq_posts_author_idempotency`,同键重试返回首次创建的帖子(同样 201);
|
||
同键不同 payload 返回 409/40905(request_hash 比对,ADR-019)。
|
||
`status` 可 draft(缺省)或 published(直接发布,服务端写 publishedAt)。
|
||
media 挂接只接受本人所有且 ready 的 asset(否则 422/42203),每帖 ≤9 图
|
||
(D3-4),position 0 起连续,isCover 至多一个(缺省取 position 0)。
|
||
纯文字帖合法(media 可空数组或缺席)。
|
||
operationId: createPost
|
||
parameters:
|
||
- $ref: '#/components/parameters/IdempotencyKeyRequiredHeader'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CreatePostRequest'
|
||
responses:
|
||
'201':
|
||
description: 创建成功(或同键幂等重试返回首次结果)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PostEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'409':
|
||
$ref: '#/components/responses/IdempotencyPayloadMismatch'
|
||
'422':
|
||
$ref: '#/components/responses/MediaNotReady'
|
||
|
||
/api/v1/posts/{postId}:
|
||
get:
|
||
tags: [posts]
|
||
summary: 帖子详情
|
||
description: |
|
||
published 对可见者开放;draft/hidden 仅作者可见,他人 404/40403 防枚举。
|
||
响应含 likedByMe/bookmarkedByMe 与作者公开摘要。
|
||
# TODO-FREEZE: 等待 T3-04 —— 权限矩阵与 403/404 边界语义随实现定型
|
||
operationId: getPost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
responses:
|
||
'200':
|
||
description: 帖子详情
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PostEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
patch:
|
||
tags: [posts]
|
||
summary: 编辑帖子 / 发布草稿(部分更新 + version 乐观锁)
|
||
description: |
|
||
仅作者(非作者对可见帖 403/40301,不可见帖 404/40403)。PATCH 部分更新惯例:
|
||
缺席字段不变,不支持清空回 null(M2 先例)。`version` 必带(缺失 400/40000,
|
||
过期 409/40902)。发布 = `status: published` 的状态迁移(draft→published,
|
||
服务端写 publishedAt,校验 ck_posts_publish_state);published→draft 不支持;
|
||
hidden/archived 不开放任何端点(D3-7 运营位保留)。
|
||
operationId: updatePost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/UpdatePostRequest'
|
||
responses:
|
||
'200':
|
||
description: 更新成功,返回新 version 的完整帖子
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PostEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'403':
|
||
$ref: '#/components/responses/PostAccessDenied'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
'409':
|
||
$ref: '#/components/responses/VersionConflict'
|
||
'422':
|
||
$ref: '#/components/responses/MediaNotReady'
|
||
delete:
|
||
tags: [posts]
|
||
summary: 删除帖子(软删,仅作者)
|
||
description: |
|
||
软删(deleted_at),删除后详情/Feed/列表一律不可见(M3 验收标准四)。
|
||
重复删除返回 404/40403(已删与不存在合并防枚举)。
|
||
operationId: deletePost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
responses:
|
||
'200':
|
||
description: 删除成功
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/VoidEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'403':
|
||
$ref: '#/components/responses/PostAccessDenied'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
|
||
/api/v1/me/posts:
|
||
get:
|
||
tags: [posts]
|
||
summary: 我的帖子列表(含草稿)
|
||
description: |
|
||
作者视角:含 draft 与 published(软删不含)。排序 `(created_at DESC, id DESC)`
|
||
走 `ix_posts_author_created` 游标。`status` 过滤可选(draft|published)。
|
||
operationId: listMyPosts
|
||
parameters:
|
||
- $ref: '#/components/parameters/PageLimitParam'
|
||
- $ref: '#/components/parameters/PageCursorParam'
|
||
- name: status
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
enum: [draft, published]
|
||
description: 按状态过滤;缺省返回全部(不含已删)
|
||
responses:
|
||
'200':
|
||
description: cursor 分页帖子列表(完整 Post 形态)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/PostListEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
|
||
/api/v1/feed:
|
||
get:
|
||
tags: [feed]
|
||
summary: 公共 Feed(游标分页)
|
||
description: |
|
||
谓词恒为 `status='published' AND visibility='public' AND deleted_at IS NULL`,
|
||
与 `ix_posts_feed` 部分索引一致;复合游标 `(published_at DESC, id DESC)`,
|
||
keyset 翻页不丢不重,禁 OFFSET。删除/hidden 帖子下一次请求即不可见。
|
||
likedByMe/bookmarkedByMe 批量查询(避免 N+1)。
|
||
# TODO-FREEZE: 等待 T3-05 —— Feed 卡片字段定型:content 摘要截断规则、
|
||
# 封面选取规则(isCover 优先→position 0 兜底)、计数口径逐项写死
|
||
operationId: getFeed
|
||
parameters:
|
||
- $ref: '#/components/parameters/PageLimitParam'
|
||
- $ref: '#/components/parameters/PageCursorParam'
|
||
responses:
|
||
'200':
|
||
description: cursor 分页 Feed 卡片列表
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/FeedListEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
|
||
/api/v1/posts/{postId}/comments:
|
||
get:
|
||
tags: [comments]
|
||
summary: 评论列表(单层平铺,游标分页)
|
||
description: |
|
||
排序 `(created_at DESC, id DESC)` 走 `ix_comments_post_created`;
|
||
仅 status=visible;帖子不可见则 404/40403。
|
||
operationId: listComments
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
- $ref: '#/components/parameters/PageLimitParam'
|
||
- $ref: '#/components/parameters/PageCursorParam'
|
||
responses:
|
||
'200':
|
||
description: cursor 分页评论列表
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CommentListEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
post:
|
||
tags: [comments]
|
||
summary: 创建评论(幂等 + 可选 @ 回复)
|
||
description: |
|
||
`Idempotency-Key` **必带**,落 `client_request_id + request_hash`
|
||
(uq author×client_request_id),同键重试返回首条评论(同样 201),
|
||
payload 不符 409/40905。`replyToUserId` 可选 @ 回复(单层,无楼中楼,
|
||
ADR-018)。帖子不可见则 404/40403。comment_count 同事务 +1。
|
||
operationId: createComment
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
- $ref: '#/components/parameters/IdempotencyKeyRequiredHeader'
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CreateCommentRequest'
|
||
responses:
|
||
'201':
|
||
description: 创建成功(或同键幂等重试返回首次结果)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CommentEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
'409':
|
||
$ref: '#/components/responses/IdempotencyPayloadMismatch'
|
||
|
||
/api/v1/comments/{commentId}:
|
||
delete:
|
||
tags: [comments]
|
||
summary: 删除评论(仅评论作者;顶层短路径)
|
||
description: |
|
||
顶层短路径先例(pets 域子资源 PATCH 同理):commentId 全局唯一。
|
||
仅评论作者可删(status→deleted,comment_count 同事务 -1);帖主删他人评论
|
||
首版不做(D3-7)。不存在/已删/所属帖不可见合并 404/40404。
|
||
operationId: deleteComment
|
||
parameters:
|
||
- name: commentId
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: 评论 ID
|
||
responses:
|
||
'200':
|
||
description: 删除成功
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/VoidEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'403':
|
||
$ref: '#/components/responses/PostAccessDenied'
|
||
'404':
|
||
$ref: '#/components/responses/CommentNotFound'
|
||
|
||
/api/v1/posts/{postId}/like:
|
||
put:
|
||
tags: [interactions]
|
||
summary: 点赞(PUT 语义幂等)
|
||
description: |
|
||
主键 (post_id, user_id) 即幂等键:`ON CONFLICT DO NOTHING`,仅实际插入才
|
||
like_count 同事务 +1。重复 PUT 返回 200 同一权威态(非 409),并发 N 次
|
||
计数恰为 1(M3 验收标准二)。帖子不可见 404/40403。
|
||
operationId: likePost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
responses:
|
||
'200':
|
||
description: 权威终态(liked 恒 true)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LikeStateEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
delete:
|
||
tags: [interactions]
|
||
summary: 取消点赞(DELETE 语义幂等)
|
||
description: 取消不存在的点赞不报错不减计数,返回 200 权威态(liked 恒 false)。
|
||
operationId: unlikePost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
responses:
|
||
'200':
|
||
description: 权威终态(liked 恒 false)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/LikeStateEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
|
||
/api/v1/posts/{postId}/bookmark:
|
||
put:
|
||
tags: [interactions]
|
||
summary: 收藏(PUT 语义幂等,与点赞同构)
|
||
operationId: bookmarkPost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
responses:
|
||
'200':
|
||
description: 权威终态(bookmarked 恒 true)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/BookmarkStateEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
delete:
|
||
tags: [interactions]
|
||
summary: 取消收藏(DELETE 语义幂等)
|
||
operationId: unbookmarkPost
|
||
parameters:
|
||
- $ref: '#/components/parameters/PostIdParam'
|
||
responses:
|
||
'200':
|
||
description: 权威终态(bookmarked 恒 false)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/BookmarkStateEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/PostNotFound'
|
||
|
||
/api/v1/me/bookmarks:
|
||
get:
|
||
tags: [interactions]
|
||
summary: 我的收藏列表(游标分页)
|
||
description: |
|
||
排序 `(bookmarks.created_at DESC, post_id DESC)` 走
|
||
`ix_post_bookmarks_user_created`。被收藏帖子已删/hidden 时该项不返回
|
||
(草案取「静默剔除」,是否保留占位随 T3-05 联动定型)。项形态复用 Feed 卡片。
|
||
operationId: listMyBookmarks
|
||
parameters:
|
||
- $ref: '#/components/parameters/PageLimitParam'
|
||
- $ref: '#/components/parameters/PageCursorParam'
|
||
responses:
|
||
'200':
|
||
description: cursor 分页收藏卡片列表
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/FeedListEnvelope'
|
||
'400':
|
||
$ref: '#/components/responses/ValidationError'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
|
||
/api/v1/users/{userId}/follow:
|
||
put:
|
||
tags: [follows]
|
||
summary: 关注(PUT 语义幂等)
|
||
description: |
|
||
主键 (follower, followee) 幂等,重复 PUT 返回 200 权威态;自关注 422/42204
|
||
(库层 ck_user_follows_self 兜底);目标用户不存在/已注销 404/40406。
|
||
关注 Feed 与 followers 可见性不在 M3(ADR-018 最小数据接口)。
|
||
operationId: followUser
|
||
parameters:
|
||
- $ref: '#/components/parameters/UserIdParam'
|
||
responses:
|
||
'200':
|
||
description: 权威终态(following 恒 true)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/FollowStateEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/UserNotFound'
|
||
'422':
|
||
description: 自关注(code 42204)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
selfFollow:
|
||
value: { code: 42204, message: 不能关注自己, data: null }
|
||
delete:
|
||
tags: [follows]
|
||
summary: 取消关注(DELETE 语义幂等)
|
||
operationId: unfollowUser
|
||
parameters:
|
||
- $ref: '#/components/parameters/UserIdParam'
|
||
responses:
|
||
'200':
|
||
description: 权威终态(following 恒 false)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/FollowStateEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/UserNotFound'
|
||
|
||
/api/v1/users/{userId}/follow-stats:
|
||
get:
|
||
tags: [follows]
|
||
summary: 关注计数(关注数/粉丝数/我是否已关注)
|
||
description: |
|
||
ADR-018 最小接口的「数量」端点:followerCount/followingCount 实时 COUNT
|
||
(user_follows 双向索引支撑),followedByMe 为调用者视角。
|
||
查自己时 followedByMe 恒 false。关注/粉丝**列表**端点不在 M3 草案
|
||
(最小接口裁剪,需时按纯增量补入)。
|
||
operationId: getFollowStats
|
||
parameters:
|
||
- $ref: '#/components/parameters/UserIdParam'
|
||
responses:
|
||
'200':
|
||
description: 计数与关注状态
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/FollowStatsEnvelope'
|
||
'401':
|
||
$ref: '#/components/responses/AccessTokenInvalid'
|
||
'404':
|
||
$ref: '#/components/responses/UserNotFound'
|
||
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
description: 'Authorization: Bearer <accessToken>(RS256 JWT)'
|
||
|
||
parameters:
|
||
PostIdParam:
|
||
name: postId
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: 帖子 ID
|
||
AssetIdParam:
|
||
name: assetId
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: 媒体 asset ID
|
||
UserIdParam:
|
||
name: userId
|
||
in: path
|
||
required: true
|
||
schema:
|
||
type: string
|
||
format: uuid
|
||
description: 目标用户 ID
|
||
PageLimitParam:
|
||
name: limit
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: integer
|
||
minimum: 1
|
||
maximum: 100
|
||
default: 20
|
||
description: 每页条数(1~100,缺省 20);越界 400/40000
|
||
PageCursorParam:
|
||
name: cursor
|
||
in: query
|
||
required: false
|
||
schema:
|
||
type: string
|
||
description: 上一页返回的 nextCursor(不透明字符串,客户端不得解析),首页不传;无效 400/40000
|
||
IdempotencyKeyRequiredHeader:
|
||
name: Idempotency-Key
|
||
in: header
|
||
required: true
|
||
schema:
|
||
type: string
|
||
maxLength: 128
|
||
description: |
|
||
**必带**幂等键(1~128 字符,缺失/超长 400/40000;与 pets 域可选 255 字符
|
||
不同——本域按 ADR-019 落表内幂等列,列宽 128)。键按作者隔离;同键重试
|
||
返回首次创建的资源(同样 201);**比对 request_hash**:同键不同 payload
|
||
返回 409/40905。客户端每次逻辑提交换新键(建议 UUID),重试间保持不变。
|
||
|
||
responses:
|
||
ValidationError:
|
||
description: 参数校验失败(code 40000)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
validation:
|
||
value: { code: 40000, message: 参数校验失败, data: null }
|
||
AccessTokenInvalid:
|
||
description: access token 缺失、无效或过期(code 40101)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
tokenInvalid:
|
||
value: { code: 40101, message: token 无效或过期, data: null }
|
||
PostNotFound:
|
||
description: |
|
||
帖子不存在、已软删、hidden,或对调用者不可见(他人 draft/private)——
|
||
防枚举语义:全部情况响应完全一致(code 40403)。
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
postNotFound:
|
||
value: { code: 40403, message: 帖子不存在, data: null }
|
||
CommentNotFound:
|
||
description: 评论不存在、已删或所属帖子不可见(code 40404,防枚举合并)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
commentNotFound:
|
||
value: { code: 40404, message: 评论不存在, data: null }
|
||
MediaNotFound:
|
||
description: asset 不存在或非本人所有(code 40405,防枚举合并)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
mediaNotFound:
|
||
value: { code: 40405, message: 媒体不存在, data: null }
|
||
UserNotFound:
|
||
description: 目标用户不存在或已注销(code 40406)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
userNotFound:
|
||
value: { code: 40406, message: 用户不存在, data: null }
|
||
PostAccessDenied:
|
||
description: |
|
||
对可见帖子/评论无权操作(code 40301):如编辑/删除他人已发布帖子。
|
||
仅发给对资源「可见」的调用者,不泄露新信息。
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
accessDenied:
|
||
value: { code: 40301, message: 无权限执行该操作, data: null }
|
||
IdempotencyPayloadMismatch:
|
||
description: 同 Idempotency-Key 不同 payload,request_hash 不符(code 40905)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
mismatch:
|
||
value: { code: 40905, message: 幂等键已用于不同请求, data: null }
|
||
MediaNotReady:
|
||
description: 引用了非 ready 状态、非本人或不存在的 asset(存在性合并 40405,状态问题 code 42203)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
notReady:
|
||
value: { code: 42203, message: 媒体尚未就绪, data: null }
|
||
VersionConflict:
|
||
description: 乐观锁版本冲突(code 40902):提交的 version 已过期,刷新后重试
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/ErrorEnvelope'
|
||
examples:
|
||
versionConflict:
|
||
value: { code: 40902, message: 数据已被修改,请刷新后重试, data: null }
|
||
|
||
schemas:
|
||
# ---------- 通用 ----------
|
||
VoidEnvelope:
|
||
type: object
|
||
required: [code, message]
|
||
properties:
|
||
code:
|
||
type: integer
|
||
enum: [0]
|
||
message:
|
||
type: string
|
||
example: success
|
||
data:
|
||
nullable: true
|
||
example: null
|
||
|
||
ErrorEnvelope:
|
||
type: object
|
||
required: [code, message]
|
||
properties:
|
||
code:
|
||
type: integer
|
||
description: 稳定业务错误码(见顶部错误码段草案)
|
||
example: 40403
|
||
message:
|
||
type: string
|
||
example: 帖子不存在
|
||
data:
|
||
nullable: true
|
||
example: null
|
||
|
||
AuthorSummary:
|
||
type: object
|
||
description: |
|
||
作者公开摘要(D3-9 方案 B 预设字段:昵称 + 头像,community 跨 schema
|
||
只读 identity 取数,ADR-017)。nickname 为空时回退 username 由**服务端**
|
||
完成(客户端不做回退拼装);avatarUrl 为 ready 头像 asset 的访问 URL,
|
||
无头像为 null(客户端出占位)。
|
||
# TODO-FREEZE: 等待 T3-05 —— 公开资料形态定型(是否补 bio/是否露 username、
|
||
# 注销用户的墓碑形态)
|
||
required: [userId, nickname]
|
||
properties:
|
||
userId:
|
||
type: string
|
||
format: uuid
|
||
nickname:
|
||
type: string
|
||
maxLength: 32
|
||
description: 昵称(空昵称已由服务端回退为 username)
|
||
example: 毛毛的铲屎官
|
||
avatarUrl:
|
||
type: string
|
||
nullable: true
|
||
description: 头像访问 URL;无头像为 null
|
||
example: http://127.0.0.1:9000/patbond-media/user_avatar/2026/09/018f...jpg
|
||
|
||
# ---------- media ----------
|
||
CreateMediaUploadRequest:
|
||
type: object
|
||
required: [kind, purpose, mimeType, byteSize]
|
||
properties:
|
||
kind:
|
||
type: string
|
||
enum: [image]
|
||
description: M3 仅 image(ADR-018 视频后置;video/document 为向后新增枚举预留)
|
||
purpose:
|
||
type: string
|
||
enum: [post_image]
|
||
description: |
|
||
用途白名单,决定 objectKey 前缀。
|
||
# TODO-FREEZE: 等待 T3-03 —— 是否随 P6 扩 user_avatar/pet_avatar
|
||
mimeType:
|
||
type: string
|
||
enum: [image/jpeg, image/png, image/webp]
|
||
description: |
|
||
白名单外 400/40000。
|
||
# TODO-FREEZE: 等待 T3-03 —— 白名单与 HEIC 支持定型
|
||
byteSize:
|
||
type: integer
|
||
format: int64
|
||
minimum: 1
|
||
maximum: 10485760
|
||
description: |
|
||
声明的文件字节数,complete 时与对象实测比对。
|
||
# TODO-FREEZE: 等待 T3-03 —— 单文件上限数值(草案 10 MiB)定型
|
||
sha256:
|
||
type: string
|
||
pattern: '^[0-9a-f]{64}$'
|
||
description: 可选,小写 hex;提供则 complete 时一并核对
|
||
|
||
MediaUploadCredentials:
|
||
type: object
|
||
description: |
|
||
预签名直传凭据(MinIO PUT,短 TTL,ADR-016)。
|
||
# TODO-FREEZE: 等待 T3-03 —— 凭据形态整体定型:uploadUrl 是否含 query 签名、
|
||
# requiredHeaders 具体键集、TTL 数值、读取侧 URL 形态(公共读桶稳定 URL vs 签名读)
|
||
required: [assetId, uploadUrl, method, expiresAt]
|
||
properties:
|
||
assetId:
|
||
type: string
|
||
format: uuid
|
||
description: 已登记的 asset ID(status=uploading)
|
||
uploadUrl:
|
||
type: string
|
||
description: 预签名 PUT 目标(客户端直传,不经应用服务器)
|
||
method:
|
||
type: string
|
||
enum: [PUT]
|
||
requiredHeaders:
|
||
type: object
|
||
additionalProperties:
|
||
type: string
|
||
description: 直传请求必须原样携带的头(如 Content-Type),键集草案态
|
||
expiresAt:
|
||
type: string
|
||
format: date-time
|
||
description: 凭据过期时刻(草案 TTL 10 分钟);过期后须重新创建上传
|
||
|
||
MediaAsset:
|
||
type: object
|
||
required: [id, kind, purpose, mimeType, status, createdAt]
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
kind:
|
||
type: string
|
||
enum: [image]
|
||
purpose:
|
||
type: string
|
||
example: post_image
|
||
mimeType:
|
||
type: string
|
||
example: image/jpeg
|
||
byteSize:
|
||
type: integer
|
||
format: int64
|
||
widthPx:
|
||
type: integer
|
||
nullable: true
|
||
heightPx:
|
||
type: integer
|
||
nullable: true
|
||
status:
|
||
type: string
|
||
enum: [uploading, ready, failed]
|
||
description: deleted 态对外恒 404/40405,不出现在响应
|
||
url:
|
||
type: string
|
||
nullable: true
|
||
description: |
|
||
访问 URL,仅 ready 态非空。
|
||
# TODO-FREEZE: 等待 T3-03 —— 公共读稳定 URL vs 签名读(随凭据形态一并定型)
|
||
readyAt:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
|
||
MediaUploadEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/MediaUploadCredentials'
|
||
|
||
MediaAssetEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/MediaAsset'
|
||
|
||
# ---------- posts ----------
|
||
PostMediaItem:
|
||
type: object
|
||
description: 帖子挂接的一张图(响应形态)
|
||
required: [assetId, position, isCover, url]
|
||
properties:
|
||
assetId:
|
||
type: string
|
||
format: uuid
|
||
position:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 8
|
||
isCover:
|
||
type: boolean
|
||
url:
|
||
type: string
|
||
description: 图片访问 URL(形态随 T3-03 凭据侧一并定型)
|
||
widthPx:
|
||
type: integer
|
||
nullable: true
|
||
heightPx:
|
||
type: integer
|
||
nullable: true
|
||
caption:
|
||
type: string
|
||
nullable: true
|
||
maxLength: 300
|
||
|
||
PostMediaAttachRequest:
|
||
type: object
|
||
description: 帖子挂接的一张图(请求形态);asset 须本人所有且 ready,否则 422/42203
|
||
required: [assetId]
|
||
properties:
|
||
assetId:
|
||
type: string
|
||
format: uuid
|
||
position:
|
||
type: integer
|
||
minimum: 0
|
||
maximum: 8
|
||
description: 0 起连续;缺省按数组序
|
||
isCover:
|
||
type: boolean
|
||
default: false
|
||
description: 至多一个 true(uq_post_media_cover);全 false 则服务端取 position 0
|
||
caption:
|
||
type: string
|
||
maxLength: 300
|
||
|
||
CreatePostRequest:
|
||
type: object
|
||
required: [content]
|
||
properties:
|
||
title:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
description: 可选标题(ck_posts_title)
|
||
content:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 10000
|
||
description: 正文,必填(ck_posts_content;纯文字帖合法,D3-4)
|
||
category:
|
||
type: string
|
||
enum: [general, help]
|
||
default: general
|
||
description: ai_creation 为 M4 预留值,M3 不开放写入
|
||
status:
|
||
type: string
|
||
enum: [draft, published]
|
||
default: draft
|
||
description: published = 创建即发布(服务端写 publishedAt)
|
||
petId:
|
||
type: string
|
||
format: uuid
|
||
description: 可选关联宠物;须为调用者可见宠物,否则 404/40401 语义沿 pets 域
|
||
media:
|
||
type: array
|
||
maxItems: 9
|
||
description: ≤9 图(D3-4);空数组或缺席 = 纯文字帖
|
||
items:
|
||
$ref: '#/components/schemas/PostMediaAttachRequest'
|
||
|
||
UpdatePostRequest:
|
||
type: object
|
||
required: [version]
|
||
description: |
|
||
部分更新:缺席字段不变;不支持清空回 null(M2 惯例)。media 若出现则**整组替换**
|
||
(position 全量重排,草案态,随 T3-04 定型)。
|
||
properties:
|
||
version:
|
||
type: integer
|
||
minimum: 0
|
||
description: 乐观锁,必带;过期 409/40902
|
||
title:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 120
|
||
content:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 10000
|
||
category:
|
||
type: string
|
||
enum: [general, help]
|
||
petId:
|
||
type: string
|
||
format: uuid
|
||
status:
|
||
type: string
|
||
enum: [published]
|
||
description: 唯一开放的状态迁移 draft→published(发布动作);其余迁移 400/40000
|
||
media:
|
||
type: array
|
||
maxItems: 9
|
||
items:
|
||
$ref: '#/components/schemas/PostMediaAttachRequest'
|
||
|
||
Post:
|
||
type: object
|
||
description: |
|
||
帖子完整形态(详情 / 我的帖子列表 / 写响应共用)。region/generationJob/topics
|
||
等裁剪字段整体不出现(ADR-018 + ADR-010 先例),后续按新增可选字段纯增量补入。
|
||
required:
|
||
- id
|
||
- author
|
||
- category
|
||
- content
|
||
- status
|
||
- visibility
|
||
- media
|
||
- likeCount
|
||
- commentCount
|
||
- bookmarkCount
|
||
- likedByMe
|
||
- bookmarkedByMe
|
||
- createdAt
|
||
- updatedAt
|
||
- version
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
author:
|
||
$ref: '#/components/schemas/AuthorSummary'
|
||
petId:
|
||
type: string
|
||
format: uuid
|
||
nullable: true
|
||
category:
|
||
type: string
|
||
enum: [general, help, ai_creation]
|
||
description: ai_creation 仅读侧预留(M3 无法写入)
|
||
title:
|
||
type: string
|
||
nullable: true
|
||
maxLength: 120
|
||
content:
|
||
type: string
|
||
maxLength: 10000
|
||
status:
|
||
type: string
|
||
enum: [draft, published]
|
||
description: hidden/archived 对非作者恒 404;对作者是否露出运营态待 T3-04(草案不露)
|
||
visibility:
|
||
type: string
|
||
enum: [public]
|
||
description: M3 恒 public(ADR-018:followers/private 语义后置,字段保留)
|
||
media:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/PostMediaItem'
|
||
likeCount:
|
||
type: integer
|
||
format: int64
|
||
commentCount:
|
||
type: integer
|
||
format: int64
|
||
bookmarkCount:
|
||
type: integer
|
||
format: int64
|
||
likedByMe:
|
||
type: boolean
|
||
bookmarkedByMe:
|
||
type: boolean
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
updatedAt:
|
||
type: string
|
||
format: date-time
|
||
publishedAt:
|
||
type: string
|
||
format: date-time
|
||
nullable: true
|
||
description: 仅 published 非空
|
||
version:
|
||
type: integer
|
||
|
||
FeedCard:
|
||
type: object
|
||
description: |
|
||
Feed / 收藏列表卡片形态(较 Post 裁剪)。
|
||
# TODO-FREEZE: 等待 T3-05 —— 卡片字段整体定型:contentPreview 截断长度与规则、
|
||
# coverImage 选取规则(isCover→position 0)、是否带 mediaCount 之外的图列表
|
||
required:
|
||
- id
|
||
- author
|
||
- category
|
||
- contentPreview
|
||
- mediaCount
|
||
- likeCount
|
||
- commentCount
|
||
- bookmarkCount
|
||
- likedByMe
|
||
- bookmarkedByMe
|
||
- publishedAt
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
author:
|
||
$ref: '#/components/schemas/AuthorSummary'
|
||
category:
|
||
type: string
|
||
enum: [general, help, ai_creation]
|
||
title:
|
||
type: string
|
||
nullable: true
|
||
contentPreview:
|
||
type: string
|
||
description: 正文摘要(服务端截断,规则待 T3-05 定型;草案 200 字符 + 完整边界截断)
|
||
coverImage:
|
||
nullable: true
|
||
allOf:
|
||
- $ref: '#/components/schemas/PostMediaItem'
|
||
description: 封面图;纯文字帖为 null
|
||
mediaCount:
|
||
type: integer
|
||
description: 帖子图片总数(卡片角标「1/9」类展示)
|
||
likeCount:
|
||
type: integer
|
||
format: int64
|
||
commentCount:
|
||
type: integer
|
||
format: int64
|
||
bookmarkCount:
|
||
type: integer
|
||
format: int64
|
||
likedByMe:
|
||
type: boolean
|
||
bookmarkedByMe:
|
||
type: boolean
|
||
publishedAt:
|
||
type: string
|
||
format: date-time
|
||
|
||
PostEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/Post'
|
||
|
||
PostListEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
type: object
|
||
description: cursor 分页正典信封;排序 created_at DESC, id DESC
|
||
required: [items, hasMore]
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/Post'
|
||
nextCursor:
|
||
type: string
|
||
nullable: true
|
||
description: 下一页游标(不透明 base64url),hasMore=false 时恒为 null
|
||
hasMore:
|
||
type: boolean
|
||
|
||
FeedListEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
type: object
|
||
description: |
|
||
cursor 分页正典信封;公共 Feed 排序 published_at DESC, id DESC;
|
||
收藏列表排序 bookmarks.created_at DESC, post_id DESC
|
||
required: [items, hasMore]
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/FeedCard'
|
||
nextCursor:
|
||
type: string
|
||
nullable: true
|
||
description: 下一页游标(不透明 base64url),hasMore=false 时恒为 null
|
||
hasMore:
|
||
type: boolean
|
||
|
||
# ---------- comments ----------
|
||
CreateCommentRequest:
|
||
type: object
|
||
required: [content]
|
||
properties:
|
||
content:
|
||
type: string
|
||
minLength: 1
|
||
maxLength: 2000
|
||
description: ck_comments_content 同宽
|
||
replyToUserId:
|
||
type: string
|
||
format: uuid
|
||
description: 可选 @ 回复目标(单层平铺,无 parentCommentId,ADR-018)
|
||
|
||
Comment:
|
||
type: object
|
||
required: [id, postId, author, content, createdAt]
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
postId:
|
||
type: string
|
||
format: uuid
|
||
author:
|
||
$ref: '#/components/schemas/AuthorSummary'
|
||
replyToUser:
|
||
nullable: true
|
||
allOf:
|
||
- $ref: '#/components/schemas/AuthorSummary'
|
||
description: '@ 回复目标的公开摘要;非回复为 null'
|
||
content:
|
||
type: string
|
||
maxLength: 2000
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
|
||
CommentEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/Comment'
|
||
|
||
CommentListEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
type: object
|
||
description: cursor 分页正典信封;排序 created_at DESC, id DESC
|
||
required: [items, hasMore]
|
||
properties:
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/Comment'
|
||
nextCursor:
|
||
type: string
|
||
nullable: true
|
||
description: 下一页游标(不透明 base64url),hasMore=false 时恒为 null
|
||
hasMore:
|
||
type: boolean
|
||
|
||
# ---------- interactions / follows ----------
|
||
LikeState:
|
||
type: object
|
||
description: 点赞权威终态(乐观更新以此对账回滚)
|
||
required: [liked, likeCount]
|
||
properties:
|
||
liked:
|
||
type: boolean
|
||
likeCount:
|
||
type: integer
|
||
format: int64
|
||
|
||
BookmarkState:
|
||
type: object
|
||
description: 收藏权威终态(与点赞同构)
|
||
required: [bookmarked, bookmarkCount]
|
||
properties:
|
||
bookmarked:
|
||
type: boolean
|
||
bookmarkCount:
|
||
type: integer
|
||
format: int64
|
||
|
||
FollowState:
|
||
type: object
|
||
description: 关注权威终态;followerCount 为目标用户的粉丝数
|
||
required: [following, followerCount]
|
||
properties:
|
||
following:
|
||
type: boolean
|
||
followerCount:
|
||
type: integer
|
||
format: int64
|
||
|
||
FollowStats:
|
||
type: object
|
||
required: [followerCount, followingCount, followedByMe]
|
||
properties:
|
||
followerCount:
|
||
type: integer
|
||
format: int64
|
||
description: 目标用户的粉丝数
|
||
followingCount:
|
||
type: integer
|
||
format: int64
|
||
description: 目标用户关注的人数
|
||
followedByMe:
|
||
type: boolean
|
||
description: 调用者是否已关注目标用户;查自己恒 false
|
||
|
||
LikeStateEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/LikeState'
|
||
|
||
BookmarkStateEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/BookmarkState'
|
||
|
||
FollowStateEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/FollowState'
|
||
|
||
FollowStatsEnvelope:
|
||
type: object
|
||
required: [code, message, data]
|
||
properties:
|
||
code: { type: integer, enum: [0] }
|
||
message: { type: string, example: success }
|
||
data:
|
||
$ref: '#/components/schemas/FollowStats'
|