36 KiB
API-04 内容系统(CONTENT)
共 17 个接口:CONTENT-01~17
内容类型枚举(post.type)
| DB值 | API值 | 说明 |
|---|---|---|
| 1 | post | 动态(图文,1-9 张图) |
| 2 | video | 视频(不区分时长) |
PPV 素材不在此枚举,独立走 API-06 PPV 系统(数据模型完全不同)。发布页 UI 上"发布 PPV 素材"按钮直接跳转到 PPV 创建流程。
状态枚举映射(post.status)
| DB值 | API响应值 | 说明 |
|---|---|---|
| 0 | draft | 草稿(仅创作者本人可见) |
| 1 | reviewing | 审核中 |
| 2 | published | 已上架 |
| 3 | rejected | 审核拒绝 |
| 4 | removed | 已下架 |
可见性与解锁关系
| visibility(API值) | DB值 | 额外字段 | 解锁规则 | 说明 |
|---|---|---|---|---|
| free | 1 | 无 | 所有人可见 | 免费公开内容 |
| subscriber | 2 | requiredTierLevel |
当前用户存在有效订阅,且 tier_level >= requiredTierLevel |
订阅可见内容 |
| ppv | 3 | price |
作者本人或已存在 content_unlock 记录 |
内容单独付费解锁 |
发布约束:
visibility=subscriber时,必须额外传requiredTierLevel,且该档位必须已在订阅设置中启用。visibility=ppv时,必须额外传price,且price > 0。- 非
ppv内容不允许传正数price。
关于评论(commentCount)
评论系统已在 P1 阶段上线,对应接口为 CONTENT-13~17。
commentCount由post.comment_count字段实时返回。
CONTENT-01 发布内容(含草稿)
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content |
| 接口用途 | 创作者发布动态/视频,或保存为草稿 |
| 权限要求 | 创作者(user.role=2) |
| 涉及表 | post, post_media, post_tag, post_mention, post_highlight, media, tag |
接口逻辑:
- 校验当前用户 user.role=2(创作者权限)
- 校验 mediaIds:查询
media表 确认 process_status=2 且 user_id=当前用户 - 若
type=video:校验coverMediaId必传,且对应media必须为当前创作者自己的已处理完成图片媒体;非视频内容可不传,传了也按同样规则校验 - 校验 tags(1-3 个):每项
id与name二选一。传id走tag表存在性校验;只传name时若同名 tag 已存在则复用,否则当场创建 source=2 的自定义标签(等价于内联 CONTENT-12) - 校验 mentions:查询
user表 确认被 @ 用户存在 - 若 type=2(video)且包含 highlights:校验 time_offset_seconds < 视频时长
- 校验可见性:
visibility=subscriber:校验requiredTierLevel必填,且该档位在当前创作者的subscription_tier中存在且is_active=truevisibility=ppv:校验price > 0- 其它可见性不允许传正数
price
- 写入
post表(status=isDraft?0:1,写入 location_name/code、visibility、required_tier_level、price_cents;有coverMediaId时写入post.cover_media_id,否则回退到首个内容媒体) - 写入关系表:post_media / post_tag / post_mention / post_highlight
- 草稿(isDraft=true)跳过审核流程;非草稿进入审核队列
- 返回新创建的内容信息
请求参数(Body):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| title | string | 否 | 标题,最长 30 字 |
| content | string | 是 | 正文内容,最长 200 字 |
| type | string | 是 | 内容类型:post/video |
| mediaIds | int64[] | 否 | 媒体ID数组(图片最多9张,或1个视频) |
| coverMediaId | string | 否 | 显式封面图媒体ID。type=video 时必填,必须是当前创作者自己的已处理完成图片媒体;其它类型可不传 |
| tags | object[] | 是 | 标签数组,1-3 个;每项 id 与 name 二选一 |
| tags[].id | string | 否 | 已存在的标签 ID(系统预设或已建好的自定义标签) |
| tags[].name | string | 否 | 仅在不传 id 时使用:同名复用,否则新建自定义标签(source=2),长度 1~32 |
| mentions | string[] | 否 | 被 @ 用户的 userId 数组,最多 10 个 |
| location | object | 否 | 地点信息 |
| location.name | string | 否 | 地点显示名(如"上海 静安区"),最长 64 字 |
| location.code | string | 否 | 行政区划代码(GB/T 2260),最长 20 字 |
| visibility | string | 是 | 可见性:free / subscriber / ppv |
| requiredTierLevel | number | 否 | visibility=subscriber 时必填,表示最低可见订阅档位 |
| price | number | 否 | visibility=ppv 时必填,单位分;其它可见性不传或传 0 |
| highlights | array | 否 | 视频章节标记数组(type=video 时),用于在播放进度条上打点跳转 |
| highlights[].timeOffsetSeconds | int | 是 | 标记时间点(秒),必须 < 视频时长 |
| highlights[].label | string | 否 | 标记说明文字(最长 40 字) |
| isDraft | boolean | 否 | 是否保存为草稿,默认 false |
校验返回码:
- 422: 字段超长 / 标签数量不在 1-3 / 媒体处理未完成 / highlights 时间点超出视频时长 / subscriber 档位未启用
- 403: 非创作者 / 媒体不属于当前用户
- 404: 标签/被@用户不存在
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| contentId | string | 内容ID |
| title | string | 标题 |
| content | string | 正文 |
| type | string | 内容类型 |
| mediaIds | int64[] | 媒体ID列表 |
| tags | object[] | 标签列表 [{id,name,source}] |
| mentions | object[] | 被 @ 用户列表 [{userId,nickname}] |
| location | object | 地点信息 |
| visibility | string | 可见性:free / subscriber / ppv |
| requiredTierLevel | number | subscriber 内容的最低可见档位 |
| visibilityTierName | string | subscriber 内容对应档位的展示名(free/ppv 时为空) |
| visibilityTierPrice | number | subscriber 内容对应档位价格(分,free/ppv 时为 0) |
| highlights | array | 视频章节标记列表 |
| status | string | 状态:draft / reviewing |
| isDraft | boolean | 是否草稿 |
| createdAt | timestamp | 创建时间 |
CONTENT-02 获取广场 Feed
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/feed |
| 接口用途 | 获取广场信息流(双列瀑布流) |
| 权限要求 | 公开(游客可访问) |
| 涉及表 | post, post_tag, post_media, tag, category, category_tag, user, media, content_boost, recommend_slot |
接口逻辑:
- 根据 sort 参数选择排序策略:
- recommend:70%算法推荐 + 20%新创作者池 + 10%运营位(查询
recommend_slot表,读) - latest:按 post.created_at 倒序
- hot:按浏览量+点赞数加权排序
- recommend:70%算法推荐 + 20%新创作者池 + 10%运营位(查询
- 处理分类筛选(categoryId 与 tag 二选一):
- 如果传 categoryId:
SELECT post.* FROM post JOIN post_tag pt ON pt.post_id = post.id JOIN category_tag ct ON ct.tag_id = pt.tag_id WHERE ct.category_id = :categoryId(读) - 如果传 tag:
SELECT post.* FROM post JOIN post_tag pt ON pt.post_id = post.id JOIN tag t ON t.id = pt.tag_id WHERE t.name = :tag(读) - 如果都不传:不加标签过滤,返回全部
- 如果传 categoryId:
- 查询
post表 WHERE status=2(已上架)(读) - 关联查询
user表 获取创作者信息(profile.display_name、profile.avatar_url)(读) - 关联查询
media表 获取封面图/缩略图(读)。优先使用post.cover_media_id对应媒体生成coverUrl;未设置时回退到内容首张图片 - 关联查询
content_boost表 获取内容注水数据,外显数据 = 真实值 + boost值(读)注:
content_boost用于单条内容的注水(view/like/favorite);creator_boost用于创作者主页的注水(follower/subscriber),后者在 API-08 CREATOR-02 中使用。 - 如果当前用户已登录,批量查询
user_subscription与content_unlock:subscriber内容按tier_level >= requiredTierLevel判断是否解锁ppv内容仅按content_unlock判断是否已购- 返回
isLocked / isUnlocked / ppvPrice
- 分页返回内容卡片列表
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| sort | string | 否 | 排序方式:recommend/latest/hot,默认recommend |
| categoryId | string | 否 | 分类ID(首页 Tab 分类筛选,内部通过 category_tag JOIN post_tag 匹配帖子) |
| tag | string | 否 | 标签筛选(tag.name),与 categoryId 二选一 |
| page | number | 否 | 页码,默认1 |
| pageSize | number | 否 | 每页条数,默认20 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| list | array | 内容卡片列表 |
| list[].contentId | string | 内容ID |
| list[].title | string | 标题 |
| list[].coverUrl | url | 封面图URL |
| list[].blurCoverUrl | url | 模糊封面URL |
| list[].type | string | 类型:post/video |
| list[].duration | number | 视频时长(秒)。type 为 video 时返回,非视频或无媒体时省略 |
| list[].videoUrl | url | 视频播放签名URL。广场/关注 Feed 中仅在 isLocked=false 时返回 |
| list[].visibility | string | 可见性:free / subscriber / ppv |
| list[].visibilityTierName | string | subscriber 内容的档位展示名(free/ppv 时为空) |
| list[].visibilityTierPrice | number | subscriber 内容档位价格(分,free/ppv 时为 0) |
| list[].ppvPrice | number | visibility=ppv 时返回内容价格(分) |
| list[].isUnlocked | boolean | 当前用户是否已解锁 |
| list[].isPinned | boolean | 是否置顶 |
| list[].hasHighlights | boolean | 是否包含视频章节标记(视频类型时) |
| list[].creator | object | 创作者信息 |
| list[].creator.userId | string | 创作者ID |
| list[].creator.nickname | string | 创作者昵称 |
| list[].creator.avatar | url | 创作者头像 |
| list[].creator.isOnline | boolean | 是否在线 |
| list[].viewCount | number | 浏览量(含boost) |
| list[].likeCount | number | 点赞数(含boost) |
| list[].favoriteCount | number | 收藏数(含boost) |
| list[].commentCount | number | 评论数 |
| list[].isLocked | boolean | 是否需订阅/付费 |
| list[].tags | object[] | 标签列表 [{id,name}] |
| list[].createdAt | timestamp | 发布时间 |
| pagination | object | 分页信息 |
CONTENT-02b 获取关注 Feed
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/feed/following |
| 接口用途 | 首页"关注" tab:返回当前用户已关注创作者发布的内容 |
| 权限要求 | 需登录(JWT) |
| 涉及表 | user_follow, post, post_media, media, user_profile, subscription_tier, user_subscription |
接口逻辑:
- 从 JWT 取 viewer_id
- 查询
user_follow表,取当前用户全部followee_id(deleted_at IS NULL);若为空直接返回空列表 - 调用与 CONTENT-02 相同的
ListFeed,在post上追加author_id = ANY($followeeIds)过滤,status=2,按 published_at 倒序 - 组装逻辑与 CONTENT-02 第 4~7 步一致(作者资料、媒体封面/时长、订阅档位、isLocked 计算)
- 分页返回
请求参数(Query): 与 CONTENT-02 一致(sort/categoryId/tag/page/pageSize)
响应数据: 与 CONTENT-02 ContentFeedResp 完全一致(list[] 为 FeedItem)
CONTENT-02c 获取视频播放流
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/video-feed |
| 接口用途 | 从首页/分类页点击某个视频后,进入沉浸式视频流;返回列表首条为当前点击的视频 |
| 权限要求 | 公开(游客可访问) |
| 涉及表 | post, post_tag, tag, category, post_media, media, user_profile, subscription_tier, user_subscription |
接口逻辑:
- 根据
seedContentId查询种子内容;不存在返回 40401 - 校验种子内容必须为
video,否则返回 40001 - 校验种子内容状态必须为已发布;未发布/已下架按不存在处理
- 复用广场 Feed 的上下文参数:
sort/categoryId/tag - 从
post中查询同上下文的候选视频流:status=2 AND type=video,并排除seedContentId offset=0时,返回列表首条固定插入 seed 视频,其余pageSize-1条由候选视频补足offset>0时,不再重复返回 seed 视频,只返回后续候选视频- 列表项组装逻辑与 CONTENT-02 一致,复用
FeedItem字段口径(封面、时长、锁态、创作者信息等) - 为兼容沉浸式播放器预加载,
video-feed会补充videoUrl;前端应以isLocked / isUnlocked判断是否真正解锁
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| seedContentId | string | 是 | 当前点击的视频内容 ID |
| sort | string | 否 | 排序方式:recommend/latest/hot,默认 recommend |
| categoryId | string | 否 | 分类 ID,沿用首页/分类页上下文 |
| tag | string | 否 | 标签筛选,沿用来源页上下文 |
| offset | number | 否 | 候选视频偏移量,默认 0 |
| pageSize | number | 否 | 每页条数,默认 20,最大 50 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| list | array | 视频播放流列表,list[0] 在 offset=0 时固定为 seed 视频 |
| list[] | FeedItem | 字段结构同 CONTENT-02 |
| nextOffset | number | 下一次请求应继续使用的 offset |
| hasMore | boolean | 是否还有后续视频 |
CONTENT-03 获取内容详情
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/:contentId |
| 接口用途 | 获取单条内容详情 |
| 权限要求 | 公开;作者本人带登录态时可查看自己未下架的草稿/审核中/拒绝内容 |
| 涉及表 | post, post_tag, tag, post_media, media, user_profile, post_like, post_favorite, subscription_tier, user_subscription |
接口逻辑:
- 查询
post表 根据 contentId 获取内容(读) - 访问控制:
status=published:公开可访问status!=published:仅作者本人带登录态且内容未下架(status!=removed)时可访问- 其它情况统一返回
40401 内容不存在
- 浏览计数:
- 仅
published内容计入公开浏览量 - 作者查看自己的草稿/审核中/拒绝内容不增加
viewCount
- 仅
- 关联查询
user_profile表 获取创作者信息(读) - 查询
media表 获取关联媒体信息(读) - 查询
user_subscription表 判断当前用户订阅状态(读):- 有权限:返回图片原图 URL / 视频播放 URL
- 无权限:图片不返回原图 URL,仅返回
blurUrl;视频不返回videoUrl,返回videoBlurPreviewUrl
- 查询
subscription_tier表 回填当前内容的visibilityTierName/visibilityTierPrice(读) - 查询
post_like表 检查 isLiked(WHERE user_id=me AND post_id=contentId)(读) - 查询
post_favorite表 检查 isFavorited(读) - 组装完整响应,并返回当前内容
status subscriber内容按有效订阅 +requiredTierLevel判断解锁;ppv内容只认content_unlock,不再因为存在任意订阅而放行
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 内容ID |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| contentId | string | 内容ID |
| creator | object | 创作者信息 {userId,username,nickname,avatar,isOnline,isFollowing} |
| title | string | 标题 |
| content | string | 正文 |
| type | string | 内容类型 post/video |
| status | string | 内容状态:draft / reviewing / published / rejected / removed |
| images | array | 图片列表(图文内容返回;视频内容通常为空数组) |
| images[].mediaId | string | 媒体ID |
| images[].url | url | 原图签名URL(已解锁时返回) |
| images[].blurUrl | url | 模糊图URL(未解锁时返回) |
| images[].thumbnailUrl | url | 缩略图URL |
| images[].width | number | 图片宽度 |
| images[].height | number | 图片高度 |
| images[].isLocked | boolean | 是否锁定 |
| videoUrl | url | 视频签名URL(已解锁时返回) |
| videoBlurPreviewUrl | url | 视频预览字段(未解锁时返回) |
| videoCoverUrl | url | 视频封面URL |
| videoWidth | number | 视频宽度 |
| videoHeight | number | 视频高度 |
| tags | string[] | 标签名称列表 |
| visibility | string | 可见性:free / subscriber / ppv |
| requiredTierLevel | number | subscriber 内容的最低可见档位 |
| visibilityTierName | string | subscriber 内容的档位展示名(free/ppv 时为空) |
| visibilityTierPrice | number | subscriber 内容的档位价格(分,free/ppv 时为 0) |
| ppvPrice | number | visibility=ppv 时返回内容价格(分) |
| isUnlocked | boolean | 当前用户是否已解锁;ppv 内容仅按 content_unlock 判断 |
| isPinned | boolean | 是否置顶 |
| viewCount | number | 浏览量 |
| likeCount | number | 点赞数 |
| favoriteCount | number | 收藏数 |
| commentCount | number | 评论数 |
| isLiked | boolean | 是否已点赞 |
| isFavorited | boolean | 是否已收藏 |
| createdAt | timestamp | 发布时间 |
CONTENT-03A 解锁 PPV 内容
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content/:contentId/unlock |
| 接口用途 | 解锁内容侧 PPV 内容 |
| 权限要求 | 需登录 |
| 涉及表 | post, content_unlock, wallet, wallet_transaction, revenue_share_record |
接口逻辑:
- 查询并锁定
post - 校验内容存在、已发布且
visibility=ppv - 校验当前用户不是作者本人
- 查询
content_unlock做幂等判断:- 已解锁:直接返回成功,
amount=0 - 未解锁:按
post.price_cents扣款、写入content_unlock、写入分润记录
- 已解锁:直接返回成功,
- 返回最新余额与解锁结果
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 内容 ID |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| contentId | string | 内容 ID |
| amount | number | 本次实际扣费金额(分);幂等命中时为 0 |
| remainingBalance | number | 扣费后的钱包余额 |
| isUnlocked | boolean | 是否已解锁,固定返回 true |
CONTENT-04 获取预设标签列表
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/tags |
| 接口用途 | 获取平台标签(系统预设 + 用户自定义热门标签) |
| 权限要求 | 公开 |
| 涉及表 | tag |
接口逻辑:
- 未传
keyword时,查询tag表中启用的系统预设标签(is_active=TRUE AND source=1) - 传入
keyword时,按标签名称做模糊匹配(ILIKE %keyword%),返回命中的启用标签 - keyword 搜索默认返回前 20 条结果
- 返回标签列表
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| keyword | string | 否 | 标签关键字,按名称模糊搜索;不传则返回默认预设标签列表 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| tags | array | 标签列表 |
| tags[].id | string | 标签ID |
| tags[].name | string | 标签名称 |
CONTENT-05 内容管理列表(创作者)
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/manage |
| 接口用途 | 创作者查看自己的内容列表 |
| 权限要求 | 创作者 |
| 涉及表 | post, media |
接口逻辑:
- 校验当前用户为创作者
- 查询
post表 WHERE creator_id=当前用户:- 若显式传
status,按该状态精确筛选 - 若不传
status,默认排除removed,避免已删除/已下架内容仍出现在默认管理列表中(读)
- 若显式传
- 关联查询
media表 获取封面图(读) - 分页返回,按 created_at 倒序
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| status | string | 否 | 筛选:draft/reviewing/published/rejected/removed;不传时默认返回除 removed 外的内容 |
| page | number | 否 | 页码 |
| pageSize | number | 否 | 每页条数 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| list | array | 内容列表 |
| list[].contentId | string | 内容ID |
| list[].title | string | 标题 |
| list[].coverUrl | url | 封面图URL |
| list[].type | string | 类型 |
| list[].status | string | 状态 |
| list[].rejectReason | string | 拒绝原因(rejected时) |
| list[].visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
| list[].visibilityTierName | string | 档位展示名(free 时为 null) |
| list[].viewCount | number | 浏览量 |
| list[].likeCount | number | 点赞数 |
| list[].favoriteCount | number | 收藏数 |
| list[].commentCount | number | 评论数 |
| list[].isPinned | boolean | 是否置顶 |
| list[].createdAt | timestamp | 发布时间 |
| pagination | object | 分页信息 |
CONTENT-06 编辑内容
| 项目 | 说明 |
|---|---|
| 接口路径 | PUT /client/api/v1/content/:contentId |
| 接口用途 | 创作者修改已发布的内容 |
| 权限要求 | 创作者(只能编辑自己的) |
| 涉及表 | post, post_tag, post_media, category_tag |
接口逻辑(全程单事务):
- 查询
post表 根据 contentId 获取内容(读) - 校验 post.creator_id = 当前用户
- 如果传 tags:在同一事务内
DELETE FROM post_tag WHERE post_id = :contentId- 批量
INSERT INTO post_tag (post_id, tag_id) VALUES ...
- 如果传 mediaIds:同样
DELETE FROM post_media WHERE post_id = :contentId然后批量 INSERT 重建 - 更新
post表 的 title/content/visibility/required_tier_level/price_cents 等字段(更新) - 返回更新后的内容信息
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 内容ID |
请求参数(Body):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| title | string | 否 | 标题,最长 30 字 |
| content | string | 否 | 正文内容,最长 200 字 |
| tags | object[] | 否 | 标签数组,1-3 个;结构同 CONTENT-01:每项 id 与 name 二选一,仅传 name 时同名复用或新建自定义标签 |
| mentions | string[] | 否 | 被 @ 用户的 userId 数组 |
| location | object | 否 | 地点 {name, code},传 null 清空 |
| visibility | string | 否 | 可见性:free / subscriber / ppv;不传则保持原值 |
| requiredTierLevel | number | 否 | visibility=subscriber 时必填;不传则保持原值 |
| price | number | 否 | visibility=ppv 时必填且 > 0;其它可见性传 0 或不传 |
| highlights | array | 否 | 视频撸点(仅视频类型) |
| publishDraft | boolean | 否 | 草稿提交发布:true 时把 status 从 0 改为 1 进入审核 |
响应数据: 同 CONTENT-01 响应结构
CONTENT-07 删除/下架内容
| 项目 | 说明 |
|---|---|
| 接口路径 | DELETE /client/api/v1/content/:contentId |
| 接口用途 | 创作者删除或下架内容 |
| 权限要求 | 创作者(只能操作自己的) |
| 涉及表 | post |
接口逻辑:
- 查询
post表 根据 contentId 获取内容(读) - 校验 post.creator_id = 当前用户
- 更新
post表 的 status=4(已下架)(更新) - 返回成功
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 内容ID |
响应数据: null
CONTENT-08 点赞/取消点赞
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content/:contentId/like |
| 接口用途 | 用户对内容点赞或取消(Toggle) |
| 权限要求 | 需登录 |
| 涉及表 | post_like, post |
接口逻辑:
- 查询
post_like表 检查是否已有记录(WHERE user_id=me AND post_id=contentId)(读) - 已点赞 → 取消:
- 软删除
post_like表 中的记录(更新 deleted_at) - 更新
post表 的 like_count-1(更新)
- 软删除
- 未点赞 → 点赞:
- 写入
post_like表(写) - 更新
post表 的 like_count+1(更新)
- 写入
- 返回当前点赞状态和最新点赞数
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 内容ID |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| isLiked | boolean | 当前点赞状态 |
| likeCount | number | 最新点赞数 |
CONTENT-09 收藏/取消收藏
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content/:contentId/favorite |
| 接口用途 | 用户收藏或取消收藏内容(Toggle) |
| 权限要求 | 需登录 |
| 涉及表 | post_favorite, post |
接口逻辑:
- 查询
post_favorite表 检查是否已有记录(WHERE user_id=me AND post_id=contentId)(读) - 已收藏 → 取消:
- 软删除
post_favorite表 中的记录(更新 deleted_at) - 更新
post表 的 favorite_count-1(更新)
- 软删除
- 未收藏 → 收藏:
- 写入
post_favorite表(写) - 更新
post表 的 favorite_count+1(更新)
- 写入
- 返回当前收藏状态和最新收藏数
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 内容ID |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| isFavorited | boolean | 当前收藏状态 |
| favoriteCount | number | 最新收藏数 |
CONTENT-10 获取收藏列表
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/favorites |
| 接口用途 | 用户查看自己收藏的内容 |
| 权限要求 | 需登录 |
| 涉及表 | post_favorite, post, media, user |
接口逻辑:
- 查询
post_favorite表 WHERE user_id=当前用户,按 created_at 倒序(读) - 关联查询
post表 获取内容信息(读) - 关联查询
media表 获取封面图(读) - 关联查询
user表 获取创作者信息(读) - 分页返回,结构同 CONTENT-02 列表,多一个 favoritedAt 字段
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| page | number | 否 | 页码 |
| pageSize | number | 否 | 每页条数 |
响应数据: 同 CONTENT-02 列表结构,额外增加 favoritedAt(timestamp)字段
CONTENT-10A 批量取消收藏
| 项目 | 说明 |
|---|---|
| 接口路径 | DELETE /client/api/v1/content/favorites |
| 接口用途 | 收藏列表场景下批量取消收藏 |
| 权限要求 | 需登录 |
| 涉及表 | post_favorite, post |
接口逻辑:
- 读取请求体
ids,要求至少 1 个,最多 100 个 - 对
ids做去重;若存在空值则返回40001 - 将当前用户在这些
post_id上仍处于有效状态的收藏记录批量软删除(更新deleted_at) - 仅对本次实际删除成功的内容执行
post.favorite_count - 1 - 对于未收藏、已取消收藏或重复传入的 id,不报错,直接忽略
- 返回实际删除成功的
removedIds和removedCount
请求参数(Body):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| ids | string[] | 是 | 要批量取消收藏的内容 ID 列表,最多 100 个 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| removedIds | string[] | 本次实际取消收藏成功的内容 ID 列表 |
| removedCount | number | 本次实际取消收藏成功的数量 |
CONTENT-11 获取首页分类列表
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/categories |
| 接口用途 | 获取首页分类 Tab 列表(美妆/旅行/美食等),用于首页顶部 Tab 渲染 |
| 权限要求 | 公开 |
| 涉及表 | category |
接口逻辑:
- 查询
category表 WHERE is_active=1,按 sort_order 正序(读) - 返回分类列表(仅返回 id/name/icon 等展示字段,不返回标签关联细节)
- 前端拿到 categoryId 后,请求 CONTENT-02
/client/api/v1/content/feed?categoryId=xxx获取该分类下的内容列表
分类与帖子的关联方式:
- 分类在后台配置时绑定多个预设标签(关系存于
category_tag关联表)- 帖子的标签存于
post_tag关联表,帖子本身不直接绑定 category- 首页按分类取内容 =
JOIN post_tag pt ON pt.post_id = post.id JOIN category_tag ct ON ct.tag_id = pt.tag_id WHERE ct.category_id = :categoryId
请求参数: 无
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| categories | array | 分类列表 |
| categories[].categoryId | string | 分类ID |
| categories[].name | string | 分类名称(如:美妆、旅行、美食) |
| categories[].icon | url | 分类图标URL(可空) |
| categories[].sortOrder | number | 排序序号 |
CONTENT-12 创建自定义标签
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content/tags/custom |
| 接口用途 | 标签管理场景下预先创建自定义标签。发布/编辑内容(CONTENT-01/06)已支持在 tags[].name 内联新建,常规发布流程无需先调本接口 |
| 权限要求 | 创作者 |
| 涉及表 | tag |
接口逻辑:
- 校验当前用户为创作者
- 校验 name 长度 1-32 字
- 查询
tag表 WHERE name=输入值,若已存在直接返回该 tag(去重) - 命中敏感词库 → 422 拒绝
- 写入
tag表(source=2,created_by=当前用户,is_active=1) - 返回新创建的 tag
请求参数(Body):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| name | string | 是 | 标签名,1-32 字 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | int64 | 标签ID |
| name | string | 标签名 |
| source | number | 1=系统 2=自定义 |
评论系统(CONTENT-13~17)
CommentVO 结构
所有评论接口返回的评论对象结构如下:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | 评论ID(int64 序列化为字符串) |
| postId | string | 所属帖子ID |
| parentId | string | 父评论ID;顶层评论时为空串 |
| userId | string | 评论者用户ID |
| nickname | string | 评论者昵称 |
| avatar | string | 评论者头像URL |
| content | string | 评论内容 |
| likeCount | number | 点赞数 |
| isLiked | boolean | 当前用户是否已点赞;匿名访问时固定 false |
| replyCount | number | 回复数;仅顶层评论列表有效,回复列表中固定 0 |
| isAuthor | boolean | 评论者是否为帖子作者 |
| isMine | boolean | 是否为当前用户所发;匿名访问时固定 false |
| createdAt | number | 发布时间(Unix 秒) |
CONTENT-13 获取帖子评论列表
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/:contentId/comments |
| 接口用途 | 获取指定帖子的顶层评论列表,支持分页 |
| 权限要求 | 可匿名(登录后返回 isLiked/isMine 真实值) |
| 涉及表 | post_comment, post_comment_like, user_profile |
接口逻辑:
- 校验 contentId 非空,查询
post表确认帖子存在(读) - 查询
post_comment表 WHERE post_id=contentId AND parent_id IS NULL AND deleted_at IS NULL,按 created_at 倒序分页(读) - 批量查询
user_profile获取评论者昵称/头像(读) - 批量统计各顶层评论的 reply_count(子查询 WHERE parent_id IN (...))(读)
- 若已登录,批量查询
post_comment_like获取当前用户点赞状态;匿名时 isLiked/isMine 固定 false(读) - 返回分页列表
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 帖子ID |
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| page | number | 否 | 页码,默认 1 |
| pageSize | number | 否 | 每页条数,默认 20 |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| list | CommentVO[] | 评论列表,见上方 CommentVO 结构 |
| pagination.total | number | 总条数 |
| pagination.page | number | 当前页码 |
| pagination.pageSize | number | 每页条数 |
CONTENT-14 获取评论回复列表
| 项目 | 说明 |
|---|---|
| 接口路径 | GET /client/api/v1/content/comments/:commentId/replies |
| 接口用途 | 获取指定顶层评论下的一级回复列表,支持分页 |
| 权限要求 | 可匿名(登录后返回 isLiked/isMine 真实值) |
| 涉及表 | post_comment, post_comment_like, user_profile |
接口逻辑:
- 解析 commentId(int64),查询
post_comment表确认评论存在(读) - 校验该评论为顶层评论(parent_id IS NULL),否则返回 400
- 查询
post_comment表 WHERE parent_id=commentId AND deleted_at IS NULL,按 created_at 正序分页(读) - 批量查询
user_profile获取昵称/头像(读) - 若已登录,批量查询
post_comment_like获取点赞状态;匿名时固定 false(读) - 返回分页列表(replyCount 固定为 0,不递归嵌套)
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| commentId | string | 是 | 顶层评论ID |
请求参数(Query):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| page | number | 否 | 页码,默认 1 |
| pageSize | number | 否 | 每页条数,默认 20 |
响应数据: 同 CONTENT-13,replyCount 固定为 0
CONTENT-15 发表评论
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content/:contentId/comments |
| 接口用途 | 用户对帖子发表评论或回复顶层评论(一级嵌套) |
| 权限要求 | 需登录 |
| 涉及表 | post_comment, post, user_profile |
接口逻辑:
- 校验 content 非空且不超过 500 字(Unicode 字符数)
- 查询
post表确认帖子存在(读) - 若传了 parentId:
- 查询
post_comment表确认父评论存在(读) - 校验父评论必须为顶层评论(不允许回复回复,只支持一级嵌套)
- 校验父评论的 post_id 与当前 contentId 一致,防跨帖回复
- 查询
- 写入
post_comment表(写) - 返回新评论的 CommentVO
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| contentId | string | 是 | 帖子ID |
请求参数(Body):
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| content | string | 是 | 评论内容,最长 500 字 |
| parentId | string | 否 | 父评论ID;不传或传空串表示顶层评论 |
响应数据: CommentVO(见上方结构)
CONTENT-16 删除评论
| 项目 | 说明 |
|---|---|
| 接口路径 | DELETE /client/api/v1/content/comments/:commentId |
| 接口用途 | 评论作者删除自己的评论(软删除) |
| 权限要求 | 需登录;仅评论作者本人可删 |
| 涉及表 | post_comment |
接口逻辑:
- 解析 commentId,查询
post_comment表确认评论存在(读) - 校验当前用户 = 评论的 user_id,不匹配返回 403
- 软删除:更新
post_comment.deleted_at,同步更新post.comment_count - 1(更新) - 返回 null
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| commentId | string | 是 | 评论ID |
响应数据: null
CONTENT-17 评论点赞/取消点赞
| 项目 | 说明 |
|---|---|
| 接口路径 | POST /client/api/v1/content/comments/:commentId/like |
| 接口用途 | 对评论点赞或取消点赞(Toggle) |
| 权限要求 | 需登录 |
| 涉及表 | post_comment_like, post_comment |
接口逻辑:
- 解析 commentId,查询
post_comment表确认评论存在(读) - 查询
post_comment_like表检查是否已点赞(WHERE user_id=me AND comment_id=commentId)(读) - 已点赞 → 取消:
- 删除
post_comment_like记录(更新) post_comment.like_count - 1(更新)
- 删除
- 未点赞 → 点赞:
- 写入
post_comment_like(写) post_comment.like_count + 1(更新)
- 写入
- 返回当前点赞状态和最新点赞数
路径参数:
| 字段 | 类型 | 必选 | 说明 |
|---|---|---|---|
| commentId | string | 是 | 评论ID |
响应数据:
| 字段 | 类型 | 说明 |
|---|---|---|
| isLiked | boolean | 当前点赞状态 |
| likeCount | number | 最新点赞数 |