Files
video/docs/03-接口文档.md
2026-10-18 21:08:54 +08:00

1673 lines
58 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 短视频应用接口文档
- 版本:v1.1
- 日期:2026-10-18
- Base URL:`https://api.example.com/api/v1`
> 注意:App 不硬编码 Base URL。启动时优先解析 `txt.256kb.top` 的 DNS TXT 记录获取真实 API 域名;解析失败时按 `md5(yyyyMM) + "_jsnbhh.vip"` 计算兜底域名(详见《01-开发文档》5.5 节)。本文示例域名仅为占位。
---
## 1. 通用说明
### 1.1 请求头
| Header | 必填 | 说明 |
|---|---|---|
| Content-Type | 是 | `application/json`(文件上传为 `multipart/form-data`) |
| Authorization | 登录接口必填 | `Bearer <access_token>` |
| X-Device-ID | 是 | 设备唯一标识(游客计费依赖,首启生成 UUID 持久化) |
| X-App-Version | 是 | App 版本号(如 1.2.0),用于版本检查与灰度 |
| X-Platform | 是 | 平台标识:`ios` / `android` |
| X-Request-ID | 建议 | 客户端请求唯一 ID,用于幂等与链路追踪 |
### 1.2 统一响应结构
```json
{
"code": 0,
"message": "ok",
"data": {},
"request_id": "8f3c2a1e-..."
}
```
- `code = 0` 表示成功;非 0 为错误码,`message` 为可展示的错误文案
- HTTP 状态码:业务错误统一 200 + 业务 code;401/403/429/500 使用标准语义
### 1.3 通用错误码
| code | 说明 |
|---|---|
| 0 | 成功 |
| 1001 | 参数错误 |
| 1002 | 未登录或 Token 无效/过期 |
| 1003 | 无权限(功能需登录/已被封禁) |
| 1004 | 验证码错误或已过期 |
| 1005 | 请求过于频繁(限流) |
| 1006 | 资源不存在 |
| 1009 | 账号已注销(冻结期内登录可撤销) |
| 2001 | 积分不足 |
| 2002 | 今日免费观看次数已用完(游客需登录) |
| 2003 | 视频不存在或未发布 |
| 2004 | 已是终身会员,无需再开通 |
| 3001 | 订单不存在 |
| 3002 | 订单状态异常(已支付/已关闭) |
| 3003 | 支付金额不匹配 |
| 5000 | 服务器内部错误 |
| 4001 | 无管理员权限或角色不允许该操作 |
| 4002 | 该操作必须填写原因(reason) |
| 2101 | 邀请码无效或不可绑定(已绑定/绑定自己/同设备已注册) |
| 2102 | 资产余额不足 |
| 2103 | 低于最低提现额度 |
| 2104 | 已有审核中的提现申请 |
| 2105 | 内容包含敏感词 |
| 2106 | 请勿重复举报 |
| 2107 | 无权操作(非作者本人) |
### 1.4 分页约定
- Feed 流:游标分页。请求 `cursor`(首次不传)+ `limit`;响应返回 `next_cursor`(null 表示没有更多)
- 普通列表:`page`(从 1 开始)+ `page_size`(默认 20,最大 50),响应含 `total`
---
## 2. 接口总览
| # | 模块 | 方法 | 路径 | 说明 | 登录 |
|---|---|---|---|---|---|
| 1 | 认证 | POST | /auth/sms/send | 发送短信验证码 | 否 |
| 2 | 认证 | POST | /auth/email/send | 发送邮箱验证码 | 否 |
| 3 | 认证 | POST | /auth/register | 注册(手机/邮箱,可填邀请码,可配置赠送体验会员) | 否 |
| 4 | 认证 | POST | /auth/login | 登录 | 否 |
| 5 | 认证 | POST | /auth/token/refresh | 刷新令牌 | 否(带refresh) |
| 6 | 认证 | POST | /auth/logout | 退出登录 | 是 |
| 7 | 认证 | POST | /auth/password/reset | 找回密码 | 否 |
| 8 | 用户 | GET | /users/me | 获取个人信息 | 是 |
| 9 | 用户 | PUT | /users/me | 修改资料 | 是 |
| 10 | 用户 | POST | /users/me/avatar | 上传头像 | 是 |
| 11 | 用户 | GET | /users/me/wallet | 钱包/积分余额 | 是 |
| 12 | 用户 | GET | /users/me/points/records | 积分明细 | 是 |
| 13 | 用户 | POST | /users/me/deactivate | 注销账号(7天冻结期) | 是 |
| 14 | 用户 | GET | /users/{user_id} | 用户公开主页 | 否 |
| 15 | 用户 | GET | /users/{user_id}/videos | 他人作品列表 | 否 |
| 16 | 视频 | GET | /videos/feed | 视频流(recommend/latest/hot/liked/viewed) | 否 |
| 17 | 视频 | GET | /videos/{video_id} | 视频详情(登录返回续播进度) | 否 |
| 18 | 视频 | POST | /videos/{video_id}/watch | 观看上报(计费,可带播放进度) | 否 |
| 19 | 视频 | POST | /videos/{video_id}/watch/complete | 完整观看上报(创作收益计数) | 否 |
| 20 | 视频 | POST | /videos/upload/token | 获取上传凭证 | 是 |
| 21 | 视频 | POST | /videos | 发布视频(上传完成回调) | 是 |
| 22 | 视频 | PUT | /videos/{video_id} | 编辑视频(仅作者) | 是 |
| 23 | 视频 | DELETE | /videos/{video_id} | 删除视频(仅作者,软删除) | 是 |
| 24 | 视频 | GET | /users/me/videos | 我的作品 | 是 |
| 25 | 视频 | GET | /videos/categories | 分类列表 | 否 |
| 26 | 视频 | GET | /tags/{tag_name}/videos | 标签视频列表 | 否 |
| 27 | 视频 | GET | /videos/search | 视频搜索 | 否 |
| 28 | 视频 | GET | /search/hot | 热搜榜 | 否 |
| 29 | 互动 | POST | /videos/{video_id}/like | 点赞/取消点赞 | 是 |
| 30 | 互动 | POST | /videos/{video_id}/favorite | 收藏/取消收藏 | 是 |
| 31 | 互动 | GET | /videos/{video_id}/comments | 评论列表 | 否 |
| 32 | 互动 | POST | /videos/{video_id}/comments | 发表评论 | 是 |
| 33 | 互动 | DELETE | /comments/{comment_id} | 删除评论 | 是 |
| 34 | 互动 | GET | /users/me/favorites | 我的收藏 | 是 |
| 35 | 互动 | GET | /users/me/history | 观看历史 | 是 |
| 36 | 会员 | GET | /membership/plans | 会员套餐列表 | 否 |
| 37 | 会员 | GET | /membership/me | 我的会员状态 | 是 |
| 38 | 会员 | POST | /membership/redeem | 积分开通会员 | 是 |
| 39 | 支付 | GET | /payments/packages | 充值套餐列表(后台可配) | 是 |
| 40 | 支付 | POST | /payments/recharge | 创建充值订单 | 是 |
| 41 | 支付 | GET | /payments/orders/{order_no} | 查询订单状态 | 是 |
| 42 | 支付 | POST | /payments/orders/{order_no}/cancel | 取消订单 | 是 |
| 43 | 回调 | POST | /callbacks/alipay | 支付宝异步通知 | 平台调用 |
| 44 | 回调 | POST | /callbacks/wechat | 微信支付异步通知 | 平台调用 |
| 45 | 激励 | GET | /referral/me | 我的邀请信息 | 是 |
| 46 | 激励 | POST | /referral/bind | 绑定邀请码 | 是 |
| 47 | 激励 | GET | /assets/me | 资产钱包 | 是 |
| 48 | 激励 | GET | /assets/records | 资产流水 | 是 |
| 49 | 激励 | POST | /assets/withdraw | 申请提现 | 是 |
| 50 | 激励 | GET | /assets/withdraw/records | 提现记录 | 是 |
| 51 | 推广归因 | POST | /invite/click | 推广点击埋点(H5 落地页打开即上报) | 否 |
| 52 | 推广归因 | POST | /invite/match | App 首启归因匹配 | 否 |
| 53 | 推广归因 | GET | /r/{code} | H5 推广落地页(根路径,非 /api/v1 前缀) | 否 |
| 54 | 通用 | GET | /app/version | 版本检查 | 否 |
| 55 | 消息 | GET | /notifications | 通知列表 | 是 |
| 56 | 消息 | GET | /notifications/unread_count | 未读数 | 是 |
| 57 | 消息 | POST | /notifications/read | 标记已读 | 是 |
| 58 | 通用 | POST | /reports | 提交举报 | 是 |
| 59 | 视频 | GET | /videos/series/{series_id} | 系列详情 + 全部集数列表 | 否 |
| 60 | 视频 | GET | /videos/{video_id}/series | 当前视频所属系列 + 上下集 | 否 |
| 61 | 通用 | GET | /m3u8/proxy | m3u8 播放列表代理(回源拉取,TS 分片不经过服务器) | 否 |
| 62~105 | 后台管理 | * | /admin/** | 登录、看板、审核、视频/系列/评论管理、用户(含重置密码)、积分、订单、补单/退款、套餐、赠送、提现审核、推荐风控、举报处理、敏感词、平台配置、采集源/采集同步、分类、标签、日志,见第 11 章 | 管理员 |
---
## 3. 认证模块
### 3.1 发送短信验证码 `POST /auth/sms/send`
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| phone | string | 是 | 手机号,11 位 |
| scene | string | 是 | 场景:`register` / `login` / `reset` |
**请求示例**
```json
{ "phone": "13800138000", "scene": "register" }
```
**响应示例**
```json
{ "code": 0, "message": "ok", "data": { "expire_seconds": 300, "retry_after": 60 } }
```
**限制**:同号码 60 秒 1 条,同号码每天 10 条,同 IP 每小时 5 条;注册场景下已注册号码返回错误码 1001("该账号已注册"),login/reset 场景下未注册号码同样返回 1001。未配置短信通道时(开发环境)响应额外返回 `dev_code` 并仅记日志。
### 3.2 发送邮箱验证码 `POST /auth/email/send`
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| email | string | 是 | 邮箱地址 |
| scene | string | 是 | `register` / `login` / `reset` |
验证码由服务端通过 SMTP 真实发送(STARTTLS),并写入 Redis(`email:code:{scene}:{email}`,10 分钟有效,验证通过即删除)。响应结构同 3.1,`expire_seconds=600`;限流规则同 3.1(同邮箱 60 秒 1 条、每天 10 条、同 IP 每小时 5 条)。未配置 SMTP 时按开发模式处理:响应额外返回 `dev_code` 并仅记日志。
### 3.3 注册 `POST /auth/register`
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | `phone` 或 `email` |
| phone | string | type=phone 时必填 | 手机号 |
| email | string | type=email 时必填 | 邮箱 |
| code | string | 是 | 验证码 |
| password | string | 是 | 密码,8-32 位,须含字母和数字 |
| nickname | string | 否 | 昵称,默认"用户+随机尾号" |
| invite_code | string | 否 | 推荐人邀请码(App 解析分享口令自动预填;未填时服务端取首启模糊归因结果,见 9.7~9.9) |
**请求示例**
```json
{ "type": "phone", "phone": "13800138000", "code": "825314", "password": "abc12345", "nickname": "小明" }
```
**响应示例**
```json
{
"code": 0,
"message": "ok",
"data": {
"user_id": 100023,
"access_token": "eyJhbGciOi...",
"refresh_token": "d9f2...",
"expires_in": 7200,
"user": { "user_id": 100023, "nickname": "小明", "avatar_url": "", "is_member": true },
"trial_granted": { "plan_code": "trial", "plan_name": "体验会员", "days": 1, "end_at": "2026-10-15 12:00:00" }
}
}
```
- `trial_granted`:注册赠送的体验会员;后台关闭赠送开关(`register_gift_trial.enabled=0`)时为 `null`,`is_member` 相应为 false
**错误码**:1004 验证码错误;1001 手机号/邮箱已注册。
### 3.4 登录 `POST /auth/login`
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | `phone_password` / `phone_code` / `email_password` |
| phone | string | 手机方式必填 | |
| email | string | 邮箱方式必填 | |
| password | string | 密码方式必填 | |
| code | string | 验证码方式必填 | |
| revoke | bool | 否 | 注销冻结期内确认撤销注销(默认 false,见 4.6) |
**请求示例**
```json
{ "type": "phone_password", "phone": "13800138000", "password": "abc12345" }
```
**响应**:同 3.3。连续失败 5 次锁定 15 分钟(错误码 1005,按 IP 维度计数)。
> 登录账号仅支持**手机号或邮箱**(按 type 使用对应字段),不支持昵称登录。冻结期内账号登录返回 1009 且 `data.can_revoke=true`;确认撤销后带 `"revoke": true` 重试即恢复账号并正常签发 Token。
### 3.5 刷新令牌 `POST /auth/token/refresh`
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| refresh_token | string | 是 | 刷新令牌 |
**响应**:同 3.3 的 token 部分;refresh_token 旋转(旧值立即失效)。
> Token 体系:access_token 有效期 2 小时,refresh_token 30 天(Redis 白名单 Set,支持多端同时在线)。服务端按用户维护令牌纪元 `auth:epoch:{user_id}`,签发时写入 Token 的 `ep` 声明,每次请求都校验:修改/重置密码、后台重置密码(11.34)后纪元递增并清空 refresh 白名单,该用户所有旧 Token(含未过期的 access)立即失效。
### 3.6 退出登录 `POST /auth/logout`(需登录)
无参数。响应 `{ "code": 0 }`。服务端将 access_token 的 jti 加入黑名单并删除 refresh 白名单。
### 3.7 找回密码 `POST /auth/password/reset`
通过已验证的手机号/邮箱 + 验证码重置密码。重置成功后该用户所有旧 Token(access + refresh)立即失效,需重新登录。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | string | 是 | `phone` 或 `email` |
| phone | string | type=phone 时必填 | 手机号 |
| email | string | type=email 时必填 | 邮箱 |
| code | string | 是 | 验证码(scene=`reset`) |
| new_password | string | 是 | 新密码,8-32 位,须含字母和数字 |
**请求示例**
```json
{ "type": "phone", "phone": "13800138000", "code": "825314", "new_password": "newabc123" }
```
**响应**:`{ "code": 0, "message": "密码已重置,请重新登录" }`
**错误码**:1004 验证码错误或已过期;1001 账号不存在。
---
## 4. 用户模块
### 4.1 获取个人信息 `GET /users/me`(需登录)
**响应示例**
```json
{
"code": 0,
"data": {
"user_id": 100023,
"phone": "138****8000",
"email": null,
"nickname": "小明",
"avatar_url": "https://cdn.example.com/avatar/100023.jpg",
"bio": "",
"is_member": true,
"member_plan": "monthly",
"member_expire_at": "2026-11-13 12:00:00",
"points": 86
}
}
```
### 4.2 修改资料 `PUT /users/me`(需登录)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| nickname | string | 否 | 2-20 字 |
| bio | string | 否 | ≤200 字 |
| gender | int | 否 | 0/1/2 |
响应返回更新后的完整用户对象(同 4.1)。
### 4.3 上传头像 `POST /users/me/avatar`(需登录)
- `multipart/form-data`,字段名 `file`,jpg/png,≤ 5MB
- 响应:`{ "code": 0, "data": { "avatar_url": "https://..." } }`
### 4.4 钱包信息 `GET /users/me/wallet`(需登录)
**响应示例**
```json
{
"code": 0,
"data": {
"points": 86,
"total_recharge": 100,
"total_earned": 6,
"total_consumed": 20,
"comment_reward_progress": { "current": 4, "target": 10, "today_rewarded": 1, "daily_limit": 5 }
}
}
```
### 4.5 积分明细 `GET /users/me/points/records`(需登录)
**查询参数**:`page`、`page_size`、`biz_type`(可选:1充值 2观看 3评论奖励 4会员 5赠送 6调整 7扣回)
**响应示例**
```json
{
"code": 0,
"data": {
"total": 35,
"list": [
{ "id": 9001, "change_amount": -1, "balance_after": 86, "biz_type": 2, "remark": "观看视频", "created_at": "2026-10-14 12:01:00" },
{ "id": 8990, "change_amount": 1, "balance_after": 87, "biz_type": 3, "remark": "评论奖励", "created_at": "2026-10-14 11:30:00" }
]
}
}
```
---
### 4.6 注销账号 `POST /users/me/deactivate`(需登录)
用户主动注销。提交后进入 **7 天冻结期**:期间再次登录即自动撤销注销;到期后账号匿名化(手机号/邮箱置空释放、昵称头像清空、作品全部下架、钱包余额清零作废),Token 立即失效。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| password | string | 是 | 当前登录密码(二次确认) |
```json
// 响应
{ "code": 0, "data": { "deactivate_at": "2026-10-22 12:00:00", "revoke_before": "2026-10-22 12:00:00" } }
```
**说明**:冻结期内登录返回错误码 1009 并附带 `can_revoke=true`,App 弹窗询问是否撤销注销。
### 4.7 用户公开主页 `GET /users/{user_id}`(游客可)
```json
{
"code": 0,
"data": {
"user_id": 100023,
"nickname": "小明",
"avatar_url": "https://...",
"bio": "",
"video_count": 12,
"total_likes": 3456
}
}
```
### 4.8 他人作品列表 `GET /users/{user_id}/videos`(游客可)
仅返回"已发布"状态作品。
**查询参数**:`page`、`page_size`
**响应**:`list` 结构同 Feed 单条,含 `total`。
---
## 5. 视频模块
### 5.1 推荐视频流 `GET /videos/feed`(游客可)
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| cursor | string | 否 | 上一页返回的 next_cursor |
| limit | int | 否 | 默认 10,最大 20 |
| category_id | int | 否 | 分类筛选 |
| type | string | 否 | 策略:`recommend`(默认,个性化)/ `latest`(最新发布)/ `hot`(热度)/ `liked`(口碑,按点赞量倒序)/ `viewed`(热播,按播放量倒序) |
**响应示例**
```json
{
"code": 0,
"data": {
"next_cursor": "eyJwIjoxNzYwfQ==",
"list": [
{
"video_id": 88001,
"title": "海边日落",
"cover_url": "https://cdn.example.com/hls/88001/cover.jpg",
"video_url": "https://cdn.example.com/hls/88001/index.m3u8",
"duration": 32,
"author": { "user_id": 100023, "nickname": "小明", "avatar_url": "https://..." },
"view_count": 12034,
"like_count": 456,
"comment_count": 78,
"favorite_count": 90,
"tags": ["日落", "海边"],
"is_liked": false,
"is_favorited": false
}
]
}
}
```
> `is_liked/is_favorited` 仅登录用户返回真实值,游客恒为 false。
>
> **recommend 策略**:以"当天日期 + 用户/设备"为随机种子每日重排(同一用户每天的推荐顺序不同、同一天内多次刷新顺序稳定);当日已观看的视频尽量不再出现(候选不足时放开过滤兜底);个性化基于近 7 天观看/点赞/收藏视频的标签偏好加权,游客及新用户退化为热度排序。
>
> **游标格式**(均为 base64url 编码的 JSON,客户端无需解析,原样回传即可):
> - `liked` / `viewed`:「值,id」双键游标 `{"n": 末条点赞数/播放数, "i": 末条 video_id}`,按"值 DESC, id DESC"严格翻页,点赞/播放数变化不影响已翻过的页
> - `latest`:`{"p": 末条发布时间戳, "i": 末条 video_id}`
> - `recommend` / `hot`:当日种子下的偏移游标
### 5.2 视频详情 `GET /videos/{video_id}`(游客可)
响应字段同 Feed 单条,另含 `description`、`category`、`published_at`。
登录用户额外返回 `my_position`(本人上次观看进度,秒;无记录为 0),用于断点续播。
错误码:2003 视频不存在或未发布。
### 5.3 观看上报(计费接口)`POST /videos/{video_id}/watch`(游客可)
开始播放时调用。客户端本地按 `video_id+日期` 去重,同日同视频只上报一次。
**请求参数**(身份取自 Token 或 X-Device-ID)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| position | int | 否 | 当前播放进度(秒),服务端记录用于续播 |
**响应示例(扣费场景)**
```json
{
"code": 0,
"data": {
"cost_type": 1,
"cost_points": 1,
"remaining_points": 86,
"free_remaining_today": 0
}
}
```
**cost_type 说明**
| 值 | 含义 | cost_points |
|---|---|---|
| 0 | 占用今日免费额度 | 0 |
| 1 | 积分扣减 | 1 |
| 2 | 会员免费 | 0 |
| 3 | 重复观看/本人作品,不计费 | 0 |
**错误响应(游客超限)**
```json
{ "code": 2002, "message": "今日免费观看次数已用完,登录后继续观看", "data": { "need_login": true } }
```
**错误响应(积分不足)**
```json
{ "code": 2001, "message": "积分不足,请充值或开通会员", "data": { "points": 0 } }
```
### 5.4 完整观看上报 `POST /videos/{video_id}/watch/complete`(游客可)
播放进度 ≥95% 或自然播完时调用,用于创作者收益计数(每 1000 次完整观看 = 1 资产)。服务端校验 `watch_duration` 合理性(不得明显低于视频时长),同一用户/设备同一视频每日仅首次有效。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| watch_duration | int | 是 | 本次实际观看时长(秒) |
```json
// 请求
{ "watch_duration": 31 }
// 响应
{ "code": 0, "data": { "credited": true } }
```
`credited=false`:今日已计数、观看的是本人作品、或时长校验未通过。
### 5.5 获取上传凭证 `POST /videos/upload/token`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file_name | string | 是 | 原始文件名 |
| file_size | int | 是 | 字节数,≤ 配置上限 |
| upload_mode | string | 否 | `raw`(默认,服务端转码) / `hls`(已转码的m3u8包) |
**响应示例**
```json
{
"code": 0,
"data": {
"upload_type": "oss_sts",
"sts": { "access_key_id": "STS.xxx", "access_key_secret": "xxx", "security_token": "xxx", "expiration": "2026-10-14T13:00:00Z" },
"object_key": "upload/tmp/20261014/uuid.mp4",
"bucket": "video-upload",
"endpoint": "https://oss-cn-hangzhou.aliyuncs.com"
}
}
```
> 小部署环境可返回 `upload_type: "server"`,客户端改用 `POST /videos/upload/file`(multipart)经服务端中转。
### 5.6 发布视频 `POST /videos`(需登录)
上传完成后调用,创建视频记录并进入转码/审核流程。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| object_key | string | 是 | 5.4 返回的 object_key(raw 模式) |
| m3u8_url | string | hls 模式必填 | 已上传 m3u8 的地址 |
| title | string | 是 | 1-100 字 |
| description | string | 否 | ≤500 字 |
| category_id | int | 是 | 分类 ID |
| duration | int | hls 模式必填 | 时长(秒) |
| tags | array[string] | 否 | 标签,0~5 个,每个 1~20 字 |
**请求示例**
```json
{ "object_key": "upload/tmp/20261014/uuid.mp4", "title": "海边日落", "description": "拍摄于青岛", "category_id": 3, "tags": ["日落", "海边"] }
```
**响应示例**
```json
{
"code": 0,
"data": { "video_id": 88001, "status": 0, "status_text": "转码中", "is_priority": true }
}
```
`status`:0转码中 1待审核 2已发布 3拒绝 4下架 5转码失败。会员 `is_priority=true` 进入优先转码队列。
### 5.7 我的作品 `GET /users/me/videos`(需登录)
查询参数:`page`、`page_size`、`status`(可选)。
响应列表项同 Feed 单条,另含 `status`、`status_text`、`reject_reason`。
### 5.8 分类列表 `GET /videos/categories`
```json
{ "code": 0, "data": [ { "category_id": 1, "name": "生活" }, { "category_id": 2, "name": "美食" } ] }
```
### 5.9 标签视频列表 `GET /tags/{tag_name}/videos`(游客可)
播放页点击标签后跳转页面调用本接口。
**路径参数**:`tag_name` — 标签名(URL 编码)
**查询参数**:`page`、`page_size`、`sort`(`hot` 默认 / `latest`)
**响应示例**
```json
{
"code": 0,
"data": {
"tag": "日落",
"total": 236,
"list": [
{
"video_id": 88001,
"title": "海边日落",
"tags": ["日落", "海边"],
"cover_url": "https://cdn.example.com/hls/88001/cover.jpg",
"video_url": "https://cdn.example.com/hls/88001/index.m3u8",
"author": { "user_id": 100023, "nickname": "小明", "avatar_url": "https://..." },
"view_count": 12034, "like_count": 456, "comment_count": 78, "favorite_count": 90
}
]
}
}
```
### 5.10 视频搜索 `GET /videos/search`(游客可)
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| keyword | string | 是 | 关键词,1-30 字;命中敏感词返回空结果 |
| sort | string | 否 | `hot`(默认)/ `latest` |
| page | int | 否 | 页码 |
| page_size | int | 否 | 默认 20,最大 50 |
**响应**:`list` 结构同 Feed 单条,另含 `total`;搜索行为计入热搜统计(Redis `search:hot`)。
### 5.11 热搜榜 `GET /search/hot`(游客可)
```json
{ "code": 0, "data": { "words": ["日落", "美食教程", "猫咪"] } }
```
返回 Top 10 热搜词,来源于 Redis ZSet `search:hot`(搜索计数,定时衰减)。
### 5.12 编辑视频 `PUT /videos/{video_id}`(需登录,仅作者)
**请求参数**(均可选,传哪个改哪个)
| 参数 | 类型 | 说明 |
|---|---|---|
| title | string | 1-100 字 |
| description | string | ≤500 字 |
| category_id | int | 分类 ID |
| tags | array[string] | 标签,0~5 个,每个 1~20 字 |
**响应**:`{ "code": 0, "data": { "video_id": 88001 } }`
**错误码**:2107 无权操作(非作者本人);2003 视频不存在。
### 5.13 删除视频 `DELETE /videos/{video_id}`(需登录,仅作者)
软删除,立即下架不可见;互动计数保留用于后台审计。
**响应**:`{ "code": 0 }`
**错误码**:2107 无权操作(非作者本人);2003 视频不存在。
### 5.14 系列详情 `GET /videos/series/{series_id}`(游客可)
返回系列信息 + 全部已发布集数(按 `episode_no` 升序),用于短剧播放页选集。
**响应示例**
```json
{
"code": 0,
"data": {
"series": { "id": 12, "title": "霸道总裁", "cover_url": "https://...", "description": "...", "total_episodes": 80 },
"episodes": [
{ "video_id": 88001, "episode_no": 1, "title": "第1集", "cover_url": "https://...", "duration": 95, "video_url": "https://.../index.m3u8" }
]
}
}
```
**错误码**:1006 系列不存在或已隐藏。
### 5.15 当前视频所属系列 `GET /videos/{video_id}/series`(游客可)
用于 PlayerScreen 系列抽屉入口。视频不属于任何系列(或未发布)时返回 `{ "has_series": false }`;否则返回系列信息、全部集数(字段同 5.14)及 `current_episode_no` / `prev_video_id` / `next_video_id`(无上下集时为 null)。
---
## 6. 互动模块
### 6.1 点赞/取消 `POST /videos/{video_id}/like`(需登录)
无 body,切换式接口(已点赞则取消)。
**响应示例**
```json
{ "code": 0, "data": { "is_liked": true, "like_count": 457 } }
```
### 6.2 收藏/取消 `POST /videos/{video_id}/favorite`(需登录)
```json
{ "code": 0, "data": { "is_favorited": true, "favorite_count": 91 } }
```
### 6.3 评论列表 `GET /videos/{video_id}/comments`(游客可)
**查询参数**:`page`、`page_size`、`parent_id`(查二级回复时传父评论 ID)
**响应示例**
```json
{
"code": 0,
"data": {
"total": 78,
"list": [
{
"comment_id": 55001,
"user": { "user_id": 100030, "nickname": "阿花", "avatar_url": "https://..." },
"content": "拍得真好",
"like_count": 12,
"reply_count": 3,
"created_at": "2026-10-14 10:00:00"
}
]
}
}
```
### 6.4 发表评论 `POST /videos/{video_id}/comments`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| content | string | 是 | 1-500 字;≥5 字才计入积分奖励进度 |
| parent_id | int | 否 | 回复某评论时传其 ID |
| reply_to_user_id | int | 否 | 被回复人 ID |
**请求示例**
```json
{ "content": "这个日落太美了,求定位!" }
```
**响应示例**
```json
{
"code": 0,
"data": {
"comment_id": 55010,
"reward_progress": { "current": 5, "target": 10, "rewarded_this_time": false }
}
}
```
当 `current` 满 10 时服务端自动发放 1 积分并清零进度,此时返回 `rewarded_this_time: true, reward_points: 1`。
### 6.5 删除评论 `DELETE /comments/{comment_id}`(需登录)
本人评论或本人视频下的评论可删。已计入奖励进度的评论删除时进度回退(奖励已发的扣回)。
响应:`{ "code": 0, "data": { "reward_progress": { "current": 4, "target": 10 } } }`
### 6.6 我的收藏 `GET /users/me/favorites`(需登录)
分页列表,结构同 Feed 单条。
### 6.7 观看历史 `GET /users/me/history`(需登录)
分页列表,每项:`video`(同 Feed 单条)+ `watched_at` + `cost_type`。
---
## 7. 会员模块
### 7.1 套餐列表 `GET /membership/plans`(游客可)
**响应示例**(价格来自服务端配置,前端不硬编码)
```json
{
"code": 0,
"data": [
{ "plan_code": "trial", "name": "体验会员", "days": 1, "points": 5, "enabled": true, "desc": "1天体验,活动同享" },
{ "plan_code": "monthly", "name": "月卡", "days": 30, "points": 300, "enabled": true, "desc": "30天无限观看" },
{ "plan_code": "yearly", "name": "年卡", "days": 365, "points": 3650, "enabled": true, "desc": "365天无限观看" },
{ "plan_code": "lifetime", "name": "终身卡", "days": 0, "points": 10000, "enabled": true, "desc": "永久无限观看" }
],
"benefits": ["无限观看视频", "优先上传(转码/审核优先)"]
}
```
### 7.2 我的会员 `GET /membership/me`(需登录)
```json
{
"code": 0,
"data": {
"is_member": true,
"is_lifetime": false,
"current_plan": "monthly",
"plan_name": "月卡",
"expire_at": "2026-11-13 12:00:00",
"remaining_days": 30,
"history": [
{ "order_no": "M20261014120001", "plan_name": "月卡", "points_cost": 300, "start_at": "2026-10-14 12:00:00", "end_at": "2026-11-13 12:00:00", "source": 1 }
]
}
}
```
### 7.3 积分开通会员 `POST /membership/redeem`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| plan_code | string | 是 | trial/monthly/yearly/lifetime |
**请求示例**
```json
{ "plan_code": "monthly" }
```
**响应示例**
```json
{
"code": 0,
"message": "ok",
"data": {
"order_no": "M20261014120001",
"plan_name": "月卡",
"points_cost": 300,
"remaining_points": 86,
"expire_at": "2026-11-13 12:00:00"
}
}
```
**规则**:有效期内重复开通时长叠加;终身卡用户调用返回错误码 2004;积分不足返回 2001。
---
## 8. 支付模块
### 8.1 充值套餐 `GET /payments/packages`(需登录)
```json
{
"code": 0,
"data": {
"packages": [
{ "id": 1, "amount_yuan": 10, "points": 100, "bonus": 0 },
{ "id": 2, "amount_yuan": 30, "points": 300, "bonus": 0 },
{ "id": 3, "amount_yuan": 100, "points": 1000, "bonus": 50 },
{ "id": 4, "amount_yuan": 200, "points": 2000, "bonus": 150 }
],
"custom": { "enabled": true, "min_yuan": 1, "max_yuan": 10000, "points_per_yuan": 10 },
"pay_methods": [
{ "method": 1, "name": "支付宝" },
{ "method": 2, "name": "微信支付" },
{ "method": 3, "name": "USDT (TRC20)" }
],
"usdt_rate": 7.2
}
}
```
> 充值档位由管理后台实时维护(增删改/上下架/排序),前端不硬编码;`points = amount_yuan × points_per_yuan`,自定义金额同样按此汇率换算。
### 8.2 创建充值订单 `POST /payments/recharge`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| amount_yuan | int | 是 | 充值金额(元),须为后台已上架档位,或 `custom.enabled=true` 时的自定义金额 |
| pay_method | int | 是 | 1支付宝 2微信 3USDT |
**请求示例**
```json
{ "amount_yuan": 30, "pay_method": 1 }
```
**响应示例(支付宝)**
```json
{
"code": 0,
"data": {
"order_no": "R20261014123000001",
"pay_method": 1,
"amount_yuan": 30,
"points": 300,
"expire_at": "2026-10-14 13:00:00",
"pay_params": { "order_string": "alipay_sdk=alipay-sdk-php-dynamic&app_id=..." }
}
}
```
**响应示例(微信)**:`pay_params` 含 `appid/partnerid/prepayid/package/noncestr/timestamp/sign`,供 App SDK 调起。
**响应示例(USDT)**
```json
{
"code": 0,
"data": {
"order_no": "R20261014123000002",
"pay_method": 3,
"amount_yuan": 30,
"points": 300,
"usdt_rate": 7.2,
"usdt_amount": "4.167",
"receive_address": "TXYz...abc",
"network": "TRC20",
"expire_at": "2026-10-14 13:00:00"
}
}
```
> USDT 金额含随机尾差用于订单匹配;用户须按显示金额**精确转账**,满 19 个区块确认后自动到账。
### 8.3 查询订单 `GET /payments/orders/{order_no}`(需登录)
App 调起支付后每 2 秒轮询,直至 `status != 0` 或超过 30 次。
**响应示例**
```json
{
"code": 0,
"data": {
"order_no": "R20261014123000001",
"status": 1,
"status_text": "已支付",
"points": 300,
"paid_at": "2026-10-14 12:35:20",
"wallet_points": 386
}
}
```
`status`:0待支付 1已支付 2已关闭 3已退款。
### 8.4 取消订单 `POST /payments/orders/{order_no}/cancel`(需登录)
仅"待支付"可取消。响应 `{ "code": 0 }`。
### 8.5 支付宝回调 `POST /callbacks/alipay`(平台 → 服务端)
- Content-Type:`application/x-www-form-urlencoded`
- 服务端处理:RSA2 验签 → 校验 `out_trade_no`/`total_amount` → 幂等入账 → 返回纯文本 `success`
- App 不直接调用本接口
### 8.6 微信支付回调 `POST /callbacks/wechat`(平台 → 服务端)
- V3 回调:验签(`Wechatpay-Signature` 头)→ AES-GCM 解密 `resource` → 幂等入账 → 返回 `{ "code": "SUCCESS" }`
### 8.7 USDT 到账说明
USDT 无支付平台回调,由服务端每 15 秒轮询 TronGrid:
匹配条件 = 收款地址一致 + 金额(含尾差)精确相等 + 转账时间在订单有效期内 + 确认数 ≥ 19。
匹配成功即入账(`tx_hash` 写入 `third_trade_no`,唯一索引保证幂等);超时未匹配订单自动关闭。
---
## 9. 激励模块
资产体系:**10 资产 = 1 元**,资产存放于资产钱包,可申请提现(后台人工审核打款)。推荐奖励与充值返利仅在**推荐人为会员**时发放(发放时点校验会员身份)。
### 9.1 我的邀请 `GET /referral/me`(需登录)
**响应示例**
```json
{
"code": 0,
"data": {
"invite_code": "K7X2M9",
"invite_url": "https://app.example.com/r/K7X2M9",
"eligible": true,
"total_invitees": 12,
"activated_count": 8,
"reward_points_total": 80,
"rebate_assets_total": 150.0
}
}
```
- `eligible`:当前是否会员(false 时被推荐人仍可绑定,但不产生奖励)
### 9.2 绑定邀请码 `POST /referral/bind`(需登录)
注册时未填邀请码的用户,注册后 24 小时内可补绑一次(每人仅能绑定一次)。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| invite_code | string | 是 | 推荐人邀请码 |
```json
// 请求
{ "invite_code": "K7X2M9" }
// 响应
{ "code": 0, "data": { "bound": true, "status": 0, "status_text": "待激活" } }
```
**错误码**:2101(邀请码无效 / 已绑定过 / 绑定自己 / 同设备已注册其他账号)
> 被推荐人激活(72h 内累计观看 ≥3 个视频且次日有登录)后,推荐人获得 10 积分(可配置,不限量)。被推荐人此后每次充值成功,推荐人获得"充值积分 × 5%"的资产(不限量)。
### 9.3 资产钱包 `GET /assets/me`(需登录)
**响应示例**
```json
{
"code": 0,
"data": {
"assets": 236.5,
"assets_value_yuan": 23.65,
"total_earned": 400.0,
"total_withdrawn": 163.5,
"min_withdraw_assets": 100,
"has_pending_withdraw": false
}
}
```
### 9.4 资产流水 `GET /assets/records`(需登录)
**查询参数**:`page`、`page_size`、`biz_type`(可选:1充值返利 2完整观看收益 3点赞收益 4提现 5提现退回 6返利扣回 7后台调整)
**响应示例**
```json
{
"code": 0,
"data": {
"total": 21,
"list": [
{ "id": 301, "change_amount": 1.0, "balance_after": 236.5, "biz_type": 2, "remark": "视频完整观看收益(视频88001)", "created_at": "2026-10-14 15:00:00" },
{ "id": 300, "change_amount": 15.0, "balance_after": 235.5, "biz_type": 1, "remark": "推荐用户充值返利", "created_at": "2026-10-14 14:20:00" }
]
}
}
```
### 9.5 申请提现 `POST /assets/withdraw`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| assets | number | 是 | 提现资产数,≥100(可配置),按 10 资产 = 1 元折算 |
| channel | int | 是 | 1支付宝 2微信 3USDT |
| account | string | 是 | 收款账号(支付宝账号/微信号/USDT 地址) |
```json
// 请求
{ "assets": 150, "channel": 1, "account": "user@example.com" }
// 响应
{ "code": 0, "data": { "withdraw_no": "W20261014153000001", "assets": 150.0, "amount_yuan": 15.0, "status": 0, "status_text": "待审核" } }
```
**错误码**:2102 资产余额不足;2103 低于最低提现额度;2104 已有审核中的提现申请。
> 提交即冻结扣减资产,后台人工审核后打款;审核拒绝资产自动退回资产钱包。
### 9.6 提现记录 `GET /assets/withdraw/records`(需登录)
**响应示例**
```json
{
"code": 0,
"data": {
"total": 2,
"list": [
{ "withdraw_no": "W20261014153000001", "assets": 150.0, "amount_yuan": 15.0, "channel": 1, "status": 0, "status_text": "待审核", "created_at": "2026-10-14 15:30:00" }
]
}
}
```
### 9.7 推广点击埋点 `POST /invite/click`(无需登录)
H5 推广落地页(9.9)打开时自动调用,记录「邀请码 + IP + UA」用于后续归因。同 IP+code 60 秒内重复上报自动去重。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| code | string | 是 | 推广链接中的邀请码(8 位,自动转大写) |
```json
// 请求
{ "code": "K7X2M9AB" }
// 响应
{ "code": 0, "data": { "recorded": true } }
```
> IP 取自 `X-Real-IP` 头(Nginx 反代透传)或直连来源;点击记录存 Redis 24h 有效。
### 9.8 首启归因匹配 `POST /invite/match`(无需登录)
App 首次启动且剪贴板无分享口令时调用一次(每安装仅一次)。服务端按 IP 取最近 24h 内最近点击的邀请码,命中则按 device_id 缓存 7 天;用户注册时自动消费该缓存完成推荐绑定(客户端无需也无法回传)。
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| device_id | string | 是 | 设备指纹(与 X-Device-ID 一致) |
```json
// 请求
{ "device_id": "a1b2c3d4-..." }
// 响应(未命中)
{ "code": 0, "data": { "matched": false } }
// 响应(命中)
{ "code": 0, "data": { "matched": true, "invite_code": "K7X2M9AB" } }
```
> 命中后重复调用直接返回缓存结果(幂等);注册绑定优先级:显式 invite_code(口令/手填)> 本接口归因缓存。
### 9.9 H5 推广落地页 `GET /r/{code}`(浏览器访问,HTML)
分享推广链接指向的落地页(根路径,**非** /api/v1 前缀),返回 HTML:
- 页面展示邀请人昵称、"复制邀请口令"按钮(口令格式 `#短视频邀请码{code}#`,供被邀请人首启剪贴板解析)
- system_configs `app.download_url` 已配置时展示"下载 APP"按钮
- 页面加载即自动调用 9.7 完成点击埋点
无效邀请码正常渲染通用页面(不报错),保证分享链接永可用。
---
## 10. 通用与消息模块
### 10.1 版本检查 `GET /app/version`(游客可)
App 启动时调用,检查版本更新与公告。
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| platform | string | 是 | `ios` / `android` |
| version_code | int | 是 | 当前 App 版本号 |
**响应示例**
```json
{
"code": 0,
"data": {
"latest_version": "1.2.0",
"latest_version_code": 12,
"force_update": false,
"update_url": "https://app.example.com/download",
"notice": "新版本优化了播放体验"
}
}
```
`force_update=true` 时 App 弹强制更新弹窗(不可跳过)。
### 10.2 通知列表 `GET /notifications`(需登录)
**查询参数**:`type`(可选:1审核结果 2提现进度 3系统公告 4评论回复)、`page`、`page_size`
**响应示例**
```json
{
"code": 0,
"data": {
"total": 3,
"list": [
{ "id": 701, "type": 1, "title": "视频审核通过", "content": "你发布的《海边日落》已通过审核", "biz_id": "88001", "is_read": false, "created_at": "2026-10-14 15:00:00" }
]
}
}
```
### 10.3 未读数 `GET /notifications/unread_count`(需登录)
```json
{ "code": 0, "data": { "unread": 3 } }
```
App 我的页消息入口红点依赖本接口(直接读 Redis `notif:unread:{user_id}`)。
### 10.4 标记已读 `POST /notifications/read`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| ids | array[int] | 否 | 通知 ID 数组;与 all 二选一 |
| all | bool | 否 | true 表示全部标记已读 |
**响应**:`{ "code": 0 }`
### 10.5 提交举报 `POST /reports`(需登录)
**请求参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| target_type | int | 是 | 1视频 2评论 |
| target_id | int | 是 | 举报对象 ID |
| reason_type | int | 是 | 1色情低俗 2暴力血腥 3侵权 4广告营销 5其他 |
| remark | string | 否 | 补充说明,≤200 字 |
```json
// 请求
{ "target_type": 1, "target_id": 88001, "reason_type": 1, "remark": "包含不适内容" }
// 响应
{ "code": 0, "message": "已收到举报,我们会尽快处理" }
```
**错误码**:2106 请勿重复举报(同一用户对同一对象仅一次有效举报,唯一索引 `uk_user_target` 保证);限流 10 次/天。
### 10.6 m3u8 播放列表代理 `GET /m3u8/proxy`(游客可)
采集内容源站的 m3u8 内 TS 链接带时效签名,需经服务端每次回源拉取新鲜播放列表;**TS 分片不经过本服务器**,由客户端按播放列表中的源站地址直连下载。
**查询参数**
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| path | string | 是 | 源站播放列表的相对资源路径(防 SSRF:仅允许字母数字及 `/._-`,禁止 `..`),1-300 字 |
**响应**:非统一 JSON 包装,直接返回 m3u8 文本(`Content-Type: application/vnd.apple.mpegurl`,`Cache-Control: no-cache`,`Access-Control-Allow-Origin: *`)。
**错误码**:1001 非法路径;1006 播放列表获取失败(源站非 200 或内容非 m3u8)。
---
## 11. 管理后台接口
通用约定:
- 前缀 `/api/v1/admin`,统一响应结构同 App 接口
- 认证:`Authorization: Bearer <admin_token>`(Token payload `type="admin"`,有效期 12h,与 App 用户体系互不相通)
- 角色:`super`(全部)/ `reviewer`(仅审核)/ `operator`(套餐、活动、看板、采集源/采集同步、系列/分类/标签查询);越权返回 4001
- 所有写操作记录操作日志;标注【敏感】的接口 `reason` 必填,缺失返回 4002
### 11.1 管理员登录 `POST /admin/auth/login`
```json
// 请求
{ "username": "admin", "password": "xxxx" }
// 响应
{ "code": 0, "data": { "admin_token": "eyJ...", "expires_in": 43200, "admin": { "id": 1, "name": "超管", "role": "super" } } }
```
### 11.2 仪表盘 `GET /admin/dashboard`
```json
{
"code": 0,
"data": {
"today": { "dau": 1520, "new_users": 86, "recharge_yuan": 4600, "points_issued": 46000, "points_consumed": 12300, "views": 30211 },
"pending_review_count": 17,
"total": { "users": 50210, "videos": 8200, "recharge_yuan": 128000 }
}
}
```
### 11.3 待审核视频列表 `GET /admin/videos/pending`
查询参数:`page`、`page_size`。响应列表项含视频全部字段(含 `video_url` 供在线预览)+ 作者信息。
### 11.4 审核视频 `POST /admin/videos/{video_id}/review`
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | 是 | `approve` / `reject` |
| reason | string | reject 时必填 | 拒绝原因(回显给作者) |
```json
// 请求
{ "action": "approve" }
// 响应
{ "code": 0, "data": { "video_id": 88001, "status": 2, "status_text": "已发布" } }
```
### 11.5 下架视频 `POST /admin/videos/{video_id}/offline`【敏感】
```json
{ "reason": "内容违规" }
```
### 11.6 用户列表 `GET /admin/users`
查询参数:`keyword`(ID/手机号/邮箱)、`status`、`page`、`page_size`。
响应列表项:用户基础信息 + `points` + `is_member` + `video_count`。
### 11.7 用户详情 `GET /admin/users/{user_id}`
响应:用户信息 + 钱包 + 会员状态 + 近 10 条积分流水 + 作品数/获赞数。
### 11.8 封禁/解封用户 `PUT /admin/users/{user_id}/status`【敏感】
```json
{ "status": 2, "reason": "发布违规内容" }
```
`status`:1 正常 / 2 封禁。封禁后该用户 Token 立即失效(Redis 白名单剔除)。
### 11.9 积分调整 `POST /admin/points/adjust`【敏感】
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| user_id | int | 是 | 目标用户 |
| change_amount | int | 是 | 正=加,负=减 |
| reason | string | 是 | 调整原因 |
```json
// 响应
{ "code": 0, "data": { "user_id": 100023, "change_amount": 100, "balance_after": 486 } }
```
### 11.10 积分流水查询 `GET /admin/points/records`
查询参数:`user_id`、`biz_type`、`page`、`page_size`。响应结构同 App 端积分明细。
### 11.11 充值订单查询 `GET /admin/orders`
查询参数:`order_no`、`user_id`、`pay_method`、`status`、`start_date`、`end_date`、`page`。
响应列表项:订单全字段(含 usdt_amount / third_trade_no)。
### 11.12 人工补单 `POST /admin/orders/{order_no}/resettle`【敏感】
用于"用户已付款但回调未到账"的异常单。核实支付平台/链上确已收款后调用,走与回调相同的幂等入账流程。
```json
{ "reason": "微信回调丢失,商户后台已核实收款", "third_trade_no": "4200001234567890" }
```
### 11.13 充值套餐列表 `GET /admin/packages`
返回全部套餐(含下架),字段同 App 端 8.1 的 packages 项 + `status`、`sort`。
### 11.14 新增套餐 `POST /admin/packages`
```json
// 请求(points 由服务端按汇率计算,也可显式传入覆盖)
{ "amount_yuan": 50, "bonus_points": 20, "sort": 10 }
// 响应
{ "code": 0, "data": { "id": 5, "amount_yuan": 50, "points": 500, "bonus_points": 20, "sort": 10, "status": 1 } }
```
### 11.15 修改套餐 `PUT /admin/packages/{package_id}`
可改 `amount_yuan` / `bonus_points` / `sort` / `status`,实时生效。
### 11.16 删除套餐 `DELETE /admin/packages/{package_id}`
物理删除或下架(存在历史订单引用的套餐仅允许下架)。
### 11.17 赠送会员/积分 `POST /admin/gift`【敏感】
```json
// 请求:赠送体验会员
{ "user_ids": [100023, 100024], "gift_type": "membership", "plan_code": "trial", "reason": "国庆活动" }
// 请求:赠送积分
{ "user_ids": [100023], "gift_type": "points", "points": 100, "reason": "客服补偿" }
// 响应
{ "code": 0, "data": { "success": 2, "failed": 0 } }
```
赠送会员写入 membership_orders(source=2,points_cost=0);赠送积分写积分流水(biz_type=5)。
### 11.18 操作日志 `GET /admin/logs`
查询参数:`admin_id`、`action`、`page`。仅 super 可查。
### 11.19 提现列表 `GET /admin/withdrawals`
查询参数:`status`、`user_id`、`page`、`page_size`。
响应列表项:提现单全字段 + 用户昵称。
### 11.20 提现审核 `POST /admin/withdrawals/{withdraw_no}/review`【敏感】
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | 是 | `approve`(已完成线下打款,确认)/ `reject`(拒绝并退回资产) |
| reason | string | reject 时必填 | 拒绝原因 |
```json
// 请求
{ "action": "reject", "reason": "收款账号信息有误" }
// 响应
{ "code": 0, "data": { "withdraw_no": "W20261014153000001", "status": 2, "refunded_assets": 150.0 } }
```
### 11.21 推荐关系列表 `GET /admin/referrals`
查询参数:`inviter_id`、`status`、`risk_flag`、`page`、`page_size`。
响应列表项:推荐关系全字段 + 双方用户昵称。
### 11.22 推荐风控处理 `POST /admin/referrals/{referral_id}/review`【敏感】
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | 是 | `release`(解冻,恢复待激活流程)/ `invalid`(判无效,奖励不发放) |
| reason | string | 是 | 处理说明 |
```json
{ "action": "invalid", "reason": "同设备批量注册小号" }
```
### 11.23 平台配置列表 `GET /admin/configs`
返回 system_configs 全部键值。
```json
{
"code": 0,
"data": {
"list": [
{ "config_key": "register_gift_trial.enabled", "config_value": "1", "description": "注册赠送体验会员开关(1开/0关)", "updated_at": "2026-10-14 10:00:00" },
{ "config_key": "register_gift_trial.days", "config_value": "1", "description": "注册赠送体验会员天数", "updated_at": "2026-10-14 10:00:00" }
]
}
}
```
### 11.24 修改平台配置 `PUT /admin/configs/{config_key}`
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| value | string | 是 | 新配置值 |
```json
// 请求:关闭注册赠送体验会员
{ "value": "0" }
// 响应
{ "code": 0, "data": { "config_key": "register_gift_trial.enabled", "config_value": "0" } }
```
> 修改实时生效(服务端配置缓存 60s 自动失效),操作写入 admin_logs。
### 11.25 视频列表 `GET /admin/videos`
查询参数:`keyword`(ID/标题)、`status`、`author_id`、`page`、`page_size`。
响应列表项:视频全字段 + 作者昵称。
### 11.26 评论列表 `GET /admin/comments`
查询参数:`video_id`、`user_id`、`keyword`(评论内容)、`page`、`page_size`。
响应列表项:评论全字段 + 用户昵称 + 视频标题。
### 11.27 删除评论 `DELETE /admin/comments/{comment_id}`【敏感】
```json
{ "reason": "垃圾广告" }
```
删除后触发评论奖励回退(已计入进度回退、已发奖励扣回),与作者自删逻辑一致。
### 11.28 举报列表 `GET /admin/reports`
查询参数:`status`、`target_type`、`page`、`page_size`。
响应列表项:举报全字段 + 举报人昵称 + 被举报对象摘要(视频标题/评论内容)。
### 11.29 举报处理 `POST /admin/reports/{report_id}/review`【敏感】
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| action | string | 是 | `offline_video`(下架视频)/ `delete_comment`(删除评论)/ `reject`(驳回举报) |
| reason | string | 是 | 处理说明 |
```json
// 请求
{ "action": "offline_video", "reason": "核实违规,下架处理" }
// 响应
{ "code": 0, "data": { "report_id": 301, "status": 1, "status_text": "已处理" } }
```
处理结果通过站内通知(type=1)告知举报人;下架/删除动作复用对应业务流程(含作者通知、评论奖励回退)。
### 11.30 敏感词列表 `GET /admin/sensitive-words`
查询参数:`keyword`、`page`、`page_size`。
```json
{ "code": 0, "data": { "total": 120, "list": [ { "id": 1, "word": "违禁词", "created_at": "2026-10-14 10:00:00" } ] } }
```
### 11.31 添加敏感词 `POST /admin/sensitive-words`
```json
// 请求
{ "word": "新违禁词" }
// 响应
{ "code": 0, "data": { "id": 121, "word": "新违禁词" } }
```
重复添加返回错误码 1001;添加后 Redis 词库缓存(`words:sensitive`)失效重建,实时生效。
### 11.32 删除敏感词 `DELETE /admin/sensitive-words/{id}`
响应 `{ "code": 0 }`,同样触发词库缓存重建。
### 11.33 重置用户密码 `PUT /admin/users/{user_id}/password`【敏感】(仅 super)
客服场景下由后台直接重置某个 App 用户的登录密码。
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| new_password | string | 是 | 新密码,6-100 位(bcrypt 入库,不校验字母数字组合) |
| reason | string | 是 | 操作原因(写入 admin_logs,action=`user_password_reset`) |
```json
// 请求
{ "new_password": "newpass123", "reason": "用户申诉核实通过" }
// 响应
{ "code": 0 }
```
重置后该用户**全端 Token 立即失效**:服务端递增 `auth:epoch:{user_id}` 并删除 refresh 白名单(机制见 3.5),用户需用新密码重新登录。
**错误码**:1006 用户不存在;1001 账号已注销不可操作。
### 11.34 退款登记 `POST /admin/orders/{order_no}/refund`【敏感】(仅 super)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| reason | string | 是 | 退款原因 |
### 11.35 修改管理员密码 `PUT /admin/auth/password`(仅 super)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| old_password | string | 是 | 当前密码 |
| new_password | string | 是 | 新密码,6-100 位 |
响应 `{ "code": 0 }`。
### 11.36 后台新增视频 `POST /admin/videos`(仅 super)
两种模式二选一:
- **upload_token 模式**:先调 `POST /admin/videos/upload` 拿凭证 → `POST /admin/videos/upload/file` 上传 mp4/mov/mkv/avi(multipart,字段 `file` + query `token`)→ 拿到 `upload_token` 后调本接口(服务端自动转码)
- **video_url 模式**:直接填已转码的 m3u8 地址(外站/CDN 资源)
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| title | string | 是 | 1-100 字 |
| description | string | 否 | ≤500 字 |
| category_id | int | 是 | 分类 ID |
| tag_names | array[string] | 否 | 标签 |
| upload_token | string | 模式A必填 | upload/file 返回的凭证(封面可选 cover_upload_token) |
| video_url | string | 模式B必填 | m3u8 地址 |
| cover_url | string | 模式B可选 | 封面 |
| duration | int | 否 | 时长(秒),1-36000 |
| status | int | 否 | 1待审核(默认)/ 2直接发布 |
配套接口:
- `POST /admin/videos/upload`:获取 admin 上传凭证,返回 `{ upload_url, method, token, expire_seconds: 3600 }`
- `POST /admin/videos/upload/file?token=xxx`:接收文件落盘 server/uploads,返回 `{ upload_token, object_key, file_size }`(凭证一次性,upload_token 24h 内有效)
- `POST /admin/upload/image`:通用图片上传(封面/头像/系列图,jpg/jpeg/png/webp/gif),返回可直接访问的完整 URL `{ url, file_size }`
### 11.37 后台编辑视频 `PUT /admin/videos/{video_id}`(仅 super)
均可选,传哪个改哪个:`title`(1-100)/ `description` / `cover_url` / `video_url` / `category_id` / `series_id` / `episode_no`(≥1)/ `status`(0-5)。`series_id` 显式传 `null` 表示移出系列(同时清空 episode_no)。响应 `{ "code": 0, "data": { "video_id": 88001 } }`。
### 11.38 后台删除视频 `DELETE /admin/videos/{video_id}`(仅 super)
**物理删除**:先解绑系列,再级联删除标签/点赞/收藏/评论/观看记录/收益/举报等全部关联数据。响应 `{ "code": 0, "data": { "video_id": 88001, "deleted": true } }`。
### 11.39 系列管理
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | /admin/series | operator | 系列分页列表(keyword 按标题模糊),含 `source_id`(采集来源标记,供"同步更新"按钮) |
| POST | /admin/series | super | 新建系列:`title`(1-100 必填)、`cover_url`、`description`、`category_id` |
| PUT | /admin/series/{series_id} | super | 改 `title` / `cover_url` / `description` / `category_id` / `status`(0隐藏 1正常) |
| DELETE | /admin/series/{series_id} | super | 删除系列(集数视频的 series_id 需先解绑) |
| POST | /admin/videos/{video_id}/series | super | 绑定视频到系列:`{ "series_id": 12, "episode_no": 3 }`,自动重算 series.total_episodes(仅计已发布) |
### 11.40 短剧采集同步(operator)
- `POST /admin/collection/sync/{series_id}`:手动同步单部采集短剧(补充源站新出的集数);非采集内容返回 1006。响应为同步统计 `{新增集数等}`,写 admin_logs(action=`collection_sync`)
- `POST /admin/collection/sync-all`:触发内置短剧源增量同步,**后台异步执行,立即返回** `{ "started": true, "pages": 2 }`。入参 `pages`(1-20,默认 2)为增量同步的列表页数;已有任务进行中返回 1005
### 11.41 采集源管理(单视频类,自助添加)
采集源 = "分类名 + 列表页 URL 模板",URL 必须包含 `{{page}}` 页码占位符;创建/改名时按名称自动建/找分类。
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | /admin/collection/sources | operator | 全部源列表(id/name/list_url/category_id/status/last_sync_at/created_at) |
| POST | /admin/collection/sources | operator | 新增源:`name`(1-50)、`list_url`(须含 `{{page}}`,否则 1001) |
| PUT | /admin/collection/sources/{source_id} | operator | 改 `name` / `list_url` / `status`(0停用 1启用) |
| DELETE | /admin/collection/sources/{source_id} | super | 删除源 |
| POST | /admin/collection/sources/batch | operator | 批量添加:body `{ "text": "分类名 URL\n..." }` 每行一条(URL 可带反引号);返回 `created/skipped/failed` 清单,新建源自动触发后台采集 |
| POST | /admin/collection/sources/{source_id}/sync | operator | 同步单个源(后台异步):入参 `max_pages`(0=不限,增量自动提前停止,上限 200);该源同步中返回 1005 |
| POST | /admin/collection/sources/sync-all | operator | 同步全部启用源(后台异步),返回 `{ "started": [...], "skipped_running": [...] }` |
### 11.42 分类管理
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | /admin/categories | operator | 全部启用分类(不分页,供下拉框):`[{id, name}]` |
| GET | /admin/categories/admin | operator | 分页列表(keyword 模糊),含 `sort` / `status` / 实时统计的 `video_count` |
| POST | /admin/categories | super | 新建:`name`、`sort`(默认0)、`status`(默认1);重名返回错误 |
| PUT | /admin/categories/{cid} | super | 改 `name` / `sort` / `status` |
| DELETE | /admin/categories/{cid} | super | 删除;分类下仍有视频时拒绝并提示数量 |
### 11.43 标签管理
| 方法 | 路径 | 角色 | 说明 |
|---|---|---|---|
| GET | /admin/tags | operator | 分页列表(keyword 模糊,page_size 默认 50 最大 200),`video_count` 由 video_tags 实时统计 |
| POST | /admin/tags | super | 新建:`name`(去空格,空名报错);已存在返回错误 |
| PUT | /admin/tags/{tid} | super | 改名 |
| DELETE | /admin/tags/{tid} | super | 删除标签并清理全部视频关联 |
---
## 12. App 端关键调用时序
### 12.1 启动与域名发现
```
App 启动 → 解析 txt.256kb.top 的 DNS TXT 记录(DoH)
├─ 成功 → 以 TXT 内容作为 API Base URL(写入本地缓存)
└─ 失败/为空 → 计算 md5(yyyyMM) + "_jsnbhh.vip" 作为 Base URL
→ 进入首页拉取 Feed;缓存域名连续请求失败达阈值时重新执行发现流程
```
### 12.2 播放计费
```
App 滑入视频 → 本地查"今日已上报"集合
├─ 已上报 → 直接播放
└─ 未上报 → POST /videos/{id}/watch
├─ code=0 → 记录已上报;若 cost_type=1 弹 Toast"本次消耗 1 积分"
├─ code=2002 → 弹登录引导(游客)
└─ code=2001 → 弹"积分不足"(去充值 / 开通会员)
```
### 12.3 充值
```
选套餐+支付方式 → POST /payments/recharge → 按 pay_method:
支付宝/微信 → 调起 SDK → 返回 App → 轮询 GET /payments/orders/{order_no} → 成功刷新钱包
USDT → 展示金额/地址/二维码+倒计时 → 用户转账 → 轮询订单 → 到账刷新钱包
```
### 12.4 开通会员
```
GET /membership/plans → 选择套餐 → POST /membership/redeem
├─ code=0 → 刷新 /users/me 与会员状态
└─ code=2001 → 跳充值页(带回跳)
```