# VideoGen 部署文档 ## 项目结构 ``` video_item/ ├── video-gen-api/ # 后端 (Python FastAPI + SQLAlchemy + Alembic) ├── video-gen-app/ # 前台/用户端 (React 19 + Vite 8 + Ant Design 6) ├── video-gen-admin/ # 后台管理端 (React 19 + Vite 8 + Ant Design 6) └── DEPLOYMENT.md # 本文档 ``` **三个前端/后端的关系:** `video-gen-app` (用户前台) 和 `video-gen-admin` (管理后台) 都连接同一个 `video-gen-api` 后端。 --- ## 一、环境要求 | 组件 | 版本要求 | 说明 | |------|---------|------| | Python | >= 3.10 | 推荐 3.12 | | Node.js | >= 18 | 推荐 20+ | | PostgreSQL | >= 14 | 推荐 16;**必须** | | Redis | >= 6 | 推荐用于: 限流/验证码/Celery/任务状态 | | FFmpeg | 任意 | 用于视频封面截帧,留空时从 PATH 自动查找 | | ca-certificates | 任意 | **必须**,HTTPS 请求需要(新服务器/容器常缺) | | alipay-sdk-python | >=3.7.1160 | 可选,支付宝支付 | | wechatpayv3 | >=2.0.2 | 可选,微信支付 | | volcengine-python-sdk | >=1.1.0 | 可选,视频生成/短信 | --- ## 二、后端部署 (video-gen-api) ### 1. 安装依赖 ```bash # ⚠️ 新服务器/容器必须先装 CA 证书,否则 HTTPS(支付宝/火山等)全部失败 # CentOS/RHEL sudo yum install -y ca-certificates # Ubuntu/Debian sudo apt-get install -y ca-certificates cd video-gen-api # 创建虚拟环境 python -m venv .venv # Windows .venv\Scripts\activate # Linux/Mac source .venv/bin/activate # 安装基础依赖 pip install -e . # 安装 PostgreSQL 驱动(生产环境必须) pip install -e ".[pg]" # 如需 Redis 支持(限流、验证码、Celery) pip install -e ".[pg,redis]" # 如需 Celery 异步任务(ChatAPI 生成流水线) pip install -e ".[pg,redis,celery]" # 如需支付宝 pip install -e ".[pg,redis,celery,alipay]" # 如需微信支付 pip install -e ".[pg,redis,celery,alipay,wxpay]" # 如需火山引擎 SDK(短信等) pip install -e ".[pg,redis,celery,alipay,wxpay,volc]" ``` ### 2. 配置环境变量 复制 `.env.example` 为 `.env`,修改关键配置: ```bash cp .env.example .env ``` ```ini # ── 基础配置 ── APP_NAME=VideoGen API APP_VERSION=1.0.0 DEBUG=false SECRET_KEY=改成一个随机的长字符串(JWT 签名密钥) # ── 数据库(必须) ── DATABASE_URL=postgresql+asyncpg://用户名:密码@localhost:5432/videogen # ── Redis(可选,留空则禁用限流/验证码/Celery) ── REDIS_URL=redis://localhost:6379/0 # ── JWT ── JWT_ALGORITHM=HS256 JWT_EXPIRE_MINUTES=1440 # 普通登录 24h JWT_EXPIRE_REMEMBER_MINUTES=10080 # 记住登录 7天 # ── 视频生成(火山引擎 Ark) ── SEEDANCE_API_KEY=你的API Key SEEDANCE_API_BASE=https://ark.cn-beijing.volces.com/api/v3 SEEDANCE_CALLBACK_URL=https://你的域名/api/generation-records/callback # ── LLM 提示词优化 ── LLM_API_BASE=https://api.openai.com/v1 LLM_API_KEY= LLM_MODEL=gpt-4o LLM_MOCK=true # true=使用 mock 响应,不调用真实 LLM # ── 前后端通信加密(32字节 base64,留空则禁用) ── ENCRYPTION_KEY=你的32字节base64密钥 # ── 存储路径(本地存储) ── STORAGE_TYPE=local STORAGE_LOCAL_PATH=./storage/generate/videos STORAGE_IMAGE_LOCAL_PATH=./storage/generate/images STORAGE_VIDEO_COVER_LOCAL_PATH=./storage/generate/covers UPLOAD_LOCAL_PATH=./storage/uploads # ── 跨域(生产环境务必限制域名,默认 ["*"] 允许所有) ── CORS_ORIGINS=["https://你的前台域名.com", "https://你的后台域名.com"] # ── 回调基础地址 ── BASE_URL=https://你的域名 # ── 验证码 ── CAPTCHA_ENABLED=true # ── 短信(火山引擎 SDK,SMS_MOCK=false 时生效) ── SMS_MOCK=true VOLC_SMS_ACCESS_KEY_ID= VOLC_SMS_SECRET_ACCESS_KEY= VOLC_SMS_ACCOUNT=消息组ID VOLC_SMS_TEMPLATE_ID=模板ID VOLC_SMS_SIGN=短信签名 # ── 支付(PAYMENT_MOCK=false 时生效) ── PAYMENT_MOCK=true WECHAT_MCH_ID= WECHAT_API_KEY= ALIPAY_APP_ID= ALIPAY_PRIVATE_KEY= ALIPAY_PUBLIC_KEY= ALIPAY_NOTIFY_URL= # ── Celery(可选,留空则禁用 ChatAPI 异步流水线) ── CELERY_BROKER_URL=redis://localhost:6379/5 CELERY_RESULT_BACKEND=redis://localhost:6379/6 # ── FFmpeg(留空自动从 PATH 查找) ── FFMPEG_BIN=/usr/bin/ffmpeg ``` > **关于 ENCRYPTION_KEY 的生成:** 需要 32 字节(256 位)的 base64 编码字符串。生成方式:`openssl rand -base64 32`。前后端必须使用**完全相同**的密钥。 ### 3. 初始化数据库 ```bash # 创建 PostgreSQL 数据库 psql -U postgres -c "CREATE DATABASE videogen OWNER videogen;" # 启动后端(首次启动自动建表) python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 ``` 首次启动时会**自动创建所有数据表**(41 个 model)。 > ⚠️ **关于种子数据:** 代码中包含 `_seed_data()` 函数(创建管理员/演示用户、系统配置、引擎配置等),但当前在 `main.py` 中被**注释掉了**(`# await _seed_data()`)。因此首次启动**不会**自动创建管理员账号。 > > **如果你需要种子数据,**有以下选择: > 1. 在 `main.py` 中取消注释 `# await _seed_data()` 后重启 > 2. 手动通过 API 或数据库脚本创建管理员账号 > 3. 自行编写独立的种子脚本调用 `_seed_data()` ### 4. 数据库迁移 (Alembic) 项目使用 Alembic 管理数据库结构变更。 ```bash cd video-gen-api # 修改 model 后,自动生成迁移文件 python -m alembic revision --autogenerate -m "描述改动内容" # 执行迁移 python -m alembic upgrade head # 查看当前版本 python -m alembic current # 查看迁移历史 python -m alembic history # 回滚一步 python -m alembic downgrade -1 ``` **部署流程:** 拉取代码后先执行 `alembic upgrade head`,再重启后端服务。 ### 5. 生产运行 ```bash # 直接运行(推荐 4 workers) python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 ``` **systemd 服务文件** `/etc/systemd/system/videogen-api.service`: ```ini [Unit] Description=VideoGen API After=network.target postgresql.service [Service] Type=simple User=www-data WorkingDirectory=/opt/video-gen-api Environment=PATH=/opt/video-gen-api/.venv/bin ExecStart=/opt/video-gen-api/.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` ```bash sudo systemctl daemon-reload sudo systemctl enable videogen-api sudo systemctl start videogen-api ``` ### 6. Celery Worker(可选) ChatAPI 异步生成流水线需要 Celery Worker,依赖 Redis 作为 Broker。 Celery 使用 **6 个队列**,按功能分离: | 队列 | 用途 | |------|------| | `gen_chatapi_create` | ChatAPI 生成任务创建(含爆款开头/拆镜复刻的提词步骤) | | `gen_provider_poll` | 轮询火山引擎生成状态 | | `gen_result_download` | 下载生成的视频/图片结果 | | `gen_recovery` | 容灾恢复任务(统一队列,避免占用业务 worker) | | `gen_private_portrait` | 真人素材认证与同步 | | `default` | 默认队列(用户 OAuth、清理任务等) | ```bash # 启动 Worker(消费所有队列) celery -A app.tasks.celery_app worker -l info -Q gen_chatapi_create,gen_provider_poll,gen_result_download,gen_recovery,gen_private_portrait,default ``` **systemd 服务文件** `/etc/systemd/system/videogen-worker.service`: ```ini [Unit] Description=VideoGen Celery Worker After=network.target redis.service [Service] Type=simple User=www-data WorkingDirectory=/opt/video-gen-api Environment=PATH=/opt/video-gen-api/.venv/bin ExecStart=/opt/video-gen-api/.venv/bin/celery -A app.tasks.celery_app worker -l info -Q gen_chatapi_create,gen_provider_poll,gen_result_download,gen_recovery,gen_private_portrait,default Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` ### 7. Docker 部署(可选) 项目提供 `Dockerfile` 和 `docker-compose.yml`。 > ⚠️ **注意:** 默认 `Dockerfile` 只安装基础依赖(`pip install .`),生产使用需改为 `".[pg,redis,celery]"`。 ```bash cd video-gen-api # 使用前需修改 Dockerfile 第 6 行为: # RUN pip install --no-cache-dir ".[pg,redis,celery]" docker compose up -d ``` 启动的服务: | 服务 | 端口 | 说明 | |------|------|------| | api | 8000 | FastAPI 应用(`--reload` 开发模式) | | worker | - | Celery Worker(消费所有队列) | | postgres | 5432 | PostgreSQL 16 | | redis | 6379 | Redis 7 | ### 8. Nginx 反向代理 ```nginx server { listen 80; server_name api.yourdomain.com; # 上传文件大小限制(视频上传需要较大值) client_max_body_size 100M; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; } # 静态文件 (上传的视频/图片) location /uploads/ { alias /opt/video-gen-api/storage/uploads/; expires 7d; } } ``` --- ## 三、前台部署 (video-gen-app) 用户前台,面向最终用户。 ### 1. 安装依赖 & 构建 ```bash cd video-gen-app npm install # 配置 API 地址(创建 .env.production) echo "VITE_API_BASE=https://api.yourdomain.com" > .env.production # 如需前后端加密通信(与后端 ENCRYPTION_KEY 相同) echo "VITE_ENCRYPTION_KEY=密钥" >> .env.production # 构建 npm run build ``` 构建产物在 `dist/` 目录。 ### 2. 前端环境变量 | 变量 | 说明 | 默认值 | |------|------|--------| | `VITE_API_BASE` | 后端 API 地址 | `http://localhost:8000` | | `VITE_USE_MOCK` | 是否使用 mock 数据(无需后端) | `false` | | `VITE_ENCRYPTION_KEY` | 前后端通信加密密钥(需与后端一致) | 空(不加密) | ### 3. Nginx 配置 ```nginx server { listen 80; server_name yourdomain.com; root /opt/video-gen-app/dist; index index.html; # SPA 路由:所有页面请求回退到 index.html location / { try_files $uri $uri/ /index.html; } # 静态资源缓存(带 hash 的文件名可长期缓存) location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` --- ## 四、后台管理部署 (video-gen-admin) 管理员后台,面向运营/管理人员。 ### 1. 安装依赖 & 构建 ```bash cd video-gen-admin npm install # 配置 API 地址 echo "VITE_API_BASE=https://api.yourdomain.com" > .env.production # 如需前后端加密通信 echo "VITE_ENCRYPTION_KEY=密钥" >> .env.production # 构建 npm run build ``` ### 2. 后台环境变量 | 变量 | 说明 | 默认值 | |------|------|--------| | `VITE_API_BASE` | 后端 API 地址 | `http://localhost:8000` | | `VITE_USE_MOCK` | 是否使用 mock 数据 | `false` | | `VITE_ENCRYPTION_KEY` | 前后端通信加密密钥 | 空(不加密) | ### 3. Nginx 配置 ```nginx server { listen 80; server_name admin.yourdomain.com; root /opt/video-gen-admin/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } } ``` --- ## 五、完整 Nginx 配置示例 (单机部署) ```nginx # 后端 API server { listen 80; server_name api.yourdomain.com; client_max_body_size 100M; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 300s; } location /uploads/ { alias /opt/video-gen-api/storage/uploads/; expires 7d; } } # 前台 server { listen 80; server_name yourdomain.com; root /opt/video-gen-app/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets/ { expires 1y; } } # 后台管理 server { listen 80; server_name admin.yourdomain.com; root /opt/video-gen-admin/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets/ { expires 1y; } } ``` --- ## 六、SSL 配置 (推荐) 使用 Certbot 获取免费证书: ```bash sudo apt install certbot python3-certbot-nginx sudo certbot --nginx -d yourdomain.com -d admin.yourdomain.com -d api.yourdomain.com ``` --- ## 七、默认账号 > ⚠️ 默认账号仅在种子数据被执行后存在(见第三节第 3 点说明)。 | 角色 | 用户名 | 手机号 | 密码 | 积分 | |------|--------|--------|------|------| | 管理员 | admin | 13800000000 | 123456 | 10000 | | 演示用户 | demo | 13888888888 | 123456 | 2680 | **首次部署后务必修改默认密码。** --- ## 八、架构说明 ### 后端启动时的后台任务 后端启动时(`lifespan`)会自动启动以下**进程内**后台任务: | 任务 | 说明 | |------|------| | `task_queue` (video_queue) | 内置 asyncio 任务队列,轮询视频/图片生成状态 | | `upload_queue` | 上传任务队列 | | `material_consumption_queue` | 素材消耗队列 | | `token_refresh_scheduler` | 每 5 分钟检查并刷新即将过期的 token | | `poll_pre_test_results` | 每分钟轮询前测结果 | | `schedule_daily_sync` | 每天 9 点自动同步素材消耗 | | `_order_expiry_loop` | 每分钟同步待支付订单状态 + 自动过期订单 | 加上 **Celery Worker**(可选,处理 ChatAPI 异步流水线)。 ### 中间件栈(从外到内,即请求到达的顺序) 1. `RequestLoggingMiddleware` — 请求/响应日志记录 2. `AntiCrawlerMiddleware` — 反爬虫(拦截空 UA 和常见 bot) 3. `RateLimitMiddleware` — 滑动窗口限流(Redis 支撑) 4. `RequestEncryptMiddleware` — AES-256-GCM 请求/响应加密 5. `CORSMiddleware` — 跨域(expose `X-Encrypted` 响应头) ### 前后端通信加密 **机制:** AES-256-GCM 对称加密,前后端共享同一个 `ENCRYPTION_KEY`。使用 Web Crypto API(前端)和 `cryptography` 库(后端)。 **生效条件:** 同时满足以下两个条件才启用加密: - 后端 `.env` 中 `ENCRYPTION_KEY` 非空 - 前端 `.env` 中 `VITE_ENCRYPTION_KEY` 非空 - 运行在安全上下文(HTTPS 或 localhost) **加密范围:** | 请求类型 | 请求体 | 响应体 | |---------|--------|--------| | GET(无 body) | 不加密(无内容) | **加密** | | POST/PUT/DELETE(有 body) | **加密** | **加密** | **工作流:** 1. 前端发送 POST 请求时,将 JSON 请求体加密为 `{ data: "<密文>" }`,并加 `X-Encrypted: true` 请求头 2. 后端中间件检测到 `X-Encrypted: true` 时解密请求体,处理完后加密响应体 3. 支付回调接口(`/payments/alipay/callback`, `/payments/wechat/callback`)白名单跳过加密 > **GET 请求注意:** GET 没有请求体,但响应仍会被加密。前端会自动检测并解密。 ### 文件日志加密(独立机制) 与通信加密不同,**文件日志存储**使用另一套独立的 AES-CBC-256 加密: | 日志类型 | 目录 | 加密方式 | |---------|------|---------| | 请求/响应日志 | `log/RequestResponse/{日期}.log` | AES-CBC-256,密钥硬编码 | | AI 模型日志 | `log/AiModel/{日期}.log` | AES-CBC-256,密钥硬编码 | 日志加密密钥与 `ENCRYPTION_KEY` 无关,是代码中硬编码的值。解密工具:`/internal/decrypt-data` 页面。 ### 内部管理端点 | 端点 | 用途 | |------|------| | `/internal/` | 后端入口导航页 | | `/internal/health` | 健康检查 (`{"status": "ok"}`) | | `/internal/status` | 服务状态监控页 (Celery/Redis 连接状态) | | `/internal/decrypt-data` | 日志数据解密工具(AES-CBC) | | `/internal/api-docs` | Swagger API 文档 | | `/internal/api-redoc` | ReDoc API 文档 | | `/uploads/` | 用户上传的静态文件(挂载为静态目录) | | `/api/decrypt` | 解密接口(POST,供解密工具调用) | ### 前端 Mock 模式 前端支持 Mock 数据模式,无需后端即可开发: - 设置 `VITE_USE_MOCK=true` 时,所有 API 调用返回本地假数据 - 适用场景:纯前端开发、演示、无后端环境 - 注意:mock 模式下仍会调用部分真实 API(如站点信息、验证码) ### 存储路径 | 路径 | 用途 | |------|------| | `storage/generate/videos/` | 生成的视频文件 | | `storage/generate/images/` | 生成的图片文件 | | `storage/generate/covers/` | 视频封面缩略图 | | `storage/uploads/` | 用户上传的原始文件 | ### 外部服务依赖 | 服务 | 用途 | 是否必须 | |------|------|---------| | 火山引擎 Ark | 视频生成(Seedance 2.0)、图片生成(Seedream 5.0)、LLM | 是 | | 火山引擎短信 | 短信验证码 | 否(可 mock) | | 微信支付 / 支付宝 | 充值支付 | 否(可 mock,未完整实现) | | FFmpeg | 视频封面截帧 | 否(留空自动查找) | --- ## 九、目录结构 (部署后) ``` /opt/ ├── video-gen-api/ # 后端 │ ├── .env # 环境变量 │ ├── .venv/ # Python 虚拟环境 │ ├── app/ # 应用代码 │ │ ├── api/ # API 路由 │ │ │ ├── v1/ # v1 版本接口(前端用户端 + 部分管理接口) │ │ │ └── admin/ # 管理端接口 │ │ ├── middleware/ # 中间件 │ │ ├── models/ # 数据模型 (41 个) │ │ ├── services/ # 业务服务 │ │ ├── tasks/ # Celery 任务 │ │ ├── enums/ # 枚举定义 │ │ └── utils/ # 工具函数 │ ├── alembic/ # 数据库迁移文件 │ ├── storage/ │ │ ├── generate/ │ │ │ ├── videos/ # 生成的视频 │ │ │ ├── images/ # 生成的图片 │ │ │ └── covers/ # 视频封面 │ │ └── uploads/ # 用户上传 │ └── log/ │ ├── AiModel/ # AI 模型请求日志(加密) │ └── RequestResponse/ # 请求响应日志(加密) ├── video-gen-app/dist/ # 前台构建产物 └── video-gen-admin/dist/ # 后台构建产物 ``` --- ## 十、常用运维命令 ```bash # 查看后端日志 sudo journalctl -u videogen-api -f # 重启后端 sudo systemctl restart videogen-api # 重启 Celery Worker sudo systemctl restart videogen-worker # 重新构建前端 cd /opt/video-gen-app && npm run build cd /opt/video-gen-admin && npm run build # 数据库备份 pg_dump -U videogen videogen > backup_$(date +%Y%m%d).sql # 数据库恢复 psql -U videogen videogen < backup_20260512.sql # 执行数据库迁移 cd /opt/video-gen-api && python -m alembic upgrade head # 生成迁移文件 cd /opt/video-gen-api && python -m alembic revision --autogenerate -m "描述" # 查看 Celery 活动任务 celery -A app.tasks.celery_app inspect active ``` --- ## 十一、环境变量完整参考 ### 后端 (video-gen-api/.env) | 变量 | 必填 | 默认值 | 说明 | |------|------|--------|------| | `SECRET_KEY` | **是** | `change-me` | JWT 签名密钥 | | `DATABASE_URL` | **是** | sqlite | PostgreSQL 连接串 | | `REDIS_URL` | 否 | 空 | Redis 连接串,留空禁用限流/验证码 | | `JWT_EXPIRE_MINUTES` | 否 | 1440 | Token 有效期(分钟) | | `JWT_EXPIRE_REMEMBER_MINUTES` | 否 | 10080 | 记住登录 Token 有效期 | | `SEEDANCE_API_KEY` | **是** | 空 | 火山引擎 API Key | | `SEEDANCE_API_BASE` | 否 | 火山地址 | API 基础 URL | | `SEEDANCE_CALLBACK_URL` | 否 | 空 | 生成结果回调 URL | | `LLM_API_BASE` | 否 | OpenAI | LLM API 地址 | | `LLM_API_KEY` | 否 | 空 | LLM API Key | | `LLM_MODEL` | 否 | gpt-4o | LLM 模型名称 | | `LLM_MOCK` | 否 | true | 是否 mock LLM 响应 | | `ENCRYPTION_KEY` | 否 | 占位符 | 前后端通信加密密钥 | | `SMS_MOCK` | 否 | true | 是否 mock 短信 | | `PAYMENT_MOCK` | 否 | false | 是否 mock 支付 | | `CORS_ORIGINS` | 否 | `["*"]` | 允许的跨域来源 | | `BASE_URL` | 否 | 测试地址 | 站点基础 URL,用于回调拼接 | | `STORAGE_TYPE` | 否 | local | 存储类型 | | `UPLOAD_LOCAL_PATH` | 否 | `./storage/uploads` | 上传文件存储路径 | | `FFMPEG_BIN` | 否 | 空 | FFmpeg 路径(留空自动查找) | | `CAPTCHA_ENABLED` | 否 | true | 是否启用验证码 | | `CELERY_BROKER_URL` | 否 | 空 | Celery Broker(留空禁用 Celery) | | `CELERY_RESULT_BACKEND` | 否 | 空 | Celery 结果后端 | | `RATE_LIMIT_ENABLED` | 否 | true | 是否启用限流 | ### 前台 (video-gen-app/.env.production) | 变量 | 必填 | 默认值 | 说明 | |------|------|--------|------| | `VITE_API_BASE` | **是** | localhost:8000 | 后端 API 地址 | | `VITE_USE_MOCK` | 否 | false | 是否使用 mock 数据 | | `VITE_ENCRYPTION_KEY` | 否 | 空 | 通信加密密钥(与后端一致) | ### 后台管理 (video-gen-admin/.env.production) 同前台。 --- ## 十二、故障排查 | 现象 | 可能原因 | 解决方案 | |------|---------|---------| | 前端登录后 401 | Token 过期或 SECRET_KEY 不一致 | 检查后端 `SECRET_KEY` 是否变更 | | 上传文件失败 413 | Nginx 上传大小限制 | 增大 `client_max_body_size` | | 加密请求报错"解密失败" | 前后端密钥不一致 | 确保 `VITE_ENCRYPTION_KEY` = 后端 `ENCRYPTION_KEY` | | 非 HTTPS 环境加密无效 | Web Crypto API 需要安全上下文 | 本地开发用 localhost,生产用 HTTPS | | Celery 任务不执行 | Redis 未启动或地址错误 | 检查 `CELERY_BROKER_URL` 和 Redis | | 限流不生效 | Redis 未配置 | 检查 `REDIS_URL` | | 短信发送失败 | 火山配置缺失或 `SMS_MOCK=true` | 填入 `VOLC_SMS_*` 变量并设 `SMS_MOCK=false` | | 视频封面无法生成 | FFmpeg 未安装 | 安装 FFmpeg 或设置 `FFMPEG_BIN` | | HTTPS 请求支付宝/火山失败 | 缺少 CA 证书 | 安装 `ca-certificates` | | 数据库迁移失败 | Model 定义与迁移不一致 | 重新生成迁移文件后执行 |