974929e091
- docker-compose.server.yml:生产编排(复用 1Panel MySQL、去 mysql/redis、 端口仅绑 127.0.0.1、TZ=Asia/Shanghai、MYSQL_HOST 必填守卫) - 两份 Dockerfile 内置国内构建源:apt/pip→阿里云,npm/playwright→npmmirror (ECS 上直连官方源慢/超时;对本地构建同样可用) - DEPLOY.md 重写为 v2 runbook:单站点反代架构、mysqldump+media 搬迁管道 (ngram 索引导入重建 ~12min)、验收清单、运维表、本地停 scheduler 收尾 Co-Authored-By: Claude Code <noreply@anthropic.com>
209 lines
8.6 KiB
Markdown
209 lines
8.6 KiB
Markdown
# 云客数据镜像 — 服务器部署 runbook(阿里云 ECS + 1Panel)
|
|
|
|
> 2026-09-01 v2|形态:1Panel 管理,OpenResty 单站点反代,复用 1Panel MySQL,
|
|
> 数据从本地整体搬迁。本地开发栈(五服务 docker-compose.yml)不在本文范围。
|
|
|
|
## 架构
|
|
|
|
```
|
|
用户浏览器 ──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 不对公网开,无风险)
|
|
- [ ] 域名已解析到 ECS 公网 IP,大陆区域已**备案**(80/443 必须)
|
|
- [ ] 阿里云安全组:公网只开 **80/443**;SSH 与 1Panel 面板端口限源 IP;
|
|
**3306 / 5173 / 8000 不对公网开**
|
|
- [ ] 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. 拿代码
|
|
|
|
```bash
|
|
# 部署目标与自建 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(仓库根目录)
|
|
|
|
```bash
|
|
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. 起栈
|
|
|
|
```bash
|
|
docker compose -f docker-compose.server.yml --env-file .env up -d --build
|
|
```
|
|
|
|
首次构建 5-15 分钟(后端镜像 ~2.6GB 含 Chromium;源已配国内镜像)。
|
|
此时库还是空的,backend 日志可能有表不存在的报错——正常,下一步导数据。
|
|
|
|
```bash
|
|
docker compose -f docker-compose.server.yml ps # 三服务 Up
|
|
```
|
|
|
|
## 4. 数据搬迁(本地 → 服务器,压缩后约 3GB)
|
|
|
|
### 4.1 本地导出(Windows,仓库根目录)
|
|
|
|
```bash
|
|
# 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 传输
|
|
|
|
```bash
|
|
scp -P 22 yunke.sql.gz media.tar.gz root@<ECS公网IP>:/opt/yunke/
|
|
```
|
|
|
|
(1Panel 文件管理上传也行,但 3GB 大文件 scp 更稳。)
|
|
|
|
### 4.3 服务器导入
|
|
|
|
```bash
|
|
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)
|
|
tar xzf media.tar.gz # 解出 yunke-server/media/
|
|
|
|
# 数据自带 alembic_version=head,不需要跑 alembic upgrade。
|
|
```
|
|
|
|
### 4.4 导入后验证 + 补差量
|
|
|
|
```bash
|
|
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. 站点 + HTTPS(1Panel)
|
|
|
|
1. 「网站→创建网站→反向代理」:主域名填**真实域名**,代理地址
|
|
`127.0.0.1:5173`(已建好站点的话跳过)
|
|
2. 「网站→证书」:Let's Encrypt 申请该域名证书 → 网站设置里开启 HTTPS
|
|
3. 打不开/502 时排查:栈是否 Up(`docker compose -f docker-compose.server.yml ps`)、
|
|
`curl 127.0.0.1:5173` 是否通、OpenResty 是否 host 网络(1Panel 默认是)
|
|
|
|
前端登录鉴权即 `.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 | gzip > /opt/backups/yunke-$(date +%F).sql.gz` |
|
|
| 备份媒体 | `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. 本地收尾(部署验证通过后)
|
|
|
|
```bash
|
|
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 靠安全组
|
|
兜底不对公网暴露。
|