自动登录接口 + 有状态端容器化部署 + 三服务合并 openapi 导出
自动登录(此前只能人工跑 scripts/login.py 再 /api/auth/reload):
- 新增 POST /api/auth/login:动作顺序与下单前的 require_logged_in 一致(探测 →
未登录则按 account.yaml 登一次 → 再探测),已登录直接跳过不白起浏览器。
刻意**不抛 5001**:失败以 logged_in=false + 各站 detail 正常返回,调用方自己
决定是人工接管还是换账号。
- 新增 RAKUTEN_AUTO_LOGIN_ON_START(默认 false):启动即准备登录态,为容器部署
而存在(镜像里没有落盘的 storage_state)。做成后台任务而非启动阻塞——登录最长
等 relogin_timeout_seconds(默认 300s,撞验证码时在等人工),阻塞会让 /health
在这段时间里连端口都不通;关服务时 cancel 掉在途的那次。
- 自动登录不绕过站点校验:凭据是用户自己配在 account.yaml 里的,代填进站点自己的
登录表单,撞 reCAPTCHA / 设备验证会停在有头浏览器等人工,等不到就超时失败。
容器化部署(新增 Dockerfile.trading + docker-compose.yml):
- 有状态端单独出镜像不是为了整洁:下单/结算必须用**有头** Chromium(headless 会让
结算 SPA 失灵),镜像要带 Xvfb + 日文字体 + 给人工接管用的可选 x11vnc,抓取镜像
没有这些。网关复用同一镜像只换 command。
- Jenkinsfile 一条流水线产出两个镜像,BUILD_SCRAPING / BUILD_TRADING 两个开关控制。
- .dockerignore 补上 account.yaml / .auth/ / .browser-data/ / data/:明文密码+卡号、
可直接冒充账号的 cookie、带登录态的浏览器 profile、含真实 PII 的证据快照,都不该
进镜像也不该进 build context,运行时一律走挂载。
- .env.example 里 RAKUTEN_AUTO_LOGIN_ON_START 刻意留成注释:compose 的变量插值与
env_file 读的是同一个 ./.env,这里写成显式值会让 compose 的 `${...:-true}` 失效,
按 compose 文件头「cp .env.example .env」走反而不会自动登录。
openapi 导出(scripts/export_openapi.py):三服务合并成一份可直接导入 Apifox /
Postman 的文档,每条接口带 operation 级 servers(不必手动切端口)。鉴权标注是遍历
FastAPI 依赖树认出真的挂了 require_bearer_token 的接口,不按路径猜。
openapi.json 本身仍是 gitignore 的本地生成物,因此 tests/test_openapi_export.py
只在内存里校验合并逻辑(三服务覆盖、operation 级 servers、除 /health 外全部标鉴权、
operationId 唯一、$ref 可解析),不断言「文件内容 == 当前导出结果」——CI 的全新
clone 里没有这个文件,那种断言必然失败。代价是「改了接口忘了重新导出」没有自动
兜底,得手动跑 --check,已在 README 里点明。
398 测试全绿;另单独验证过缺 openapi.json 时该文件 5 个用例仍通过(CI 场景)。
compose 的变量插值行为只按文档核对,本机没有 docker 未能实测。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
# 有状态端部署(下单购买 + 订单查询/监控)
|
||||
#
|
||||
# 默认只起交易服务 trading(本地机侧:持登录态、执行下单、常驻订单监控):
|
||||
# docker compose up -d
|
||||
# 下单任务网关按设计部署在**服务器侧**(与抓取服务同机),是另一个部署单元,
|
||||
# 所以放在 gateway profile 里,不会被默认启动。真要在这台机上一起跑(自包含
|
||||
# 联调 / 单机部署)时:
|
||||
# docker compose --profile gateway up -d
|
||||
#
|
||||
# 两个服务都**只能单实例**:登录态 cookie 全局唯一、订单监控是常驻轮询、SQLite
|
||||
# 单连接 + 全局并发度 1。不要 `--scale`,不要在前面挂多副本。
|
||||
#
|
||||
# 部署前置(缺一样服务就跑不通,不是可选项):
|
||||
# 1. cp .env.example .env,至少填 RAKUTEN_BEARER_TOKEN / RAKUTEN_ORDER_GATEWAY_URL /
|
||||
# RAKUTEN_SCRAPER_BASE_URL(后两个留空则 worker 不启动,只剩购物车与登录态接口)。
|
||||
# 2. cp account.yaml.example account.yaml 并填真实凭据(session upgrade 复核密码、
|
||||
# 手机号补录、支付方式核对都要读它)。**必须先建文件再 up**,否则 Docker 会
|
||||
# 把这个挂载点当目录创建,容器里读到的是个空目录。
|
||||
# 3. 登录态 cookie(.auth/rakuten_state.json):**不必**先在宿主机准备——
|
||||
# RAKUTEN_AUTO_LOGIN_ON_START 默认开,容器起来就按 account.yaml 自己登一次。
|
||||
# 撞 reCAPTCHA / 设备验证会停在浏览器里等人工,这时把 RAKUTEN_VNC_ENABLED 设成
|
||||
# true(配 RAKUTEN_VNC_PASSWORD)连 127.0.0.1:5900 接管,完成后:
|
||||
# curl -X POST -H "Authorization: Bearer $TOKEN" http://127.0.0.1:31108/api/auth/login
|
||||
# 也可以照旧在宿主机跑 scripts/login.py 再挂载进来,两条路都行。
|
||||
#
|
||||
# 敏感面:.auth/(可直接冒充账号的 cookie)、account.yaml(明文密码+卡号)、
|
||||
# data/(真实姓名地址等 PII 与证据快照)全在宿主机目录里,注意宿主机权限与备份加密。
|
||||
|
||||
services:
|
||||
trading:
|
||||
# 交易服务:加购 → 下单 → 付款 → 订单监控,以及 /api/auth/* /api/cart/*
|
||||
image: ${RAKUTEN_TRADING_IMAGE:-git.jerryyan.net/jp/rakuten-trading}:${RAKUTEN_TRADING_TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.trading
|
||||
container_name: rakuten-trading
|
||||
restart: unless-stopped
|
||||
# PID 1 用 docker-init:Chromium 会派生一堆子进程,崩溃时需要有人收尸
|
||||
init: true
|
||||
# Chromium 默认 /dev/shm 只有 64MB,渲染稍重的结算页会直接 crash
|
||||
shm_size: 1gb
|
||||
# 停容器时给正在执行的下单任务留出收尾时间(uvicorn 关 lifespan → worker 停轮询
|
||||
# → 取消订单监控 → 关浏览器)。**停容器前最好先确认没有在途任务**:进程被硬杀
|
||||
# 时站点侧可能已经提交成功,本地没记录,恢复要走 verify_on_site 人工核对。
|
||||
stop_grace_period: 120s
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
# 容器内必须监听 0.0.0.0,对外只从 127.0.0.1 映射(见 ports)
|
||||
RAKUTEN_TRADING_HOST: 0.0.0.0
|
||||
RAKUTEN_TRADING_PORT: 31108
|
||||
RAKUTEN_HEALTH_PORT: 31108
|
||||
RAKUTEN_APP_ENV: prod
|
||||
# worker 标识:默认取主机名,容器主机名是随机 ID,重建就变——显式固定,
|
||||
# 便于在网关侧对上是哪台机在领任务
|
||||
RAKUTEN_WORKER_ID: ${RAKUTEN_WORKER_ID:-rakuten-trading-docker}
|
||||
# 有头 Chromium 需要虚拟显示(镜像内 Xvfb),别关
|
||||
RAKUTEN_XVFB_ENABLED: "true"
|
||||
# 启动即按 account.yaml 自动登录(后台跑,不阻塞端口)。撞 reCAPTCHA / 设备验证
|
||||
# 时会停在浏览器里等人工——那时开 VNC 接管,或事后调 POST /api/auth/login 重试。
|
||||
RAKUTEN_AUTO_LOGIN_ON_START: ${RAKUTEN_AUTO_LOGIN_ON_START:-true}
|
||||
RAKUTEN_RELOGIN_ENABLED: ${RAKUTEN_RELOGIN_ENABLED:-true}
|
||||
# 需要远程盯着浏览器(登录验证码、3DS、needs_human 人工接管)时改成 true,
|
||||
# 并设 RAKUTEN_VNC_PASSWORD;端口只映射到 127.0.0.1,走 SSH 隧道访问
|
||||
RAKUTEN_VNC_ENABLED: ${RAKUTEN_VNC_ENABLED:-false}
|
||||
RAKUTEN_VNC_PASSWORD: ${RAKUTEN_VNC_PASSWORD:-}
|
||||
ports:
|
||||
# 这些接口能操作真实账号,只对本机开放;要跨机访问请走内网地址或 SSH 隧道
|
||||
- "127.0.0.1:31108:31108"
|
||||
- "127.0.0.1:5900:5900"
|
||||
volumes:
|
||||
# 登录态 cookie:容器要能写(自动重登会重写 storage_state)
|
||||
- ./.auth:/app/.auth
|
||||
# 账号凭据:只读挂载。文件必须先存在,见文件头「部署前置」第 2 条
|
||||
- ./account.yaml:/app/account.yaml:ro
|
||||
# login.py 的持久化浏览器目录(保住设备指纹,减少重登触发风控核验)
|
||||
- ./.browser-data:/app/.browser-data
|
||||
# 订单 SQLite(执行事实的权威记录)+ 证据快照,丢了没法追溯下过什么单
|
||||
- ./data:/app/data
|
||||
- ./logs:/app/logs
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "5"
|
||||
# Chromium 的 namespace sandbox 在容器里可能被 seccomp/AppArmor 挡住(表现为
|
||||
# 浏览器起不来、Playwright 报 Target closed)。宿主机内核不允许非特权 user
|
||||
# namespace 时,按下面两条择一放行;两条都不想放,则需要在
|
||||
# site_interact.py 的 launch(args=...) 里加 --no-sandbox(改生产代码,需另行决定)。
|
||||
# security_opt:
|
||||
# - seccomp:unconfined
|
||||
# cap_add:
|
||||
# - SYS_ADMIN
|
||||
|
||||
gateway:
|
||||
# 下单任务网关:任务队列 + 状态镜像,上游业务系统在这里提交下单任务与查任务状态。
|
||||
# 正式拓扑里它在服务器侧、trading 在本地,两者靠出站长轮询联通(docs/order-gateway.md);
|
||||
# 这里只是为了单机自包含跑起来,默认不启动。
|
||||
profiles: ["gateway"]
|
||||
image: ${RAKUTEN_TRADING_IMAGE:-git.jerryyan.net/jp/rakuten-trading}:${RAKUTEN_TRADING_TAG:-latest}
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.trading
|
||||
container_name: rakuten-gateway
|
||||
restart: unless-stopped
|
||||
init: true
|
||||
command: ["python", "-m", "app.gateway.main"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
RAKUTEN_GATEWAY_HOST: 0.0.0.0
|
||||
RAKUTEN_GATEWAY_PORT: 31109
|
||||
RAKUTEN_HEALTH_PORT: 31109
|
||||
RAKUTEN_APP_ENV: prod
|
||||
# 网关不碰浏览器,跳过 Xvfb
|
||||
RAKUTEN_XVFB_ENABLED: "false"
|
||||
ports:
|
||||
# 上游业务系统要能调到;对外暴露时请在反代上收紧来源,鉴权只有一个 Bearer token
|
||||
- "${RAKUTEN_GATEWAY_BIND:-127.0.0.1}:31109:31109"
|
||||
volumes:
|
||||
# 任务队列 SQLite(data/gateway.db)必须持久化,丢了等于丢一批下单任务
|
||||
- ./data:/app/data
|
||||
- ./logs:/app/logs
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "50m"
|
||||
max-file: "5"
|
||||
Reference in New Issue
Block a user