Files
yunke/docs/DEPLOY.md
T
浅念 0a5eb49359 docs(deploy): DEPLOY.md 对齐 2026-09-03 实测落地(NAT 端口模式)
- 拓扑现实:服务器在路由器 NAT 后,公网 80 被其他设备占用
- 对外访问实测:1Panel 域名管理加 公网IP:32002 + 路由器转发,零配置直访
- 修媒体解压路径 bug:包内根是 media/,须 -C yunke-server
- HTTPS 改为将来项:域名 + 1Panel DNS 验证(绕开被占的 80)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-03 14:35:00 +08:00

9.1 KiB

云客数据镜像 — 服务器部署 runbook(1Panel 服务器,2026-09-03 实测落地版)

形态:1Panel 管理,OpenResty 站点 + 公网高位端口,复用 1Panel MySQL, 数据从本地整体搬迁。本地开发栈(五服务 docker-compose.yml)不在本文范围。 实测拓扑:服务器在路由器 NAT 后(网卡 192.168.100.x,公网 IP 为路由器, 公网 80 端口被同网其他设备占用)。

架构

用户浏览器 ──https──▶ 1Panel OpenResty(host 网络, 80/443)
                        │ 反向代理
                        ▼
              127.0.0.1:5173  frontend(nginx 容器)
                ├─ 静态页面(Vue SPA)
                └─ /api/* ──容器网络──▶ backend:8000(FastAPI,不映射宿主机)
                                            │
              scheduler(Playwright+Chromium)│
                抓 crm2.yunkecn.com ────────┤
                                            ▼
                    宿主机内网IP:3306  1Panel MySQL(yunke 库)
              媒体:./yunke-server/media(bind mount)
  • 站点只有一个:前端容器内 nginx 已把 /api 反代到 backend,浏览器只跟 一个域名说话(不要给后端单独建站,会跨域+双证书)。
  • 调度:每晚 02:00 全量 + 每 4h 消息增量(北京时间,compose 已设 TZ)。

0. 前置条件核对

  • ECS 已装 1Panel + Docker;建议 2C4G 起步,磁盘 ≥ 20GB(库 2.2GB + 媒体 2.5GB + 镜像 2.6GB)
  • 1Panel 应用商店已装 OpenResty(建站依赖)
  • 1Panel MySQL 版本 8.x;已建库 yunke + 专用用户,库权限设 「所有区域(%)」(容器从 docker 网段连入;3306 不对公网开,无风险)
  • 对外访问走「1Panel 域名管理加 公网IP:高位端口(实测 32002)+ 路由器端口转发到本机同端口」;3306 / 5173 / 8000 不对公网开 (公网 80 已被其他设备占用,域名+HTTPS 为将来可选项)
  • Docker Hub 镜像加速:1Panel「容器→设置」里配加速器(拉 python/node/nginx 基础镜像用);apt/pip/npm/playwright 源已在 Dockerfile 内置国内镜像
  • 服务器可访问 crm2.yunkecn.com 和 *.oss.yunkecn.com(抓取+媒体下载)
  • 从本地 .env 拿到云客凭据:YK_USERID / YK_COMPANY / YK_COOKIE

1. 拿代码

# 部署目标与自建 Git 同一台机器——clone 走本机回环,不出公网;
# 后续升级 git pull 同样走 localhost,快且不依赖外网
git clone http://127.0.0.1:25311/xinyouhe/yunke.git /opt/yunke
cd /opt/yunke

可选:想在 1Panel 界面管理这个栈,用「容器→编排→创建编排」选路径 /opt/yunke,指定 docker-compose.server.yml。命令行 docker compose 与 1Panel 编排管理的是同一套容器,不冲突。

2. 配 .env(仓库根目录)

cd /opt/yunke && cat > .env <<'EOF'
# ---- 1Panel MySQL(在 1Panel「数据库」里建的 yunke 库)----
MYSQL_HOST=172.19.x.x        # 宿主机内网 IP(ECS 控制台看,不是公网 IP)
MYSQL_PORT=3306
MYSQL_DATABASE=yunke
MYSQL_USER=yunke
MYSQL_PASSWORD=<1Panel 里建库时设的密码>

# ---- 云客会话(从本地 .env 原样搬)----
YK_USERID=<32位userId>
YK_COMPANY=<公司码>
YK_COOKIE=user=xxx; userToken=yyy
YUNKE_BASE_URL=https://crm2.yunkecn.com

# ---- 查看后台鉴权(公网必填!)----
API_TOKEN=<openssl rand -hex 32 生成>

# ---- 凭据加密密钥(必填)----
ENCRYPTION_KEY=<python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())">

# ---- 调度(北京时间)----
FULL_SYNC_HOUR=2
MESSAGE_INTERVAL_HOURS=4
EOF
chmod 600 .env

3. 起栈

docker compose -f docker-compose.server.yml --env-file .env up -d --build

首次构建 5-15 分钟(后端镜像 ~2.6GB 含 Chromium;源已配国内镜像)。 此时库还是空的,backend 日志可能有表不存在的报错——正常,下一步导数据。

docker compose -f docker-compose.server.yml ps       # 三服务 Up

4. 数据搬迁(本地 → 服务器,压缩后约 3GB)

4.1 本地导出(Windows,仓库根目录)

# MySQL 一致性快照(--single-transaction 不锁表;ngram 全文索引随表定义导出)
docker compose exec mysql mysqldump -uroot -p<本地root密码> \
  --single-transaction --routines --triggers --default-character-set=utf8mb4 \
  yunke | gzip > yunke.sql.gz

# 媒体(2.5GB)
tar czf media.tar.gz -C yunke-server media/

本地 root 密码在本地根目录 .env 的 MYSQL_ROOT_PASSWORD。

4.2 传输

scp -P 22 yunke.sql.gz media.tar.gz root@<ECS公网IP>:/opt/yunke/

(1Panel 文件管理上传也行,但 3GB 大文件 scp 更稳。)

4.3 服务器导入

cd /opt/yunke

# MySQL:1Panel MySQL 容器名在「容器」列表看(形如 1Panel-mysql-xxxx)
gunzip yunke.sql.gz
docker exec -i <1Panel-mysql容器名> mysql -uyunke -p<密码> \
  --default-character-set=utf8mb4 yunke < yunke.sql
# ⚠️ 86 万行 message 的 ngram 全文索引导入时重建,约 12 分钟——慢是正常,
#    不要中断;期间导入脚本看似卡在索引阶段属预期。

# 媒体:解到 bind mount 目录(compose 已挂 ./yunke-server/media)。
# 包内根目录是 media/(打包时 -C yunke-server),必须 -C 解到 yunke-server 下
tar xzf media.tar.gz -C yunke-server

# 数据自带 alembic_version=head,不需要跑 alembic upgrade。

4.4 导入后验证 + 补差量

docker exec -it <1Panel-mysql容器名> mysql -uyunke -p<密码> yunke \
  -e "SELECT COUNT(*) FROM message; SELECT COUNT(*) FROM customer;"
# 数字应与本地一致(message ≈ 86 万)

# 补齐 dump 之后新产生的消息(增量,水位已随数据搬来)
docker compose -f docker-compose.server.yml exec scheduler \
  python scripts/scrape_via_page.py --entity messages --max-conversations 200

5. 站点与对外访问(实测:1Panel 端口模式)

  1. 1Panel「网站→创建网站→反向代理」:代理地址 127.0.0.1:5173(已建则跳过)
  2. 1Panel「网站→设置→域名管理」添加一条 公网IP:高位端口(实测 219.157.255.213:32002)→ 站点多监听该端口
  3. 路由器端口转发:外部 32002 → 192.168.100.132:32002
  4. 访问 http://219.157.255.213:32002,别人零配置直接用
  5. 502 排查:栈是否 Up、curl 127.0.0.1:5173 是否通(OpenResty 是 host 网络, 1Panel 默认)

将来要 HTTPS:买真实域名 → 1Panel 证书走 DNS 验证(HTTP 验证需要 80, 已被占)→ 路由器加 443 转发。

前端登录鉴权即 .env 的 API_TOKEN(Authorization: Bearer <token> 或 ?token=;公网部署绝不能留空)。

6. 验收清单

  • docker compose -f docker-compose.server.yml ps 三服务 Up
  • curl http://127.0.0.1:5173/ 返回前端页面
  • curl http://127.0.0.1:8000/api/health 返回 {"status":"ok"}
  • 域名 HTTPS 可打开,客户列表/聊天记录有数据(抽查语音列表→聊天记录)
  • 不带 token 调 /api/* 返回 401(鉴权生效)
  • scheduler 日志无 cookie 过期告警:docker compose -f docker-compose.server.yml logs scheduler
  • 安全组复核:3306 / 5173 / 8000 未对公网开放

7. 日常运维

操作 命令
看调度日志 docker compose -f docker-compose.server.yml logs -f scheduler
手动触发全量 docker compose -f docker-compose.server.yml exec scheduler bash scripts/full_sync_all.sh
换云客 cookie 改 .env 的 YK_COOKIE → docker compose -f docker-compose.server.yml up -d(重建注入 env)
备份 MySQL `docker exec <1Panel-mysql容器名> mysqldump -uyunke -p<密码> --single-transaction yunke
备份媒体 tar czf /opt/backups/media-$(date +%F).tgz -C yunke-server media/
升级版本 git pull && docker compose -f docker-compose.server.yml --env-file .env up -d --build
  • 备份可挂 1Panel「计划任务」(Shell 脚本),如每日 03:00 跑上面两条。
  • 升级时若有新 alembic 迁移:加跑 docker compose -f docker-compose.server.yml exec backend alembic upgrade head (类型变更类迁移在 MySQL 上锁大表写入,避开抓取窗口:北京 02:00 全量、 每 4h 增量;增量耗时约 15-30 分钟)。

8. 本地收尾(部署验证通过后)

docker stop yunke-scheduler-1   # 停本地调度,避免两端同时抓取触发云客风控
  • 本地栈保留作开发环境,连本地 MySQL;此后本地改代码 → git push → 服务器 git pull 升级,两边数据各自独立。
  • 本地 backend/frontend(8000/5173)按需启停,不影响服务器。

9. 已知注意事项

  1. cookie 过期:云客会话定期失效,scheduler 日志出现 ⚠️ cookie 可能已过期 → 浏览器重新复制 YK_COOKIE 并按上表换。
  2. 时区:server 编排已设 TZ=Asia/Shanghai,02:00 即北京时间全量。
  3. 搜索性能:message.text 已建 ngram FULLTEXT 索引(迁移 b41e8fa0c7d2), 数据搬迁导入时索引重建约 12 分钟属正常;重导后无需手工处理。
  4. 服务器需能访问云客域名(抓取 + OSS 媒体下载),ECS 出网正常即满足。
  5. MySQL 权限:yunke 库用户须允许 % 或 docker 网段来源;3306 靠安全组 兜底不对公网暴露。