短视频应用技术文档
- 版本:v1.0
- 日期:2026-10-14
- 阅读对象:开发、测试、运维
1. 项目架构
1.1 总体架构
1.2 部署架构(单机起步版,docker-compose)
| 容器 |
镜像 |
说明 |
| nginx |
nginx:1.25 |
443 端口;/api 反代到 app;/hls 直出 OSS 回源或本地 |
| app |
自建 python:3.11-slim |
uvicorn --workers 4,FastAPI 服务 |
| worker |
同 app 镜像 |
celery worker -Q transcode.high,transcode.normal,default |
| mysql |
mysql:8.0 |
主数据,挂载数据卷,开启 binlog |
| redis |
redis:7 |
缓存 + Celery Broker,开启 AOF |
| admin |
node 构建产物,nginx 托管静态文件 |
管理后台(Vue3),建议仅内网/VPN 访问 |
扩展路径:App 无状态 → 多副本 + SLB;MySQL → 云 RDS 主从;Redis → 云 Redis;转码 → 独立 GPU/多核 Worker 机器或云转码服务。
1.3 关键链路
- 播放链路:App →
GET /hls/{vid}/index.m3u8 → Nginx → OSS/CDN(m3u8 与 ts 均为静态文件,天然适合 CDN 加速)
- 上传链路:App → 获取上传凭证 → 直传 OSS(推荐,节省服务端带宽)或经服务端中转 → 回调发布 → 转码 → 审核 → 上架
- 支付链路:App 下单 → 调起支付 SDK → 平台异步回调服务端 → 验签 → 事务入账 → App 轮询查单确认结果
2. 技术栈
2.1 选型总表
| 层 |
技术 |
版本 |
选型理由 |
| 后端框架 |
FastAPI |
≥0.110 |
原生异步、Pydantic 校验、自动生成 OpenAPI 文档、性能高 |
| ASGI 服务器 |
Uvicorn / Gunicorn+UvicornWorker |
latest |
生产多进程 |
| ORM |
SQLAlchemy (async) |
2.0 |
生态成熟,2.0 异步支持完善 |
| 迁移 |
Alembic |
latest |
与 SQLAlchemy 官方配套 |
| 校验 |
Pydantic |
v2 |
性能强,与 FastAPI 深度集成 |
| 数据库 |
MySQL |
8.0 |
团队熟悉度高、事务/唯一约束完备;utf8mb4 |
| 缓存/队列 |
Redis |
7 |
计数、Set 去重、Lua 原子脚本、Celery Broker |
| 异步任务 |
Celery |
5.x |
转码、落库、关单等重任务异步化 |
| 转码 |
FFmpeg |
≥6.0 |
HLS 转码事实标准 |
| 对象存储 |
阿里云 OSS / 腾讯云 COS / MinIO |
- |
国内上线选云 OSS;自建测试用 MinIO(S3 兼容) |
| 支付 SDK |
alipay-sdk-python / wechatpay-python / tronpy |
latest |
官方/主流 SDK |
| 短信/邮件 |
阿里云短信 / SMTP |
- |
验证码通道 |
| 前端框架 |
Flutter |
3.x (Dart 3) |
单代码库双端,生态成熟 |
| 状态管理 |
flutter_riverpod |
2.x |
编译安全、可测试、适合中大型项目 |
| 网络 |
dio |
5.x |
拦截器体系(Token 注入/刷新/设备ID) |
| 播放器 |
media_kit |
latest |
全平台 HLS(m3u8) 支持好,可定制 UI;备选 video_player+chewie |
| 路由 |
go_router |
latest |
声明式路由、深链支持 |
| 本地存储 |
shared_preferences + flutter_secure_storage |
- |
配置 / Token 安全存储 |
| 图片缓存 |
cached_network_image |
- |
封面、头像 |
| 管理后台 |
Vue 3 + Element Plus + Vite |
3.x |
一期人工审核与运营后台 |
| DNS 解析 |
DoH(阿里 223.5.5.5 / 腾讯 119.29.29.29) |
- |
App 域名发现,避免本地 DNS 污染 |
2.2 后端框架对比(为什么是 FastAPI)
| 维度 |
FastAPI |
Django REST Framework |
Flask |
| 异步 IO |
★★★★★ 原生 |
★★★ 部分 |
★★ 需 gevent 等 |
| 开发效率 |
★★★★ 类型即文档 |
★★★ 全家桶但重 |
★★★ 简单但都要自己装 |
| 自动 API 文档 |
内置 Swagger/ReDoc |
第三方 |
第三方 |
| 项目规模匹配 |
中小团队微服务/单 API 服务 |
大型全栈站 |
玩具到中型 |
| 生态(支付/任务等) |
与 Django 相当的可组合性 |
最丰富 |
丰富 |
结论:本项目为纯 API 后端 + 异步任务场景,选 FastAPI;Django 适合还需要全功能 Admin 的场景(若一期就要管理后台,可并行评估 Django Admin 或独立前端 + FastAPI 管理接口)。
3. 数据库设计(技术实现)
3.1 通用约定
- 引擎 InnoDB,字符集 utf8mb4 / utf8mb4_unicode_ci
- 主键统一
BIGINT UNSIGNED AUTO_INCREMENT
- 时间字段
DATETIME,默认 CURRENT_TIMESTAMP / ON UPDATE CURRENT_TIMESTAMP,全部存服务器本地时区(Asia/Shanghai)
- 金额一律整型存储:人民币用分(amount_fen);积分 INT
- 逻辑删除仅在评论等少数表使用(status 字段),其余物理保留
3.2 核心表 DDL
CREATE TABLE users (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
phone VARCHAR(20) NULL COMMENT '手机号',
email VARCHAR(100) NULL COMMENT '邮箱',
password_hash VARCHAR(255) NOT NULL,
nickname VARCHAR(50) NOT NULL DEFAULT '',
avatar_url VARCHAR(500) NOT NULL DEFAULT '',
gender TINYINT NOT NULL DEFAULT 0,
bio VARCHAR(200) NOT NULL DEFAULT '',
status TINYINT NOT NULL DEFAULT 1 COMMENT '1正常 2封禁',
register_type TINYINT NOT NULL COMMENT '1手机 2邮箱',
invite_code VARCHAR(10) NOT NULL DEFAULT '' COMMENT '我的邀请码',
register_ip VARCHAR(45) NOT NULL DEFAULT '' COMMENT '注册IP',
last_login_at DATETIME NULL,
last_login_ip VARCHAR(45) NOT NULL DEFAULT '' COMMENT '最后登录IP',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_phone (phone),
UNIQUE KEY uk_email (email),
UNIQUE KEY uk_invite_code (invite_code),
KEY idx_status_created (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE user_wallets (
user_id BIGINT UNSIGNED PRIMARY KEY,
points INT UNSIGNED NOT NULL DEFAULT 0,
total_recharge INT UNSIGNED NOT NULL DEFAULT 0,
total_earned INT UNSIGNED NOT NULL DEFAULT 0,
total_consumed INT UNSIGNED NOT NULL DEFAULT 0,
version INT NOT NULL DEFAULT 0 COMMENT '乐观锁',
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_wallet_user FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE videos (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
category_id INT NOT NULL DEFAULT 0,
title VARCHAR(100) NOT NULL DEFAULT '',
description VARCHAR(500) NOT NULL DEFAULT '',
cover_url VARCHAR(500) NOT NULL DEFAULT '',
video_url VARCHAR(500) NOT NULL DEFAULT '' COMMENT 'm3u8地址',
duration INT NOT NULL DEFAULT 0,
file_size BIGINT NOT NULL DEFAULT 0,
width INT NOT NULL DEFAULT 0,
height INT NOT NULL DEFAULT 0,
status TINYINT NOT NULL DEFAULT 0 COMMENT '0转码中 1待审核 2已发布 3拒绝 4下架 5转码失败',
reject_reason VARCHAR(200) NOT NULL DEFAULT '',
reviewer_id BIGINT UNSIGNED NULL COMMENT '审核人(admin_users.id)',
reviewed_at DATETIME NULL COMMENT '审核时间',
is_priority TINYINT NOT NULL DEFAULT 0,
view_count BIGINT NOT NULL DEFAULT 0,
like_count INT NOT NULL DEFAULT 0,
comment_count INT NOT NULL DEFAULT 0,
favorite_count INT NOT NULL DEFAULT 0,
published_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_user_status (user_id, status),
KEY idx_feed (status, published_at),
KEY idx_category (category_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE watch_records (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
subject_type TINYINT NOT NULL COMMENT '1用户 2游客',
subject_id VARCHAR(64) NOT NULL COMMENT 'user_id或device_id',
video_id BIGINT UNSIGNED NOT NULL,
watch_date DATE NOT NULL,
cost_type TINYINT NOT NULL COMMENT '0免费 1积分 2会员 3本人',
cost_points INT NOT NULL DEFAULT 0,
watch_duration INT NOT NULL DEFAULT 0,
position INT NOT NULL DEFAULT 0 COMMENT '最后播放进度(秒),用于续播',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_dedup (subject_type, subject_id, video_id, watch_date),
KEY idx_subject (subject_type, subject_id, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE point_transactions (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
change_amount INT NOT NULL COMMENT '正增负减',
balance_after INT NOT NULL,
biz_type TINYINT NOT NULL COMMENT '1充值 2观看 3评论奖励 4会员 5赠送 6调整 7扣回',
biz_id VARCHAR(64) NOT NULL DEFAULT '',
remark VARCHAR(200) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_user_created (user_id, created_at),
KEY idx_biz (biz_type, biz_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE recharge_orders (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
order_no VARCHAR(32) NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
amount_fen INT NOT NULL,
points INT NOT NULL,
pay_method TINYINT NOT NULL COMMENT '1支付宝 2微信 3USDT',
status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已关闭 3已退款',
third_trade_no VARCHAR(64) NULL,
usdt_amount DECIMAL(18,6) NULL,
usdt_rate DECIMAL(10,4) NULL,
paid_at DATETIME NULL,
expire_at DATETIME NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_order_no (order_no),
UNIQUE KEY uk_third_trade_no (third_trade_no),
KEY idx_user_status (user_id, status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- likes / favorites 同构
CREATE TABLE likes (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
video_id BIGINT UNSIGNED NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_user_video (user_id, video_id),
KEY idx_video (video_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 激励域:推荐关系 / 资产钱包 / 资产流水 / 提现 / 视频收益
CREATE TABLE referrals (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
inviter_id BIGINT UNSIGNED NOT NULL COMMENT '推荐人',
invitee_id BIGINT UNSIGNED NOT NULL COMMENT '被推荐人',
invite_code VARCHAR(10) NOT NULL,
status TINYINT NOT NULL DEFAULT 0 COMMENT '0待激活 1有效 2无效 3风控冻结',
risk_flag VARCHAR(50) NOT NULL DEFAULT '',
register_ip VARCHAR(45) NOT NULL DEFAULT '',
activated_at DATETIME NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_invitee (invitee_id),
KEY idx_inviter (inviter_id, created_at),
KEY idx_status (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE asset_wallets (
user_id BIGINT UNSIGNED PRIMARY KEY,
assets DECIMAL(12,1) NOT NULL DEFAULT 0 COMMENT '当前资产(允许为负)',
total_earned DECIMAL(14,1) NOT NULL DEFAULT 0,
total_withdrawn DECIMAL(14,1) NOT NULL DEFAULT 0,
version INT NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE asset_transactions (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT UNSIGNED NOT NULL,
change_amount DECIMAL(12,1) NOT NULL,
balance_after DECIMAL(12,1) NOT NULL,
biz_type TINYINT NOT NULL COMMENT '1充值返利 2完整观看 3点赞 4提现 5提现退回 6返利扣回 7后台调整',
biz_id VARCHAR(64) NOT NULL DEFAULT '',
remark VARCHAR(200) NOT NULL DEFAULT '',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY idx_user_created (user_id, created_at),
KEY idx_biz (biz_type, biz_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE withdrawals (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
withdraw_no VARCHAR(32) NOT NULL,
user_id BIGINT UNSIGNED NOT NULL,
assets DECIMAL(12,1) NOT NULL,
amount_fen INT NOT NULL COMMENT '折合人民币(分)',
channel TINYINT NOT NULL COMMENT '1支付宝 2微信 3USDT',
account VARCHAR(200) NOT NULL,
status TINYINT NOT NULL DEFAULT 0 COMMENT '0待审核 1已打款 2已拒绝',
reason VARCHAR(200) NOT NULL DEFAULT '',
admin_id BIGINT UNSIGNED NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
processed_at DATETIME NULL,
UNIQUE KEY uk_withdraw_no (withdraw_no),
KEY idx_user (user_id, created_at),
KEY idx_status (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE video_earnings (
video_id BIGINT UNSIGNED PRIMARY KEY,
author_id BIGINT UNSIGNED NOT NULL,
complete_views INT NOT NULL DEFAULT 0,
views_settled INT NOT NULL DEFAULT 0,
likes_accrued INT NOT NULL DEFAULT 0,
likes_settled INT NOT NULL DEFAULT 0,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
KEY idx_author (author_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
其余表(comments、favorites、membership_orders、user_membership、categories、transcode_tasks)结构见《01-开发文档》第 3 章,DDL 随代码工程提供。
3.3 索引与查询策略
| 查询场景 |
命中索引 |
| Feed 列表 |
videos.idx_feed(status, published_at) + 游标分页 |
| 我的作品 |
videos.idx_user_status(user_id, status) |
| 观看去重 |
watch_records.uk_dedup 唯一索引(冲突即已看) |
| 点赞防重 |
likes.uk_user_video 唯一索引,INSERT IGNORE/捕获 1062 |
| 积分流水翻页 |
point_transactions.idx_user_created |
| 回调幂等 |
recharge_orders.uk_third_trade_no |
- 计数列(like_count 等)为冗余计数:写路径"明细表 + 计数列 +1/-1"同事务;Redis 仅作显示缓冲时以 DB 为准定期校准
- 大字段(description)不参与列表 SELECT,列表接口只取必要列
- watch_records 增长快:按月归档(二期分区表)
3.4 并发与一致性设计
- 积分变更(统一入口
wallet_service.change_points):
SELECT ... FROM user_wallets WHERE user_id=? FOR UPDATE 行锁
- 校验余额(扣减场景)→ 更新余额/累计字段 → 插入流水 → 提交
- 可选叠加乐观锁 version 防应用层并发
- 免费额度 + 去重(Redis Lua 原子脚本):
- 支付幂等:订单状态机
0→1 条件更新(UPDATE ... WHERE status=0)+ 第三方流水号唯一索引,双保险
- 评论奖励:Redis 进度增减 + 发放走积分事务;删除回退以
reward_counted 标记防重复回退
3.5 Redis 详细设计
| Key |
类型 |
过期 |
说明 |
sms:code:{phone} |
String |
300s |
短信验证码;同号 60s 内禁止重发(另建 sms:limit:{phone}) |
email:code:{email} |
String |
600s |
邮箱验证码 |
auth:refresh:{user_id} |
String |
30d |
refresh_token 白名单(支持多端+踢下线) |
token:blacklist:{jti} |
String |
token 剩余 TTL |
登出/改密失效 |
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 |
首启归因结果缓存;注册时 GETDEL 式一次性消费 |
wd:pending:{user_id} |
String |
审核期间 |
提现在途唯一锁 |
free:watch:{subject}:{yyyyMMdd} |
Set |
48h |
当日免费观看集合(去重+计数) |
watch:reported:{subject}:{video_id}:{yyyyMMdd} |
String |
48h |
watch 接口幂等(防客户端重复上报) |
comment:prog:{user_id} |
Hash{count,daily,date} |
7d |
奖励进度与当日已发 |
comment:counted:{user_id}:{video_id} |
String |
30d |
同视频首评去重 |
view:video:{video_id} |
String |
- |
播放计数缓冲,定时任务批量落库 |
feed:latest |
ZSet(score=publish_ts) |
- |
最新发布视频 ID 缓存,Feed 加速 |
video:detail:{video_id} |
String(JSON) |
5min |
视频详情缓存,状态变更时失效 |
search:hot |
ZSet(score=搜索次数) |
- |
热搜榜,搜索接口计数,定时衰减 |
notif:unread:{user_id} |
String(计数) |
- |
消息未读数,读/写通知时增减 |
words:sensitive |
String(JSON) |
60s |
敏感词库缓存(DFA 词源),后台增删后失效重建 |
config:system |
Hash |
60s |
system_configs 全量缓存,后台修改即时失效 |
4. 接口设计(技术规范)
4.1 通用规范
- 风格:RESTful,版本化前缀
/api/v1
- 编码:UTF-8 JSON;时间格式
YYYY-MM-DD HH:mm:ss;金额字段人民币为分,USDT 为字符串(防精度丢失)
- 认证:
Authorization: Bearer <access_token>;游客必带 X-Device-ID
- 统一响应:
- 错误码:0 成功;1xxx 通用/参数/认证;2xxx 业务(计费/积分/会员);3xxx 支付;5xxx 服务端。完整码表见《03-接口文档》附录
- 分页:Feed 用游标(
cursor=上次返回的下一游标);管理/列表用 page/page_size(≤50)
- 幂等:写接口客户端带
X-Request-ID(或 watch 用天然键),服务端去重
- 限流:Nginx limit_req + Redis 滑动窗口双层;触发返回 429
4.2 认证体系(JWT)
- access_token:HS256,2h,payload
{sub:user_id, jti, exp, type:"access"}
- refresh_token:30d,Redis 白名单存储;刷新时旋转(旧的作废)
- 登出:jti 入黑名单 + 删 refresh 白名单
- 中间件解析顺序:Bearer Token 有效 → 用户态;否则取 X-Device-ID → 游客态;都无 → 401
4.3 签名与回调安全
- 支付宝回调:RSA2 验签(支付宝公钥)
- 微信回调:V3 平台证书验签 + AES-GCM 解密回调资源
- USDT:无回调,服务端主动轮询 TronGrid,校验收款地址、金额(含尾差)、确认数 ≥ 19、时间窗在订单有效期内
4.4 管理后台接口规范
- 前缀
/api/v1/admin,与 App 接口隔离注册
- 管理员独立账号体系(admin_users 表),Token payload
type:"admin",有效期 12h
- 角色:super(全部)/ reviewer(仅视频审核)/ operator(套餐、活动、看板),中间件按角色鉴权
- 所有写操作异步写入 admin_logs(操作人、动作、参数、IP)
- 敏感操作(积分调整、补单、封禁、下架)要求
reason 必填,前端二次确认
- 建议 Nginx 层对
/api/v1/admin 加 IP 白名单
4.5 接口限流
Nginx limit_req(IP 维度)+ Redis 滑动窗口(主体维度)双层限流;触发返回 HTTP 429(业务码 1005)。
| 接口 |
限流规则 |
维度 |
| 发送验证码(短信/邮箱) |
1 条/分钟 + 5 条/小时(另有同号每天 10 条上限) |
手机号/邮箱 + IP |
| 登录 |
10 次/分钟;连续失败 5 次锁定 15 分钟 |
IP |
| 观看上报 |
60 次/分钟 |
计费主体(用户/设备) |
| 发表评论 |
10 条/分钟 |
用户 |
| 上传(凭证 + 发布) |
5 次/小时 |
用户 |
| 申请提现 |
1 次/分钟 |
用户 |
| 提交举报 |
10 次/天 |
用户 |
| 搜索 |
30 次/分钟 |
IP + 用户 |
| 其它写接口 |
30 次/分钟(默认兜底) |
用户 |
5. 前端设计(Flutter 技术实现)
5.1 工程结构(分层架构)
5.2 关键技术点
| 点 |
方案 |
| 网络层 |
dio 单例 + 拦截器:注入 Authorization/X-Device-ID/X-Request-ID;401 时刷新 Token 并重放原请求(单飞 Future 防并发刷新) |
| 播放器 |
media_kit 封装 ShortVideoPlayer:预加载下一条(keepAlive 双缓冲)、滑出 dispose、首帧秒开(m3u8 低延迟配置);双击点赞手势区与播放手势分层 |
| Feed 滑动 |
PageView(scrollDirection: vertical),onPageChanged 触发观看上报(防抖 500ms,同视频当日只报一次,本地记录) |
| 状态 |
Riverpod:authStateProvider(游客/登录)、walletProvider、vipProvider、feedProvider(分页游标) |
| 本地持久 |
device_id(首启 UUID 写 secure_storage)、Token(secure_storage)、观看已上报集合(sp,按日清理) |
| 上传 |
优先 OSS STS 直传(后端签 STS,App 分片上传,失败续传);兜底服务端 multipart |
| 支付 |
支付宝/微信调起官方 SDK(alipay_kit / fluwx 等社区插件或自建 MethodChannel);USDT 展示金额+收款地址+二维码(qr_flutter)+ 倒计时,轮询订单状态 |
| 构建 |
flutter flavor 分 dev/prod;Android minSdk 21+;iOS 12+ |
| 域名发现 |
启动时经 DoH(223.5.5.5)解析 txt.256kb.top 的 TXT 记录获取 API 域名;失败则按 md5(yyyyMM) + "_jsnbhh.vip" 计算兜底域名;结果写入本地缓存,请求连续失败达阈值自动重新发现;dio BaseOptions 的 baseUrl 由该服务动态提供 |
| 设备指纹 |
首启生成 UUID(v4),Android 写 Keystore / iOS 写 Keychain 持久化(重装不丢失);dio 拦截器统一注入公共 Header X-Device-ID / X-App-Version / X-Platform,用于游客计费与风控 |
5.3 关键交互实现细节
- 游客拦截点:观看上报返回
FREE_LIMIT_EXCEEDED → 全局拦截器弹登录引导(Router Guard 不做强制,保证可浏览前 10 个)
- 扣费提示:watch 响应含
cost_type/cost_points/remaining → Toast 差异化提示
- 会员 UI:
vipProvider 驱动昵称旁金标、会员中心状态、上传"优先"标识
- 支付结果确认:调起 SDK 返回 + 轮询
GET /payments/orders/{order_no}(2s×15 次),成功刷新 walletProvider
6. 后端设计(技术实现)
6.1 目录结构与依赖注入
api/v1/* 只负责参数解析与响应组装;业务全在 services/
- 依赖注入用 FastAPI
Depends:DB session(async)、Redis、当前身份(用户/游客)
- 配置:
pydantic-settings 读取 .env + config.yaml(业务参数热重载预留)
- 注册赠送体验会员:注册成功事务内读取
system_configs(register_gift_trial.enabled / register_gift_trial.days,Redis 缓存 60s,后台修改实时生效),开启则赠送体验会员(membership_orders:plan_code=trial、source=2、points_cost=0)
6.2 异步任务设计(Celery)
| 队列 |
任务 |
说明 |
| transcode.high |
会员视频转码 |
优先消费 |
| transcode.normal |
普通视频转码 |
|
| default |
播放量落库、超时关单、通知 |
轻任务 |
- Broker/Backend 均用 Redis;任务幂等(转码以 video_id 状态机推进)
- 超时关单:每分钟扫
recharge_orders status=0 AND expire_at<now → 关闭
6.3 FFmpeg 转码实现
- 转码产出目录:
oss://bucket/hls/{video_id}/
- 多码率(可选):生成 master.m3u8 引用各档 variant
- 失败处理:捕获非零退出码 → retry_count+1(指数退避),>3 次置失败
6.4 推荐与标签服务实现
- Feed 接口参数
type=recommend|latest|hot,默认 recommend
- recommend(个性化):
- 候选池:近 30 天"已发布"视频 ID(Redis ZSet
feed:latest 缓存,发布/下架时增删)
- 个性化分:统计用户近 7 天观看/点赞/收藏视频的标签,得到偏好标签及权重;候选视频命中偏好标签加权,未命中取热度归一分;游客/新用户退化为热度分
- 每日重排:
seed = md5(yyyyMMdd + subject_id),以该固定种子对打分后列表做确定性洗牌(Fisher-Yates)—— 保证同一用户每天顺序不同,且同一天内多次刷新顺序稳定(游标翻页不错乱)
- 当日去重:翻页时过滤
watched:{subject}:{date}(观看上报时写入);候选耗尽时放开过滤兜底,避免 Feed 刷空
- latest:published_at 游标倒序;hot:score = view×1 + like×5 + comment×10(近 7 天,时间衰减)
- 标签:发布时写入 tags / video_tags(标签名全局唯一、大小写不敏感);标签视频页按热度或最新排序
6.5 支付适配层
- 三渠道产出统一的
PaymentResult(order_no, third_trade_no, paid_amount) 交给同一入账服务 settle_service(事务幂等入账)
- USDT 尾差策略:
base_amount + random(0.001,0.999),有效期内金额唯一;轮询间隔 15s
- 充值档位与赠送积分从
recharge_packages 表读取(管理后台实时可配);自定义金额按 points_per_yuan(默认 10)换算
- 退款流程:商户后台(支付宝/微信)人工发起退款 → 服务端接收退款回调或运营登记 → 订单状态置
3已退款 → 事务内扣回已发放积分(余额允许为负,后续充值抵扣)→ 若该订单产生过推荐返利,按原记录扣回推荐人资产(biz_type=6,资产允许为负)→ 全程写积分/资产流水与 admin_logs
6.6 激励结算与防作弊
Celery 周期任务(default 队列):
| 任务 |
频率 |
逻辑 |
| referral_activate_scan |
每 10 分钟 |
扫描 72h 内"待激活"绑定:满足观看 ≥3 个视频且次日有登录 → 置"有效"并发放邀请积分(积分事务,biz_type=8,校验推荐人为会员);超期未达标置"无效" |
| video_earning_settle |
每 5 分钟 |
遍历 video_earnings:新增完整观看 = complete_views - views_settled,每满 1000 发 1 资产;新增点赞 = likes_accrued - likes_settled,每满 100 发 1 资产;结算时点校验作者为会员,是则发资产(资产事务+流水)并推进 settled 刻度,否则仅保留计数 |
| referral_rebate |
事件触发 |
支付入账成功后检查订单用户是否存在"有效"推荐关系 → 推荐人为会员则发放"充值积分 × 5%"资产(异步补偿任务,不阻塞支付主流程);退款时按原返利记录扣回(biz_type=6,资产允许为负) |
关键实现:
- 完整观看计数先入 Redis(
video:cview:{video_id}),随播放量落库任务批量入库后结算
- 资产钱包与积分钱包同一套"行锁 + 流水 + 乐观锁"变更规范(asset_transactions 记录 balance_after)
- 提现申请:事务内冻结扣减 → 生成提现单 + Redis 在途锁(
wd:pending:{user_id});后台拒绝时退回并释放锁
- 风控埋点:注册/绑定时记录 device_id 与 IP;命中 same_device、same_ip、batch_register、inviter_burst 规则 → referrals.status=3 冻结,仅人工审核后可发放
推广归因(三段式自建方案,invite_service):
- 分享口令(主路径):推广文案含
#短视频邀请码XXXXXXXX#;App 首启解析剪贴板正则提取,本地持久化 pending 邀请码并在注册页预填,准确率 100%
- H5 点击埋点 + 模糊匹配(辅助路径):
/r/{code} 落地页加载即调 POST /invite/click(写入 invite:ip:{ip} zset,score=时间戳,24h TTL;invite:dup 60s 去重);App 首启无口令时调 POST /invite/match 上报 device_id,服务端按 IP 取最近点击(最多 5 个候选,就近优先)匹配,命中缓存 invite:attr:{device_id}(7 天)
- 注册手填(兜底):注册接口
invite_code 选填;注册后 24h 内可经 /referral/bind 补绑
- 安全设计:归因结果仅存服务端,注册时
pop_attribution 一次性消费(读后即删),客户端无法伪造归因;绑定优先级 显式 invite_code(口令/手填)> 模糊归因缓存;进入绑定后复用推荐风控全部规则
6.7 内容安全与敏感词
- 采用 DFA(确定性有穷自动机) 算法做敏感词匹配:启动时从
sensitive_words 表加载全量词库构建 DFA 树,词库存入 Redis(words:sensitive,60s 缓存);后台增删词后缓存失效重建,实时生效
- 应用范围:评论内容、用户昵称、视频标题/描述/标签、搜索关键词
- 命中处理:写接口命中敏感词 → 拒绝并返回错误码 2105;搜索词命中 → 返回空结果并记录日志
- 词库来源:合规基础词库初始化 + 运营后台持续维护(
uk_word 唯一索引去重)
6.8 日志与监控
- 结构化日志(JSON):request_id 贯穿;慢查询 >500ms 告警
- 指标:Prometheus(QPS、转码队列长度、支付成功率、积分发放量)
- 关键业务告警:回调验签失败、入账失败、转码失败率、USDT 超时未匹配
6.9 测试与质量
- pytest + httpx AsyncClient:核心用例覆盖计费判定矩阵(会员/免费/去重/扣费/不足/游客超限)
- 支付回调:构造签名报文做幂等与篡改用例
- 积分服务:并发扣费压测(locust)验证无负余额、无重复扣
6.10 部署与配置
.env:DB/Redis 连接、JWT 密钥、支付商户密钥、OSS 密钥(严禁入库/入 git)
config.yaml:业务参数(积分/会员/套餐/上传限制),见《01-开发文档》6.4
- CI/CD:GitHub Actions(lint → pytest → build image → deploy)
- 备份:MySQL 每日全量 + binlog 增量;OSS 跨区复制(可选)
- 兜底域名与证书:
jsnbhh.vip 系列兜底域名配置 DNS 泛解析 + 通配符证书(*.jsnbhh.vip),接入 CDN 回源;主域名失效时 App 按域名发现策略自动切换(见 5.2)