原进度表严重过期(仍写 06 待做、07 创作者中心待做、08 部分、09 待做), 与代码和 api-phase1.md 都不一致,容易误导排期与联调。本次按实测重写: - 模块表 01-10 逐条核对(04/05/06/07/08 改为已实现;09/10 标部分并写明差什么) - 新增「已完成批次」表,列出本轮冲刺 13 个批次与对应提交,便于回溯 - 新增「尚未完成」清单,分四类:后端研发 6 项、上线前必补 6 项(P0/Go-No-Go)、 基础设施 6 项、非后端范围(客户端与 Web 分工),每条标注依据文档 - 写明已作废方案:module-analytics 收敛因创作者中心改由 Web 端承载而不再需要 为什么改:README 是接手者第一眼看的文档,进度表过期会直接误导排期与联调。
506 lines
29 KiB
Markdown
506 lines
29 KiB
Markdown
# FruitShorts 短剧服务(后端)
|
||
|
||
短剧服务的服务端,Spring Boot + PostgreSQL,对外两个入口:
|
||
管理后台 `/admin/**`(8080)与客户端 `/app/**`(8081)。
|
||
|
||
| 文档 | 内容 |
|
||
| --- | --- |
|
||
| [docs/prd-phase1.md](docs/prd-phase1.md) | 第一阶段 PRD:范围、验收口径、里程碑 |
|
||
| [docs/backend-design-phase1.md](docs/backend-design-phase1.md) | 后端设计:1 万并发观看的容量推导、成本、部署 |
|
||
| [docs/api-phase1.md](docs/api-phase1.md) | **接口清单:后台管理 + 客户端,标注已实现 / 下一批** |
|
||
| [docs/architecture-design.md](docs/architecture-design.md) | 早期架构设计文档 |
|
||
|
||
| 项 | 版本 |
|
||
| --- | --- |
|
||
| 运行时 | JDK 21(LTS),编译目标 Java 17 |
|
||
| 框架 | Spring Boot 3.2.5、Spring Security 6、MyBatis-Plus 3.5.7 |
|
||
| 数据库 | PostgreSQL 18 |
|
||
| 构建 | Maven 3.10 多模块 |
|
||
|
||
## 目录结构
|
||
|
||
```
|
||
FruitServer/
|
||
├── fruitshorts-common/ 统一响应、分页、错误码、业务异常
|
||
├── fruitshorts-framework/ JWT 鉴权(管理端/客户端双密钥)、权限校验、全局异常、分区维护、MyBatis-Plus、CORS
|
||
├── fruitshorts-module-system/ 管理员、角色、权限、菜单、角色分类、配置、操作日志
|
||
├── fruitshorts-module-content/ 短剧、剧集、题材、轮播、审核发布
|
||
├── fruitshorts-module-media/ 分片上传、媒资、播放地址签名、转码任务与多码率产物
|
||
├── fruitshorts-module-user/ C 端账号:验证码、注册登录、会话、资料、注销、风控
|
||
├── fruitshorts-module-playback/ 播放票据、观看进度与流水、收藏、下载授权
|
||
├── fruitshorts-admin-api/ 启动模块与 /admin/** 控制器(8080)
|
||
├── fruitshorts-app-api/ 启动模块与 /app/** 控制器(8081)
|
||
├── sql/ 建表、初始化数据、分区维护(见下)
|
||
└── docs/ PRD、后端设计、接口清单
|
||
```
|
||
|
||
## 开发环境
|
||
|
||
本机环境由 Homebrew 统一管理(版本以本机实际安装为准):
|
||
|
||
| 组件 | 版本 | 说明 |
|
||
| --- | --- | --- |
|
||
| Homebrew | 7.0.9 | 远端指向清华镜像,见文末常见问题 |
|
||
| JDK | OpenJDK 21.0.12.1 | `brew install openjdk@21`,`JAVA_HOME` 已写入 `~/.zshrc` |
|
||
| Maven | 3.10.0 | `brew install maven`,命令为 `/opt/homebrew/bin/mvn` |
|
||
| PostgreSQL | 18.6 | `brew install postgresql@18`,`brew services start postgresql@18` 启动 |
|
||
| ffmpeg | 9.0.2(Homebrew 当前稳定版) | `brew install ffmpeg`;**只有转码 worker 需要**,两个在线入口(8080/8081)不依赖它 |
|
||
|
||
> `postgresql@18` 是 keg-only,命令行工具在 `/opt/homebrew/opt/postgresql@18/bin`,
|
||
> 用之前把它加到 PATH:`export PATH="/opt/homebrew/opt/postgresql@18/bin:$PATH"`。
|
||
|
||
```sh
|
||
java -version # openjdk 21.0.12.1
|
||
mvn -v # Maven 3.10.0 / Java 21.0.12.1
|
||
ffmpeg -version # 转码 worker 用;没装也能跑在线服务,只是转码任务会一直排队
|
||
```
|
||
|
||
> **ffmpeg 只被 `fruitshorts-transcode-worker` 使用**:`backend-design-phase1.md` §8.5 要求转码与在线服务隔离,
|
||
> 所以 app-api / admin-api 里 `fruitshorts.media.transcode.worker-enabled` 默认 false,转码要单独起 worker 进程
|
||
> (`java -jar fruitshorts-transcode-worker/target/fruitshorts-transcode-worker-1.0.0-SNAPSHOT.jar`)。
|
||
|
||
说明:
|
||
|
||
- 工程编译目标为 Java 17(`maven.compiler.release=17`),在 JDK 21 上构建与运行。
|
||
- 父 POM 显式声明了注解处理器(`<proc>full</proc>` + `annotationProcessorPaths`):
|
||
JDK 23 起不再默认扫描 classpath 上的处理器,缺这段配置 Lombok 会失效。
|
||
- 旧的 JDK 18 仍留在 `/Library/Java/JavaVirtualMachines/jdk-18.0.2.jdk`,删除需要 sudo,
|
||
不影响使用(`java_home` 与 `JAVA_HOME` 都已指向 21)。
|
||
|
||
## 快速开始
|
||
|
||
服务有两个入口,可以只启动其中一个:
|
||
|
||
| 服务 | 模块 | 端口 | 面向 |
|
||
| --- | --- | --- | --- |
|
||
| 管理端 | `fruitshorts-admin-api` | 8080 | Vue 后台 |
|
||
| 客户端 | `fruitshorts-app-api` | 8081 | Flutter 客户端 |
|
||
|
||
### 1. 初始化数据库
|
||
|
||
```sh
|
||
export PATH="/opt/homebrew/opt/postgresql@18/bin:$PATH"
|
||
cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
|
||
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/schema.sql
|
||
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/data.sql
|
||
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/schema-phase1.sql
|
||
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/partition-maintenance.sql
|
||
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -f sql/data-phase1.sql
|
||
```
|
||
|
||
| 脚本 | 作用 |
|
||
| --- | --- |
|
||
| `schema.sql` / `data.sql` | 系统域与内容域的基础表与初始化数据 |
|
||
| `schema-phase1.sql` | 第一阶段增量表:用户、播放记录、互动、交易、创作者、转码、运营 |
|
||
| `partition-maintenance.sql` | 分区函数 + 把高写入表转成分区 + 预建未来 3 个月分区 |
|
||
| `data-phase1.sql` | 新增权限点、超管授权、占位商品 |
|
||
|
||
四个脚本都幂等可重复执行,**顺序不要颠倒**:分区转换要求目标表是空的。
|
||
|
||
数据库名 `fruitshorts`,角色 `fruitshorts`(密码 `fruitshorts`)。
|
||
首次搭建环境时先建角色与库:
|
||
|
||
```sh
|
||
psql -d postgres -c "CREATE ROLE fruitshorts LOGIN PASSWORD 'fruitshorts';"
|
||
psql -d postgres -c "CREATE DATABASE fruitshorts OWNER fruitshorts ENCODING 'UTF8';"
|
||
```
|
||
|
||
连接信息在
|
||
`fruitshorts-admin-api/src/main/resources/application.yml`,可用环境变量
|
||
`DB_USER` / `DB_PASSWORD` / `JWT_ADMIN_SECRET` 覆盖。
|
||
|
||
### 关于 PostgreSQL 的两点说明
|
||
|
||
- 自增列用 `GENERATED BY DEFAULT AS IDENTITY`;`sql/data.sql` 里用了显式 ID,
|
||
脚本末尾会把序列 `setval` 到当前最大值,新增数据不会主键冲突。
|
||
- PostgreSQL 没有 `ON UPDATE CURRENT_TIMESTAMP`,`updated_at` 由 `set_updated_at()`
|
||
触发器统一维护(见 `sql/schema.sql` 末尾),实体侧仍不参与更新该字段。
|
||
- 轮播图的展示位置列名叫 `slot`(`position` 在 PostgreSQL 里是关键字),
|
||
实体用 `@TableField("slot")` 映射,对外接口字段仍是 `position`,前端无需改动。
|
||
|
||
### 大表分区与保留策略
|
||
|
||
**为什么要一开始就分区**:`watch_history`(观看流水)、`playback_ticket`(播放票据)、
|
||
`order_event`(订单流水)都是「每次行为写一行」的表。按 1 万并发观看估算,观看流水约
|
||
300 万行/天、播放票据约 360 万行/天,一年就是十亿级。这类表后补分区的代价很高
|
||
(要在维护窗口里整表重建),所以第一阶段就按 `watched_at` / `created_at` 做按月声明式分区。
|
||
|
||
**光建表没用**:声明式分区不会自动长分区,只建 default 分区的话数据会全堆进 default,
|
||
分区等于白做。所以有两层保障:
|
||
|
||
1. `sql/partition-maintenance.sql` 里的 `ensure_monthly_partitions(table, from, months)`
|
||
负责按月建分区,执行脚本时会建好当前月 + 未来 3 个月;
|
||
2. `fruitshorts-framework` 的 `PartitionMaintainer` 在**启动时**和**每天 03:17** 各调一次,
|
||
跨月时即使服务停机一周也不会漏建。
|
||
|
||
验证分区是否真的生效(应落在 `watch_history_2026xx` 而不是 `_default`):
|
||
|
||
```sh
|
||
psql -h 127.0.0.1 -U fruitshorts -d fruitshorts -c \
|
||
"select tableoid::regclass as 分区, count(*) from watch_history group by 1;"
|
||
```
|
||
|
||
**保留策略默认只建不删**。删数据不可逆,第一阶段先解决"写不爆",等保留期与合规确认后
|
||
再把 `fruitshorts.partition.retention-enabled` 打开;各表保留月数写在 `PartitionMaintainer`
|
||
的 `TABLES` 常量里(观看流水 24 个月、播放票据 6 个月、订单流水 36 个月)。手工清理:
|
||
|
||
```sql
|
||
SELECT drop_partitions_before('watch_history', CURRENT_DATE - INTERVAL '24 months');
|
||
```
|
||
|
||
分区表有两条 PostgreSQL 限制要注意:唯一索引必须包含分区键,所以 `playback_ticket`
|
||
的唯一索引是 `(ticket_no, created_at)`、主键是 `(id, created_at)`。
|
||
`coin_ledger` 故意**不分区** —— 它靠 `idempotent_key` 全局唯一保证不重复发币,
|
||
这个唯一性比分区更重要。
|
||
|
||
### 2. 构建
|
||
|
||
```sh
|
||
cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
|
||
mvn -DskipTests package
|
||
```
|
||
|
||
### 3. 启动
|
||
|
||
```sh
|
||
cd /Users/jianghaifeng/Desktop/ReelShort/FruitServer
|
||
java -jar fruitshorts-admin-api/target/fruitshorts-admin-api-1.0.0-SNAPSHOT.jar
|
||
java -jar fruitshorts-app-api/target/fruitshorts-app-api-1.0.0-SNAPSHOT.jar
|
||
```
|
||
|
||
启动后:服务地址 `http://localhost:8080`,接口文档 `http://localhost:8080/doc.html`。
|
||
|
||
## 关键配置
|
||
|
||
| 配置项 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| `fruitshorts.jwt.admin-secret` | 开发默认值 | 管理端签名密钥,**生产必须用 `JWT_ADMIN_SECRET` 注入** |
|
||
| `fruitshorts.jwt.app-secret` | 开发默认值 | 客户端签名密钥,与管理端物理隔离,两边 token 不能互换 |
|
||
| `fruitshorts.jwt.app-access-ttl-minutes` | 120 | 客户端 access token 有效期 |
|
||
| `fruitshorts.jwt.app-refresh-ttl-days` | 30 | refresh token 有效期,每次刷新轮换 |
|
||
| `fruitshorts.media.signature-secret` | 开发默认值 | 播放地址签名密钥,生产必须注入 |
|
||
| `fruitshorts.media.signature-ttl-minutes` | 15 | 播放地址有效期 |
|
||
| `fruitshorts.media.signature-required` | `false` | **生产必须打开**;打开后 `/files/episode/**` 无有效签名返回 403 |
|
||
| `fruitshorts.otp.sender` | `log` | 验证码通道,开发打日志;接入短信/邮件时实现 `OtpSender` 并改这里 |
|
||
| `fruitshorts.partition.enabled` | `true` | 是否自动维护分区 |
|
||
| `fruitshorts.partition.months-ahead` | 3 | 预建未来几个月分区 |
|
||
| `fruitshorts.partition.retention-enabled` | `false` | 是否自动清理过期分区(默认只建不删) |
|
||
|
||
开发阶段签名校验默认关闭,是为了不打断正在联调的客户端。**上线前必须打开**,
|
||
否则片源地址可以被任意复用与盗链。
|
||
|
||
## 第一阶段实现进度
|
||
|
||
> 最后更新:2026-10-16(后端 `develop` 分支 `4418a84`)。**未完成清单见文末「尚未完成」。**
|
||
|
||
| 模块 | 状态 | 说明 |
|
||
| --- | --- | --- |
|
||
| 01 基础与交付准备 | 已实现 | 多模块骨架、接口契约、双密钥鉴权、分区维护;**雪花主键(S0)**、**Swagger 在线文档 `/doc.html`(S1)** |
|
||
| 02 用户账号与登录 | 已实现 | 手机号/邮箱注册登录、验证码反刷、令牌轮换、资料、注销、封禁即时生效 |
|
||
| 03 内容发现与详情 | 已实现 | 首页/发现/分类/详情;内容分级、可播区域、版权窗口、标签字段 |
|
||
| 04 播放器与播放控制 | 已实现 | 播放票据 + 签名地址 + 进度;多码率 HLS 由转码 worker 产出(见 10) |
|
||
| 05 记录、收藏与下载 | 已实现 | 进度、观看流水、收藏、离线下载授权与任务 |
|
||
| 06 评论、点赞与分享 | 已实现 | 评论(游标翻页/审核/删除)、点赞、举报、敏感词;**分享埋点待 C3** |
|
||
| 07 创作者中心 | 已实现 | **在后台管理(Web 端 `ReelShortWeb`)**:工作台、内容编辑、分片上传与断点续传、提交审核与上下架、分集收费与试看、数据看板;不含提现/结算。**App 客户端不提供创作者中心**(口径 2026-10-16) |
|
||
| 08 平台运营后台 | 已实现 | 用户管理、内容治理、订单与权益、创作者与内容审核、数据看板 + 四个后台页面 |
|
||
| 09 分集收费与支付 | 部分 | **Google Play 票据校验闭环已实现**(校验→建单→发权益、幂等与防重放、恢复购买、退款拒付回收、对账);**Apple IAP 只留端口未实现**(印尼 iOS 占比低,文档口径为"预留") |
|
||
| 10 质量、数据与上线 | 部分 | **转码 worker(C1)已实现**;**Redis 缓存与限流(C2)、埋点(C3)、压测未做** |
|
||
|
||
### 已完成批次(按提交顺序)
|
||
|
||
| 批次 | 内容 | 提交 |
|
||
| --- | --- | --- |
|
||
| S0 | 主键改雪花(58 表 / 52 实体 / 存量库幂等迁移脚本) | `37c9a14` `84aa0e1` |
|
||
| S1 | 接口文档在线化(39 个 Controller / 172 个接口中文注解 + `/doc.html`) | `c6fcfcc` |
|
||
| 契约补遗 | 补全接口契约遗漏(系统管理 / 内容 / watch-history / creators 账号 / media 详情) | `efe8af6` |
|
||
| M2 | 后端契约测试补齐(**0 → 37 条**) | `fafadfc` |
|
||
| B1-1 | Google Play 票据校验闭环(校验→建单→发权益) | `db897d5` |
|
||
| B1-2 | 恢复购买 / 退款拒付回收 / 对账真实比对 + Apple 端口预留 | `1dc97b4` |
|
||
| C1 | 转码 worker(独立进程消费 `transcode_task`,四档 HLS + 封面) | `9f76302` |
|
||
| D1 | 部署就绪(配置外置 + prod fail-fast + 自包含 Compose + 冒烟/回滚) | `b567365` |
|
||
| D1-fix | 冒烟脚本与 prod Swagger 开关解耦 | `cbb9961` |
|
||
| D2 | 部署文档按 specs 约束补全(新增 `DEPLOY-CHECKLIST.md`) | `7dd4b15` |
|
||
| 部署修复 | PG18 数据卷挂载路径 + worker 的 prod 密钥/CORS 映射(PR #1) | `8b46e53` `4c4f643` |
|
||
| E1 | 补 5 个 C 端接口(`/app/creator/apply`、`/app/config`、`/app/notices`) | `14b5e41` |
|
||
| 契约修正 | 移除 `/app/creator/works`、`/app/creator/stats` 的 C 端定义 | `bb21920` |
|
||
| E1-rollback | 撤除上述两条接口对应代码(创作者中心归 Web 端) | `4418a84` |
|
||
|
||
**门禁现状**:`mvn test` **91 条全过**(app-api 49 / commerce 27 / worker 12 / admin-api 3)。
|
||
|
||
> 客户端仓库 `ReelShort`(Flutter)另有已完成批次:网络层换 `dio`(`1853e18`)、客户端改名 `nontonki → FruitShorts` + 印尼语 `id-ID` 文案(`61454e3`)。
|
||
|
||
## 默认账号
|
||
|
||
密码均为 `admin123`。
|
||
|
||
| 账号 | 角色 | 权限范围 |
|
||
| --- | --- | --- |
|
||
| admin | 超级管理员 | 全部 14 项权限 |
|
||
| content.editor01 | 内容运营 | 短剧、剧集的增删改与封面上传 |
|
||
| content.reviewer01 | 审核专员 | 查看内容、审核发布、数据报表 |
|
||
| data.analyst01 | 数据分析师 | 查看短剧列表与数据报表 |
|
||
|
||
## 接口一览
|
||
|
||
### 认证 `/admin/auth`
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| POST | `/login` | 登录,返回 token 与权限码 |
|
||
| GET | `/menus` | 当前用户菜单路由树(前端动态路由) |
|
||
| GET | `/profile` | 当前登录人信息 |
|
||
| POST | `/logout` | 登出(JWT 无状态,前端清除 token 即可) |
|
||
| POST | `/password` | 修改自己的密码 |
|
||
|
||
### 后台管理
|
||
|
||
| 模块 | 接口 |
|
||
| --- | --- |
|
||
| 管理员 | `GET /admin/admin-users/page`、`POST /admin/admin-users`、`PUT\|DELETE /admin/admin-users/{id}`、`PUT /{id}/status`、`PUT /{id}/password` |
|
||
| 角色 | `GET /admin/roles/page`、`GET /admin/roles/list`、`POST /admin/roles`、`PUT\|DELETE /admin/roles/{id}`、`GET\|PUT /admin/roles/{id}/permissions` |
|
||
| 角色分类 | `GET\|POST /admin/role-categories`、`PUT\|DELETE /admin/role-categories/{id}` |
|
||
| 权限点 | `GET /admin/permissions`(按分组返回,供权限配置页使用) |
|
||
| 菜单 | `GET /admin/menus/tree`、`POST /admin/menus`、`PUT\|DELETE /admin/menus/{id}` |
|
||
| 系统配置 | `GET\|PUT /admin/configs` |
|
||
| 操作日志 | `GET /admin/operation-logs/page`(按操作人、模块、动作、结果、时间范围筛选)、`GET /admin/operation-logs/{id}` |
|
||
| 字典 | `GET /admin/dicts/drama-category`、`GET /admin/dicts/content-status` |
|
||
|
||
### 内容管理
|
||
|
||
| 模块 | 接口 |
|
||
| --- | --- |
|
||
| 短剧 | `GET /admin/dramas/page`、`GET /admin/dramas/{id}`、`POST /admin/dramas`、`PUT\|DELETE /admin/dramas/{id}` |
|
||
| 审核发布 | `POST /admin/dramas/{id}/submit`、`/approve`、`/reject`、`/publish`、`/offline` |
|
||
| 审核记录 | `GET /admin/dramas/{id}/review-records` |
|
||
| 剧集 | `GET /admin/episodes?dramaId=`、`POST /admin/episodes`、`PUT\|DELETE /admin/episodes/{id}`、`PUT /admin/episodes/sort` |
|
||
| 轮播图 | `GET /admin/banners/page`、`POST /admin/banners`、`PUT\|DELETE /admin/banners/{id}`、`PUT /admin/banners/{id}/status`、`PUT /admin/banners/sort` |
|
||
| 短剧类型 | `GET\|POST /admin/drama-categories`、`PUT\|DELETE /admin/drama-categories/{id}`(权限沿用 `drama:*`) |
|
||
|
||
### 媒资 `/admin/media`
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| POST | `/upload/init` | 建立分片会话,返回 `uploadId`、`chunkSize`、`totalChunks` |
|
||
| POST | `/upload/chunk` | 上传单个分片(multipart:`uploadId`、`index`、`file`) |
|
||
| POST | `/upload/complete` | 合并分片,返回媒资(含可访问的 `url`) |
|
||
| GET | `/{id}` | 查询媒资 |
|
||
|
||
## 媒资存储(本地)
|
||
|
||
当前没有对象存储,文件直接落在本机磁盘:
|
||
|
||
| 项 | 默认值 | 说明 |
|
||
| --- | --- | --- |
|
||
| 存储目录 | `./data/uploads` | 配置项 `fruitshorts.media.root-dir`,按 `业务类型/年月/uuid.ext` 分目录 |
|
||
| 访问前缀 | `/files/**` | 配置项 `fruitshorts.media.public-prefix`,映射为静态资源 |
|
||
| 分片大小 | 5MB | `fruitshorts.media.chunk-size`,单文件上限 2GB |
|
||
| 请求上限 | 20MB | `spring.servlet.multipart`,只需容纳一个分片 |
|
||
|
||
要点:
|
||
|
||
- `/files/**` 在 `SecurityConfig` 里是公开路径,因为 `<img>` / `<video>` 无法携带请求头。
|
||
- 上传权限:封面用 `cover:upload`,视频用 `episode:upload`。
|
||
- `data/` 是运行时产物,不需要提交到版本库。
|
||
- 换成 OSS/S3 时只需替换 `MediaStorageService`(分片落盘、合并、生成访问地址三处),
|
||
`MediaService` 与前端接口都不用动。
|
||
|
||
## 统一约定
|
||
|
||
- 响应体 `{ code, message, data }`,成功 `code=0`。
|
||
- 分页请求 `pageIndex`(从 1 开始)、`pageSize`,响应 `{ total, list }`。
|
||
- 鉴权头 `Authorization: Bearer <token>`;未登录 `code=401`,无权限 `code=403`。
|
||
- 错误码见架构文档附录 A;业务错误如 `100000` 账号密码错误、`200003` 状态不允许该操作。
|
||
|
||
## 客户端接口 `/app/**`
|
||
|
||
面向 Flutter 客户端,只暴露已上线内容,不含审核状态、操作日志等管理端字段。
|
||
**完整接口清单(含请求/响应示例与错误码)见 [docs/api-phase1.md](docs/api-phase1.md)。**
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| POST | `/app/auth/otp` | 发送验证码(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` 吊销全部会话 |
|
||
| GET/PUT | `/app/me` | 我的资料 |
|
||
| GET | `/app/me/devices` | 登录设备 |
|
||
| GET | `/app/me/export` | 数据导出(隐私合规) |
|
||
| POST | `/app/me/deactivate` `/cancel` | 申请/撤销注销(15 天冷静期) |
|
||
| GET | `/app/home/feed` | 首页聚合:轮播(HOME_TOP)+ 分区(热门 / 新剧 / 各分类) |
|
||
| GET | `/app/discover/feed` | 发现页聚合:轮播(DISCOVER)+ 分类分区 |
|
||
| GET | `/app/categories` | 分类列表 |
|
||
| GET | `/app/dramas/{id}` | 详情,含剧集列表,一次请求即可渲染详情页 |
|
||
| GET | `/app/episodes/{id}/play` | 播放票据:服务端判定权益后签发**短时效签名地址** |
|
||
| POST | `/app/watch/progress` | 上报观看进度(建议每 10s 一次) |
|
||
| GET | `/app/watch/progress?dramaId=` | 某剧续看位置 |
|
||
| GET | `/app/watch/recent` | 最近观看 |
|
||
| DELETE | `/app/watch/progress[/{dramaId}]` | 删除 / 清空记录 |
|
||
| POST | `/app/favorites/toggle` | 收藏切换 |
|
||
| GET | `/app/favorites` | 收藏列表(卡片结构与首页一致) |
|
||
|
||
约定:
|
||
|
||
- 只返回 `status = published` 的短剧与剧集,未上线按不存在处理。
|
||
- 图片与视频返回相对路径(`/files/...`),客户端拼上服务地址即可;
|
||
播放地址会带 `?e=&s=` 签名参数,客户端**原样使用、不要自己拼接**。
|
||
- 播放票据四种结果:`FREE`、`ENTITLED`(已购权益)、`COIN_REQUIRED`(返回 `unlockCoin`)、
|
||
`NO_MEDIA`(已上线但还没传视频或未转码完成)。
|
||
- 鉴权边界:只有浏览类接口(`/app/auth/**`、`/app/home|discover|categories|dramas`、
|
||
`/app/episodes/*/play`)允许匿名,其余 `/app/**` 一律需要客户端 token;
|
||
管理端 token 打到客户端接口会被拒(两边密钥不同)。
|
||
- 需要登录的接口建议带 `X-Device-Id`,用于下载授权、并发播放限制与风控。
|
||
|
||
性能(本机实测,预热后 30 次采样,目标 50ms):
|
||
|
||
| 接口 | 平均 | P95 | 最大 | 响应体 |
|
||
| --- | --- | --- | --- | --- |
|
||
| `/app/home/feed` | 13.6ms | 20.3ms | 23.4ms | 3.9KB |
|
||
| `/app/discover/feed` | 7.3ms | 8.1ms | 8.8ms | 1.7KB |
|
||
| `/app/categories` | 2.0ms | 2.1ms | 3.4ms | 0.2KB |
|
||
| `/app/dramas/{id}` | 2.8ms | 3.1ms | 3.5ms | 0.7KB |
|
||
| `/app/episodes/{id}/play` | 2.2ms | 2.6ms | 3.3ms | 0.2KB |
|
||
|
||
`SlowRequestLogger` 会对超过 50ms 的 `/app/**` 请求打 WARN 日志,
|
||
内容量增长或索引失效时能第一时间发现;首页聚合是查询最多的一档(分类 + 热门 + 新剧 + 各分类列表),
|
||
如果将来分类变多,优先给它加缓存。
|
||
|
||
## 权限模型
|
||
|
||
- 权限码随 JWT 下发(如 `drama:create`、`banner:list`),控制器用
|
||
`@PreAuthorize("@ss.hasPermission('drama:create')")` 校验。
|
||
- 菜单树由权限码派生:角色拥有哪些权限,就可见哪些菜单。
|
||
- 内容状态机:`draft → reviewing → approved → published → offline`。
|
||
草稿不能直接发布,已上线的短剧需先下线才能删除;每次流转写入审核记录与操作日志。
|
||
- 鉴权实时读库(`PermissionChecker` 通过 `PermissionProvider` 查当前用户的权限),
|
||
后台改了角色权限或菜单,**前端刷新一下即可生效,不用重新登录**。
|
||
前端在每次进入应用时会重新拉取 `GET /admin/auth/menus` 与 `GET /admin/auth/profile`。
|
||
|
||
## 操作日志
|
||
|
||
后台写操作由切面自动记录,**不需要在每个接口里手写**,在控制器方法上加注解即可:
|
||
|
||
```java
|
||
@OperationLog(module = "内容管理", action = "新增短剧类型")
|
||
public Result<Long> create(@Valid @RequestBody DramaCategorySaveReqVO req) { ... }
|
||
```
|
||
|
||
每次调用记录:操作人、操作时间、模块、动作、请求方式与地址、请求参数、请求体、
|
||
响应体、成功/失败、错误信息、耗时、IP。
|
||
|
||
约定:
|
||
|
||
- 只记写操作(POST/PUT/DELETE),GET 查询不入库,避免刷屏。
|
||
- 请求体与响应体超过 4000 字符会截断;密码、密钥、token 等字段自动替换成 `***`。
|
||
- 业务异常记 `FAIL` 并带错误信息;`@PreAuthorize` 拦下的越权访问由
|
||
`AccessDeniedRecorder` 单独记一条(模块=安全,动作=越权访问被拒绝)。
|
||
- 日志写入失败不影响业务(切面内兜底 try/catch)。
|
||
- 查看入口:后台管理 → 操作日志,权限码 `log:list`,默认只给超级管理员。
|
||
|
||
## 前端对接
|
||
|
||
管理后台 `../ReelShortWeb` 已完成全部对接,本地演示数据已删除:
|
||
|
||
| 页面 | 使用接口 |
|
||
| --- | --- |
|
||
| 登录 / 左侧菜单 | `POST /admin/auth/login`、`GET /admin/auth/menus` |
|
||
| 工作台 | `GET /admin/dramas/page`(统计与最近更新) |
|
||
| 短剧管理 | `/admin/dramas/**`、`/admin/dicts/drama-category`、审核发布动作 |
|
||
| 剧集管理 | `/admin/episodes/**`、`PUT /admin/episodes/sort` |
|
||
| 角色权限配置 | `/admin/roles/**`、`GET /admin/permissions`、`GET /admin/role-categories` |
|
||
| 管理员用户列表 | `/admin/admin-users/**` |
|
||
| 菜单管理 | `GET /admin/menus/tree` + 增删改 |
|
||
| 角色分类 | `/admin/role-categories/**` |
|
||
| 系统设置 | `GET\|PUT /admin/configs` |
|
||
|
||
前端侧改动:
|
||
|
||
1. `.env.base` 关闭 mock(`VITE_USE_MOCK=false`);
|
||
2. `vite.config.mts` 增加 `/admin` 代理到 `http://127.0.0.1:8080`;
|
||
3. `src/api/admin/*.ts` 封装全部管理端接口;
|
||
4. 请求头自动携带 `Authorization: Bearer <token>`;
|
||
5. `src/stores/rbac.js` 的 `can(code)` 改用登录返回的权限码,
|
||
原 `src/data/`、`src/permissions/` 下的演示数据已删除;
|
||
6. 表格状态列改用接口返回的 `status` / `statusText`,
|
||
大数值用 `src/utils/format.js` 的 `formatCount` 格式化。
|
||
|
||
## 尚未完成
|
||
|
||
> 最后更新:2026-10-16。完成后请同步本表与 [docs/api-phase1.md](docs/api-phase1.md)。
|
||
|
||
### 一、后端研发(剩余)
|
||
|
||
| # | 项 | 内容 | 依据 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | **C2 Redis 缓存与限流** | 首页/发现/分类等热点接口缓存(Cache-Aside)+ 验证码/注册/登录限流;生产 Redis 规格 `4C16G 主从 2~3 哨兵` | 任务清单 C2、`17-风险评估` R-10、`10-安全方案` §三 |
|
||
| 2 | **C3 埋点** | 播放/支付/转化事件 + 数据字典对齐(含 06 的分享埋点) | 任务清单 C3、`api-phase1` §3 |
|
||
| 3 | **Apple IAP 票据校验** | 目前只留端口与枚举,未做真实校验(印尼 iOS 占比低,文档口径为"预留") | `api-phase1` §1.8 |
|
||
| 4 | **压测** | 目标 `P95 < 50ms`(预热后);容量口径文档里有两套("1 万并发"与"2 万并发 + 3000 QPS"),需定稿 | `09-测试计划` §五、`13/14-容量` |
|
||
| 5 | **对账数据源** | 结算金额/手续费/税只能从 Play Console 财务报告取(需报表权限);当前 `storeAmountSum` 恒为 `null` + `notes` 说明 | B1-2 待确认 |
|
||
| 6 | **退款口径完善** | 部分退款按比例回收、退款实时性(当前由每日对账 + 手动驱动,未接 RTDN 推送) | B1-2 待确认 |
|
||
|
||
### 二、上线前必补(P0 / Go-No-Go:非研发,但阻塞上线)
|
||
|
||
| # | 项 | 当前状态 | 目标 |
|
||
| --- | --- | --- | --- |
|
||
| 1 | 生产密钥集中托管 + 轮换 | 仅 `.env`(权限 600),无托管、无轮换流程 | 密钥集中托管、最小权限、可轮换(`10-安全方案` §五) |
|
||
| 2 | 安全专项测试 | 未做 | 越权/注入/敏感数据专项,高危上线前清零(`12-上线检查清单` §三) |
|
||
| 3 | 备份与恢复演练 | 仅手动 `pg_dump`,且与数据库**同机** | **RPO ≤ 5min / RTO ≤ 30min**;全量 + WAL 归档 + 加密 + 异地 + 季度演练(`08-备份恢复`) |
|
||
| 4 | 监控告警接入 | 未部署,仅日志 + `SlowRequestLogger` 兜底 | Prometheus / Grafana / Loki + P0/P1 告警规则(`07-监控告警`;**需先加 actuator 指标端点**) |
|
||
| 5 | HTTPS + 域名 + 反代 | 无 | 管理端 / 客户端 API / 媒资**分别域名**,HTTPS 强制(`04-部署架构` §四、`10-安全方案` §二) |
|
||
| 6 | 媒资迁 OSS/S3 + CDN | 本地磁盘 `./data/uploads` | 生产必须换 OSS/S3 + CDN(`08-备份恢复` §六、`12-上线检查清单` §四) |
|
||
|
||
### 三、基础设施
|
||
|
||
- **CI/CD 流水线**:当前手动跑门禁,未接流水线(`06-CI-CD方案`)
|
||
- **多实例 / 高可用**:当前单点部署;文档要求应用 `8C16G × 3~4`、转码 worker `8C16G × 2~3`,生产用 K8s(`14-生产资源规划`)
|
||
- **短信 / 邮件真实通道**:开发走 log,生产待接通道(`05-中间件清单`)
|
||
- **分区保留策略**:`partition.retention-enabled=false`,待合规确认后打开(`03-数据架构`)
|
||
- **`watch_history` 异步落库**:Kafka 异步通道为 Phase 2 预留(`13-容量与扩展规划` §四)
|
||
- 备份存储位置与成本待定(`08-备份恢复` §六)
|
||
|
||
### 四、不在后端范围(另有分工)
|
||
|
||
- **客户端 Flutter(`ReelShort`)**:A2~A6 与后端联调、B2 Google Play Billing 购买流程、秒开基线(需低端安卓设备);网络层 `dio`、改名与印尼语文案已完成
|
||
- **Web 后台(`ReelShortWeb`)**:由另一位同事侧负责
|
||
|
||
### 五、文档待同步(供文档维护人)
|
||
|
||
`docs/api-phase1.md` 与 specs `docs/` 下仍有 20+ 处**过期陈述**(例:`api-phase1` §3 仍把 09 支付标"下一批";specs `17-风险评估` 的 R-01/R-03/R-04/R-09 仍标"未开始")。
|
||
**遇到冲突以本表与本仓库 `api-phase1.md` 正文为准。**
|
||
|
||
> **已作废的方案**:曾提议新建 `fruitshorts-module-analytics` 收敛"两份创作者看板"。
|
||
> 因 2026-10-16 口径变更(**创作者中心由 Web 端承载**)撤除了 App 侧 `/app/creator/works`、`/app/creator/stats`,
|
||
> **两份看板问题已从根上消失,该方案不再需要**。
|
||
|
||
## 常见问题
|
||
|
||
**编译报「找不到符号 getXxx / 无法推断类型参数」**
|
||
Lombok 注解处理器没跑起来。确认是在 JDK 21 下构建(`mvn -v` 应显示 Java 21),
|
||
父 POM 已声明 `annotationProcessorPaths`,若仍报错先 `mvn clean`。
|
||
|
||
**8080 端口被占用**
|
||
|
||
```sh
|
||
lsof -tiTCP:8080 -sTCP:LISTEN | xargs kill
|
||
```
|
||
|
||
**brew 连不上 GitHub**
|
||
本机访问 `github.com` 不通,brew 仓库已改用清华镜像:
|
||
|
||
```sh
|
||
git -C /opt/homebrew remote set-url origin https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git
|
||
```
|
||
|
||
Bottle 走 USTC 镜像(环境变量 `HOMEBREW_BOTTLE_DOMAIN`)。
|
||
要换回官方源,把 `origin` 改回 `https://github.com/Homebrew/brew` 即可。
|
||
|
||
**临时切换 JDK**
|
||
|
||
```sh
|
||
export JAVA_HOME=$(/usr/libexec/java_home -v 21)
|
||
```
|
||
|
||
Homebrew 的 Maven 在 `JAVA_HOME` 未设置时会回退到它自带的 JDK(当前为 27),
|
||
父 POM 的注解处理器配置保证这种情况下也能编译通过。
|