This commit is contained in:
lz_db
2026-10-18 21:08:54 +08:00
parent 1253c159f5
commit 1d440b2916
9 changed files with 901 additions and 167 deletions
+270 -124
View File
@@ -1,62 +1,166 @@
# 04-部署运行文档
> 本文说明如何在服务器上把项目跑起来。项目共三端:**服务端**(FastAPI,无需编译)、**管理后台**(Vue3,构建为静态文件)、**App**(Flutter,**必须编译**出 APK/IPA)。
> 本文记录**真实生产环境**的部署架构与运维操作手册(截至 2026-10-18)。
> 服务器:腾讯云 CVM(14G 内存),已装宝塔面板;系统 Linux。
> 项目根目录:`/root/project/video`,三个业务域名均启用 HTTPS。
## 1. 组成与依赖
## 1. 总体架构
| 端 | 目录 | 技术栈 | 运行/构建产物 |
|---|---|---|---|
| 服务端 | `server/` | Python ≥ 3.11 + FastAPI + MySQL 8 + Redis 7 | 直接运行,无需编译 |
| 管理后台 | `admin/` | Node ≥ 18 + Vue3 + Vite | `npm run build` → `admin/dist/` 静态文件 |
| App | `app/` | Flutter 3.x(Dart ≥ 3.9) | `flutter build apk` / `flutter build ipa` |
```
┌─────────────────────────────────────────┐
用户浏览器/App │ 腾讯云服务器 │
│ │
h5.256kb.top ───────► │ nginx(宝塔) ──► /root/project/video/h5 │
(下载页/邀请落地页) │ ├─ /downloads /videos /uploads │
│ │ → /root/project/video/runtime/* │
│ └─ /r/ /api/v1/invite/ ──┐ │
vm.256kb.top ───────► │ root:/root/project/video/admin/dist │
(管理后台 SPA) │ /api/ /static/ /r/ ──────┤ │
│ ▼ │
vapi.256kb.top ──────► │ 全量反代 ──────────► uvicorn 127.0.0.1:8000
(App API 入口) │ (video-api.service,│
│ 2 workers) │
│ │ │ │
│ ┌───────────┘ └────┐ │
│ ▼ ▼ │
│ docker: mysql8 docker: my-redis
│ 127.0.0.1:3307 127.0.0.1:6379
│ 数据卷 /opt/mysql8-data 配置/数据 /home/redis
└─────────────────────────────────────────┘
服务器需先装好:MySQL 8(utf8mb4)、Redis 7、Python 3.11+、Nginx(托管后台 + 反向代理)。
视频 ts 分片流量:App → 直连源站 CDN(不经过本服务器,见 02-技术文档)
```
## 2. 服务端部署(server/)
| 域名 | 用途 | nginx 要点 |
|---|---|---|
| `h5.256kb.top` | App 下载页 + 邀请落地页 + 静态媒体 | root=`/root/project/video/h5`;`/downloads/` `/videos/` `/uploads/` alias 到 `runtime/`;`/r/`、`/api/v1/invite/` 反代 8000 |
| `vm.256kb.top` | 管理后台 | root=`/root/project/video/admin/dist`(SPA 回退 index.html);`/api/` `/static/` `/r/` 反代 8000 |
| `vapi.256kb.top` | App 业务 API | 全量反代 127.0.0.1:8000 |
| `codeserver.256kb.top` | code-server(运维用,默认容器未自启) | 反代 code-server 容器 |
### 2.1 安装依赖
证书:Let's Encrypt ECC,由 acme.sh/宝塔申请到 `/www/server/panel/vhost/cert/<域名>/fullchain.pem`,crontab 有续期任务(`5 4,10,16,22 * * * acme.sh --cron` + 宝塔 acme_v2.py),80 端口仅用于 acme 挑战与跳转 HTTPS。
## 2. 目录规划
| 路径 | 用途 |
|---|---|
| `/root/project/video/server` | FastAPI 后端源码 + `.venv` 虚拟环境 + `.env` + `config.yaml` |
| `/root/project/video/admin` | Vue3 后台源码;构建产物 `admin/dist/` 即 vm 站点根目录 |
| `/root/project/video/app` | Flutter App 源码(编译机即本服务器) |
| `/root/project/video/h5` | H5 下载页(`index.html`) |
| `/root/project/video/runtime` | 运行期产出:`downloads/`(APK)、`videos/`(转码视频)、`uploads/`(上传文件) |
| `/root/project/video/docs` | 项目文档(本目录) |
| `/root/project/video/deploy.sh` | App 一键发版脚本(见 §7) |
| `/root/project/video/project-ctl.sh` | 项目一键启停脚本(见 §8) |
| `/opt/mysql8-data` | MySQL 8 数据卷(Docker 挂载) |
| `/home/redis/conf`、`/home/redis/data` | Redis 配置与数据(Docker 挂载) |
| `/root/backups/video-app-signing` | App 签名 keystore 备份(**极其重要**,见 §7.3) |
## 3. Docker 组件(MySQL / Redis)
两个容器均为 `--restart unless-stopped`:服务器重启自动拉起;**手动 `docker stop` 后重启不会恢复**(project-ctl.sh stop 利用此语义实现"暂停")。
### 3.1 MySQL 8(容器名 mysql8)
- 镜像 `mysql:8`(当前 8.4.11),端口映射 **宿主机 3307 → 容器 3306**
- 数据卷 `/opt/mysql8-data:/var/lib/mysql`,时区 Asia/Shanghai
- 业务库 `video`,账号 `root` / 密码 `256kb.cn`
- **连接必须加 `-h 127.0.0.1`**:不加时 mysql 客户端走 unix socket 会连到宿主机旧版 MySQL 5.7,曾导致 deploy.sh 把版本号写错库
```bash
cd server
python3.11 -m venv .venv
source .venv/bin/activate
pip install . # 核心依赖
pip install ".[pay]" # 可选:支付渠道 SDK(支付宝/微信/Tron),不接支付可跳过
# 日常连接
mysql -h 127.0.0.1 -uroot -p256kb.cn -P 3307 video
# 或容器内
docker exec -it mysql8 mysql -uroot -p256kb.cn video
# 如需重建容器(数据在 /opt/mysql8-data,不丢)
docker run -d --name mysql8 --restart unless-stopped \
-p 3307:3306 -v /opt/mysql8-data:/var/lib/mysql \
-e TZ=Asia/Shanghai -e MYSQL_ROOT_PASSWORD=256kb.cn mysql:8
```
### 2.2 准备数据库与 .env
### 3.2 Redis(容器名 my-redis)
```sql
CREATE DATABASE video CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 镜像 `redis:latest`(7.x),端口 **6379**,挂载 `/home/redis/conf` 与 `/home/redis/data`
- 启动命令 `redis-server /usr/local/etc/redis/redis.conf`
- **注意:宿主机曾装过 Redis 5.0.3 并已禁用自启**。若它抢占了 6379,新版 redis-py 客户端默认 RESP3 的 `HELLO` 握手会失败 → 后端全接口 500。排查时先确认 `docker ps` 里 my-redis 在跑、且 6379 被 docker-proxy 占用
- 后端 redis-py 客户端显式指定 `protocol=2`(RESP2),新旧服务端都兼容
- 清业务缓存一律容器内执行:`docker exec my-redis redis-cli DEL config:system`
```bash
# 如需重建容器
docker run -d --name my-redis --restart unless-stopped \
-p 6379:6379 -v /home/redis/conf:/usr/local/etc/redis -v /home/redis/data:/data \
redis:latest redis-server /usr/local/etc/redis/redis.conf
```
在 `server/.env` 配置环境项(不入库的连接串/密钥):
## 4. 后端服务(video-api.service)
### 4.1 systemd 单元(/etc/systemd/system/video-api.service)
```ini
APP_ENV=prod # prod 时 /docs 自动关闭
APP_HOST=0.0.0.0
APP_PORT=8000
DATABASE_URL=mysql+aiomysql://user:pass@127.0.0.1:3306/video?charset=utf8mb4
REDIS_URL=redis://127.0.0.1:6379/0
JWT_SECRET=换成足够长的随机串 # 生产必须更换
[Unit]
Description=Video App FastAPI Backend (uvicorn)
After=network.target docker.service redis.service
Requires=docker.service
# 以下按需配置(不配置则对应功能自动降级:短信/邮件发不出去、支付走 dev_mock、USDT 不启用)
# ALIPAY_APP_ID / ALIPAY_PRIVATE_KEY / ALIPAY_PUBLIC_KEY / ALIPAY_NOTIFY_URL
# WECHAT_MCHID / WECHAT_APP_ID / WECHAT_API_V3_KEY / WECHAT_PRIVATE_KEY / WECHAT_CERT_SERIAL_NO / WECHAT_PLATFORM_CERT / WECHAT_NOTIFY_URL
# TRON_API_KEY(USDT 链上查账)
# SMS_ACCESS_KEY_ID / SMS_ACCESS_KEY_SECRET / SMS_SIGN_NAME(短信验证码)
# SMTP_HOST / SMTP_PORT / SMTP_USER / SMTP_PASSWORD(邮箱验证码)
[Service]
Type=simple
User=root
WorkingDirectory=/root/project/video/server
ExecStart=/root/project/video/server/.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000 --workers 2
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target
```
业务参数(积分/会员/激励/提现规则)在 `server/config.yaml`,其中部分项上线后可在管理后台「系统配置」热修改。
- 只监听 **127.0.0.1:8000**,对外全靠 nginx 反代
- `--workers 2`:⚠️ 结算周期任务(推荐激活/创作收益/超时关单)随每个 worker 的 lifespan 各启动一份(`app/services/settle_service.py`)。当前任务均为条件更新、重复执行无副作用;**修改周期任务逻辑时必须保证幂等**
- 日志走 journald:`journalctl -u video-api -f`
### 2.3 初始化表结构 + 首个管理员
常用命令:
```bash
cd server
.venv/bin/alembic upgrade head # 建 27 张表
systemctl restart video-api # 改后端代码后重启
systemctl enable --now video-api # 启动并恢复开机自启
journalctl -u video-api -n 100 --no-pager
```
# 创建后台管理员(代码不自动播种,需手动执行一次)
### 4.2 环境配置(server/.env)
键清单(敏感值不入文档;`.env` 本身不入 git):
```
APP_ENV=prod APP_HOST APP_PORT
DATABASE_URL=mysql+aiomysql://root:256kb.cn@127.0.0.1:3307/video?charset=utf8mb4
REDIS_URL=redis://127.0.0.1:6379/0
CELERY_BROKER_URL CELERY_RESULT_BACKEND # 预留,当前未启用
JWT_SECRET ACCESS_TOKEN_TTL REFRESH_TOKEN_TTL ADMIN_TOKEN_TTL
OSS_ENDPOINT OSS_BUCKET OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET # 未启用则为占位
ALIPAY_APP_ID ALIPAY_PRIVATE_KEY ALIPAY_PUBLIC_KEY # 支付占位→dev_mock
WECHAT_MCHID WECHAT_APP_ID WECHAT_PRIVATE_KEY
TRON_API_KEY # USDT 查账
SMSBAO_USERNAME SMSBAO_API_KEY SMS_SIGN_NAME # 短信宝验证码
SMTP_HOST SMTP_PORT SMTP_USER SMTP_PASSWORD # 邮箱验证码(已启用)
```
业务参数在 `server/config.yaml`(顶层键:`points` / `membership` / `recharge` / `referral` / `asset` / `withdraw` / `upload`),其中白名单项可在后台「系统配置」热改。
### 4.3 数据库初始化(新环境一次性)
```bash
cd /root/project/video/server
python3.11 -m venv .venv && .venv/bin/pip install .
docker exec mysql8 mysql -uroot -p256kb.cn -e \
"CREATE DATABASE IF NOT EXISTS video CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
.venv/bin/alembic upgrade head # 4 个迁移,建 30 张业务表(含 series 补漏迁移 e7a1f3c58d02)
```
创建首个超管(代码不自动播种):
```bash
.venv/bin/python - <<'EOF'
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
@@ -77,127 +181,169 @@ asyncio.run(main())
EOF
```
### 2.4 启动
后台角色:`super`(全部权限)、`operator`(仪表盘/套餐/赠送)、`reviewer`(视频审核)。
```bash
cd server
.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
```
- 健康检查:`curl http://127.0.0.1:8000/health` → `{"status":"ok"}`
- **生产请单 worker 运行**(不要加 `--workers > 1`):结算周期任务(推荐激活/创作收益/超时关单)随进程内 lifespan 启动,多 worker 会重复执行。
- 生产建议用 systemd/supervisor 托管进程。
服务暴露的路径(Nginx 反代时用):
### 4.4 服务暴露路径(反代依据)
| 路径 | 说明 |
|---|---|
| `/api/v1/*` | App 全部业务接口 |
| `/api/v1/*` | App 业务接口(9 个路由文件 + admin,共 100+ 端点) |
| `/api/v1/admin/*` | 管理后台接口 |
| `/r/{code}` | 推广 H5 落地页(邀请链接) |
| `/static/*` | 本地文件存储(头像/封面/视频,未接 OSS 时) |
| `/r/{code}` | 邀请落地页 |
| `/static/*` | 本地媒体文件 |
| `/health` | 健康检查 |
## 3. 管理后台部署(admin/)
## 5. 管理后台发布(admin/)
```bash
cd admin
cd /root/project/video/admin
npm ci
npm run build # 产物在 admin/dist/
npm run build # 产物直接覆盖 admin/dist/,即 vm 站点根目录
```
Nginx 参考配置(同一站点托管后台 + 反代 API):
- 纯静态站点,**构建即发布,无需重启任何服务**;浏览器强刷(Ctrl+F5)生效
- 前端 API baseURL 为 `/api/v1`,由 vm 站点的 `/api/` 反代到后端
- 菜单按角色显示:仪表盘/套餐/赠送=operator,视频审核=reviewer,其余=super
## 6. nginx 配置(宝塔管理)
配置文件:`/www/server/panel/vhost/nginx/{h5,vm,vapi,codeserver}.256kb.top.conf`。
改配置在宝塔面板「网站 → 设置 → 配置文件」编辑,或改文件后 `nginx -t && nginx -s reload`。
关键规则摘要:
**h5.256kb.top**
```nginx
server {
listen 443 ssl;
server_name admin.example.com;
# ssl_certificate / ssl_certificate_key ...
root /path/to/video/admin/dist;
index index.html;
location / { try_files $uri $uri/ /index.html; } # SPA 回退
location /api/ { proxy_pass http://127.0.0.1:8000; } # 后台接口
location /static/ { proxy_pass http://127.0.0.1:8000; } # 媒体文件
location /r/ { proxy_pass http://127.0.0.1:8000; } # 推广落地页
}
root /root/project/video/h5; # index.html 下载页
location /downloads/ { alias /root/project/video/runtime/downloads/; } # APK
location /videos/ { alias /root/project/video/runtime/videos/; }
location /uploads/ { alias /root/project/video/runtime/uploads/; }
location /r/ { proxy_pass http://127.0.0.1:8000; } # 邀请落地
location /api/v1/invite/ { proxy_pass http://127.0.0.1:8000; }
```
浏览器访问 `https://admin.example.com`,用 2.3 创建的账号登录。
**vm.256kb.top**
## 4. App 编译(app/)—— 需要编译
```nginx
root /root/project/video/admin/dist;
location / { try_files $uri $uri/ /index.html; } # SPA 回退
location /api/ { proxy_pass http://127.0.0.1:8000; }
location /static/ { proxy_pass http://127.0.0.1:8000; }
location /r/ { proxy_pass http://127.0.0.1:8000; }
```
App 是 Flutter 工程,**必须编译出安装包**分发给用户,服务器上不运行 App。
**vapi.256kb.top**:`location / { proxy_pass http://127.0.0.1:8000; }` 全量反代。
### 4.1 关键:App 如何找到服务器(不硬编码域名)
站点日志在 `/www/wwwlogs/<域名>.log`(排查 App 接口 401/500 时非常管用)。
启动时按优先级确定 API Base URL([api_domain.dart](../app/lib/core/network/api_domain.dart)):
## 7. App 编译与发版
1. **`--dart-define=API_BASE` 编译期指定**(测试/自建最简单);
2. 本地缓存的上次成功域名;
3. DNS TXT 发现:解析 `txt.256kb.top` 的 TXT 记录,记录内容即 Base URL 主体;
4. 兜底域名:`md5(yyyyMM) + "_jsnbhh.vip"`(每月变化)。
### 7.1 域名发现(不硬编码 API 地址)
> 注意:客户端业务路径**不带** `/api/v1` 前缀,因此 Base URL 必须包含前缀,例如 `https://api.example.com/api/v1`(TXT 记录内容写 `api.example.com/api/v1` 即可,客户端自动补 `https://`)。
启动时优先级:`--dart-define=API_BASE` > 本地缓存 > DNS TXT(`txt.256kb.top`) > 兜底域名(md5(yyyyMM)+后缀)。
正式包**不带 dart-define 直接构建**,走 TXT 发现(当前指向 `vapi.256kb.top/api/v1`)。
上线二选一:
- **方式 A(推荐,免改代码)**:把 `txt.256kb.top` 的 TXT 记录指到你的 API 入口(内容:`api.example.com/api/v1`),并按第 3 节方式用 Nginx 把 `/api/v1/` 反代到 uvicorn。
- **方式 B(自己的域名体系)**:修改 [app_config.dart](../app/lib/core/config/app_config.dart) 的 `dnsTxtHost` / `fallbackDomainSuffix` 为你控制的域名后重新编译。
### 4.2 Android
### 7.2 一键发版 deploy.sh
```bash
cd app
flutter pub get
# 联调/内测:直连服务器
flutter build apk --release --dart-define=API_BASE=https://api.example.com/api/v1
# 正式(走 TXT 域名发现):不带 dart-define 直接构建
flutter build apk --release
# 1. 改 app/pubspec.yaml 的 version(如 1.3.7+23,+后为 versionCode)
# 2. 执行
bash /root/project/video/deploy.sh
```
产物:`build/app/outputs/flutter-apk/app-release.apk`,直接分发安装即可。
脚本自动完成:`flutter analyze`(warning 不阻断)→ `flutter build apk --release` → `gradlew --stop` 清理 Gradle Daemon(防内存泄漏)→ 覆盖 `runtime/downloads/video-app.apk` → 同步 DB `system_configs.app.version.android`(latest_version/code 更新、**min_version_code 保留**、连接指定 `-h 127.0.0.1`)→ `docker exec my-redis` 清配置缓存。
老用户打开 App 触发 `/api/v1/app/version` 检查 → 弹更新提示 → 跳 h5 下载页。强制更新:把 `min_version_code` 调大于用户版本。
### 4.3 iOS
### 7.3 正式签名(2026-10-17 起,v1.3.7+23 为首个正式签名版)
```bash
flutter build ipa --release
```
需要 macOS + Xcode + Apple 开发者证书签名(本机未装 Xcode 时可只交付 Android)。iOS 端上架前在管理后台「系统配置」确认 `ios_payment.enabled` 开关(规避苹果虚拟支付审核)。
### 4.4 支付渠道打包注意
`app/pubspec.yaml` 中 `tobias.url_scheme`、`fluwx.app_id`、两处 `universal_link` 是占位值,**正式支付上线前必须替换为商户申请的真实值**并重新编译;未接真实支付时服务端返回 `dev_mock`,App 自动走轮询查单兜底。
## 5. 上线后必改的后台配置
登录管理后台「系统配置」:
| 配置 key | 说明 |
| 项 | 值 |
|---|---|
| `app.invite_base_url` | 推广落地页地址,改为 `https://你的域名/r/`(影响 App 邀请链接) |
| `app.version.android` / `app.version.ios` | JSON:`latest_version` / `latest_version_code` / `min_version_code` / `update_url` / `notice`,控制版本检查与强制更新 |
| `ios_payment.enabled` | iOS 支付开关(审核期可关闭) |
| 敏感词、轮播公告等 | 按需 |
| keystore | `app/android/app/upload-keystore.jks`(PKCS12,RSA 2048,有效期 30 年) |
| 密码配置 | `app/android/key.properties`(alias=`video_release`) |
| 证书指纹 SHA-256 | `8fb75f6a:3a9f:bc39:...:aef4`(完整值见 `apksigner verify --print-certs`) |
| git | 两者均已 gitignore,**不入库** |
| 备份 | `/root/backups/video-app-signing/`(keystore + key.properties) |
## 6. 端到端验证清单
`build.gradle.kts` 逻辑:key.properties 存在 → release 签名;否则回退 debug(保证新环境可编译)。
> ⚠️ **keystore 丢失 = 永远无法给老用户推更新**。除服务器备份外,务必另存一份到本地电脑/网盘。
> ⚠️ 首个正式签名版与旧 debug 签名不一致,**老用户必须先卸载再安装**(版本 notice 已注明)。
> 侧载安装时小米等厂商会提示"未备案",勾选"已了解风险"即可继续安装;消除该提示需办理 App ICP 备案。
### 7.4 iOS
`flutter build ipa --release`(需 macOS + Xcode + 开发者证书,当前仅交付 Android)。上架前确认后台 `ios_payment.enabled` 开关规避虚拟支付审核。
## 8. 项目启停 project-ctl.sh
```bash
# 服务端
curl http://127.0.0.1:8000/health # ok
curl "http://127.0.0.1:8000/api/v1/videos/feed?type=hot&limit=1" # code:0
curl "http://127.0.0.1:8000/api/v1/search/hot" # code:0
# 管理后台:浏览器打开 → 登录 → 视频审核/用户列表有数据
# App:安装 APK → 启动页过版本检查 → Feed 出视频 → 注册/登录 → 观看计费
bash /root/project/video/project-ctl.sh start # docker mysql8/my-redis → 等待就绪 → video-api(enable+start) → 健康检查
bash /root/project/video/project-ctl.sh stop # video-api 停止并 disable + 停两个容器(重启服务器也不会拉起)
bash /root/project/video/project-ctl.sh restart # 停 → 启
bash /root/project/video/project-ctl.sh status # 服务/容器/接口探测
```
## 7. 已知限制(当前版本)
- 管理范围:`video-api.service` + `mysql8` + `my-redis`;**不含 nginx**(系统组件)
- 服务器重启后如项目处于"运行态",docker 与 systemd 会自动拉起全部组件,无需人工干预
- 项目当前(2026-10-18 起)处于**暂停态**,恢复运行执行 `start` 即可
- **媒体存储为本地文件**(`server/uploads`,经 `/static` 访问),接口返回相对路径;生产接入 OSS/CDN 使服务端返回绝对 URL 需二次开发(`storage_service` 已按此边界隔离)。
- 短信/邮件/USDT/支付宝/微信未配置密钥时自动降级(验证码发不出、支付走 dev_mock),不影响其余功能。
- CORS 当前为 `*`,生产可在 `app/main.py` 按需收紧。
- 结算周期任务在 API 进程内运行,见 2.4 单 worker 说明。
## 9. 备份策略
| 对象 | 方式 | 频率建议 |
|---|---|---|
| MySQL 数据 | `docker exec mysql8 mysqldump -uroot -p256kb.cn video \| gzip > backup-$(date +%F).sql.gz`(或宝塔计划任务) | 每日 |
| keystore + key.properties | 已备份 `/root/backups/video-app-signing/`,另离线保存 | 变更即备 |
| `runtime/`(视频/APK/上传) | 文件级备份(rsync/对象存储) | 每周 |
| `server/.env`、`config.yaml` | 纳入文件备份 | 变更即备 |
恢复:导入 sql → `alembic upgrade head` 校验版本 → 启动服务。
## 10. 日常运维与故障排查
### 10.1 常用命令
```bash
journalctl -u video-api -f # 后端日志
docker logs -f mysql8 / my-redis # 容器日志
tail -f /www/wwwlogs/vapi.256kb.top.log # App 接口访问日志
docker exec my-redis redis-cli DEL config:system # 清系统配置缓存
docker exec my-redis redis-cli KEYS 'auth:*' # 查登录态键
free -h && docker stats --no-stream # 资源占用
```
### 10.2 历史故障速查表
| 症状 | 根因 | 处置 |
|---|---|---|
| 后端全接口 500,Redis 报错 | 服务器重启后 my-redis 未自启,宿主机 Redis 5.0.3 抢 6379,RESP3 握手失败 | 停本机 redis 自启;docker start my-redis;客户端已固定 protocol=2 |
| 重启后服务缺失/端口错乱 | Docker 容器未配 restart 策略 | 两容器均已 `unless-stopped` |
| App 更新提示反复弹 | 硬编码版本号与 APK 不一致 | 已改用 package_info_plus 读真实版本号 |
| 发版后 DB 版本没更新 | deploy.sh mysql 未加 `-h 127.0.0.1` 写错宿主机库 | 已修复;注意检查 |
| 编译后内存爆满 | Gradle Daemon 常驻 | deploy.sh 已自动 `gradlew --stop` |
| IDE 里 OpenJDK 吃内存 | Trae 的 fwcd.kotlin 扩展语言服务器 | 禁用该扩展并重载窗口 |
| 编译卡死无输出 | Gradle 进程僵死 | 杀掉重跑即可 |
| 会员/钱包页加载失败 | 僵尸登录态(access 过期+无 refresh) | App 端已修复(401 清态+启动检测);临时:退出重登 |
| 切"我的"仍听到视频声 | 播放器池 open 竞态 | 已修复(suspend/resume) |
| 进度条不走/拖动无效 | 本地代理未支持 Range | 已修复(Accept-Ranges + 206) |
### 10.3 端到端验证清单(发版/重启后)
```bash
curl http://127.0.0.1:8000/health # {"status":"ok"}
curl "http://127.0.0.1:8000/api/v1/videos/feed?type=hot&limit=1" # code:0
curl -I https://h5.256kb.top/downloads/video-app.apk # 200
# 浏览器开 https://vm.256kb.top → 登录 → 仪表盘有数据
# App:启动过版本检查 → Feed 出视频 → 登录 → 播放/计费正常
```
## 11. 新环境从零部署速查
1. 装 Docker / Python3.11 / Node18+ / Flutter / nginx(或宝塔)
2. 建 mysql8、my-redis 容器(§3 命令)
3. `server/` 建 venv、装依赖、写 `.env`、`alembic upgrade head`、建超管(§4.3)
4. 写 systemd unit(§4.1)→ `systemctl enable --now video-api`
5. `admin/` 构建 dist;配三个 nginx 站点 + 证书(§6)
6. App:放入 keystore + key.properties → `deploy.sh` 发版(§7)
7. 后台「系统配置」:`app.invite_base_url`、`app.version.android/ios` 等(见 03 接口文档配置键)
8. 跑 §10.3 验证清单