docs(api): update content-side ppv unlock docs
This commit is contained in:
parent
b6b8afca9f
commit
24c5fae1ba
@ -114,16 +114,23 @@
|
||||
|------|------|
|
||||
| 接口路径 | GET /client/api/v1/media/:mediaId/signed-url |
|
||||
| 接口用途 | 获取媒体文件的对外访问地址 |
|
||||
| 权限要求 | 登录即可(内容层面的订阅/PPV 权限在内容接口中判定) |
|
||||
| 涉及表 | media |
|
||||
| 权限要求 | 需登录;是否返回真实地址由媒体所属内容的访问权限决定 |
|
||||
| 涉及表 | media, post_media, post, user_subscription, ppv_unlock, content_unlock |
|
||||
|
||||
**接口逻辑:**
|
||||
1. 查询 `media` 获取 `storage_path`、`type`、`process_status`
|
||||
2. 若 `process_status != 2` → 返回 `40901 media 仍在处理中`
|
||||
3. 根据 `type` 生成 URL:
|
||||
3. 判定当前用户是否有访问权限:
|
||||
- 自己上传的媒体,直接允许
|
||||
- 命中 IM 侧 `ppv_unlock`,允许
|
||||
- 媒体关联到内容时:
|
||||
- `free`:允许
|
||||
- `subscriber`:校验有效订阅且 `tier_level >= requiredTierLevel`
|
||||
- `ppv`:校验 `content_unlock`
|
||||
4. 根据 `type` 生成 URL:
|
||||
- 图片:`img_cn_cdn + replaceExt(storage_path, ".bnc")`
|
||||
- 视频:`movie_cn_cdn + storage_path + ?sign=md5(time-rand-apikey)&time=..&rand=..&uid=..`
|
||||
4. 返回 URL(视频签名有时效,建议**即取即用**,不要缓存)
|
||||
5. 返回结果;无权限时不返回 `signedUrl`,仅返回 `hasAccess=false` 及预览字段
|
||||
|
||||
**路径参数:**
|
||||
|
||||
@ -142,7 +149,7 @@
|
||||
| mimeType | string | 文件MIME类型 |
|
||||
| size | number | 文件大小(字节) |
|
||||
| expiresAt | timestamp | 视频签名过期时间戳;图片字段为 0 |
|
||||
| hasAccess | boolean | 保留字段,当前固定返回 true(细粒度权限判断移至内容接口) |
|
||||
| hasAccess | boolean | 当前用户是否有访问权限 |
|
||||
| width | number | 媒体宽度(像素) |
|
||||
| height | number | 媒体高度(像素) |
|
||||
| duration | number | 视频时长(秒,图片为 0) |
|
||||
|
||||
@ -21,23 +21,18 @@
|
||||
| 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 | 至尊会员(独占)|
|
||||
| visibility(API值) | DB值 | 额外字段 | 解锁规则 | 说明 |
|
||||
|---------------------|------|----------|----------|------|
|
||||
| free | 1 | 无 | 所有人可见 | 免费公开内容 |
|
||||
| subscriber | 2 | `requiredTierLevel` | 当前用户存在有效订阅,且 `tier_level >= requiredTierLevel` | 订阅可见内容 |
|
||||
| ppv | 3 | `price` | 作者本人或已存在 `content_unlock` 记录 | 内容单独付费解锁 |
|
||||
|
||||
> **判断逻辑:** `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。
|
||||
> **发布约束:**
|
||||
> 1. `visibility=subscriber` 时,必须额外传 `requiredTierLevel`,且该档位必须已在订阅设置中启用。
|
||||
> 2. `visibility=ppv` 时,必须额外传 `price`,且 `price > 0`。
|
||||
> 3. 非 `ppv` 内容不允许传正数 `price`。
|
||||
|
||||
### 关于评论(commentCount)
|
||||
|
||||
@ -61,8 +56,11 @@
|
||||
4. 校验 tags(1-3 个):每项 `id` 与 `name` 二选一。传 `id` 走 `tag` 表存在性校验;只传 `name` 时若同名 tag 已存在则复用,否则当场创建 source=2 的自定义标签(等价于内联 CONTENT-12)
|
||||
5. 校验 mentions:查询 `user` 表 确认被 @ 用户存在
|
||||
6. 若 type=2(video)且包含 highlights:校验 time_offset_seconds < 视频时长
|
||||
7. 校验 visibility:若不是 `free`,查询 `subscription_tier` 表 WHERE creator_id=当前用户 AND tier_level=映射值 AND is_active=true,不存在则返回 422("该档位未启用")
|
||||
8. 写入 `post` 表(status=isDraft?0:1,写入 location_name/code,visibility 存映射后的 int 值;有 `coverMediaId` 时写入 `post.cover_media_id`,否则回退到首个内容媒体)
|
||||
7. 校验可见性:
|
||||
- `visibility=subscriber`:校验 `requiredTierLevel` 必填,且该档位在当前创作者的 `subscription_tier` 中存在且 `is_active=true`
|
||||
- `visibility=ppv`:校验 `price > 0`
|
||||
- 其它可见性不允许传正数 `price`
|
||||
8. 写入 `post` 表(status=isDraft?0:1,写入 location_name/code、visibility、required_tier_level、price_cents;有 `coverMediaId` 时写入 `post.cover_media_id`,否则回退到首个内容媒体)
|
||||
9. 写入关系表:post_media / post_tag / post_mention / post_highlight
|
||||
10. 草稿(isDraft=true)跳过审核流程;非草稿进入审核队列
|
||||
11. 返回新创建的内容信息
|
||||
@ -72,7 +70,7 @@
|
||||
| 字段 | 类型 | 必选 | 说明 |
|
||||
|------|------|------|------|
|
||||
| title | string | 否 | 标题,最长 30 字 |
|
||||
| content | string | 否 | 正文内容,最长 200 字 |
|
||||
| content | string | 是 | 正文内容,最长 200 字 |
|
||||
| type | string | 是 | 内容类型:post/video |
|
||||
| mediaIds | int64[] | 否 | 媒体ID数组(图片最多9张,或1个视频) |
|
||||
| coverMediaId | string | 否 | 显式封面图媒体ID。`type=video` 时必填,必须是当前创作者自己的已处理完成图片媒体;其它类型可不传 |
|
||||
@ -83,14 +81,16 @@
|
||||
| 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 |
|
||||
| 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 时间点超出视频时长 / visibility 不在当前创作者已启用档位内
|
||||
- 422: 字段超长 / 标签数量不在 1-3 / 媒体处理未完成 / highlights 时间点超出视频时长 / subscriber 档位未启用
|
||||
- 403: 非创作者 / 媒体不属于当前用户
|
||||
- 404: 标签/被@用户不存在
|
||||
|
||||
@ -106,8 +106,10 @@
|
||||
| tags | object[] | 标签列表 [{id,name,source}] |
|
||||
| mentions | object[] | 被 @ 用户列表 [{userId,nickname}] |
|
||||
| location | object | 地点信息 |
|
||||
| visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||||
| visibilityTierName | string | 可见档位的展示名(创作者自定义名称,free 时为 null) |
|
||||
| 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 | 是否草稿 |
|
||||
@ -138,7 +140,10 @@
|
||||
5. 关联查询 `media` 表 获取封面图/缩略图(读)。优先使用 `post.cover_media_id` 对应媒体生成 `coverUrl`;未设置时回退到内容首张图片
|
||||
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` 回填(读)
|
||||
7. 如果当前用户已登录,批量查询 `user_subscription` 与 `content_unlock`:
|
||||
- `subscriber` 内容按 `tier_level >= requiredTierLevel` 判断是否解锁
|
||||
- `ppv` 内容仅按 `content_unlock` 判断是否已购
|
||||
- 返回 `isLocked / isUnlocked / ppvPrice`
|
||||
8. 分页返回内容卡片列表
|
||||
|
||||
**请求参数(Query):**
|
||||
@ -162,10 +167,12 @@
|
||||
| list[].blurCoverUrl | url | 模糊封面URL |
|
||||
| list[].type | string | 类型:post/video |
|
||||
| list[].duration | number | 视频时长(秒)。type 为 video 时返回,非视频或无媒体时省略 |
|
||||
| list[].videoUrl | url | 视频播放签名URL。type 为 video 且 `isLocked=false` 时返回,锁定内容不返回 |
|
||||
| list[].visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||||
| list[].visibilityTierName | string | 该档位在当前创作者下的展示名(free 时为 null) |
|
||||
| list[].visibilityTierPrice | number | 该档位价格(分),free 时省略 |
|
||||
| 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 | 创作者信息 |
|
||||
@ -223,7 +230,8 @@
|
||||
5. 从 `post` 中查询同上下文的候选视频流:`status=2 AND type=video`,并排除 `seedContentId`
|
||||
6. `offset=0` 时,返回列表首条固定插入 seed 视频,其余 `pageSize-1` 条由候选视频补足
|
||||
7. `offset>0` 时,不再重复返回 seed 视频,只返回后续候选视频
|
||||
8. 列表项组装逻辑与 CONTENT-02 一致,复用 `FeedItem` 字段口径(封面、时长、视频地址、锁态、创作者信息等)
|
||||
8. 列表项组装逻辑与 CONTENT-02 一致,复用 `FeedItem` 字段口径(封面、时长、锁态、创作者信息等)
|
||||
9. 为兼容沉浸式播放器预加载,`video-feed` 会补充 `videoUrl`;前端应以 `isLocked / isUnlocked` 判断是否真正解锁
|
||||
|
||||
**请求参数(Query):**
|
||||
|
||||
@ -274,6 +282,7 @@
|
||||
8. 查询 `post_like` 表 检查 isLiked(WHERE user_id=me AND post_id=contentId)(读)
|
||||
9. 查询 `post_favorite` 表 检查 isFavorited(读)
|
||||
10. 组装完整响应,并返回当前内容 `status`
|
||||
11. `subscriber` 内容按有效订阅 + `requiredTierLevel` 判断解锁;`ppv` 内容只认 `content_unlock`,不再因为存在任意订阅而放行
|
||||
|
||||
**路径参数:**
|
||||
|
||||
@ -305,10 +314,12 @@
|
||||
| videoWidth | number | 视频宽度 |
|
||||
| videoHeight | number | 视频高度 |
|
||||
| tags | string[] | 标签名称列表 |
|
||||
| visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||||
| visibilityTierName | string | 该档位在内容作者下的展示名(创作者自定义;free 时为 null) |
|
||||
| visibilityTierPrice | number | 该档位月价(代币,free 时为 0)— 用于无权限时引导订阅 |
|
||||
| isUnlocked | boolean | 当前用户是否已解锁(基于 user_subscription.tier_level ≥ post.visibility 且订阅有效) |
|
||||
| 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 | 点赞数 |
|
||||
@ -320,6 +331,41 @@
|
||||
|
||||
---
|
||||
|
||||
#### CONTENT-03A 解锁 PPV 内容
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 接口路径 | POST /client/api/v1/content/:contentId/unlock |
|
||||
| 接口用途 | 解锁内容侧 PPV 内容 |
|
||||
| 权限要求 | 需登录 |
|
||||
| 涉及表 | post, content_unlock, wallet, wallet_transaction, revenue_share_record |
|
||||
|
||||
**接口逻辑:**
|
||||
1. 查询并锁定 `post`
|
||||
2. 校验内容存在、已发布且 `visibility=ppv`
|
||||
3. 校验当前用户不是作者本人
|
||||
4. 查询 `content_unlock` 做幂等判断:
|
||||
- 已解锁:直接返回成功,`amount=0`
|
||||
- 未解锁:按 `post.price_cents` 扣款、写入 `content_unlock`、写入分润记录
|
||||
5. 返回最新余额与解锁结果
|
||||
|
||||
**路径参数:**
|
||||
|
||||
| 字段 | 类型 | 必选 | 说明 |
|
||||
|------|------|------|------|
|
||||
| contentId | string | 是 | 内容 ID |
|
||||
|
||||
**响应数据:**
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| contentId | string | 内容 ID |
|
||||
| amount | number | 本次实际扣费金额(分);幂等命中时为 0 |
|
||||
| remainingBalance | number | 扣费后的钱包余额 |
|
||||
| isUnlocked | boolean | 是否已解锁,固定返回 true |
|
||||
|
||||
---
|
||||
|
||||
#### CONTENT-04 获取预设标签列表
|
||||
|
||||
| 项目 | 说明 |
|
||||
@ -415,7 +461,7 @@
|
||||
- `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 等字段(更新)
|
||||
5. 更新 `post` 表 的 title/content/visibility/required_tier_level/price_cents 等字段(更新)
|
||||
6. 返回更新后的内容信息
|
||||
|
||||
**路径参数:**
|
||||
@ -433,7 +479,9 @@
|
||||
| tags | object[] | 否 | 标签数组,1-3 个;结构同 CONTENT-01:每项 `id` 与 `name` 二选一,仅传 `name` 时同名复用或新建自定义标签 |
|
||||
| mentions | string[] | 否 | 被 @ 用户的 userId 数组 |
|
||||
| location | object | 否 | 地点 {name, code},传 null 清空 |
|
||||
| visibility | string | 否 | 可见性档位 key:`free` 或当前创作者已启用的档位之一(同 CONTENT-01 校验规则),不传则保持原值 |
|
||||
| visibility | string | 否 | 可见性:`free` / `subscriber` / `ppv`;不传则保持原值 |
|
||||
| requiredTierLevel | number | 否 | `visibility=subscriber` 时必填;不传则保持原值 |
|
||||
| price | number | 否 | `visibility=ppv` 时必填且 > 0;其它可见性传 0 或不传 |
|
||||
| highlights | array | 否 | 视频撸点(仅视频类型) |
|
||||
| publishDraft | boolean | 否 | 草稿提交发布:true 时把 status 从 0 改为 1 进入审核 |
|
||||
|
||||
|
||||
@ -142,7 +142,10 @@
|
||||
- `private`:仅返回 `visibility >= 1`(付费档内容)
|
||||
3. 关联查询 `media` 表 获取封面图(读)
|
||||
4. 查询 `content_boost` 表 获取注水数据(读)
|
||||
5. 如果用户已登录:批量查询 `user_subscription` 判断订阅状态,按 `visibility=0 OR tier_level>=post.visibility` 决定 `isLocked`(读)
|
||||
5. 如果用户已登录:批量查询 `user_subscription` 与 `content_unlock`
|
||||
- `subscriber` 内容按 `tier_level >= requiredTierLevel` 判断是否解锁
|
||||
- `ppv` 内容按 `content_unlock` 判断是否已购
|
||||
- 返回 `isLocked / isUnlocked / ppvPrice`
|
||||
6. 按 created_at 倒序,分页返回
|
||||
|
||||
**路径参数:**
|
||||
@ -189,14 +192,16 @@
|
||||
| list[].videos[].height | number | 视频高度 |
|
||||
| list[].videos[].duration | number | 视频时长(秒) |
|
||||
| list[].videos[].isLocked | boolean | 单条视频是否锁定 |
|
||||
| list[].visibility | string | 可见性档位 key(free/junior/basic/senior/supreme) |
|
||||
| list[].visibilityTierName | string/null | 该档位在当前创作者下的展示名 |
|
||||
| list[].visibilityTierPrice | number | 该档位价格(代币) |
|
||||
| list[].visibility | string | 可见性:free / subscriber / ppv |
|
||||
| list[].requiredTierLevel | number | subscriber 内容的最低可见档位 |
|
||||
| list[].visibilityTierName | string/null | subscriber 内容的档位展示名(free/ppv 时为空) |
|
||||
| list[].visibilityTierPrice | number | subscriber 内容的档位价格(分,free/ppv 时为 0) |
|
||||
| list[].ppvPrice | number | `visibility=ppv` 时返回内容价格(分) |
|
||||
| list[].creator | object | 创作者信息 `{userId,username,nickname,avatar,isOnline,isFollowing}` |
|
||||
| list[].isUnlocked | boolean | 当前用户是否已解锁该内容 |
|
||||
| list[].isLiked | boolean | 当前用户是否已点赞 |
|
||||
| list[].isFavorited | boolean | 当前用户是否已收藏 |
|
||||
| list[].isLocked | boolean | 是否需要订阅才能查看(锁定态展示「订阅查看全文」CTA) |
|
||||
| list[].isLocked | boolean | 是否仍需订阅/购买才能查看(锁定态展示对应 CTA) |
|
||||
| list[].favoriteCount | number | 收藏数 |
|
||||
| list[].likeCount | number | 点赞数 |
|
||||
| list[].commentCount | number | 评论数 |
|
||||
|
||||
@ -72,6 +72,7 @@
|
||||
- CONTENT-02b 获取关注 Feed — `GET /client/api/v1/content/feed/following`
|
||||
- CONTENT-02c 获取视频播放流 — `GET /client/api/v1/content/video-feed`
|
||||
- CONTENT-03 获取内容详情 — `GET /client/api/v1/content/:contentId`
|
||||
- CONTENT-03A 解锁 PPV 内容 — `POST /client/api/v1/content/:contentId/unlock`
|
||||
- CONTENT-04 获取预设标签列表 — `GET /client/api/v1/content/tags`
|
||||
- CONTENT-05 内容管理列表(创作者) — `GET /client/api/v1/content/manage`
|
||||
- CONTENT-06 编辑内容 — `PUT /client/api/v1/content/:contentId`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user