From 974929e09195dbd3c815ebcf9d79b2b20f0db64c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Tue, 1 Sep 2026 16:30:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E9=98=BF=E9=87=8C=E4=BA=91ECS+?= =?UTF-8?q?1Panel=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E9=83=A8=E7=BD=B2=E6=96=B9?= =?UTF-8?q?=E6=A1=88=E8=90=BD=E5=9C=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docker-compose.server.yml | 85 +++++++++++ docs/DEPLOY.md | 301 +++++++++++++++++++++----------------- yunke-server/Dockerfile | 11 +- yunkes/Dockerfile | 3 +- 4 files changed, 261 insertions(+), 139 deletions(-) create mode 100644 docker-compose.server.yml diff --git a/docker-compose.server.yml b/docker-compose.server.yml new file mode 100644 index 0000000..74196ef --- /dev/null +++ b/docker-compose.server.yml @@ -0,0 +1,85 @@ +# docker-compose.server.yml — 服务器(阿里云 ECS + 1Panel)专用生产编排。 +# +# 与 docker-compose.yml(本地五服务开发栈)的差异: +# - 不含 mysql/redis:MySQL 复用 1Panel 已装实例(已建 yunke 库,用户权限 +# 「所有区域(%)」,容器经宿主机内网 IP 访问,3306 不对公网);redis 应用 +# 尚未接线(config 里只有一个默认 URL),省一份常驻内存——app 将来接 +# redis 时记得同步回这里。 +# - DB_URL 主机/端口走 MYSQL_HOST / MYSQL_PORT 环境变量(.env 里填宿主机 +# 内网 IP,如 172.19.x.x;容器→宿主机内网 IP 是 docker bridge 默认可达)。 +# - 端口只绑 127.0.0.1:frontend 供 1Panel OpenResty(host 网络)反代; +# backend 不映射宿主机(/api 由 frontend 容器内 nginx 反代,见 +# yunkes/nginx.conf)。 +# - TZ=Asia/Shanghai:调度按北京时间触发(FULL_SYNC_HOUR=2 即北京 02:00)。 +# +# 用法(服务器仓库根目录,完整步骤见 docs/DEPLOY.md): +# docker compose -f docker-compose.server.yml --env-file .env up -d --build +# docker compose -f docker-compose.server.yml logs -f scheduler + +services: + backend: + build: + context: ./yunke-server + dockerfile: Dockerfile + restart: unless-stopped + command: + - uvicorn + - app.api.main:app + - --host + - 0.0.0.0 + - --port + - "8000" + environment: + DB_URL: mysql+asyncmy://${MYSQL_USER:-yunke}:${MYSQL_PASSWORD}@${MYSQL_HOST:?set MYSQL_HOST in .env}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-yunke} + MEDIA_DIR: /data/media + YUNKE_BASE_URL: ${YUNKE_BASE_URL:-https://crm2.yunkecn.com} + YUNKE_USERNAME: ${YUNKE_USERNAME:-} + YUNKE_PASSWORD: ${YUNKE_PASSWORD:-} + YK_USERID: ${YK_USERID:-} + YK_COMPANY: ${YK_COMPANY:-} + YK_COOKIE: ${YK_COOKIE:-} + API_TOKEN: ${API_TOKEN:-} + ENCRYPTION_KEY: ${ENCRYPTION_KEY:-} + TZ: Asia/Shanghai + volumes: + # 媒体 bind mount:服务器上 ./yunke-server/media 数据直观可见、好备份 + - ./yunke-server/media:/data/media + + scheduler: + build: + context: ./yunke-server + dockerfile: Dockerfile + restart: unless-stopped + command: + - python + - -m + - app.sync.cli + - scheduler + environment: + DB_URL: mysql+asyncmy://${MYSQL_USER:-yunke}:${MYSQL_PASSWORD}@${MYSQL_HOST:?set MYSQL_HOST in .env}:${MYSQL_PORT:-3306}/${MYSQL_DATABASE:-yunke} + MEDIA_DIR: /data/media + YUNKE_BASE_URL: ${YUNKE_BASE_URL:-https://crm2.yunkecn.com} + YUNKE_USERNAME: ${YUNKE_USERNAME:-} + YUNKE_PASSWORD: ${YUNKE_PASSWORD:-} + YK_USERID: ${YK_USERID:-} + YK_COMPANY: ${YK_COMPANY:-} + YK_COOKIE: ${YK_COOKIE:-} + ENCRYPTION_KEY: ${ENCRYPTION_KEY:-} + # 调度节奏(北京时间):每晚全量 + 每 4h 消息增量 + FULL_SYNC_HOUR: ${FULL_SYNC_HOUR:-2} + MESSAGE_INTERVAL_HOURS: ${MESSAGE_INTERVAL_HOURS:-4} + TZ: Asia/Shanghai + volumes: + - ./yunke-server/media:/data/media + + frontend: + build: + context: ./yunkes + dockerfile: Dockerfile + restart: unless-stopped + depends_on: + - backend + ports: + # 只绑 127.0.0.1:公网流量一律走 1Panel OpenResty 反代(80/443), + # 5173 不暴露到安全组 + - "127.0.0.1:5173:80" diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 3e5dda9..c3e6a62 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -1,181 +1,208 @@ -# 云客数据镜像 — 服务器部署指南 +# 云客数据镜像 — 服务器部署 runbook(阿里云 ECS + 1Panel) -> 2026-08-21|适用于 Linux 服务器 + Docker Compose 五服务部署 +> 2026-09-01 v2|形态:1Panel 管理,OpenResty 单站点反代,复用 1Panel MySQL, +> 数据从本地整体搬迁。本地开发栈(五服务 docker-compose.yml)不在本文范围。 ## 架构 ``` -┌─────────────────────────────────────────────────┐ -│ Docker Compose │ -│ ┌────────┐ ┌────────┐ ┌────────────────────┐ │ -│ │ MySQL 8│ │ Redis 7│ │ backend (FastAPI) │ │ -│ │ :3306 │ │ :6379 │ │ :8000 │ │ -│ └────┬───┘ └────┬───┘ └─────────▲──────────┘ │ -│ │ │ │ /api │ -│ ┌────▼───────────▼───┐ ┌────────┴──────────┐ │ -│ │ scheduler │ │ frontend (nginx) │ │ -│ │ APScheduler │ │ :5173 → :80 │ │ -│ │ + Playwright │ └───────────────────┘ │ -│ │ + Chromium │ │ -│ └────────────────────┘ │ -└─────────────────────────────────────────────────┘ +用户浏览器 ──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) ``` -- **scheduler**:每晚 02:00 全量 + 每 4h 消息增量 + 每周日 05:00 全量对齐(全部浏览器 fetch 零风控)→ 需要 Playwright + Chromium(镜像已内置) -- **restart: unless-stopped**:容器崩溃自动重启(服务器上的守护方案,无需本地计划任务) +- **站点只有一个**:前端容器内 nginx 已把 `/api` 反代到 backend,浏览器只跟 + 一个域名说话(不要给后端单独建站,会跨域+双证书)。 +- **调度**:每晚 02:00 全量 + 每 4h 消息增量(北京时间,compose 已设 TZ)。 -## 前置要求 +## 0. 前置条件核对 -- Linux 服务器(2C4G 起步,磁盘 ≥ 20GB:MySQL 数据 + 媒体文件 2.5GB+) -- Docker 24+ 与 Docker Compose v2 -- 一个有效的云客会话 cookie(从浏览器登录云客后复制) +- [ ] 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. 上传代码 +## 1. 拿代码 ```bash -git clone <你的仓库地址> yunke -cd yunke +# 部署目标与自建 Git 同一台机器——clone 走本机回环,不出公网; +# 后续升级 git pull 同样走 localhost,快且不依赖外网 +git clone http://127.0.0.1:25311/xinyouhe/yunke.git /opt/yunke +cd /opt/yunke ``` -### 2. 配置 .env +> 可选:想在 1Panel 界面管理这个栈,用「容器→编排→创建编排」选路径 +> `/opt/yunke`,指定 `docker-compose.server.yml`。命令行 `docker compose` 与 +> 1Panel 编排管理的是同一套容器,不冲突。 + +## 2. 配 .env(仓库根目录) ```bash -cp yunke-server/.env.example .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 里建库时设的密码> -必须设置的项: - -```ini -# 云客会话(从浏览器 F12 → Network → 任意请求的 Cookie 头复制) +# ---- 云客会话(从本地 .env 原样搬)---- YK_USERID=<32位userId> YK_COMPANY=<公司码> YK_COOKIE=user=xxx; userToken=yyy +YUNKE_BASE_URL=https://crm2.yunkecn.com -# MySQL(docker-compose 自动创建库和用户) -MYSQL_ROOT_PASSWORD=<强密码> -MYSQL_PASSWORD=<强密码> +# ---- 查看后台鉴权(公网必填!)---- +API_TOKEN= -# 查看后台的访问令牌(留空=无鉴权,仅限内网) -API_TOKEN=<随机长字符串> +# ---- 凭据加密密钥(必填)---- +ENCRYPTION_KEY= + +# ---- 调度(北京时间)---- +FULL_SYNC_HOUR=2 +MESSAGE_INTERVAL_HOURS=4 +EOF +chmod 600 .env ``` -### 3. 启动 +## 3. 起栈 ```bash -docker compose --env-file .env up -d --build +docker compose -f docker-compose.server.yml --env-file .env up -d --build ``` -首次构建约 5-10 分钟(含 Chromium 下载 ~150MB)。启动后: +首次构建 5-15 分钟(后端镜像 ~2.6GB 含 Chromium;源已配国内镜像)。 +此时库还是空的,backend 日志可能有表不存在的报错——正常,下一步导数据。 ```bash -docker compose ps # 五服务全部 Up(healthy) -docker compose logs -f scheduler # 看调度器日志 +docker compose -f docker-compose.server.yml ps # 三服务 Up ``` -### 4. 数据库迁移(alembic schema) +## 4. 数据搬迁(本地 → 服务器,压缩后约 3GB) -后端启动**不会**自动建表,schema 由 alembic 迁移链管理(镜像已打包 -`alembic.ini` + `migrations/`,`DB_URL` 由 compose 注入)。 - -**新库首次部署**(空 MySQL 直接建全量表): +### 4.1 本地导出(Windows,仓库根目录) ```bash -docker compose exec backend alembic upgrade head -``` +# 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 -**旧库升级**(2026-08-22 `migrate_sqlite_to_mysql.py` 搬迁产物,没有 -alembic_version 表)——2026-08-25 实战踩通的完整路径,原样记录: - -```bash -# 1) 前置校验:chat_group 无 NULL owner_account_id(复合 PK 迁移要求 NOT NULL, -# 非 0 则是必须人工核对的数据问题,不要带病升级) -docker compose exec mysql mysql -uyunke -p<密码> yunke \ - -e "SELECT COUNT(*) FROM chat_group WHERE owner_account_id IS NULL" - -# 2) stamp 到搬迁脚本对应的链上位置,再升级到 head -docker compose exec backend alembic stamp d8eee96cd017 -docker compose exec backend alembic upgrade head -``` - -stamp 版本号不是拍脑袋定的:`d8eee96cd017` 是与 2026-08-22 -`migrate_sqlite_to_mysql`(按当时模型 `create_all` 建表)产物 schema -一致的链上位置——stamp 它即声明「到此为止已应用」,其后迁移(chat_group -复合 PK、群主列、类型修正等)逐一正常补上。其它日期建的旧库须自行核对 -表结构选 stamp 点,不要照抄这个版本号。 - -**每次拉新代码后**: - -```bash -docker compose exec backend alembic upgrade head -``` - -> ⚠️ 类型变更类迁移(如 fe63c6789e94)在 MySQL 上为 COPY 算法、会锁 -> `message` 等大表写入——避开抓取窗口(每 4h 增量、02:00 全量)执行。 - -涉及存量数据回填的更新另跑回填脚本(幂等,重跑 0 改动;脚本默认连 -`./smoke.db`,服务器上须显式传 DB_URL——容器 env 已带,直接透传): - -```bash -docker compose exec backend python -c "import asyncio,os; from scripts.backfill_chat_group_fields import main; asyncio.run(main(os.environ['DB_URL']))" -docker compose exec backend python -c "import asyncio,os; from scripts.backfill_msg_types import main; asyncio.run(main(os.environ['DB_URL']))" -``` - -### 5. 验证 - -```bash -curl http://localhost:8000/api/health # {"status":"ok"} -curl http://localhost:5173/ # 前端页面 -``` - -### 6. 数据迁移(把本地 SQLite 数据搬到服务器 MySQL) - -**方式 A:重抓(推荐,数据最新)** - -服务器上手动跑一轮全量: - -```bash -docker compose exec scheduler bash -DB_URL="mysql+asyncmy://yunke:<密码>@mysql:3306/yunke" \ - bash scripts/full_sync_all.sh -``` - -耗时 6-12 小时(消息全量翻页),跑完后调度器自动接管日常增量。 - -**方式 B:SQLite → MySQL 数据搬迁** - -把本地 smoke.db 和 media/ 目录传到服务器,用 Python 脚本逐表导: -(如有需要我可以写迁移脚本,~100 行) - -### 7. 媒体文件 - -媒体存在 Docker volume `media_data` 里。本地已有 2.5GB 媒体的话: - -```bash -# 本地打包 +# 媒体(2.5GB) tar czf media.tar.gz -C yunke-server media/ - -# 传到服务器后灌入 volume -docker run --rm -v yunke_media_data:/target -v $(pwd):/src alpine \ - sh -c "cd /target && tar xzf /src/media.tar.gz --strip-components=1" ``` -## 日常运维 +本地 root 密码在本地根目录 `.env` 的 `MYSQL_ROOT_PASSWORD`。 + +### 4.2 传输 + +```bash +scp -P 22 yunke.sql.gz media.tar.gz root@:/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=`;**公网部署绝不能留空**)。 + +## 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 logs -f scheduler` | -| 手动触发全量 | `docker compose exec scheduler bash scripts/full_sync_all.sh` | -| 换 cookie | 改 `.env` 的 `YK_COOKIE` → `docker compose restart scheduler backend` | -| 备份 MySQL | `docker compose exec mysql mysqldump -uroot -p<密码> yunke > backup.sql` | -| 备份媒体 | `docker run --rm -v yunke_media_data:/data -v $(pwd):/out alpine tar czf /out/media.tgz -C /data .` | -| 升级版本 | `git pull && docker compose up -d --build && docker compose exec backend alembic upgrade head`(回填见「数据库迁移」) | +| 看调度日志 | `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 分钟)。 -1. **cookie 过期**:云客会话定期失效,过期后日志出现 `⚠️ cookie 可能已过期` 提示 → 从浏览器重新复制 `YK_COOKIE` 并 restart -2. **首次消息全量很慢**(6-12h),之后增量每轮 15-30 分钟 -3. **FTS 搜索**:MySQL 上自动回退 LIKE(暂无全文索引);如搜索量大可在 MySQL 建 FULLTEXT 索引 -4. **时区**:容器默认 UTC;调度按容器时区触发(FULL_SYNC_HOUR=2 即 UTC 02:00)。要北京时间可在 compose 加 `TZ: Asia/Shanghai` -5. **服务器需能访问 crm2.yunkecn.com 和 *.oss.yunkecn.com**(抓取 + 媒体下载) +## 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 靠安全组 + 兜底不对公网暴露。 diff --git a/yunke-server/Dockerfile b/yunke-server/Dockerfile index ca52c5f..7d248a5 100644 --- a/yunke-server/Dockerfile +++ b/yunke-server/Dockerfile @@ -11,6 +11,11 @@ FROM python:3.12-slim # cryptography.``libmariadb`` is what asyncmy links against on Debian. # Playwright 系统依赖:Chromium headless 需要的共享库(libnss3/libatk/libx11 # 等,playwright install-deps 的等价精简集,2026-08-21 部署阻塞点#1)。 +# 国内构建加速(阿里云 ECS 上 apt/pip 直连官方源慢或超时;对本地构建同样 +# 可用,镜像源无地域限制)。bookworm/trixie 的源都在 debian.sources 里; +# 文件不存在(更老基础镜像)则跳过,不影响构建。 +RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || true + RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential \ @@ -61,7 +66,11 @@ COPY migrations/ ./migrations/ # Install the package + dev dependencies (the dev extras include aiosqlite + # pytest, which are tiny and convenient for in-container smoke checks). Pin # the install to the local source so the package import path resolves. -RUN pip install --no-cache-dir -e ".[dev]" \ +# 国内构建加速:pip 走阿里云源;Chromium 二进制(~160MB)走 npmmirror, +# 否则 ECS 上从 playwright CDN 下载很慢。 +ENV PLAYWRIGHT_DOWNLOAD_HOST=https://cdn.npmmirror.com/binaries/playwright + +RUN pip install --no-cache-dir -i https://mirrors.aliyun.com/pypi/simple/ -e ".[dev]" \ && playwright install chromium --with-deps # Default command — overridden per-service in docker-compose. The API service diff --git a/yunkes/Dockerfile b/yunkes/Dockerfile index 5c5542d..3f8b89f 100644 --- a/yunkes/Dockerfile +++ b/yunkes/Dockerfile @@ -9,7 +9,8 @@ WORKDIR /app # Install deps first (cache-friendly: only re-installs when package*.json # changes; node_modules is never copied from the host via .dockerignore). COPY package.json package-lock.json* ./ -RUN npm install --no-audit --no-fund # ci 对锁同步严格,本地/容器 npm 版本差异致 EUSAGE +# registry 指定 npmmirror(国内构建加速,ECS 上直连 npmjs 慢/超时) +RUN npm install --no-audit --no-fund --registry=https://registry.npmmirror.com # ci 对锁同步严格,本地/容器 npm 版本差异致 EUSAGE # Copy sources and build the production bundle into ./dist. COPY tsconfig*.json vite.config.ts index.html ./