13 KiB
13 KiB
生产部署手册:elane.xiangsilian.com
创建日期:2026-09-14 | 适用:meida_backend 首次公网部署 目标服务器:elane.xiangsilian.com → 175.178.100.240(腾讯云 Ubuntu,已有 nginx) 前置:代码 review 已完成(12 项结论全部核实),本文所有命令均按当前代码核实过。
0. 部署前必读(三个硬提醒)
- ⚠️ 华为登录还差一步 AGC 配置。 登录系统已开发完成:邮箱验证码(前后端全
链路可用)+ 华为账号登录(端侧已接入,后端验签早就绪)。唯一缺口:AGC 开通
Account Kit 后把 client_id 填进 App 的
entry/src/main/module.json5(替换TODO_AGC_CLIENT_ID占位值),并确保 AGC 上的包名/证书指纹与本机 签名一致。在此之前华为登录按钮会提示「暂未开放」,邮箱登录可正常使用。 - ⚠️ 备案:
elane.xiangsilian.com是现有备案下新增的子域名。需在腾讯云 控制台「备案 → 变更备案」中提交变更(主体不变、免费、线上办理、约 1~2 周)。 部署不依赖备案完成,但华为应用市场审核会核对。 - ⚠️ 服务器安全组:放行 443;不要对公网开放 55432/56379(数据库端口)。
1. 目标架构
App ──https──▶ elane.xiangsilian.com:443 (nginx + Let's Encrypt 证书)
└─ /api/v1/ ──▶ 127.0.0.1:8666 (gunicorn/uvicorn)
└─ /healthz、/readyz ──▶ 127.0.0.1:8666(监控用,可只内网)
本机进程:PostgreSQL 16+pgvector(docker, 127.0.0.1:55432)
Redis 7(docker, 127.0.0.1:56379)
celery worker -Q cpu,io(systemd)
数据卷: pgdata / redisdata(docker 卷)+ /home/ubuntu/meida_data/{uploads,exports}
说明:本部署为单进程 api + 单 worker(当前业务全部走 cpu/io 队列,gpu 队列 尚未启用,见 review §10)。不上 K8s,与架构文档 §14.1 的 V1 形态一致。
2. 服务器初始化
# 系统要求:Ubuntu 22.04+,2C4G 起步(报告生成是 LLM IO 密集型,不吃 CPU)
# 2.1 Docker(如未装)
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER && newgrp docker
# 2.2 uv(Python 包管理,项目要求 Python >= 3.12)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
# 2.3 weasyprint 系统依赖(PDF 导出用,缺了首次导出必炸——review 重点项)
sudo apt-get update && sudo apt-get install -y \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz-subset0
# 2.4 nginx + certbot(机器上已有 nginx 则跳过安装,直接进 §5)
sudo apt-get install -y nginx certbot python3-certbot-nginx
3. 拉代码 + 数据库
git clone https://git.xiangsilian.com/xsl/meida_backend.git ~/meida_backend
cd ~/meida_backend
# 3.1 改数据库口令:编辑 deploy/docker-compose.dev.yml
# POSTGRES_PASSWORD: meida → 换成强口令(记为 <PG_PASS>)
# 并把两个端口映射绑到本机(防公网暴露):
# "55432:5432" → "127.0.0.1:55432:5432"
# "56379:6379" → "127.0.0.1:56379:6379"
# 3.2 起 PG + Redis(只起数据层,不起应用)
docker compose -f deploy/docker-compose.dev.yml up -d postgres redis
# 3.3 应用依赖(生产:不装 dev 依赖、锁死版本)
uv sync --no-dev --frozen
4. 生产 .env
cp .env.example .env 后按下表修改。带 ⚠️ 的行漏配会导致安全事故或拒启动。
# ── 环境(最关键:prod 自动关闭 dev-token 登录和 /api/v1/docs)──
APP_ENV=prod # ⚠️ 不设这行 = dev-token 对外敞开
LOG_LEVEL=INFO
LOG_JSON=true
# ── 数据依赖(默认值指向 localhost:55432/56379,口令与 3.1 一致)──
DATABASE_URL=postgresql+asyncpg://meida:<PG_PASS>@127.0.0.1:55432/meida
REDIS_URL=redis://127.0.0.1:56379/0
CELERY_BROKER_URL=redis://127.0.0.1:56379/1
CELERY_RESULT_BACKEND=redis://127.0.0.1:56379/2
# ── 鉴权 ──
JWT_SECRET=<执行 openssl rand -hex 32 生成> # ⚠️ <32 字符或占位符会拒启动(有意保护)
DEV_AUTH_ENABLED=false # 双保险(APP_ENV=prod 本身已关)
# ── LLM(DeepSeek 主 / 智谱备,key 从开发机 .env 抄)──
DEEPSEEK_API_KEY=sk-...
ZHIPU_API_KEY=...
# LLM_DAILY_BUDGET_CENTS=100000 # ⚠️ 当前是占位值(¥1000/天),按实测成本重算后再定
# ── 存储:无 COS 走本地磁盘降级(当前形态)──
# COS_* 全部留空;上传走 POST /api/v1/uploads/direct
UPLOAD_DIR=/home/ubuntu/meida_data/uploads # ⚠️ 绝对路径,相对 cwd 在 systemd 下不可靠
REPORT_EXPORT_DIR=/home/ubuntu/meida_data/exports
# ── 登录通道 ──
# 华为账号(后端已支持;HUAWEI_AUDIENCE 不配则华为登录 40102,是有意设计)
# HUAWEI_AUDIENCE=6917613748100335131
# HUAWEI_AUDIENCE 对应 AGC 应用的 App ID(前置开通清单已登记)
#
# 邮箱验证码(正式兜底通道,前后端已实现,2026-09-14):
# ⚠️ EMAIL_PROVIDER=fake 会把验证码明文打进日志,仅限本地开发,生产必须 smtp
EMAIL_PROVIDER=smtp
SMTP_HOST=smtp.qq.com # 或 smtp.163.com
SMTP_PORT=465
SMTP_USER=your_sender@qq.com
SMTP_PASS=<邮箱 SMTP 授权码> # QQ/163 邮箱设置里生成,不是登录密码
SMTP_USE_SSL=true
MAIL_FROM=your_sender@qq.com # 留空则用 SMTP_USER
# 频控默认值(对齐短信口径):同邮箱 60s 冷却 / 1h≤5 / 24h≤10,IP 24h≤20,设备 24h≤10
#
# 短信通道:已废弃(个人主体办不下签名),SMS_* 保持默认不启用即可
注:短信通道已废弃不做。
SMS_PROVIDER保持默认 fake 即可——短信端点未接路由、 App 端无入口,永远不会被触发;但生产日志审计时留意别让任何人把它接到路由上。
5. 迁移 + 内测配额口径
cd ~/meida_backend
# 5.1 迁移(alembic 走 DATABASE_URL;0003 会自动灌 bl_quota_rule 种子,
# 含 full_report 行——全新库不需要手动补)
uv run alembic upgrade head
# 5.2 (可选,内测期放宽;上架前必须改回)开发库当前口径:
docker exec meida-postgres psql -U meida -d meida -c \
"UPDATE bl_quota_rule SET base_limit=base_limit*10, task_bonus_max=task_bonus_max*10, hard_cap=hard_cap*10;"
6. 进程常驻(systemd)
sudo tee /etc/systemd/system/meida-api.service <<'EOF'
[Unit]
Description=meida api
After=network-online.target docker.service
Wants=network-online.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/meida_backend
Environment=PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/ubuntu/.local/bin/uv run --no-sync gunicorn apps.api.main:app \
--worker-class uvicorn.workers.UvicornWorker --workers 2 \
--bind 127.0.0.1:8666 --timeout 120 --graceful-timeout 30 --access-logfile -
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo tee /etc/systemd/system/meida-worker.service <<'EOF'
[Unit]
Description=meida celery worker (cpu,io)
After=network-online.target docker.service
Wants=network-online.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/meida_backend
Environment=PATH=/home/ubuntu/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PYTHONUNBUFFERED=1
ExecStart=/home/ubuntu/.local/bin/uv run --no-sync celery -A apps.worker.celery_app worker \
-Q cpu,io --pool=solo --loglevel=INFO
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now meida-api meida-worker
# 验证(本机)
curl -s http://127.0.0.1:8666/healthz # {"code":0,...,"status":"ok"}
curl -s http://127.0.0.1:8666/readyz # 依赖就绪(查 PG+Redis)
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8666/api/v1/auth/dev-token \
-H 'Content-Type: application/json' -d '{"device_key":"x"}' # 必须是 404(dev-token 已关)
要点:
--no-sync防止 systemd 环境下 uv 试图联网解析依赖;依赖已在 §3.3 装好。- 不需要 celery beat(全仓库无定时任务;temp 目录清理等上线后再说,见 §8)。
- worker 与 api 的 UPLOAD_DIR/REPORT_EXPORT_DIR 是同一绝对路径,天然共享,无需挂卷。
7. nginx + HTTPS
# 7.1 签证书(自动改 nginx 配置 + 配置续期)
sudo certbot --nginx -d elane.xiangsilian.com
# 7.2 编辑 /etc/nginx/sites-enabled/ 下 elane 的 server 块,追加反代:
server {
listen 443 ssl;
server_name elane.xiangsilian.com;
# certbot 自动填 ssl_certificate / ssl_certificate_key
location /api/v1/ {
proxy_pass http://127.0.0.1:8666;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto https;
client_max_body_size 20m; # 上传上限与 UPLOAD_MAX_BYTES 对齐
proxy_read_timeout 120s; # 报告生成等长请求
}
# 监控探活(可选;不配则只在服务器本机 curl)
location = /healthz { proxy_pass http://127.0.0.1:8666/healthz; }
location = /readyz { proxy_pass http://127.0.0.1:8666/readyz; }
# /metrics 不要暴露公网(代码里它不受 PROMETHEUS_ENABLED 控制,见 §8)
}
sudo nginx -t && sudo systemctl reload nginx
# 7.3 安全组:腾讯云控制台放行 443/tcp;确认 55432/56379/8666 未对公网开放
# 7.4 公网验证
curl -s https://elane.xiangsilian.com/healthz
curl -s -o /dev/null -w '%{http_code}\n' https://elane.xiangsilian.com/api/v1/quota # 401 = 服务活了
curl -s -o /dev/null -w '%{http_code}\n' https://elane.xiangsilian.com/api/v1/docs # 404 = swagger 已关
8. 已知限制与待办(代码 review 结论,部署后按此跟进)
| # | 事项 | 处置 |
|---|---|---|
| 1 | 华为登录差 client_id:端侧已接入(占位拦截不 crash),需 AGC 开通 Account Kit 后填 module.json5 |
用户在 AGC 操作(前置开通清单 §1),拿到 client_id 替换 TODO_AGC_CLIENT_ID |
| 2 | 生产 .env 用 EMAIL_PROVIDER=smtp + 真实邮箱授权码 |
|
| 3 | CORS 中间件不存在 | 原生 App 不受影响;admin 后台浏览器访问前必须加白名单 |
| 4 | COS temp 桶 2 小时生命周期 | 未接 COS 前, uploads 目录只增不减,需 cron 清理:如 find /home/ubuntu/meida_data/uploads -type f -mtime +2 -delete(temp 场景文件 2 小时后可删) |
| 5 | 数据备份脚本缺失(FR-SYS-09) | 最低限度:pg_dump 每日 cron + meida_data/ 快照 |
| 6 | LLM_DAILY_BUDGET_CENTS 是占位值 |
用真实 token 成本重算后改 .env |
| 7 | /metrics 无条件暴露 |
已按 §7 不反代公网;要监控可后期加 IP 白名单 |
| 8 | DEBUG 配置项是死代码 | 无行为影响,忽略即可 |
| 9 | 本地上传的照片无对外 URL(已知缺口) | 报告场景服务端自用不受影响;需回显时接 COS |
| 10 | 运营后台(行为分析)需设 ADMIN_TOKEN 才装配 |
端点 /admin/api/v1/users、/users/{id}、/events、/stats/overview(X-Admin-Token 鉴权,PII 掩码后输出);admin 是独立应用需单独进程:uv run uvicorn "apps.admin.app:create_admin_app" --host 127.0.0.1 --port 8001,nginx 按需反代(注意先解决 #3 CORS) |
9. 故障速查
| 症状 | 排查 |
|---|---|
systemctl status meida-api 启动即失败 |
多半是 JWT_SECRET 太短/占位符(config.py 启动校验)——journalctl -u meida-api -n 50 看原因 |
| readyz 非 2xx | PG/Redis 没起:docker compose -f deploy/docker-compose.dev.yml ps |
| 报告卡在生成中 | 看 worker:journalctl -u meida-worker -f;LLM key 失效是最常见原因 |
| PDF 导出失败报 weasyprint 相关错 | §2.3 的系统库没装 |
| nginx 502 | api 没起 / 8666 端口不对;ss -ltnp | grep 8666 |
| 华为登录 40102 | HUAWEI_AUDIENCE 没配(预期行为,配 AGC App ID) |
10. 上线前最终检查单
- 变更备案通过(elane.xiangsilian.com 在备案系统可见)
- dev-token 返回 404、docs 返回 404、metrics 不公网可达
- JWT_SECRET / PG 口令均为强随机值,
.env权限chmod 600 - 华为登录真机回归通过(client_id 已填入 module.json5、AGC 指纹与签名一致)
- 邮箱验证码登录真机回归通过(生产 EMAIL_PROVIDER=smtp 实测收到邮件)
- 账号注销功能可用(华为审核硬性要求)
- 隐私政策/用户协议可公开访问,App 内有入口
- 配额规则从内测口径改回产品口径(若执行过 §5.2)
- pg_dump 定时备份已配并演练过一次恢复
- App
DEFAULT_BASE_URL切换为https://elane.xiangsilian.com,Release 签名出包