refactor: 后端标识统一为 FruitShorts,并落地 05 离线下载

为什么:
1) 产品已定名 FruitShorts,但后端仍是 reelshort 系列标识 —— Java 包 com.reelshort、
   模块名与 jar 名 reelshort-*、spring.application.name、配置前缀 reelshort.*,
   这些会出现在启动日志、命令行和 IDE 里,与产品名不一致。本次整体替换为 fruitshorts,
   数据库连接名上一轮已改名,保持不变。
2) 第一阶段下一批第 1 步(05 离线下载)要求把下载授权(设备绑定/有效期/吊销)、
   下载任务与断点续传、配额与过期清理、仅 Wi-Fi 规则做成完整接口,本提交一并落地。
3) 顺带修复:schema-phase1.sql 里 playback_ticket / order_event 仍按普通表定义唯一索引,
   表分区化之后重跑脚本必定报错(分区表的唯一索引必须包含分区键)。改为在本脚本里就
   按分区表定义,并验证脚本可重复执行。
This commit is contained in:
Vicky committed 2026-10-15 11:44:26 +08:00
1 parent a4aa749cc6
commit b3179fc836
251 files changed
+7261 -767

No files matched your search

+264
View File
@@ -0,0 +1,264 @@
# 第一阶段接口清单(后台管理 + 客户端)
> 版本 v1.0 · 配套 [PRD](./prd-phase1.md) 与 [后端设计](./backend-design-phase1.md)
> 状态说明:**已实现** = 代码已落地可联调;**下一批** = 表结构与契约已冻结,代码待补。
## 0. 通用约定
**服务入口**:后台管理 `fruitshorts-admin-api` 只暴露 `/admin/**`(8080);
客户端 `fruitshorts-app-api` 只暴露 `/app/**` 与媒资 `/files/**`(8081)。
**统一响应体**
```json
{ "code": 0, "message": "ok", "data": {} }
```
`code = 0` 成功;非 0 时 `message` 可直接展示给用户。分页统一为 `{ total, list }`。
**鉴权**:`Authorization: Bearer <token>`。
管理端与客户端用**两套独立密钥**签发,管理端 token 打到客户端接口会直接被拒。
客户端 `access token` 默认 2 小时,`refresh token` 30 天且每次刷新轮换。
**设备标识**:客户端建议所有请求都带 `X-Device-Id`,用于下载授权、并发播放限制与风控。
**主要错误码**
| code | 含义 | 客户端处理 |
| --- | --- | --- |
| 0 | 成功 | — |
| 400 | 参数校验失败 | 提示 message |
| 401 | 未登录或登录过期 | 用 refresh token 换新 token,失败则跳登录 |
| 403 | 无权限 | 提示无权限 |
| 429 | 请求过于频繁 | 提示稍后再试(验证码频控走这里) |
| 100000 | 账号或密码错误 | 提示 |
| 100001 | 账号已停用 | 展示封禁申诉入口 |
| 100002 | 金币余额不足 | 拉起充值 |
| 100003 | 剧集未发布或不存在 | 返回上一页 |
| 100005 | 订单已支付或已关闭 | 刷新订单状态 |
| 200002 | 数据不存在 | 提示 |
| 200003 | 当前状态不允许该操作 | 提示 |
## 1. 客户端接口(/app/**)
### 1.1 账号(已实现)
| 方法 | 路径 | 鉴权 | 说明 |
| --- | --- | --- | --- |
| POST | `/app/auth/otp` | 匿名 | 发送验证码。`scene` = REGISTER/LOGIN/RESET_PWD/BIND |
| POST | `/app/auth/register` | 匿名 | 手机号/邮箱注册,返回令牌 |
| POST | `/app/auth/login` | 匿名 | 验证码或密码登录 |
| POST | `/app/auth/refresh` | 匿名 | 用 refresh token 换新令牌并轮换 |
| POST | `/app/auth/logout` | 可选 | 登出;`allDevices=true` 吊销全部会话 |
发送验证码:
```json
POST /app/auth/otp
{ "scene": "REGISTER", "targetType": "PHONE", "target": "08123456789", "deviceId": "d-1" }
```
手机号会自动规范化成 E.164(印尼本地 `08xx` → `+62xxx`)。频控:同号 60 秒一次、每日 10 次,
同 IP 每小时 20 次;连续输错 5 次该验证码作废。
注册:
```json
POST /app/auth/register
{
"targetType": "PHONE", "target": "08123456789", "code": "123456",
"password": "可选,设置后可用密码登录",
"nickname": "可选",
"deviceId": "d-1", "platform": "IOS", "appVersion": "1.0.0",
"agreementVersion": "2026-10-01", "marketingConsent": false
}
```
响应(登录、刷新同结构):
```json
{
"code": 0, "message": "ok",
"data": {
"accessToken": "...", "refreshToken": "...", "expiresIn": 7200, "tokenType": "Bearer",
"newUser": true,
"user": { "uid": "U8KQ2ZP1", "nickname": "User8KQ2ZP", "status": "normal",
"countryCode": "ID", "language": "id-ID", "currency": "IDR",
"maskedTarget": "+628****789", "vipLevel": 0, "vipExpireAt": null }
}
}
```
### 1.2 个人中心(已实现)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/app/me` | 当前用户资料 |
| PUT | `/app/me` | 修改昵称/头像/性别/生日/语言/时区/区域 |
| GET | `/app/me/devices` | 登录设备列表 |
| GET | `/app/me/export` | 数据导出(商店与隐私法规要求) |
| POST | `/app/me/deactivate` | 申请注销,进入 15 天冷静期并立即登出全部设备 |
| POST | `/app/me/deactivate/cancel` | 冷静期内撤销注销 |
### 1.3 内容浏览(已实现)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/app/home/feed` | 首页:轮播 + 分区,卡片带 `preview` 可播预览集 |
| GET | `/app/discover/feed` | 发现页分区 |
| GET | `/app/categories` | 分类列表 |
| GET | `/app/dramas/{id}` | 详情(含剧集列表,免费集带**已签名**播放地址) |
### 1.4 播放(已实现,链路已重做)
| 方法 | 路径 | 鉴权 | 说明 |
| --- | --- | --- | --- |
| GET | `/app/episodes/{id}/play` | 可选 | 签发播放票据与签名地址 |
```json
{
"code": 0, "message": "ok",
"data": {
"episodeId": 12, "dramaId": 5, "number": 1, "title": "第 1 集", "durationSec": 180,
"accessible": true,
"accessType": "FREE",
"playUrl": "/files/episode/202610/xxx.mp4?e=1760000000&s=9f2c...",
"qualities": ["AUTO"],
"unlockCoin": 0,
"previewSeconds": 0
}
}
```
`accessType` 取值:`FREE`(免费集)、`ENTITLED`(已购权益)、`COIN_REQUIRED`(需解锁,
此时 `playUrl` 为空、`unlockCoin` 有值)、`NO_MEDIA`(还没上传片源或未转码完成)。
**判定顺序由服务端决定,客户端不得自行推断可否播放。** 签名默认 15 分钟有效;
开启 `fruitshorts.media.signature-required` 后,`/files/episode/**` 没带有效签名会返回 403。
### 1.5 观看记录与收藏(已实现)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | `/app/watch/progress` | 上报进度(建议每 10s 一次,切集时补一次) |
| GET | `/app/watch/progress?dramaId=` | 某剧续看位置 |
| GET | `/app/watch/recent?limit=20` | 最近观看 |
| DELETE | `/app/watch/progress/{dramaId}` | 删除某剧记录 |
| DELETE | `/app/watch/progress` | 清空记录 |
| POST | `/app/favorites/toggle` | 收藏/取消收藏,返回切换后状态 |
| GET | `/app/favorites` | 收藏列表(卡片结构与首页一致) |
| DELETE | `/app/favorites/{dramaId}` | 取消收藏 |
进度合并规则:同一集内只前进不后退;换集直接采纳新集;`finished` 只在接近片尾或客户端显式上报时置位。
这样用户换设备不会看到进度倒退。
### 1.6 离线下载(已实现)
服务端只发**授权**与**短时效地址**,分片由客户端直接向 CDN 拉取 ——
这样并发下载不会打到应用服务器,服务端只记账(进度、配额、有效期)。
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/app/downloads/policy` | 策略与配额:单设备上限、授权天数、仅 Wi-Fi、清晰度上限、已用/剩余 |
| PUT | `/app/downloads/settings` | 偏好设置:仅 Wi-Fi 下载、最高清晰度 |
| POST | `/app/downloads` | 申请下载授权(同剧集重复申请幂等,只刷新有效期) |
| GET | `/app/downloads` | 下载列表(授权 + 任务进度,可直接渲染) |
| GET | `/app/downloads/{episodeId}/url` | 断点续传前重新取地址(授权不变,地址是短时效的) |
| POST | `/app/downloads/{episodeId}/progress` | 上报下载进度,用于断点续传与跨端展示 |
| DELETE | `/app/downloads/{episodeId}` | 删除单条下载(吊销授权) |
| DELETE | `/app/downloads` | 清空当前设备全部下载,返回清除条数 |
设备标识优先取请求体 `deviceId`,其次取请求头 `X-Device-Id`。
授权默认 30 天有效,过期由每日定时任务置为 `EXPIRED`;被吊销或过期的授权客户端下次联网即失效。
需要解锁的剧集走权益判定,未解锁不允许下载(当前内容均为免费集,付费链路见 09)。
配额与有效期由 `fruitshorts.download.*` 配置,通过 `policy` 下发,客户端不要写死。
### 1.7 互动、交易、创作者(下一批)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| POST | `/app/comments` | 发表评论(先审后发) |
| GET | `/app/comments?dramaId=&cursor=` | 评论列表(游标分页) |
| DELETE | `/app/comments/{id}` | 删除自己的评论 |
| POST | `/app/likes/toggle` | 点赞(DRAMA/EPISODE/COMMENT) |
| POST | `/app/reports` | 举报 |
| POST | `/app/orders/verify` | 提交 IAP / Google Play 票据,服务端二次校验后发权益 |
| GET | `/app/orders` | 我的订单 |
| GET | `/app/wallet` | 金币余额与流水 |
| POST | `/app/episodes/{id}/unlock` | 金币解锁分集 |
| POST | `/app/creator/apply` | 创作者入驻 |
| GET | `/app/creator/works` | 我的作品与审核状态 |
| GET | `/app/creator/stats` | 创作者数据看板 |
| GET | `/app/config` | 启动配置(协议版本、开关、最低版本) |
| GET | `/app/notices` | 系统公告 |
## 2. 后台管理接口(/admin/**)
### 2.1 认证与系统(已实现)
`POST /admin/auth/login`、`GET /admin/auth/menus`、`GET /admin/auth/profile`、
`POST /admin/auth/logout`、`POST /admin/auth/password`;
管理员、角色、权限、菜单、角色分类、系统配置、操作日志各自一套增删改查。
### 2.2 内容与媒资(已实现)
短剧(含是否热门、审核发布状态机)、剧集、分类、轮播图;媒资分片上传(init/chunk/complete)。
新增字段:短剧 `rating`(内容分级)、`regionCodes`(可播区域)、版权窗口、
`creatorId`、`tags`、`trailerUrl`;剧集 `transcodeStatus`、`hlsUrl`、`previewSeconds`、字幕。
### 2.3 App 用户管理(已实现)
| 方法 | 路径 | 权限点 | 说明 |
| --- | --- | --- | --- |
| GET | `/admin/app-users/page` | `app-user:list` | 检索:keyword(uid/昵称/手机号/邮箱)、status、countryCode、registerSource |
| GET | `/admin/app-users/{id}` | `app-user:list` | 详情 |
| GET | `/admin/app-users/{id}/ban-records` | `app-user:list` | 封禁与申诉记录 |
| PUT | `/admin/app-users/{id}/status` | `app-user:update` | 改状态:normal/limited/banned |
状态处置会**立即吊销该用户全部会话**并写入封禁记录,避免"封了还能用"。
### 2.4 下载管理(已实现)
| 方法 | 路径 | 权限点 | 说明 |
| --- | --- | --- | --- |
| GET | `/admin/downloads/page` | `download:list` | 按用户 / 设备 / 状态检索下载授权,含剧名与集号 |
| GET | `/admin/downloads/stats` | `download:list` | 授权与任务的分布统计 |
| PUT | `/admin/downloads/{id}/revoke` | `download:revoke` | 吊销授权,用户侧下次联网即失效 |
| POST | `/admin/downloads/cleanup` | `download:revoke` | 手动清理过期授权(默认每天凌晨自动执行) |
### 2.5 内容治理、订单、创作者审核、数据看板(下一批)
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| GET | `/admin/comments/page` | 评论审核队列 |
| PUT | `/admin/comments/{id}/audit` | 通过/驳回(带原因码) |
| GET | `/admin/reports/page` | 举报工单 |
| PUT | `/admin/reports/{id}/handle` | 处理举报(可联动封禁) |
| GET/POST/PUT | `/admin/sensitive-words` | 敏感词库 |
| GET | `/admin/orders/page` | 订单检索(可按用户、状态、平台) |
| POST | `/admin/orders/{orderNo}/compensate` | 票据丢失的补发 |
| PUT | `/admin/entitlements/{id}/revoke` | 权益回收 |
| POST | `/admin/reconcile/run` | 手动触发对账 |
| GET | `/admin/creators/page` | 创作者审核队列 |
| PUT | `/admin/creators/{id}/audit` | 入驻审核 |
| GET | `/admin/submissions/page` | 内容提交审核 |
| PUT | `/admin/submissions/{id}/audit` | 内容审核(通过/驳回/驳回原因码) |
| PUT | `/admin/prices` | 分集价格与免费集配置 |
| GET | `/admin/dashboard/overview` | 经营大盘(用户/播放/收入/成本) |
| GET | `/admin/dashboard/transcode` | 转码队列与失败任务 |
| POST | `/admin/notices` | 发布系统公告 |
## 3. 契约与实现状态对照
| 模块 | 表结构 | 接口 | 代码 |
| --- | --- | --- | --- |
| 01 基础与交付准备 | 已就绪 | 约定已冻结 | 脚手架 / 契约 / 签名 / 转码骨架 已实现 |
| 02 用户账号与登录 | 已建 | 已冻结 | **已实现**(手机号/邮箱,第三方登录走同一凭证表) |
| 03 内容发现与详情 | 已建 | 已冻结 | 已实现(补了分级、区域、版权、标签字段) |
| 04 播放器与播放控制 | 已建 | 已冻结 | 票据 + 签名 + 进度 已实现;多码率依赖转码 worker |
| 05 记录、收藏与下载 | 已建 | 已冻结 | **已实现**:进度、观看流水、收藏、离线下载授权与任务 |
| 06 评论、点赞与分享 | 已建 | 已冻结 | 下一批 |
| 07 创作者中心 | 已建 | 已冻结 | 下一批(转码任务与产物记账已实现) |
| 08 平台运营后台 | 已建 | 用户管理已实现 | 治理/订单/看板 下一批 |
| 09 分集收费与支付 | 已建 | 已冻结 | 下一批(权益判定端口已预留,接上即生效) |
| 10 质量、数据与上线 | 已建 | 已冻结 | 压测与埋点 下一批 |