581 lines
25 KiB
Markdown
581 lines
25 KiB
Markdown
# API-04 内容系统(CONTENT)
|
||
|
||
> 共 12 个接口:CONTENT-01~12
|
||
|
||
### 内容类型枚举(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 | 已下架 |
|
||
|
||
### 可见性与订阅等级对应关系(free + 4 档)
|
||
|
||
| visibility(可见性) | DB值 | 所需 subscription_tier.tier_level | 说明 |
|
||
|---------------------|------|----------------------------------|------|
|
||
| free | 0 | 无需订阅 | 免费公开,所有用户可见 |
|
||
| junior | 1 | ≥ 1 | 初级会员及以上 |
|
||
| basic | 2 | ≥ 2 | 基础会员及以上 |
|
||
| senior | 3 | ≥ 3 | 高级会员及以上 |
|
||
| supreme | 4 | ≥ 4 | 至尊会员(独占)|
|
||
|
||
> **判断逻辑:** `user_subscription.tier_level >= post.visibility` 且订阅状态有效,满足即有权限。`visibility=0` (free) 的内容无需检查订阅状态。
|
||
>
|
||
> **档位可配置说明:** 系统固定 4 个 tier_level 槽位,但每个创作者可在 SUB-07 中独立启用 1~4 档(必须从 level=1 起连续启用),并自定义档位名称、描述、价格。**因此创作者实际可选的 visibility 档位 = 该创作者已启用的 tier_level + free**。
|
||
>
|
||
> **App 发布表单约束(CONTENT-01):** 发布页加载时应先调用 **SUB-01** 拉取当前创作者自己已启用的档位列表(`GET /subscription/tiers/:creatorId`,creatorId 传自己),按返回的 tier_level 渲染"可见档位"下拉选项;不应再硬编码 5 项。后端会校验提交的 `visibility` 必须为 `free` 或当前创作者已启用且 `is_active=true` 的 tier_level 对应字符串。
|
||
>
|
||
> **若创作者尚未配置任何档位**:仅允许选择 `free`;提交其它值返回 422。
|
||
|
||
### 关于评论(commentCount)
|
||
|
||
> 评论系统为 P1 扩展功能(参见数据库设计文档 `post_comment` 表)。当前阶段 `commentCount` 固定返回 `0`,评论 CRUD 接口将在 P1 阶段补充。
|
||
|
||
---
|
||
|
||
#### CONTENT-01 发布内容(含草稿)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/content |
|
||
| 接口用途 | 创作者发布动态/视频,或保存为草稿 |
|
||
| 权限要求 | 创作者(user.role=2) |
|
||
| 涉及表 | post, post_media, post_tag, post_mention, post_highlight, media, tag |
|
||
|
||
**接口逻辑:**
|
||
1. 校验当前用户 user.role=2(创作者权限)
|
||
2. 校验 mediaIds:查询 `media` 表 确认 process_status=2 且 user_id=当前用户
|
||
3. 校验 tags(1-3 个):每项 `id` 与 `name` 二选一。传 `id` 走 `tag` 表存在性校验;只传 `name` 时若同名 tag 已存在则复用,否则当场创建 source=2 的自定义标签(等价于内联 CONTENT-12)
|
||
4. 校验 mentions:查询 `user` 表 确认被 @ 用户存在
|
||
5. 若 type=2(video)且包含 highlights:校验 time_offset_seconds < 视频时长
|
||
6. 校验 visibility:若不是 `free`,查询 `subscription_tier` 表 WHERE creator_id=当前用户 AND tier_level=映射值 AND is_active=true,不存在则返回 422("该档位未启用")
|
||
7. 写入 `post` 表(status=isDraft?0:1,写入 location_name/code,visibility 存映射后的 int 值)
|
||
8. 写入关系表:post_media / post_tag / post_mention / post_highlight
|
||
9. 草稿(isDraft=true)跳过审核流程;非草稿进入审核队列
|
||
10. 返回新创建的内容信息
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| title | string | 否 | 标题,最长 30 字 |
|
||
| content | string | 否 | 正文内容,最长 200 字 |
|
||
| type | string | 是 | 内容类型:post/video |
|
||
| mediaIds | int64[] | 否 | 媒体ID数组(图片最多9张,或1个视频) |
|
||
| 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` 或当前创作者已启用的档位 key(junior/basic/senior/supreme 之一)。后端会校验该 key 对应的 tier_level 在当前创作者的 `subscription_tier` 中存在且 `is_active=true`,否则返回 422 |
|
||
| highlights | array | 否 | 视频章节标记数组(type=video 时),用于在播放进度条上打点跳转 |
|
||
| highlights[].timeOffsetSeconds | int | 是 | 标记时间点(秒),必须 < 视频时长 |
|
||
| highlights[].label | string | 否 | 标记说明文字(最长 40 字) |
|
||
| isDraft | boolean | 否 | 是否保存为草稿,默认 false |
|
||
|
||
**校验返回码:**
|
||
- 422: 字段超长 / 标签数量不在 1-3 / 媒体处理未完成 / highlights 时间点超出视频时长 / visibility 不在当前创作者已启用档位内
|
||
- 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 | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||
| visibilityTierName | string | 可见档位的展示名(创作者自定义名称,free 时为 null) |
|
||
| 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 |
|
||
|
||
**接口逻辑:**
|
||
1. 根据 sort 参数选择排序策略:
|
||
- recommend:70%算法推荐 + 20%新创作者池 + 10%运营位(查询 `recommend_slot` 表,读)
|
||
- latest:按 post.created_at 倒序
|
||
- hot:按浏览量+点赞数加权排序
|
||
2. 处理分类筛选(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`(读)
|
||
- 如果都不传:不加标签过滤,返回全部
|
||
3. 查询 `post` 表 WHERE status=2(已上架)(读)
|
||
4. 关联查询 `user` 表 获取创作者信息(profile.display_name、profile.avatar_url)(读)
|
||
5. 关联查询 `media` 表 获取封面图/缩略图(读)
|
||
6. 关联查询 `content_boost` 表 获取内容注水数据,外显数据 = 真实值 + boost值(读)
|
||
> 注:`content_boost` 用于单条内容的注水(view/like/favorite);`creator_boost` 用于创作者主页的注水(follower/subscriber),后者在 API-08 CREATOR-02 中使用。
|
||
7. 如果当前用户已登录,批量查询 `user_subscription` 表(按本页 author_id 列表 + 当前 viewer_id 一次查齐,避免 N+1),逐条按 `visibility=0 OR (订阅有效 AND user_subscription.tier_level >= post.visibility)` 计算 `isLocked`。同时关联 `subscription_tier` 取 `name` 作为 `visibilityTierName` 回填(读)
|
||
8. 分页返回内容卡片列表
|
||
|
||
**请求参数(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[].visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||
| list[].visibilityTierName | string | 该档位在当前创作者下的展示名(free 时为 null) |
|
||
| list[].visibilityTierPrice | number | 该档位价格(分),free 时省略 |
|
||
| 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 |
|
||
|
||
**接口逻辑:**
|
||
1. 从 JWT 取 viewer_id
|
||
2. 查询 `user_follow` 表,取当前用户全部 `followee_id`(`deleted_at IS NULL`);若为空直接返回空列表
|
||
3. 调用与 CONTENT-02 相同的 `ListFeed`,在 `post` 上追加 `author_id = ANY($followeeIds)` 过滤,status=2,按 published_at 倒序
|
||
4. 组装逻辑与 CONTENT-02 第 4~7 步一致(作者资料、媒体封面/时长、订阅档位、isLocked 计算)
|
||
5. 分页返回
|
||
|
||
**请求参数(Query):** 与 CONTENT-02 一致(sort/categoryId/tag/page/pageSize)
|
||
|
||
**响应数据:** 与 CONTENT-02 `ContentFeedResp` 完全一致(list[] 为 FeedItem)
|
||
|
||
---
|
||
|
||
#### CONTENT-03 获取内容详情
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/content/:contentId |
|
||
| 接口用途 | 获取单条内容详情 |
|
||
| 权限要求 | 按订阅档位判断 |
|
||
| 涉及表 | post, post_tag, tag, post_media, media, user, view_history, post_like, post_favorite, user_subscription, content_boost |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `post` 表 根据 contentId 获取内容(读)
|
||
2. 浏览去重 + 计数(同一事务):
|
||
- `INSERT INTO view_history (user_id, post_id, last_viewed_at) VALUES (:uid, :pid, NOW()) ON CONFLICT (user_id, post_id, view_date) DO NOTHING RETURNING id`
|
||
- 仅当 RETURNING 返回行数 > 0(即当天首次浏览)才执行 `UPDATE post SET view_count = view_count + 1 WHERE id = :pid`
|
||
- 保证 `post.view_count` 与 `view_history` 按日去重一致
|
||
3. 关联查询 `user` 表 获取创作者信息(读)
|
||
4. 查询 `media` 表 获取关联媒体信息(读)
|
||
5. 查询 `user_subscription` 表 判断当前用户订阅状态(读):
|
||
- **有权限**:签发媒体签名URL
|
||
- **无权限**:返回模糊图URL、文字截断前2行、视频返回3秒模糊预览URL(media.preview_url)
|
||
6. 查询 `content_boost` 表 获取注水数据(读)
|
||
7. 查询 `post_like` 表 检查 isLiked(WHERE user_id=me AND post_id=contentId)(读)
|
||
8. 查询 `post_favorite` 表 检查 isFavorited(读)
|
||
9. 组装完整响应
|
||
|
||
**路径参数:**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| contentId | string | 是 | 内容ID |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| contentId | string | 内容ID |
|
||
| creator | object | 创作者信息 |
|
||
| title | string | 标题 |
|
||
| content | string | 正文(无权限时截断前2行) |
|
||
| type | string | 内容类型 post/video |
|
||
| images | array | 图片列表 |
|
||
| images[].mediaId | string | 媒体ID |
|
||
| images[].url | url | 原图签名URL(有权限时) |
|
||
| images[].blurUrl | url | 模糊图URL |
|
||
| images[].thumbnailUrl | url | 缩略图URL |
|
||
| images[].isLocked | boolean | 是否锁定 |
|
||
| videoUrl | url | 视频签名URL(有权限时) |
|
||
| videoBlurPreviewUrl | url | 3秒模糊预览URL(无权限时) |
|
||
| videoCoverUrl | url | 视频封面URL |
|
||
| videoDuration | number | 视频时长(秒) |
|
||
| highlights | array | 视频章节标记列表 [{timeOffsetSeconds,label}] |
|
||
| tags | object[] | 标签列表 [{id,name,source}] |
|
||
| mentions | object[] | 被 @ 用户列表 [{userId,nickname,avatar}] |
|
||
| location | object | 地点信息 {name,code} |
|
||
| visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||
| visibilityTierName | string | 该档位在内容作者下的展示名(创作者自定义;free 时为 null) |
|
||
| visibilityTierPrice | number | 该档位月价(代币,free 时为 0)— 用于无权限时引导订阅 |
|
||
| isUnlocked | boolean | 当前用户是否已解锁(基于 user_subscription.tier_level ≥ post.visibility 且订阅有效) |
|
||
| isPinned | boolean | 是否置顶 |
|
||
| viewCount | number | 浏览量(含boost) |
|
||
| likeCount | number | 点赞数(含boost) |
|
||
| favoriteCount | number | 收藏数(含boost) |
|
||
| commentCount | number | 评论数 |
|
||
| isLiked | boolean | 是否已点赞 |
|
||
| isFavorited | boolean | 是否已收藏 |
|
||
| createdAt | timestamp | 发布时间 |
|
||
|
||
---
|
||
|
||
#### CONTENT-04 获取预设标签列表
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/content/tags |
|
||
| 接口用途 | 获取平台标签(系统预设 + 用户自定义热门标签) |
|
||
| 权限要求 | 公开 |
|
||
| 涉及表 | tag |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `tag` 表 WHERE is_active=1(读)
|
||
2. 根据 source 过滤:source=1 仅系统预设;source=2 仅用户自定义;不传则全部
|
||
3. 根据 sort:默认按 sort_order 升序;sort=hot 时按 use_count DESC 排序
|
||
4. 返回标签列表(含 source 与 useCount,与 CONTENT-03 详情字段对齐)
|
||
|
||
**请求参数(Query):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| source | int | 否 | 1=系统预设 2=用户自定义;不传返回全部 |
|
||
| sort | string | 否 | 排序:默认按 sort_order;hot=按 use_count 倒序 |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| tags | array | 标签列表 |
|
||
| tags[].id | string | 标签ID |
|
||
| tags[].name | string | 标签名称 |
|
||
| tags[].source | int | 1=系统预设 2=用户自定义 |
|
||
| tags[].useCount | number | 累计使用次数(用于热度展示) |
|
||
|
||
---
|
||
|
||
#### CONTENT-05 内容管理列表(创作者)
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/content/manage |
|
||
| 接口用途 | 创作者查看自己的内容列表 |
|
||
| 权限要求 | 创作者 |
|
||
| 涉及表 | post, media |
|
||
|
||
**接口逻辑:**
|
||
1. 校验当前用户为创作者
|
||
2. 查询 `post` 表 WHERE creator_id=当前用户,可选按 status 筛选(读)
|
||
3. 关联查询 `media` 表 获取封面图(读)
|
||
4. 分页返回,按 created_at 倒序
|
||
|
||
**请求参数(Query):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| status | string | 否 | 筛选:draft/reviewing/published/rejected/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 |
|
||
|
||
**接口逻辑(全程单事务):**
|
||
1. 查询 `post` 表 根据 contentId 获取内容(读)
|
||
2. 校验 post.creator_id = 当前用户
|
||
3. 如果传 tags:在同一事务内
|
||
- `DELETE FROM post_tag WHERE post_id = :contentId`
|
||
- 批量 `INSERT INTO post_tag (post_id, tag_id) VALUES ...`
|
||
4. 如果传 mediaIds:同样 `DELETE FROM post_media WHERE post_id = :contentId` 然后批量 INSERT 重建
|
||
5. 更新 `post` 表 的 title/content/visibility/category_id 等字段(更新)
|
||
6. 返回更新后的内容信息
|
||
|
||
**路径参数:**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| 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 | 否 | 可见性档位 key:`free` 或当前创作者已启用的档位之一(同 CONTENT-01 校验规则),不传则保持原值 |
|
||
| highlights | array | 否 | 视频撸点(仅视频类型) |
|
||
| publishDraft | boolean | 否 | 草稿提交发布:true 时把 status 从 0 改为 1 进入审核 |
|
||
|
||
**响应数据:** 同 CONTENT-01 响应结构
|
||
|
||
---
|
||
|
||
#### CONTENT-07 删除/下架内容
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | DELETE /client/api/v1/content/:contentId |
|
||
| 接口用途 | 创作者删除或下架内容 |
|
||
| 权限要求 | 创作者(只能操作自己的) |
|
||
| 涉及表 | post |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `post` 表 根据 contentId 获取内容(读)
|
||
2. 校验 post.creator_id = 当前用户
|
||
3. 更新 `post` 表 的 status=4(已下架)(更新)
|
||
4. 返回成功
|
||
|
||
**路径参数:**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| contentId | string | 是 | 内容ID |
|
||
|
||
**响应数据:** null
|
||
|
||
---
|
||
|
||
#### CONTENT-08 点赞/取消点赞
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/content/:contentId/like |
|
||
| 接口用途 | 用户对内容点赞或取消(Toggle) |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | post_like, post |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `post_like` 表 检查是否已有记录(WHERE user_id=me AND post_id=contentId)(读)
|
||
2. **已点赞 → 取消**:
|
||
- 软删除 `post_like` 表 中的记录(更新 deleted_at)
|
||
- 更新 `post` 表 的 like_count-1(更新)
|
||
3. **未点赞 → 点赞**:
|
||
- 写入 `post_like` 表(写)
|
||
- 更新 `post` 表 的 like_count+1(更新)
|
||
4. 返回当前点赞状态和最新点赞数
|
||
|
||
**路径参数:**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| contentId | string | 是 | 内容ID |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| isLiked | boolean | 当前点赞状态 |
|
||
| likeCount | number | 最新点赞数 |
|
||
|
||
---
|
||
|
||
#### CONTENT-09 收藏/取消收藏
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | POST /client/api/v1/content/:contentId/favorite |
|
||
| 接口用途 | 用户收藏或取消收藏内容(Toggle) |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | post_favorite, post |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `post_favorite` 表 检查是否已有记录(WHERE user_id=me AND post_id=contentId)(读)
|
||
2. **已收藏 → 取消**:
|
||
- 软删除 `post_favorite` 表 中的记录(更新 deleted_at)
|
||
- 更新 `post` 表 的 favorite_count-1(更新)
|
||
3. **未收藏 → 收藏**:
|
||
- 写入 `post_favorite` 表(写)
|
||
- 更新 `post` 表 的 favorite_count+1(更新)
|
||
4. 返回当前收藏状态和最新收藏数
|
||
|
||
**路径参数:**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| contentId | string | 是 | 内容ID |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| isFavorited | boolean | 当前收藏状态 |
|
||
| favoriteCount | number | 最新收藏数 |
|
||
|
||
---
|
||
|
||
#### CONTENT-10 获取收藏列表
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/content/favorites |
|
||
| 接口用途 | 用户查看自己收藏的内容 |
|
||
| 权限要求 | 需登录 |
|
||
| 涉及表 | post_favorite, post, media, user |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `post_favorite` 表 WHERE user_id=当前用户,按 created_at 倒序(读)
|
||
2. 关联查询 `post` 表 获取内容信息(读)
|
||
3. 关联查询 `media` 表 获取封面图(读)
|
||
4. 关联查询 `user` 表 获取创作者信息(读)
|
||
5. 分页返回,结构同 CONTENT-02 列表,多一个 favoritedAt 字段
|
||
|
||
**请求参数(Query):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| page | number | 否 | 页码 |
|
||
| pageSize | number | 否 | 每页条数 |
|
||
|
||
**响应数据:** 同 CONTENT-02 列表结构,额外增加 `favoritedAt`(timestamp)字段
|
||
|
||
---
|
||
|
||
#### CONTENT-11 获取首页分类列表
|
||
|
||
| 项目 | 说明 |
|
||
|------|------|
|
||
| 接口路径 | GET /client/api/v1/content/categories |
|
||
| 接口用途 | 获取首页分类 Tab 列表(美妆/旅行/美食等),用于首页顶部 Tab 渲染 |
|
||
| 权限要求 | 公开 |
|
||
| 涉及表 | category |
|
||
|
||
**接口逻辑:**
|
||
1. 查询 `category` 表 WHERE is_active=1,按 sort_order 正序(读)
|
||
2. 返回分类列表(仅返回 id/name/icon 等展示字段,不返回标签关联细节)
|
||
3. 前端拿到 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 |
|
||
|
||
**接口逻辑:**
|
||
1. 校验当前用户为创作者
|
||
2. 校验 name 长度 1-32 字
|
||
3. 查询 `tag` 表 WHERE name=输入值,若已存在直接返回该 tag(去重)
|
||
4. 命中敏感词库 → 422 拒绝
|
||
5. 写入 `tag` 表(source=2,created_by=当前用户,is_active=1)
|
||
6. 返回新创建的 tag
|
||
|
||
**请求参数(Body):**
|
||
|
||
| 字段 | 类型 | 必选 | 说明 |
|
||
|------|------|------|------|
|
||
| name | string | 是 | 标签名,1-32 字 |
|
||
|
||
**响应数据:**
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| id | int64 | 标签ID |
|
||
| name | string | 标签名 |
|
||
| source | number | 1=系统 2=自定义 |
|
||
|