# 短视频应用技术文档 - 版本:v1.0 - 日期:2026-10-14 - 阅读对象:开发、测试、运维 --- ## 1. 项目架构 ### 1.1 总体架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ 客户端层 │ │ Flutter App (iOS / Android) + Web 管理后台 (Vue3) │ └──────────────────────────┬──────────────────────────────────┘ │ HTTPS (JSON / 文件上传) ▼ ┌─────────────────────────────────────────────────────────────┐ │ 接入层:Nginx │ │ TLS 终止 / 反向代理 / 静态与HLS分片加速 / 限流 │ └───────┬──────────────────────────────┬──────────────────────┘ ▼ ▼ ┌───────────────────┐ ┌──────────────────────────────┐ │ FastAPI 应用服务 │ │ 对象存储 OSS / MinIO │ │ (Uvicorn x N) │ │ 源视频 / m3u8+ts / 封面/头像 │ │ 无状态水平扩展 │ └──────────────▲───────────────┘ └──┬────┬────┬──────┘ │ 转码产物上传 │ │ │ │ │ │ └───────────────┐ │ ▼ ▼ ▼ │ ┌───────┐ ┌──────────────────────┐ ┌──────┴───────────────┐ │MySQL 8│ │ Redis 7 │ │ Celery Worker │ │主数据 │ │ 缓存/计数/队列(Broker) │ │ FFmpeg 转码 / 异步任务 │ └───────┘ └──────────────────────┘ └──────────────────────┘ ▲ ▲ │ 支付异步回调 │ USDT 链上监控轮询 ┌───────┴───────────────────────────────────┴───────────────┐ │ 支付宝开放平台 / 微信支付V3 / TronGrid(TRC20 节点服务) │ └───────────────────────────────────────────────────────────┘ ``` ### 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 ```sql 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; ``` ```sql -- 标签 CREATE TABLE tags ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL, video_count INT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE video_tags ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, video_id BIGINT UNSIGNED NOT NULL, tag_id INT UNSIGNED NOT NULL, UNIQUE KEY uk_video_tag (video_id, tag_id), KEY idx_tag (tag_id, video_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 充值套餐(管理后台可配) CREATE TABLE recharge_packages ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, amount_yuan INT NOT NULL, points INT NOT NULL COMMENT '基础积分=金额x汇率', bonus_points INT NOT NULL DEFAULT 0, sort INT NOT NULL DEFAULT 0, status TINYINT NOT NULL DEFAULT 1 COMMENT '1上架 0下架', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 管理员与操作日志 CREATE TABLE admin_users ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL, name VARCHAR(50) NOT NULL DEFAULT '', role VARCHAR(20) NOT NULL COMMENT 'super/reviewer/operator', status TINYINT NOT NULL DEFAULT 1, last_login_at DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE admin_logs ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, admin_id BIGINT UNSIGNED NOT NULL, action VARCHAR(50) NOT NULL, target VARCHAR(100) NOT NULL DEFAULT '', detail VARCHAR(1000) NOT NULL DEFAULT '', ip VARCHAR(45) NOT NULL DEFAULT '', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_admin_created (admin_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 平台配置(后台可改,Redis 缓存 60s) CREATE TABLE system_configs ( config_key VARCHAR(50) PRIMARY KEY, config_value VARCHAR(500) NOT NULL DEFAULT '', description VARCHAR(200) NOT NULL DEFAULT '', updated_by BIGINT UNSIGNED NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 预置:register_gift_trial.enabled=1(注册赠送体验会员开关)、register_gift_trial.days=1(赠送天数) ``` ```sql -- 激励域:推荐关系 / 资产钱包 / 资产流水 / 提现 / 视频收益 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; ``` ```sql -- 通用域:通知 / 举报 / 敏感词 CREATE TABLE notifications ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL COMMENT '接收人', type TINYINT NOT NULL COMMENT '1审核结果 2提现进度 3系统公告 4评论回复', title VARCHAR(100) NOT NULL DEFAULT '', content VARCHAR(500) NOT NULL DEFAULT '', biz_id VARCHAR(64) NOT NULL DEFAULT '' COMMENT '关联单据(视频ID/提现单号/评论ID)', is_read TINYINT NOT NULL DEFAULT 0, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_user_read (user_id, is_read, id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE reports ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL COMMENT '举报人', target_type TINYINT NOT NULL COMMENT '1视频 2评论', target_id BIGINT UNSIGNED NOT NULL, reason_type TINYINT NOT NULL COMMENT '1色情低俗 2暴力血腥 3侵权 4广告营销 5其他', remark VARCHAR(200) NOT NULL DEFAULT '', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待处理 1已处理 2已驳回', handle_result VARCHAR(50) NOT NULL DEFAULT '' COMMENT '处理动作(offline_video/delete_comment)', admin_id BIGINT UNSIGNED NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, handled_at DATETIME NULL, UNIQUE KEY uk_user_target (user_id, target_type, target_id), KEY idx_status (status, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE sensitive_words ( id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT, word VARCHAR(50) NOT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_word (word) ) 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 并发与一致性设计 1. **积分变更**(统一入口 `wallet_service.change_points`): - `SELECT ... FROM user_wallets WHERE user_id=? FOR UPDATE` 行锁 - 校验余额(扣减场景)→ 更新余额/累计字段 → 插入流水 → 提交 - 可选叠加乐观锁 version 防应用层并发 2. **免费额度 + 去重**(Redis Lua 原子脚本): ``` -- KEYS[1]=free:watch:{subject}:{date} ARGV[1]=video_id ARGV[2]=limit if redis.call('SISMEMBER', KEYS[1], ARGV[1]) == 1 then return 1 end -- 已看过 local n = redis.call('SCARD', KEYS[1]) if n < tonumber(ARGV[2]) then redis.call('SADD', KEYS[1], ARGV[1]) return 2 end -- 免费占用 return 0 -- 额度用尽 ``` 3. **支付幂等**:订单状态机 `0→1` 条件更新(`UPDATE ... WHERE status=0`)+ 第三方流水号唯一索引,双保险 4. **评论奖励**: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 `;游客必带 `X-Device-ID` - 统一响应: ```json { "code": 0, "message": "ok", "data": { }, "request_id": "uuid" } ``` - 错误码: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 工程结构(分层架构) ``` app/lib/ ├── main.dart ├── config/ # 环境配置、常量、主题 ├── router/ # go_router 路由表、登录守卫 ├── models/ # 数据模型(json_serializable) ├── services/ # api_client(dio)、auth_service、storage、device ├── providers/ # Riverpod providers(auth/feed/wallet/vip) ├── pages/ # 页面(home/login/mine/wallet/vip/upload/...) │ └── home/widgets/ ├── widgets/ # 通用组件(按钮/弹窗/空态/互动栏) └── utils/ # 格式化、埋点、防抖 ``` ### 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_at3 次置失败 ### 6.4 推荐与标签服务实现 - Feed 接口参数 `type=recommend|latest|hot`,默认 recommend - **recommend(个性化)**: 1. 候选池:近 30 天"已发布"视频 ID(Redis ZSet `feed:latest` 缓存,发布/下架时增删) 2. 个性化分:统计用户近 7 天观看/点赞/收藏视频的标签,得到偏好标签及权重;候选视频命中偏好标签加权,未命中取热度归一分;游客/新用户退化为热度分 3. **每日重排**:`seed = md5(yyyyMMdd + subject_id)`,以该固定种子对打分后列表做确定性洗牌(Fisher-Yates)—— 保证同一用户**每天顺序不同**,且同一天内多次刷新顺序稳定(游标翻页不错乱) 4. **当日去重**:翻页时过滤 `watched:{subject}:{date}`(观看上报时写入);候选耗尽时放开过滤兜底,避免 Feed 刷空 - **latest**:published_at 游标倒序;**hot**:score = view×1 + like×5 + comment×10(近 7 天,时间衰减) - 标签:发布时写入 tags / video_tags(标签名全局唯一、大小写不敏感);标签视频页按热度或最新排序 ### 6.5 支付适配层 ``` payment/ ├── base.py # PayAdapter 抽象:create_order / verify_callback / parse_callback ├── alipay.py # alipay-sdk-python:app_pay 下单、RSA2 验签 ├── wechat.py # wechatpay-python:V3 下单、平台证书验签解密 └── usdt.py # tronpy+TronGrid:生成唯一金额、轮询匹配、确认数判定 ``` - 三渠道产出统一的 `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)**: 1. **分享口令(主路径)**:推广文案含 `#短视频邀请码XXXXXXXX#`;App 首启解析剪贴板正则提取,本地持久化 pending 邀请码并在注册页预填,准确率 100% 2. **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 天) 3. **注册手填(兜底)**:注册接口 `invite_code` 选填;注册后 24h 内可经 `/referral/bind` 补绑 4. **安全设计**:归因结果仅存服务端,注册时 `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)