From 0075c130bd00e8b3364c47d656d970ad7f1379f3 Mon Sep 17 00:00:00 2001 From: xiangsilian Date: Tue, 15 Sep 2026 00:03:13 +0800 Subject: [PATCH] =?UTF-8?q?docs(deploy):=20=E7=94=9F=E4=BA=A7=E9=83=A8?= =?UTF-8?q?=E7=BD=B2=E6=89=8B=E5=86=8C-elane=EF=BC=88=E5=90=AB=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20review=20=E7=BB=93=E8=AE=BA=EF=BC=9A=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=B8=85=E5=8D=95/=E9=9A=90=E6=82=A3/=E5=BE=85?= =?UTF-8?q?=E5=8A=9E=EF=BC=89=EF=BC=9Bmeida=5Fbackend=20=E5=AD=90=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E6=8C=87=E9=92=88=20=E2=86=92=2090349f7=EF=BC=88?= =?UTF-8?q?=E6=B7=B1=E5=BA=A6=E8=AF=8A=E6=96=AD+=E9=82=AE=E7=AE=B1?= =?UTF-8?q?=E7=99=BB=E5=BD=95+admin=20=E7=9C=8B=E6=9D=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/生产部署手册-elane.md | 281 +++++++++++++++++++++++++++++++++++++ meida_backend | 2 +- 2 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 docs/生产部署手册-elane.md diff --git a/docs/生产部署手册-elane.md b/docs/生产部署手册-elane.md new file mode 100644 index 0000000..63fae10 --- /dev/null +++ b/docs/生产部署手册-elane.md @@ -0,0 +1,281 @@ +# 生产部署手册:elane.xiangsilian.com + +> 创建日期:2026-09-14 | 适用:meida_backend 首次公网部署 +> 目标服务器:elane.xiangsilian.com → 175.178.100.240(腾讯云 Ubuntu,已有 nginx) +> 前置:代码 review 已完成(12 项结论全部核实),本文所有命令均按当前代码核实过。 + +--- + +## 0. 部署前必读(三个硬提醒) + +1. **⚠️ 华为登录还差一步 AGC 配置。** 登录系统已开发完成:邮箱验证码(前后端全 + 链路可用)+ 华为账号登录(端侧已接入,后端验签早就绪)。唯一缺口:AGC 开通 + Account Kit 后把 **client_id** 填进 App 的 `entry/src/main/module.json5` + (替换 `TODO_AGC_CLIENT_ID` 占位值),并确保 AGC 上的包名/证书指纹与本机 + 签名一致。在此之前华为登录按钮会提示「暂未开放」,邮箱登录可正常使用。 +2. **⚠️ 备案**:`elane.xiangsilian.com` 是现有备案下**新增的子域名**。需在腾讯云 + 控制台「备案 → 变更备案」中提交变更(主体不变、免费、线上办理、约 1~2 周)。 + 部署不依赖备案完成,但华为应用市场审核会核对。 +3. **⚠️ 服务器安全组**:放行 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. 服务器初始化 + +```bash +# 系统要求: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. 拉代码 + 数据库 + +```bash +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 → 换成强口令(记为 ) +# 并把两个端口映射绑到本机(防公网暴露): +# "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` 后按下表修改。**带 ⚠️ 的行漏配会导致安全事故或拒启动**。 + +```bash +# ── 环境(最关键: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:@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. 迁移 + 内测配额口径 + +```bash +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) + +```bash +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 + +```bash +# 7.1 签证书(自动改 nginx 配置 + 配置续期) +sudo certbot --nginx -d elane.xiangsilian.com + +# 7.2 编辑 /etc/nginx/sites-enabled/ 下 elane 的 server 块,追加反代: +``` + +```nginx +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) +} +``` + +```bash +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 | ~~SMS_PROVIDER 默认 fake~~ 短信已废弃;邮箱通道已上线 | 生产 `.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 签名出包 diff --git a/meida_backend b/meida_backend index a1d0171..90349f7 160000 --- a/meida_backend +++ b/meida_backend @@ -1 +1 @@ -Subproject commit a1d0171fc5fb43e9077b0c0e582795172db925d7 +Subproject commit 90349f735a5839f024aed4d7c98062a8aef3cec8