# 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 说明。