This commit is contained in:
macbook_max
2026-10-15 20:36:59 +08:00
commit 6bf949949c
7923 changed files with 1369582 additions and 0 deletions
+203
View File
@@ -0,0 +1,203 @@
# 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 说明。