Files
video/docs/01-开发文档.md
T
2026-10-18 21:08:54 +08:00

1135 lines
58 KiB
Markdown
Raw 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.0
- 日期:2026-10-14
- 适用端:Flutter App(iOS / Android)、Python 后端
---
## 1. 项目介绍
### 1.1 项目概述
一款竖屏沉浸式短视频应用(产品形态参考抖音/快手)。核心商业模式为:
**免费额度 + 积分付费观看 + 会员订阅**
- 所有用户(含游客)每天可免费观看 10 个视频
- 超出免费额度后,注册用户每观看 1 个视频消耗 1 积分,游客需注册/登录
- 积分通过充值获得(支付宝 / 微信 / USDT),**1 元 = 10 积分**
- 积分可开通会员,会员享有**无限观看**和**优先上传**权益
- 用户可通过评论赚取积分(每评论 10 个视频奖励 1 积分),形成内容互动激励闭环
### 1.2 项目组成
```
video/ # 项目根目录(当前目录)
├── app/ # Flutter 手机端(前端)
├── server/ # Python 后端服务
├── admin/ # 管理后台 Web(Vue3,一期包含)
└── docs/ # 项目文档
```
### 1.3 名词定义
| 名词 | 定义 |
|---|---|
| 游客 | 未注册/未登录用户,以设备 ID(device_id)标识 |
| 注册用户 | 通过手机号或邮箱注册并登录的用户 |
| 会员 | 开通体验/月卡/年卡/终身卡且在有效期内的注册用户 |
| 积分 | 平台虚拟货币,1 元 = 10 积分,用于观看扣费、开通会员 |
| 免费额度 | 每个计费主体(游客=设备,注册用户=账号)每天可免费观看的视频数,默认 10 个 |
| 计费主体 | 观看计费的统计单位:注册用户为 user_id,游客为 device_id |
| 资产 | 创作者激励体系的虚拟货币,**10 资产 = 1 元**,存放于资产钱包,可提现 |
| 推荐人/被推荐人 | 通过邀请码绑定的邀请关系双方;推荐人须为会员方可获得奖励 |
---
## 2. 功能介绍
### 2.1 用户模块
#### 2.1.1 游客模式
- 无需注册登录,打开 App 即可观看视频
- App 首次启动生成唯一 device_id 并本地持久化,之后所有请求携带 `X-Device-ID` 头
- 游客每天可免费观看 10 个视频,第 11 个视频弹出登录/注册引导
- 游客不可点赞、收藏、评论、上传
#### 2.1.2 注册
- 注册页**手机号 / 邮箱 Tab 切换**:手机号 + 短信验证码 + 密码,或 邮箱 + 邮箱验证码 + 密码
- 邮箱验证码由服务端**真实 SMTP 发送**(`POST /auth/email/send`),验证码写 Redis(`email:code:{email}`,600s),与短信验证码同一套校验机制
- 注册即登录,返回 Token
- 约束:手机号/邮箱全局唯一;密码 ≥ 8 位,含字母和数字
#### 2.1.3 登录
- 支持**手机号或邮箱**作为账号:手机号 + 密码 / 手机号 + 短信验证码 / 邮箱 + 密码
- 登录成功后,游客时期的设备 ID 与账号可关联(观看记录合并,可选)
#### 2.1.4 资料管理
- 昵称(默认"用户+尾号")、头像、性别、个人简介
- 修改手机号/邮箱需验证码验证
#### 2.1.5 找回密码与账号注销
- **找回密码**:已验证的手机号/邮箱 + 验证码 + 新密码完成重置(`POST /auth/password/reset`),重置后旧 Token 全部失效
- **账号注销**:用户主动发起 → 7 天冻结期(期间登录即撤销)→ 到期匿名化:手机号/邮箱置空释放、昵称头像清空、作品全部下架、钱包余额清零作废、Token 立即失效
### 2.2 观看与计费模块(核心业务)
#### 2.2.1 计费规则
1. 会员:观看不限量,不消耗免费额度与积分
2. 非会员(含游客):每个计费主体每天前 10 个不同视频免费
3. 免费额度用完后:注册用户每看 1 个视频扣 1 积分;游客拦截并引导登录
4. 所有"每日"口径均按 Asia/Shanghai(服务器时区)0 点切分
5. 积分余额不足:拦截播放,引导充值或开通会员
#### 2.2.2 计费判定流程
```
用户开始播放视频
│
▼
App 调用「观看上报」接口
│
▼
是否会员? ──是──► 直接放行
│否
▼
该视频今天看过? ──是──► 直接放行(同日去重,不重复扣费)
│否
▼
今日免费额度未用完? ──是──► 占用 1 次免费额度,放行
│否
▼
是游客? ──是──► 返回"需登录",App 弹登录引导
│否
▼
积分 ≥ 1? ──是──► 扣 1 积分(写积分流水),放行
│否
▼
返回"积分不足",App 引导充值/开会员
```
#### 2.2.3 补充规则(对需求的完善)
- **同日去重**:同一视频当天重复观看,不重复占用免费额度、不重复扣积分
- **自己的视频免费**:观看自己上传的视频不计费、不占额度
- **扣费时点**:开始播放即扣费(播放页加载完成上报),简单可解释;退款场景(视频违规下架)通过后台人工处理
- **额度重置**:每日 00:00(服务器时区)重置,免费额度不可累积
- **观看记录**:所有观看(免费/扣费)均记录,用于历史列表与风控分析
### 2.3 积分模块
#### 2.3.1 积分获取
| 途径 | 规则 |
|---|---|
| 充值 | 支付宝/微信/USDT 支付成功到账,1 元 = 10 积分 |
| 评论奖励 | 每评论满 10 个**不同**视频,奖励 1 积分(循环累计,进度清零重新计) |
| 推荐奖励 | 会员邀请的新用户激活后,推荐人获 N 积分(默认 10,可配置),见 2.8 |
| 活动赠送 | 后台手动发放(含体验会员赠送活动) |
#### 2.3.2 积分消耗
| 场景 | 规则 |
|---|---|
| 观看视频 | 免费额度用完后,1 积分/个 |
| 开通会员 | 按套餐价扣减(见 2.4) |
#### 2.3.3 评论奖励防刷规则(完善项)
- 评论内容长度 ≥ 5 字才计入进度
- 同一视频仅**首次**有效评论计入进度
- 评论被删除(自删/违规)时,已计入的进度回退;已发放奖励的扣回(余额可为负,后续充值抵扣)
- 每日评论奖励上限 5 积分(可配置)
- 同一用户短时间高频评论触发限流(如 10 条/分钟)
#### 2.3.4 积分流水
- 所有积分变动必须写入积分流水表(变动值、变动后余额、业务类型、关联单据)
- 流水是积分对账、客服处理、风控审计的唯一依据
### 2.4 会员模块
#### 2.4.1 会员类型(价格/时长均可在配置文件中修改)
| 类型 | 时长 | 价格(积分) | 获取方式 |
|---|---|---|---|
| 体验会员 | 1 天 | 默认 5 积分(可配置) | 活动赠送 / 积分开通 |
| 月卡 | 30 天 | 300 | 积分开通 |
| 年卡 | 365 天 | 3650 | 积分开通 |
| 终身卡 | 永久 | 10000 | 积分开通 |
> 注:按 1 元 = 10 积分折算,即月卡=30 元、年卡=365 元、终身卡=1000 元。各套餐时长与价格均为默认值,可在配置文件中调整。
#### 2.4.2 会员权益
1. **无限观看**:不消耗免费额度和积分
2. **优先上传**:转码队列优先、审核队列优先
#### 2.4.3 会员规则(完善项)
- 必须注册登录才能开通和使用会员
- 有效期内重复开通:有效期**叠加**(如月卡剩余 10 天再开月卡 → 40 天)
- 已是终身卡:不可再开通其它套餐(前端置灰)
- 体验会员与付费卡互斥逻辑:体验会员期间开通付费卡,按付费卡生效(体验剩余时间不累加,避免活动滥用)
- 到期自动失效,不自动续费(当前无连续包月需求)
- **新注册赠送**:新用户注册成功即赠送体验会员(默认 1 天),管理后台可配置开关与天数(system_configs:`register_gift_trial.enabled` / `register_gift_trial.days`),关闭后注册不再赠送,实时生效
### 2.5 视频模块
#### 2.5.1 视频上传
- 主流程:App 选择本地视频(mp4 等)→ 上传至服务端/对象存储 → 服务端 **FFmpeg 转码为 HLS(m3u8 + ts 分片)** → 生成封面 → 提交审核 → 发布
- 兼容流程:也支持直接上传已转码好的 m3u8 + ts 文件包(服务端校验分片完整性后直接进入审核)
- 上传限制:单文件 ≤ 500MB、时长 5 秒 ~ 10 分钟(可配置)
- 会员"优先上传":转码任务进入高优先级队列,优先被消费
#### 2.5.2 视频转码
- 统一转 HLS:1080p 源 → 输出 720p(默认),可配置多码率(1080/720/480)自适应
- 从视频第 1 秒截取封面图
- 转码失败自动重试 3 次,仍失败则标记失败并通知作者
#### 2.5.3 视频审核
- 状态机:`转码中 → 待审核 → 已发布 / 审核拒绝 / 已下架`
- 一期采用**管理后台人工审核**:审核员在后台在线预览视频,执行 通过 / 拒绝(填写原因),通过后自动发布上架
- 仅"已发布"状态进入推荐流
#### 2.5.4 视频播放
- m3u8(HLS)流媒体播放,App 竖屏全屏沉浸式播放
- 上滑/下滑切换视频;**3 槽播放器池**预加载:当前页播放 + 下一页预缓冲,划走即 stop(断音 + 中断缓存下载)
- 切到"我的"等页面时播放器池**挂起(suspend)静音断流**,回到 Feed 且首页 Tab 可见才恢复(resume),修复"切页仍出声"问题
- 网络 m3u8 播放地址改写为 **App 内嵌本地媒体代理**(127.0.0.1 HTTP 服务):m3u8 每次回源保证签名时效,ts 分片落盘缓存(1GB LRU),支持拖动进度条(Range/206);ts 流量直连源站 CDN,不经过业务服务器
- 进度条拖动时显示**时间气泡**(x时xx分xx秒),标题后带**时长角标**
- 记录播放量(进入播放即 +1,Redis 计数异步落库)
#### 2.5.5 视频标签(Tag)
- 发布视频时可填写 0~5 个标签,每个标签 1~20 字(字母/数字/中文/下划线)
- 播放页在**标题后展示标签**(#标签 样式)
- 点击标签进入"标签视频列表页":展示该标签下全部已发布视频(支持 热度/最新 排序)
- 标签是推荐策略中个性化加权的主要特征
- 存量视频标签由**离线脚本批量生成**(server/scripts/build_tags_from_titles.py:jieba TF-IDF topK=6 + 番号/书名号提取 + 噪音过滤,幂等可重跑;已生成 2.2 万+ 标签、9.9 万+ 关联),后台提供"标签管理"页维护
#### 2.5.6 视频推荐策略
Feed 支持五种策略,客户端通过接口参数切换(App 顶部 Tab:推荐 / 最新 / 热度 / 口碑 / 热播,各策略独立保活分页状态):
| 策略 | 说明 |
|---|---|
| recommend(默认) | 个性化推荐 |
| latest | 最新发布倒序 |
| hot | 热度倒序:热度分 = (播放×1 + 点赞×5 + 评论×10) / (1 + 发布天数) |
| liked(口碑) | 按点赞量倒序 |
| viewed(热播) | 按播放量倒序 |
> liked / viewed 为纯数值排序,游标采用(值, id)双键避免同值翻页丢失。
**个性化推荐(recommend)规则:**
1. 候选池:全部"已发布"视频
2. 个性化打分:统计用户近 7 天观看/点赞/收藏视频的标签,得出偏好标签及权重,候选视频命中偏好标签加权;游客与无行为新用户退化为热度分
3. **每日重排**:以"当天日期 + 用户/设备"为随机种子对候选池确定性洗牌,保证**同一用户每天的推荐顺序都不一样**,且同一天内多次刷新顺序稳定(翻页不混乱)
4. **当日去重**:当天已观看过的视频**尽量不再出现**在推荐流中;候选池耗尽时放开过滤兜底,避免 Feed 刷空
#### 2.5.7 作品管理(作者)
- **编辑**:作者可修改自己视频的标题/描述/分类/标签,仅元信息变更,不影响已发布状态、不重新触发审核(视频文件不可替换)
- **删除**:作者可删除自己视频 → 软删除(status=5 已删除),立即从 Feed/搜索/标签页下架;播放/点赞等历史数据保留用于对账,资产结算即刻停止
- 两个操作仅作者本人可用(非作者返回 2107)
#### 2.5.8 搜索
- 一期:按标题、标签做关键词匹配(MySQL LIKE / FULLTEXT),支持 最新/热度 排序;二期接 Elasticsearch
- 热搜词:近 24h 搜索频次 Top10(Redis ZSet 统计,每小时截断)
- 搜索历史:App 端本地存储最近 10 条,不上报服务端
- 搜索词先过敏感词过滤,命中直接返回空结果
#### 2.5.9 视频下载(离线缓存)
- **入口**:Feed 页 / 播放页长按弹出菜单 →「下载」(已下载/下载中置灰)
- HLS 视频:4 并发下载 ts 分片与 AES-128 密钥,改写为本地 index.m3u8;mp4 整文件直接下载
- 下载过程展示进度,支持取消;失败自动清理半成品文件
- 下载列表在「我的 → 已下载」;离线播放走本地媒体代理,支持拖动进度条
- 已下载视频离线观看**不重复扣积分**(线上播放时已按正常规则计费)
### 2.6 互动模块
| 功能 | 规则 |
|---|---|
| 点赞 | 仅登录用户;同一视频重复点击为取消;视频表冗余 like_count;唯一约束防重 |
| 收藏 | 仅登录用户;同上;个人中心可查看收藏列表 |
| 评论 | 仅登录用户;支持一级评论和楼中楼回复(二级);作者可删除自己视频下的评论;评论计数入奖励进度 |
#### 2.6.4 消息通知
- 类型:审核结果(视频通过/拒绝)、提现进度(通过打款/拒绝退回)、系统公告、评论回复
- 触发即写 notifications 表,Redis 维护未读数;App 我的页消息入口带红点
- 通知为站内信,一期不做 App 推送(push 二期接厂商通道)
#### 2.6.5 举报
- 举报对象:视频、评论;原因分类:色情低俗/暴力血腥/侵权/广告营销/其他
- 同一用户对同一对象仅可举报一次(唯一约束,重复返回 2106)
- 后台处理动作:下架视频 / 删除评论 / 驳回;处理结果通过消息通知告知举报人
- 举报不自动处置内容,全部人工决策
#### 2.6.6 敏感词过滤
- 评论、昵称、视频标题/描述/标签、搜索词均先过敏感词库(DFA 算法),命中拒绝并返回 2105
- 词库由运营在管理后台维护(sensitive_words 表),服务端 Redis 缓存 60s
### 2.7 支付模块
#### 2.7.1 充值套餐(管理后台可在线调整)
- 默认档位:**10 / 30 / 100 / 200 元**,另开放**自定义金额**(1~10000 元)
- 到账规则:1 元 = 10 积分;档位可单独配置"赠送积分"(如充 100 元赠 50 积分)
- 档位存储于数据库 `recharge_packages` 表,**管理后台实时增删改、上下架、排序**,无需改配置、无需重启服务
#### 2.7.2 支付方式
| 方式 | 方案 | 说明 |
|---|---|---|
| 支付宝 | 支付宝开放平台 App 支付 | 服务端下单生成 orderString → App 调起 SDK → 异步回调到账 |
| 微信支付 | 微信支付 V3 App 支付 | 服务端下单 prepay → App 调起 SDK → 异步回调验签到账 |
| USDT | TRC20 链上收款 | 配置固定收款地址 + 汇率;订单生成"唯一金额"(如 30 USDT + 0.001~0.999 随机尾差),服务端轮询 TronGrid 匹配金额与时间窗,满 19 个确认后入账 |
#### 2.7.3 支付通用规则(完善项)
- 订单有效期 30 分钟,超时自动关闭
- 到账必须**幂等**:以第三方流水号/tx_hash 唯一约束防重复入账
- 到账在数据库事务内完成:更新订单状态 + 钱包加积分 + 写积分流水,三步原子
- 回调验签失败、金额不匹配一律拒绝并告警
- 所有支付参数(商户号、密钥、证书、USDT 地址/汇率)走配置文件/环境变量,不入库、不进代码仓库
#### 2.7.4 退款规则
- 发起:仅运营/客服在支付平台商户后台(支付宝/微信)人工发起退款;USDT 订单不支持自动退款(人工链上退回并线下登记)
- 系统处理(退款回调或后台人工登记后):
1. 订单状态置为 refunded
2. 扣回该订单已发放积分(余额**允许为负**,后续充值先抵扣负数;负余额用户禁止积分消费)
3. 扣回推荐人已获得的该笔充值返利资产(biz_type=6,资产允许为负)
- 退款不改变已发生的观看/会员事实(会员不回收,防止复杂对穿,运营层面约束)
### 2.8 推荐与创作者激励(资产体系)
平台在积分之外设有**资产钱包**:资产可提现,与人民币固定换算 **10 资产 = 1 元**(与积分同量纲,比例可配置)。所有资产变动写资产流水,变更规范与积分一致(行锁 + 流水 + 乐观锁)。
#### 2.8.1 推荐奖励(会员邀请新用户)
- 每位用户注册自动生成邀请码/邀请链接;**推荐人须为会员**才产生奖励(发放时点校验会员身份)
- 新用户注册时通过邀请码/分享口令/推广链接自动归因完成绑定(归因方案见 2.8.2),注册后 24h 内可补绑,每人仅能绑定 1 个推荐人
- 被推荐人"激活"(注册 72h 内累计观看 ≥3 个视频,且次日有登录)后,推荐人获得 **N 积分**(默认 10,可配置),**不限量**
- **充值返利**:被推荐人每次充值成功,推荐人获得"充值积分 × 5%"的**资产**(不限量,比例可配置);订单退款时对应返利扣回(资产余额允许为负,后续收益抵扣)
#### 2.8.2 推广归因(邀请链接 / 分享口令 / 模糊匹配)
用户通过推广链接下载 App,安装注册后自动识别推荐人。采用三段式自建归因方案(不依赖第三方 SDK):
| 优先级 | 路径 | 说明 |
|---|---|---|
| 1(主路径) | 分享口令 | 推广文案内含 `#短视频邀请码XXXXXXXX#` 口令,被邀请人复制后首次打开 App,客户端解析剪贴板 → 注册页自动预填邀请码,准确率 100% |
| 2(辅助路径) | H5 落地页点击埋点 + 模糊匹配 | 推广链接 `/r/{code}` 打开 H5 落地页时自动上报点击(code+IP+UA 存 Redis 24h);App 首启无口令时上报 device_id,服务端按 IP 就近匹配点击记录,命中结果按 device_id 服务端缓存(7 天),注册时自动带出 |
| 3(兜底) | 注册页手填邀请码 | 注册表单邀请码选填;注册后 24h 内还可在"邀请好友"页补绑(`/referral/bind`) |
- 归因结果**由服务端按 device_id 缓存并一次性消费**,不信任客户端回传,防止伪造归因
- 绑定优先级:显式 invite_code(口令/手填)> 模糊归因缓存;绑定沿用 2.8.3 全部风控规则
- 落地页展示邀请人昵称、复制口令按钮与 App 下载入口(下载地址由 system_configs `app.download_url` 驱动,后台可配)
#### 2.8.3 推荐防作弊规则
- 不可绑定自己;同 device_id 已存在账号的设备注册 → 绑定直接无效(防开小号)
- 注册 IP 与推荐人常用 IP 一致、或同 IP 24h 内注册 >5 个账号 → 标记风险
- 推荐人 24h 内新增绑定 >10 人 → 超出部分冻结
- 命中风控的绑定状态置为"风控冻结",奖励暂停发放,管理后台人工审核后可解冻或判无效
- 未激活的绑定不发放奖励;激活判定由定时任务执行
#### 2.8.4 会员视频创作收益
仅**会员**发布的视频参与资产结算(结算时点校验作者会员身份;非会员期间数据持续累计,成为会员后按增量结算):
| 收益项 | 规则 | 说明 |
|---|---|---|
| 完整观看收益 | 每累计 1000 次完整观看 → 1 资产 | 完整观看=播放进度 ≥95% 或看到结尾;同一用户/设备同视频每日最多计 1 次;作者本人不计 |
| 点赞收益 | 每累计 100 次点赞 → 1 资产 | 以累计新增点赞计数;取消点赞回退计数,但已结算发放的资产不扣回 |
- 结算由定时任务每 5 分钟按增量执行,按视频独立保留余数(如 2500 次完整观看 → 发 2 资产,余 500 继续累计)
#### 2.8.5 资产提现
- 满 100 资产(=10 元)可申请提现(门槛可配置)
- 渠道:支付宝 / 微信 / USDT;管理后台**人工审核**后打款
- 审核拒绝 → 资产原路退回资产钱包;同一时间仅允许一笔审核中的提现
### 2.9 管理后台(一期包含,Web 端)
独立 Web 管理后台,管理员账号体系与 App 用户完全隔离,分角色控制权限。
| 模块 | 功能 |
|---|---|
| 仪表盘 | 今日/累计:DAU、新增注册、充值金额、积分发放与消耗、播放量、待审核数 |
| 视频审核(人工) | 待审核列表(在线预览 m3u8)、通过、拒绝(必填原因)、已发布视频下架 |
| 用户管理 | 搜索(ID/手机号/邮箱)、用户详情(钱包/会员/作品/流水)、封禁与解封、**修改密码**(重置后 `auth:epoch` 递增,该用户全端 Token 立即失效) |
| 积分管理 | 用户积分流水查询、手动加/减积分(原因必填 + 二次确认,写流水 biz_type=6) |
| 订单管理 | 充值订单查询(状态/渠道/时间筛选)、异常订单人工补单(核实已收款后手动入账) |
| 提现审核 | 提现申请列表、审核通过(线下打款后确认)/ 拒绝(自动退回资产) |
| 推荐风控 | 推荐关系查询(状态/风控标记筛选)、冻结记录解冻或判无效 |
| 平台配置 | 注册赠送体验会员开关与天数等运营参数(system_configs 键值对,实时生效) |
| 充值套餐管理 | 档位与赠送积分增删改、上下架、排序,实时生效 |
| 活动运营 | 赠送体验会员/积分(支持单个或批量用户) |
| 标签管理 | 标签列表查询(关联视频数)、重命名、删除(联动清理 video_tags) |
| 采集源管理 | 采集源 CRUD(分类名 + 列表 URL 模板)、批量添加、单源/全部同步(后台异步采集外部单视频入库) |
| 管理员管理 | 管理员账号增删、角色分配(超级管理员/审核员/运营)、操作日志查询 |
角色权限:超级管理员(全部)、审核员(仅视频审核)、运营(套餐/活动/看板)。
### 2.10 二期规划清单
1. **关注/粉丝**:预留,二期开放
2. **App 推送**:厂商通道(评论回复、审核结果、提现进度等通知的 push)
3. **搜索升级**:Elasticsearch、搜索联想词
4. **分享**:视频分享链接/海报生成
5. **趋势看板**:管理后台 7/30 天数据曲线、订单导出 CSV
6. **黑名单**:用户拉黑、内容"不感兴趣"负反馈,深化个性化推荐
7. **游客数据合并**:注册后游客观看记录正式合并入账号
8. **实名认证提示**:充值环节按支付合规要求引导实名(依赖支付渠道商户资质)
9. **运维**:上传临时文件与拒绝视频分片的定时清理、SLO 指标(Feed <300ms、起播 <1.5s)、客户端埋点上报
### 2.11 合规提示
- 短视频平台运营涉及《网络文化经营许可证》《信息网络传播视听节目许可证》或备案要求,上架应用市场需提供对应资质,需提前规划
- 必须接内容审核,否则存在下架风险
- USDT 收款存在地区合规风险,请评估主体与运营地区法规
- **iOS 虚拟支付合规**:苹果要求 App 内虚拟商品(积分/会员)走 IAP,使用支付宝/微信支付存在拒审风险。对策:预留"iOS 支付开关"(system_configs:`ios_payment.enabled`),审核期/风险期切换为引导用户到 H5 网页端充值;安卓端不受限
- **注册协议**:注册页必须勾选《用户协议》与《隐私政策》后方可提交
---
## 3. 数据库设计
数据库选型:**MySQL 8.0**(InnoDB / utf8mb4),缓存与计数:**Redis 7**。
详细 DDL、索引策略见《02-技术文档》第 3 章,此处给出表结构定义。
### 3.1 表清单
现共 **30 张业务表**。Alembic 迁移链:`init_27_tables` → `add_collection_records` → `add_collection_sources`;线上数据量约 1.8 万视频。
| 表 | 说明 |
|---|---|
| users | 用户表 |
| user_wallets | 用户钱包(积分余额) |
| user_membership | 用户当前会员状态 |
| membership_orders | 会员开通记录 |
| videos | 视频表 |
| categories | 视频分类表 |
| comments | 评论表 |
| likes | 点赞表 |
| favorites | 收藏表 |
| watch_records | 观看记录表 |
| point_transactions | 积分流水表 |
| recharge_orders | 充值订单表 |
| transcode_tasks | 转码任务表 |
| tags | 标签表 |
| video_tags | 视频-标签关联表 |
| recharge_packages | 充值套餐表(后台可配) |
| admin_users | 管理员表 |
| admin_logs | 后台操作日志表 |
| referrals | 推荐关系表 |
| asset_wallets | 资产钱包表 |
| asset_transactions | 资产流水表 |
| withdrawals | 提现记录表 |
| video_earnings | 视频收益结算表 |
| notifications | 消息通知表 |
| reports | 举报表 |
| sensitive_words | 敏感词表 |
| series | 系列(合集)表:多条视频组织成集数列表 |
| collection_sources | 采集源配置表(分类名 + 列表 URL 模板) |
| collection_records | 采集记录表(外部内容源 → 本站系列/视频映射,幂等导入) |
### 3.2 用户域
#### users 用户表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK AUTO_INCREMENT | 用户 ID |
| phone | VARCHAR(20) NULL | 手机号,**唯一索引**,与 email 至少其一非空 |
| email | VARCHAR(100) NULL | 邮箱,**唯一索引** |
| password_hash | VARCHAR(255) | 密码哈希(bcrypt) |
| nickname | VARCHAR(50) | 昵称 |
| avatar_url | VARCHAR(500) | 头像 URL |
| gender | TINYINT DEFAULT 0 | 0未知 1男 2女 |
| bio | VARCHAR(200) | 简介 |
| status | TINYINT DEFAULT 1 | 1正常 2封禁 |
| register_type | TINYINT | 1手机 2邮箱 |
| invite_code | VARCHAR(10) | 我的邀请码,注册时自动生成,**唯一索引** |
| register_ip | VARCHAR(45) | 注册 IP(风控) |
| last_login_at | DATETIME NULL | 最近登录时间(推荐激活判定用) |
| last_login_ip | VARCHAR(45) | 最近登录 IP |
| created_at / updated_at | DATETIME | 创建/更新时间 |
索引:`uk_phone(phone)`、`uk_email(email)`、`uk_invite_code(invite_code)`、`idx_status_created(status, created_at)`
#### user_wallets 钱包表
| 字段 | 类型 | 说明 |
|---|---|---|
| user_id | BIGINT PK FK→users.id | 用户 ID |
| points | INT UNSIGNED DEFAULT 0 | 当前积分余额 |
| total_recharge | INT UNSIGNED DEFAULT 0 | 累计充值积分 |
| total_earned | INT UNSIGNED DEFAULT 0 | 累计奖励积分 |
| total_consumed | INT UNSIGNED DEFAULT 0 | 累计消耗积分 |
| version | INT DEFAULT 0 | 乐观锁版本号 |
| updated_at | DATETIME | 更新时间 |
> 余额变更必须走"行锁/乐观锁 + 流水",禁止直接 update 余额。
#### user_membership 当前会员状态表(冗余加速查询)
| 字段 | 类型 | 说明 |
|---|---|---|
| user_id | BIGINT PK FK | 用户 ID |
| is_member | TINYINT DEFAULT 0 | 是否会员(冗余,以 expire_at 判定为准) |
| current_plan | VARCHAR(20) | 当前套餐:trial/month/year/lifetime |
| is_lifetime | TINYINT DEFAULT 0 | 是否终身卡 |
| expire_at | DATETIME NULL | 到期时间(终身卡为 NULL) |
| updated_at | DATETIME | |
#### membership_orders 会员开通记录表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| order_no | VARCHAR(32) | 业务单号,**唯一索引** |
| user_id | BIGINT FK | |
| plan_code | VARCHAR(20) | trial/month/year/lifetime |
| points_cost | INT | 消耗积分(赠送为 0) |
| duration_days | INT | 时长(0=终身) |
| start_at / end_at | DATETIME | 生效/到期(终身 end_at 为 NULL) |
| source | TINYINT | 1积分购买 2活动赠送 |
| created_at | DATETIME | |
索引:`idx_user(user_id, created_at)`
### 3.3 视频域
#### videos 视频表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| user_id | BIGINT FK | 作者 |
| category_id | INT | 分类 |
| title | VARCHAR(100) | 标题 |
| description | VARCHAR(500) | 描述 |
| cover_url | VARCHAR(500) | 封面 URL |
| video_url | VARCHAR(500) | m3u8 播放地址 |
| duration | INT | 时长(秒) |
| file_size | BIGINT | 源文件大小 |
| width / height | INT | 分辨率 |
| status | TINYINT | 0转码中 1待审核 2已发布 3审核拒绝 4已下架 5转码失败 |
| reject_reason | VARCHAR(200) | 拒绝/下架原因 |
| reviewer_id | BIGINT NULL | 审核人(admin_users.id) |
| reviewed_at | DATETIME NULL | 审核时间 |
| is_priority | TINYINT DEFAULT 0 | 会员优先标记 |
| view_count | BIGINT DEFAULT 0 | 播放量 |
| like_count | INT DEFAULT 0 | 点赞数(冗余) |
| comment_count | INT DEFAULT 0 | 评论数(冗余) |
| favorite_count | INT DEFAULT 0 | 收藏数(冗余) |
| published_at | DATETIME NULL | 发布时间 |
| created_at / updated_at | DATETIME | |
索引:`idx_user_status(user_id, status)`、`idx_feed(status, published_at DESC)`、`idx_category(category_id, status)`
#### categories 分类表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT PK | |
| name | VARCHAR(50) | 分类名 |
| sort | INT DEFAULT 0 | 排序 |
| status | TINYINT DEFAULT 1 | 1启用 0停用 |
#### transcode_tasks 转码任务表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| video_id | BIGINT FK | |
| source_url | VARCHAR(500) | 源文件地址 |
| task_status | TINYINT | 0排队 1转码中 2成功 3失败 |
| priority | TINYINT DEFAULT 0 | 0普通 1优先(会员) |
| retry_count | INT DEFAULT 0 | 重试次数 |
| error_msg | VARCHAR(500) | 失败原因 |
| created_at / updated_at | DATETIME | |
索引:`idx_pick(task_status, priority DESC, created_at)`(消费者取任务用)
#### tags 标签表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT PK AUTO_INCREMENT | |
| name | VARCHAR(20) | 标签名,**唯一索引**(大小写不敏感) |
| video_count | INT DEFAULT 0 | 关联视频数(冗余) |
| created_at | DATETIME | |
#### video_tags 视频-标签关联表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| video_id | BIGINT FK | |
| tag_id | INT FK | |
约束:唯一索引 `uk_video_tag(video_id, tag_id)`;索引 `idx_tag(tag_id, video_id)`(标签视频列表页用)
### 3.4 互动域
#### comments 评论表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| video_id | BIGINT FK | |
| user_id | BIGINT FK | |
| parent_id | BIGINT DEFAULT 0 | 0=一级评论,否则为父评论 ID |
| reply_to_user_id | BIGINT NULL | 被回复人 |
| content | VARCHAR(500) | 内容 |
| like_count | INT DEFAULT 0 | |
| status | TINYINT DEFAULT 1 | 1正常 2已删除 3审核中 |
| reward_counted | TINYINT DEFAULT 0 | 是否已计入评论奖励进度 |
| created_at | DATETIME | |
索引:`idx_video(video_id, status, created_at)`、`idx_user_video(user_id, video_id)`(查首评用)
#### likes 点赞表 / favorites 收藏表(结构相同)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| user_id | BIGINT FK | |
| video_id | BIGINT FK | |
| created_at | DATETIME | |
约束:**唯一索引 `uk_user_video(user_id, video_id)`** 防重复点赞/收藏;另建 `idx_video(video_id)`。
### 3.5 计费域
#### watch_records 观看记录表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| subject_type | TINYINT | 1注册用户 2游客 |
| subject_id | VARCHAR(64) | user_id 或 device_id |
| video_id | BIGINT | |
| watch_date | DATE | 观看日期(用于当日去重) |
| cost_type | TINYINT | 0免费额度 1积分扣减 2会员免费 3作者本人 |
| cost_points | INT DEFAULT 0 | 本次消耗积分 |
| watch_duration | INT DEFAULT 0 | 观看时长(秒) |
| position | INT DEFAULT 0 | 最新播放进度(秒,用于续播) |
| created_at | DATETIME | |
约束:**唯一索引 `uk_dedup(subject_type, subject_id, video_id, watch_date)`**,是"同日同视频不重复扣费"的底层保证。
#### point_transactions 积分流水表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| user_id | BIGINT FK | |
| change_amount | INT | 变动值(正=增,负=减) |
| balance_after | INT | 变动后余额 |
| biz_type | TINYINT | 1充值 2观看消耗 3评论奖励 4开通会员 5活动赠送 6后台调整 7奖励扣回 8推荐奖励 |
| biz_id | VARCHAR(64) | 关联单据号(订单号/视频ID等) |
| remark | VARCHAR(200) | 备注 |
| created_at | DATETIME | |
索引:`idx_user_created(user_id, created_at)`、`idx_biz(biz_type, biz_id)`
#### recharge_orders 充值订单表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| order_no | VARCHAR(32) | 平台订单号,**唯一索引** |
| user_id | BIGINT FK | |
| amount_fen | INT | 人民币金额(分) |
| points | INT | 应到账积分 |
| pay_method | TINYINT | 1支付宝 2微信 3USDT |
| status | TINYINT | 0待支付 1已支付 2已关闭 3已退款 |
| third_trade_no | VARCHAR(64) NULL | 第三方流水号/tx_hash,**唯一索引**(幂等关键) |
| usdt_amount | DECIMAL(18,6) NULL | USDT 金额(含随机尾差) |
| usdt_rate | DECIMAL(10,4) NULL | 下单时 USDT 汇率 |
| paid_at | DATETIME NULL | 支付时间 |
| expire_at | DATETIME | 过期时间 |
| created_at / updated_at | DATETIME | |
索引:`idx_user_status(user_id, status)`
### 3.6 Redis Key 设计(配合数据库)
| Key | 类型 | TTL | 用途 |
|---|---|---|---|
| `sms:code:{phone}` | String | 5min | 短信验证码 |
| `email:code:{email}` | String | 10min | 邮箱验证码 |
| `free:watch:{subject}:{yyyyMMdd}` | Set(video_id) | 48h | 当日免费观看去重与计数 |
| `comment:prog:{user_id}` | Hash {count, date, daily} | 7d | 评论奖励进度/日上限 |
| `comment:counted:{user_id}:{video_id}` | String | 永久(或随评论删除) | 同视频首评去重 |
| `view:video:{video_id}` | String(计数) | 异步落库 | 播放量缓冲 |
| `token:blacklist:{jti}` | String | token剩余有效期 | 登出拉黑 |
| `watched:{subject}:{yyyyMMdd}` | Set(video_id) | 48h | 当日已看视频集合(推荐去重) |
| `rec:seed:{yyyyMMdd}` | String | 48h | 当日推荐重排随机种子 |
| `cview:{subject}:{video_id}:{yyyyMMdd}` | String | 48h | 完整观看当日去重 |
| `video:cview:{video_id}` | String(计数) | 异步落库 | 完整观看计数缓冲 |
| `ref:device:{device_id}` | String | 永久 | 设备已注册标记(防小号绑定) |
| `ref:risk:{inviter_id}:{yyyyMMdd}` | String(计数) | 48h | 邀请人当日新增绑定计数(风控) |
| `invite:ip:{ip}` | ZSet(code→点击时间戳) | 24h | 推广点击埋点(按 IP 的归因候选) |
| `invite:clk:{code}:{ip}` | String(JSON) | 24h | 点击详情(UA/时间戳) |
| `invite:dup:{ip}:{code}` | String | 60s | 同 IP+code 重复埋点去重 |
| `invite:attr:{device_id}` | String(邀请码) | 7d | 首启归因结果缓存,注册时一次性消费 |
| `wd:pending:{user_id}` | String | 审核期间 | 提现在途唯一锁 |
| `search:hot` | ZSet(word→次数) | 25h | 热搜统计(小时截断) |
| `notif:unread:{user_id}` | String(计数) | 随读清零 | 未读通知数 |
| `words:sensitive` | String(词库) | 60s | 敏感词缓存 |
| `config:system` | Hash | 60s | system_configs 缓存 |
### 3.7 管理后台域
#### admin_users 管理员表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| username | VARCHAR(50) | 登录名,**唯一索引** |
| password_hash | VARCHAR(255) | bcrypt |
| name | VARCHAR(50) | 姓名 |
| role | VARCHAR(20) | super / reviewer / operator |
| status | TINYINT DEFAULT 1 | 1正常 0禁用 |
| last_login_at | DATETIME NULL | |
| created_at | DATETIME | |
#### admin_logs 操作日志表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| admin_id | BIGINT FK | 操作人 |
| action | VARCHAR(50) | 动作(review/adjust_points/ban...) |
| target | VARCHAR(100) | 操作对象 |
| detail | VARCHAR(1000) | 参数与原因 |
| ip | VARCHAR(45) | 操作 IP |
| created_at | DATETIME | |
索引:`idx_admin_created(admin_id, created_at)`
#### system_configs 平台配置表
| 字段 | 类型 | 说明 |
|---|---|---|
| config_key | VARCHAR(50) PK | 配置键 |
| config_value | VARCHAR(500) | 配置值 |
| description | VARCHAR(200) | 说明 |
| updated_by | BIGINT NULL | 最后修改人(admin_users.id) |
| updated_at | DATETIME | |
预置键:`register_gift_trial.enabled`(注册赠送体验会员开关:1/0,默认 1)、`register_gift_trial.days`(赠送天数,默认 1)
#### recharge_packages 充值套餐表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT PK | |
| amount_yuan | INT | 充值金额(元) |
| points | INT | 基础到账积分(= 金额 × 汇率) |
| bonus_points | INT DEFAULT 0 | 赠送积分 |
| sort | INT DEFAULT 0 | 排序(越大越靠前) |
| status | TINYINT DEFAULT 1 | 1上架 0下架 |
| created_at / updated_at | DATETIME | |
### 3.8 激励域
#### referrals 推荐关系表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| inviter_id | BIGINT FK | 推荐人 |
| invitee_id | BIGINT | 被推荐人,**唯一索引**(一人仅一次绑定) |
| invite_code | VARCHAR(10) | 使用的邀请码 |
| status | TINYINT | 0待激活 1有效(已发奖) 2无效 3风控冻结 |
| risk_flag | VARCHAR(50) | 风控标记:same_device/same_ip/batch/inviter_burst |
| register_ip | VARCHAR(45) | 被推荐人注册 IP |
| activated_at | DATETIME NULL | 激活时间 |
| created_at | DATETIME | 绑定时间 |
索引:`idx_inviter(inviter_id, created_at)`、`idx_status(status, created_at)`
#### asset_wallets 资产钱包表
| 字段 | 类型 | 说明 |
|---|---|---|
| user_id | BIGINT PK FK | |
| assets | DECIMAL(12,1) DEFAULT 0 | 当前资产(允许为负,返利扣回场景) |
| total_earned | DECIMAL(14,1) DEFAULT 0 | 累计获得 |
| total_withdrawn | DECIMAL(14,1) DEFAULT 0 | 累计提现 |
| version | INT DEFAULT 0 | 乐观锁 |
| updated_at | DATETIME | |
#### asset_transactions 资产流水表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| user_id | BIGINT FK | |
| change_amount | DECIMAL(12,1) | 变动值(正=增,负=减) |
| balance_after | DECIMAL(12,1) | 变动后余额 |
| biz_type | TINYINT | 1充值返利 2完整观看收益 3点赞收益 4提现 5提现退回 6返利扣回 7后台调整 |
| biz_id | VARCHAR(64) | 关联单据(订单号/视频ID/提现单号) |
| remark | VARCHAR(200) | |
| created_at | DATETIME | |
索引:`idx_user_created(user_id, created_at)`、`idx_biz(biz_type, biz_id)`
#### withdrawals 提现记录表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| withdraw_no | VARCHAR(32) | 提现单号,**唯一索引** |
| user_id | BIGINT FK | |
| assets | DECIMAL(12,1) | 申请资产 |
| amount_fen | INT | 折合人民币(分) |
| channel | TINYINT | 1支付宝 2微信 3USDT |
| account | VARCHAR(200) | 收款账号 |
| status | TINYINT | 0待审核 1已打款 2已拒绝(已退回) |
| reason | VARCHAR(200) | 拒绝原因 |
| admin_id | BIGINT NULL | 审核人 |
| created_at / processed_at | DATETIME | 申请/处理时间 |
索引:`idx_user(user_id, created_at)`、`idx_status(status, created_at)`
#### video_earnings 视频收益结算表
| 字段 | 类型 | 说明 |
|---|---|---|
| video_id | BIGINT PK | |
| author_id | BIGINT | 作者(冗余,结算时查会员) |
| complete_views | INT DEFAULT 0 | 累计完整观看次数 |
| views_settled | INT DEFAULT 0 | 已结算的完整观看刻度 |
| likes_accrued | INT DEFAULT 0 | 累计点赞(取消会回退) |
| likes_settled | INT DEFAULT 0 | 已结算的点赞刻度 |
| updated_at | DATETIME | |
索引:`idx_author(author_id)`
### 3.9 通用域
#### notifications 消息通知表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| user_id | BIGINT FK | 接收人 |
| type | TINYINT | 1审核结果 2提现进度 3系统公告 4评论回复 |
| title | VARCHAR(100) | |
| content | VARCHAR(500) | |
| biz_id | VARCHAR(64) | 关联单据(视频ID/提现单号/评论ID) |
| is_read | TINYINT DEFAULT 0 | 0未读 1已读 |
| created_at | DATETIME | |
索引:`idx_user_read(user_id, is_read, id)`
#### reports 举报表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK | |
| user_id | BIGINT FK | 举报人 |
| target_type | TINYINT | 1视频 2评论 |
| target_id | BIGINT | 对象 ID |
| reason_type | TINYINT | 1色情低俗 2暴力血腥 3侵权 4广告营销 5其他 |
| remark | VARCHAR(200) | 补充说明 |
| status | TINYINT | 0待处理 1已处理 2已驳回 |
| handle_result | VARCHAR(50) | 处理动作(offline_video/delete_comment) |
| admin_id | BIGINT NULL | 处理人 |
| created_at / handled_at | DATETIME | |
约束:唯一索引 `uk_user_target(user_id, target_type, target_id)`;索引 `idx_status(status, created_at)`
#### sensitive_words 敏感词表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT PK AUTO_INCREMENT | |
| word | VARCHAR(50) | 敏感词,**唯一索引** |
| created_at | DATETIME | |
### 3.10 采集域
#### series 系列(合集)表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK AUTO_INCREMENT | |
| title | VARCHAR(100) | 系列标题 |
| cover_url | VARCHAR(500) | 封面 |
| description | VARCHAR(500) | 简介 |
| author_id | BIGINT NULL | 作者 |
| category_id | INT | 分类 |
| total_episodes | INT DEFAULT 0 | 总集数 |
| view_count | BIGINT DEFAULT 0 | 播放量 |
| status | SMALLINT DEFAULT 1 | 1正常 0隐藏 |
| created_at / updated_at | DATETIME | |
索引:`idx_series_cat_status(category_id, status)`、`idx_series_status_created(status, created_at)`;videos 表以 `series_id` 关联所属系列
#### collection_sources 采集源配置表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK AUTO_INCREMENT | |
| name | VARCHAR(50) | 分类名(添加时自动建/找本站分类) |
| list_url | VARCHAR(500) | 列表 URL 模板,`{{page}}` 为页码占位 |
| category_id | INT UNSIGNED | 本站分类 id |
| status | SMALLINT DEFAULT 1 | 1启用 0停用 |
| last_sync_at | DATETIME NULL | 最近同步时间 |
| created_at / updated_at | DATETIME | |
#### collection_records 采集记录表
| 字段 | 类型 | 说明 |
|---|---|---|
| id | BIGINT PK AUTO_INCREMENT | |
| source_key | VARCHAR(50) | 内容源标识 |
| source_id | BIGINT | 源站内容 id |
| series_id | BIGINT NULL | 本站系列 id |
| video_id | BIGINT NULL | 本站视频 id(单视频类) |
| created_at | DATETIME | |
约束:唯一索引 `uk_source(source_key, source_id)` 保证幂等导入(重复同步不产生重复视频);索引 `idx_record_series(series_id)`、`idx_record_video(video_id)`
---
## 4. 接口设计(概览)
- 统一前缀:`/api/v1`
- 认证:`Authorization: Bearer <access_token>`,游客请求携带 `X-Device-ID`
- 统一响应:`{ "code": 0, "message": "ok", "data": {...} }`
- 完整接口定义见《03-接口文档》
| 分组 | 接口数 | 说明 |
|---|---|---|
| 认证 | 7 | 验证码、注册、登录、找回密码、刷新、登出 |
| 用户 | 8 | 资料、头像、钱包、积分明细、注销、公开主页、他人作品 |
| 视频 | 13 | Feed、详情、观看/完整观看上报、上传、发布、编辑、删除、我的作品、分类、标签视频、搜索、热搜 |
| 互动 | 7 | 点赞、收藏、评论增删查、收藏/历史列表 |
| 会员 | 3 | 套餐、我的会员、积分开通 |
| 支付 | 4 + 2回调 | 套餐、下单、查单、取消;支付宝/微信回调(USDT 为服务端轮询) |
| 激励 | 6 | 邀请信息、绑定邀请码、资产钱包/流水、提现申请/记录 |
| 推广归因 | 2 + 1 落地页 | 点击埋点、首启归因匹配、H5 推广落地页(/r/{code}) |
| 消息与通用 | 5 | 版本检查、通知列表/未读数/标记已读、举报 |
| 后台管理 | 32 | 审核、视频/评论管理、用户、积分、订单、套餐配置、赠送、提现审核、推荐风控、举报处理、敏感词、平台配置、看板、日志(/admin 前缀) |
---
## 5. 前端设计
技术选型:Flutter(一套代码出 iOS/Android)。技术细节见《02-技术文档》第 5 章。
### 5.1 页面清单与路由
| 路由 | 页面 | 说明 |
|---|---|---|
| /splash | 启动页 | 初始化 device_id、Token 续期、配置拉取 |
| /home | 首页 | 沉浸式视频 Feed(默认页) |
| /video/:id | 视频播放详情 | 全屏播放 + 互动栏 |
| /login | 登录/注册 | 手机号 Tab(默认)/ 邮箱 Tab |
| /mine | 我的 | 个人中心 |
| /mine/edit | 编辑资料 | |
| /mine/wallet | 我的钱包 | 积分余额、明细、充值入口 |
| /mine/recharge | 充值页 | 套餐选择 + 支付方式 |
| /mine/recharge/usdt | USDT 支付页 | 金额/地址/二维码/倒计时 |
| /mine/referral | 邀请好友 | 邀请码/链接、累计奖励、推荐列表 |
| /mine/assets | 资产钱包 | 资产余额、流水、提现入口 |
| /mine/assets/withdraw | 提现页 | 金额/渠道/账号,提交后查进度 |
| /mine/vip | 会员中心 | 套餐卡片、权益、开通 |
| /mine/videos | 我的作品 | 含审核状态 |
| /mine/favorites | 我的收藏 | |
| /mine/history | 观看历史 | |
| /upload | 上传视频 | 选片→剪辑信息→发布 |
| /search | 搜索 | |
| /category | 分类页 | 按分类浏览视频 |
| /tags/:name | 标签视频页 | 某标签下的视频列表 |
| /mine/downloads | 已下载 | 离线视频列表(进度/删除/播放) |
| /notifications | 消息通知 | 通知列表、未读标记 |
| /settings | 设置 | 退出登录等 |
### 5.2 核心页面布局
#### 5.2.1 首页(视频 Feed)
- 全屏竖向 PageView,每个页面一个视频播放器(3 槽播放器池:当前页播放 + 下一页预缓冲,划走即停)
- 顶部:Feed 策略 Tab(推荐 / 最新 / 热度 / 口碑 / 热播)+ 搜索图标;各策略分页状态独立保活,切 Tab 不丢进度
- 右侧互动栏(自上而下):作者头像(+关注预留)、点赞(红心+计数)、评论(气泡+计数)、收藏(星标+计数)、分享
- 左下:作者昵称、标题(标题后展示 #标签,点击跳转标签视频页;并带时长角标)、描述(展开/收起)
- 底部(游客/非会员场景):今日免费剩余次数提示条(剩余 ≤ 3 次时显示)
#### 5.2.2 评论页(底部弹层)
- BottomSheet,高度 2/3 屏;顶部评论总数;列表(头像/昵称/内容/时间/点赞);底部输入框
- 二级回复:点击评论展开子回复
#### 5.2.3 我的
- 顶部:头像、昵称、ID、会员标识(金色卡片入口)
- 中部数据行:作品 / 收藏 / 历史
- 功能列表:我的钱包(积分余额)、资产钱包(可提现)、邀请好友、会员中心、上传视频、设置
#### 5.2.4 充值页
- 积分余额展示;充值套餐网格(后台配置的档位,角标"赠 x 积分")+ 自定义金额输入框
- 支付方式单选:支付宝 / 微信 / USDT(带图标)
- 底部"立即支付"按钮;支付结果页(成功/失败/查询中轮询)
#### 5.2.5 会员中心
- 当前会员状态卡片(类型、到期时间)
- 四张套餐卡:体验/月卡/年卡/终身卡(价格=积分,终身卡高亮"永久")
- 权益说明列表:无限观看、优先上传
- 开通按钮 → 积分余额确认弹窗(余额不足跳充值)
#### 5.2.6 上传页
- 三步:选择视频(系统相册/拍摄)→ 填写信息(封面选择、标题、描述、分类)→ 发布
- 发布后进入"转码中"状态页/ Toast 提示,可在"我的作品"查看进度
### 5.3 核心交互设计
| 场景 | 交互 |
|---|---|
| 切换视频 | 垂直滑动,松手吸附;滑出即暂停,滑入自动播放并**上报观看计费** |
| 单击屏幕 | 暂停 / 继续播放(暂停时显示播放图标) |
| 双击屏幕 | 点赞动画(大爱心飘出) |
| 长按屏幕 | 弹出操作菜单:下载视频 / 倍速 / 分享(已下载或下载中置灰) |
| 拖动进度条 | 跟随 thumb 显示时间气泡(x时xx分xx秒,两端自动夹在屏内) |
| 游客超限 | 播放第 11 个视频时弹出登录引导弹窗("注册解锁更多视频,今日已看 10/10") |
| 用户积分不足 | 弹窗"积分不足":去充值 / 开通会员 两个按钮 |
| 扣费提示 | 每次扣积分播放时顶部 Toast"本次消耗 1 积分" |
| 会员标识 | 昵称旁金色 VIP 角标;会员 Center 显示到期时间 |
| 上传结果 | 转码/审核状态在我的作品实时刷新(轮询或推送) |
### 5.4 UI 样式规范
| 项 | 规范 |
|---|---|
| 主题 | 深色为主(#121212 背景),保证视频沉浸感;浅色可选(二期);**弹窗/底部菜单统一白底亮色**(局部包 `ThemeData.light()`,避免深色背景下文字看不清) |
| 主色 | #FE2C55(红),辅助色 #25F4EE(青) |
| 会员色 | 金色渐变 #F7D794 → #D4A017 |
| 文字 | 主 #FFFFFF,次 #B0B0B0,弱 #6E6E6E |
| 字体 | 系统默认(中文 PingFang/思源),标题 17sp 加粗,正文 15sp,辅助 12sp |
| 间距 | 4 的倍数体系:4/8/12/16/24 |
| 圆角 | 按钮 8dp,卡片 12dp,头像圆形 |
| 组件 | 统一封装:按钮、弹窗、Toast、空态、骨架屏 |
### 5.5 域名发现(API 入口)
App 不内置固定 API 域名,启动时按以下流程动态获取:
1. **优先:DNS TXT 解析**。解析 `txt.256kb.top` 的 TXT 记录,记录内容即真实 API 域名(如 `api.xxx.com`),拼接 `https://` 作为 Base URL
2. **兜底:计算域名**。TXT 解析失败或内容为空时,取当前"年月"字符串(格式 `yyyyMM`,如 `202610`)计算 MD5(32 位小写),拼接为 `{md5(yyyyMM)}_jsnbhh.vip`(例如 `d8578ef...9c_jsnbhh.vip`)
3. 获取成功后写入本地缓存;后续启动优先使用缓存域名,同时后台异步刷新;缓存域名连续请求失败达到阈值时重新走完整发现流程
4. 所有网络请求统一从该入口获取 Base URL,业务代码中禁止硬编码任何 API 域名
5. **兜底域名配套**:`jsnbhh.vip` 需配置 DNS 泛解析 + 通配符证书(`*.jsnbhh.vip`)并接入 CDN/网关回源,保证任意 md5 前缀域名开箱可用;每月 1 日前运维确认泛解析与证书有效
---
### 5.6 设备指纹与公共请求头
- **device_id**:App 首次启动生成 UUID(v4),Android 存 Keystore 加密的 SharedPreferences,iOS 存 Keychain(卸载重装保留);即游客计费与风控的设备指纹,客户端禁止采集 IMEI/OAID 等系统标识
- 所有请求携带公共 Header:`X-Device-ID`(必带)、`X-App-Version`(如 1.0.0)、`X-Platform`(android/ios)
### 5.7 版本检查与强制更新
- App 启动及回到前台时调用 `GET /app/version`:返回最新版本号、是否强制更新、下载地址、维护公告
- 强制更新:弹窗阻断使用并跳转商店/下载页;非强制:可跳过的提示弹窗;维护公告:全 App 只读提示
### 5.8 工程结构(与当前代码对齐)
```
app/lib/
├── screens/ # 页面:splash / home(HomeShell+IndexedStack) / feed / search / category
│ # / player / membership / payment / wallet / assets / upload
│ # / profile / auth / settings / notification / downloads
├── providers/ # Riverpod 状态(12 个):auth / feed / comment / wallet / member
│ # / payment / profile / search / upload / asset / notification
│ # / tag_videos
└── core/ # 子系统:config(环境配置)/ media(local_media_server 本地代理、
# download_manager 下载)/ network(api_client、域名发现)/
# player(feed_player_pool 播放器池)/ storage(token、本地存储)/
# theme / utils
```
- Feed 策略状态按策略保活:`feedProviderFamily(strategy)` 每个策略一个独立 FeedNotifier;`activeFeedStrategyProvider` + `feedTabVisibleProvider` 配合 HomeShell(IndexedStack)在切页/回前台时挂起与恢复正确的播放器池
- 401 兜底:无 refresh token 或刷新被拒时清登录态并跳登录页,防止"僵尸登录态"
## 6. 后端设计
### 6.1 框架推荐:**FastAPI**
| 对比项 | FastAPI(推荐) | Django REST Framework | Flask |
|---|---|---|---|
| 异步支持 | 原生 async/await(IO 密集接口吞吐高) | 3.x 起部分支持 | 需额外方案 |
| API 文档 | 自动生成 OpenAPI/Swagger | 需 drf-spectacular | 需 flasgger 等 |
| 数据校验 | Pydantic 类型声明即校验 | Serializer 较繁琐 | 无内置 |
| 性能 | 高(Starlette + Uvicorn) | 中 | 中 |
| 学习/维护 | 类型提示友好,适合中小团队快速迭代 | 全家桶偏重 | 灵活但需自行拼装 |
本项目接口以高并发短请求 + 文件流转为主,FastAPI 的异步 IO、自动文档(与《03-接口文档》可互验)和 Pydantic 校验最匹配。
**技术栈**:Python 3.11 + FastAPI + SQLAlchemy 2.0(异步)+ Alembic + Pydantic v2 + Celery(转码/异步任务)+ MySQL 8 + Redis 7 + FFmpeg + Nginx。
### 6.2 模块划分
```
server/
├── app/
│ ├── main.py # 应用入口、中间件、路由注册、lifespan(settle 周期任务启动)
│ ├── core/ # 配置、日志、异常、安全(JWT/密码)
│ ├── api/v1/ # 路由层(11 个文件):admin / auth / common / incentives
│ │ # / interactions / invite / media / membership / payments
│ │ # / users / videos(管理后台同文件 /admin 前缀、独立管理员鉴权)
│ ├── schemas/ # Pydantic 请求/响应模型
│ ├── models/ # SQLAlchemy ORM 模型(30 张表)
│ ├── services/ # 业务逻辑(17 个):user/auth/wallet/video/interaction/membership
│ │ # /payment/incentive/invite/admin/common/config/sensitive
│ │ # /storage/transcode/settle/collection
│ ├── payment/ # 支付适配层:alipay / wechat / usdt
│ ├── tasks/ # Celery 任务:转码、播放量落库、订单关单
│ └── utils/ # 短信、邮件、OSS、设备指纹
├── scripts/ # 离线脚本:build_tags_from_titles.py(标签生成)、采集脚本
├── alembic/ # 数据库迁移(init_27_tables → collection_records → collection_sources)
├── config.yaml # 业务参数配置(积分/会员/支付/上传限制)
├── .env # 密钥类配置(不入库)
└── docker-compose.yml
```
### 6.3 核心流程设计
#### 6.3.1 观看计费(watch 接口内部逻辑)
1. 解析身份:有 Token → 用户;无 Token → device_id 游客
2. 会员校验:`user_membership` 终身卡或 `expire_at > now()` → 记录观看(cost_type=2)返回放行
3. 同日去重:尝试插入 `watch_records`(唯一键冲突 = 今日已看过)→ 放行返回
4. 免费额度:Redis `SADD free:watch:{subject}:{date} video_id`,若返回 1 且 `SCARD ≤ 10` → 免费占用,落库返回
5. 超额度:游客 → 返回错误码 `FREE_LIMIT_EXCEEDED`;用户 → 事务内扣积分(锁钱包行 + 写流水 + 落观看记录)→ 返回成功及剩余积分
6. 积分不足 → 返回错误码 `INSUFFICIENT_POINTS`
> 关键:步骤 3-5 的"去重 + 占额度"用 Redis Lua 脚本保证原子性;扣积分用数据库事务 + 行锁保证原子性。
#### 6.3.2 支付到账(回调逻辑,三渠道统一)
1. 验签/验来源(支付宝公钥、微信 V3 平台证书、USDT 链上数据核对)
2. 按订单号查订单,校验状态为"待支付"且金额一致
3. 幂等校验:`third_trade_no`/`tx_hash` 唯一索引冲突 → 直接返回成功(重复回调)
4. 事务:订单置已支付 → 钱包加积分(行锁)→ 写积分流水 → 提交
5. 返回支付平台要求的成功响应;失败进入重试/告警
#### 6.3.3 转码流水线
1. 上传完成 → 创建 videos(status=0) + transcode_tasks(priority=会员?1:0)
2. Celery 两个队列:`transcode.high` / `transcode.normal`,Worker 优先消费 high
3. FFmpeg 转 HLS(输出 m3u8 + ts)→ 上传 OSS → 更新 video_url、时长、封面
4. 状态流转:转码成功 → 待审核;失败重试 3 次 → 转码失败
#### 6.3.4 评论奖励结算
1. 发表评论(≥5 字、视频有效、同视频首评)→ 写评论 + `comment:counted` 去重标记
2. Redis `HINCRBY comment:prog:{uid} count 1`;达 10 → 扣减 10 并事务发放 1 积分(写流水,biz_type=3)
3. 日上限校验:`daily` 字段达 5 则当日不再发放
4. 评论删除:若 `reward_counted=1` → 进度 -1;若奖励已发放 → 发放 -1 积分流水(biz_type=7)
### 6.4 配置文件样例(config.yaml)
```yaml
points:
points_per_yuan: 10 # 1元=10积分
watch_cost: 1 # 每视频消耗积分
daily_free_count: 10 # 每日免费观看数
comment_reward:
per_count: 10 # 每评论10个视频
reward: 1 # 奖励1积分
min_length: 5
daily_limit: 5
membership:
trial: { days: 1, points: 5, enabled: true }
monthly: { days: 30, points: 300, enabled: true }
yearly: { days: 365, points: 3650, enabled: true }
lifetime: { days: 0, points: 10000, enabled: true } # days=0 表示永久
recharge:
# 充值档位在管理后台维护(recharge_packages 表),实时生效,此处不配置
usdt: { rate: 7.2, network: TRC20, address: "T...", confirmations: 19, order_expire_min: 30 }
referral:
register_reward_points: 10 # 邀请有效新用户奖励积分(N),不限量
rebate_percent: 5 # 被推荐人充值积分返利比例→资产
activate_watch_count: 3 # 激活条件:72h内观看≥3个视频
activate_hours: 72
risk_max_bind_per_day: 10 # 邀请人单日新增绑定上限(超限冻结审核)
risk_max_register_per_ip: 5 # 同IP单日注册上限(超限标记风险)
asset:
assets_per_yuan: 10 # 10资产=1元
complete_views_per_asset: 1000 # 每1000次完整观看=1资产
likes_per_asset: 100 # 每100次点赞=1资产
complete_progress: 0.95 # 完整观看判定进度
withdraw:
min_assets: 100 # 最低提现资产(=10元)
fee_percent: 0 # 提现手续费比例
channels: [alipay, wechat, usdt]
upload:
max_size_mb: 500
min_duration_s: 5
max_duration_s: 600
hls_profiles: ["720p"] # 可配 ["1080p","720p","480p"]
```
### 6.5 安全设计
- 密码 bcrypt 哈希;登录失败 5 次锁定 15 分钟(Redis 计数)
- JWT 双 Token:access 2 小时 + refresh 30 天;登出/改密加入黑名单
- 支付回调只信验签结果,金额以服务端订单为准
- 接口限流:验证码 1 条/分钟、评论 10 条/分钟、观看上报 60 次/分钟
- 上传文件类型白名单校验(mp4/mov/m3u8/ts),大小校验
- SQL 注入防护:ORM 参数化;XSS:评论内容转义输出