Files
video/docs/04-部署运行文档.md
T
2026-10-15 20:36:59 +08:00

204 lines
8.0 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.
# 04-部署运行文档
> 本文说明如何在服务器上把项目跑起来。项目共三端:**服务端**(FastAPI,无需编译)、**管理后台**(Vue3,构建为静态文件)、**App**(Flutter,**必须编译**出 APK/IPA)。
## 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` |
服务器需先装好:MySQL 8(utf8mb4)、Redis 7、Python 3.11+、Nginx(托管后台 + 反向代理)。
## 2. 服务端部署(server/)
### 2.1 安装依赖
```bash
cd server
python3.11 -m venv .venv
source .venv/bin/activate
pip install . # 核心依赖
pip install ".[pay]" # 可选:支付渠道 SDK(支付宝/微信/Tron),不接支付可跳过
```
### 2.2 准备数据库与 .env
```sql
CREATE DATABASE video CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```
在 `server/.env` 配置环境项(不入库的连接串/密钥):
```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=换成足够长的随机串 # 生产必须更换
# 以下按需配置(不配置则对应功能自动降级:短信/邮件发不出去、支付走 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(邮箱验证码)
```
业务参数(积分/会员/激励/提现规则)在 `server/config.yaml`,其中部分项上线后可在管理后台「系统配置」热修改。
### 2.3 初始化表结构 + 首个管理员
```bash
cd server
.venv/bin/alembic upgrade head # 建 27 张表
# 创建后台管理员(代码不自动播种,需手动执行一次)
.venv/bin/python - <<'EOF'
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from app.config import settings
from app.core.security import hash_password
from app.models.admin import AdminUser
async def main():
engine = create_async_engine(settings.database_url)
async with async_sessionmaker(engine)() as s:
s.add(AdminUser(username="admin",
password_hash=hash_password("请设置强密码"),
name="超管", role="super"))
await s.commit()
await engine.dispose()
asyncio.run(main())
EOF
```
### 2.4 启动
```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 反代时用):
| 路径 | 说明 |
|---|---|
| `/api/v1/*` | App 全部业务接口 |
| `/api/v1/admin/*` | 管理后台接口 |
| `/r/{code}` | 推广 H5 落地页(邀请链接) |
| `/static/*` | 本地文件存储(头像/封面/视频,未接 OSS 时) |
| `/health` | 健康检查 |
## 3. 管理后台部署(admin/)
```bash
cd admin
npm ci
npm run build # 产物在 admin/dist/
```
Nginx 参考配置(同一站点托管后台 + 反代 API):
```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; } # 推广落地页
}
```
浏览器访问 `https://admin.example.com`,用 2.3 创建的账号登录。
## 4. App 编译(app/)—— 需要编译
App 是 Flutter 工程,**必须编译出安装包**分发给用户,服务器上不运行 App。
### 4.1 关键:App 如何找到服务器(不硬编码域名)
启动时按优先级确定 API Base URL([api_domain.dart](../app/lib/core/network/api_domain.dart)):
1. **`--dart-define=API_BASE` 编译期指定**(测试/自建最简单);
2. 本地缓存的上次成功域名;
3. DNS TXT 发现:解析 `txt.256kb.top` 的 TXT 记录,记录内容即 Base URL 主体;
4. 兜底域名:`md5(yyyyMM) + "_jsnbhh.vip"`(每月变化)。
> 注意:客户端业务路径**不带** `/api/v1` 前缀,因此 Base URL 必须包含前缀,例如 `https://api.example.com/api/v1`(TXT 记录内容写 `api.example.com/api/v1` 即可,客户端自动补 `https://`)。
上线二选一:
- **方式 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
```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
```
产物:`build/app/outputs/flutter-apk/app-release.apk`,直接分发安装即可。
### 4.3 iOS
```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 支付开关(审核期可关闭) |
| 敏感词、轮播公告等 | 按需 |
## 6. 端到端验证清单
```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 出视频 → 注册/登录 → 观看计费
```
## 7. 已知限制(当前版本)
- **媒体存储为本地文件**(`server/uploads`,经 `/static` 访问),接口返回相对路径;生产接入 OSS/CDN 使服务端返回绝对 URL 需二次开发(`storage_service` 已按此边界隔离)。
- 短信/邮件/USDT/支付宝/微信未配置密钥时自动降级(验证码发不出、支付走 dev_mock),不影响其余功能。
- CORS 当前为 `*`,生产可在 `app/main.py` 按需收紧。
- 结算周期任务在 API 进程内运行,见 2.4 单 worker 说明。