"""交易服务容器:集中管理交易侧服务实例,用于依赖注入 持有三类东西: - 登录态会话(auth_session)——加购与下单必须用同一份 cookie,全程共享 - 站点交互器(site)——持有 Playwright BrowserContext,加购/校验/清空/删除共用。 不依赖 gateway URL:HTTP 接口与 worker 都要用它,故总是构造。 - 本地下单 worker(可选)——RAKUTEN_ORDER_GATEWAY_URL 留空时整套不构造 worker 的子组件(gateway_client / local_db / evidence_store / runner)都在 app.trading.worker 包内,与抓取/网关互不 import;worker 需要商品数据时走抓取 服务的 HTTP 接口,需要任务调度时走网关的 HTTP 接口。 """ from __future__ import annotations from dataclasses import dataclass from app.shared.config import Settings from app.trading.services.auth_session import AuthSession @dataclass(slots=True) class TradingContainer: """交易服务容器 与抓取容器最本质的差别不是字段多少,而是**这个进程有状态**:登录态、 订单、页面证据都属于某个具体账号,因此交易服务只能单实例运行 (或按账号分片),不能像抓取服务那样随意横向扩容。 site 字段始终构造(HTTP /api/cart/* 接口与 worker 都依赖它)。 worker_* 字段在 RAKUTEN_ORDER_GATEWAY_URL 留空时为 None——此时交易服务 只跑登录态与购物车接口,不起后台 worker。 """ settings: Settings auth_session: AuthSession # 站点交互器:持有 Playwright + 账号 cookie,加购/校验/清空/删除共用一份。 # 不依赖 gateway URL——HTTP 接口与 worker 都要用它,故总是构造。 site: object | None = None # app.trading.worker.site_interact.SiteInteractor # 以下四项仅在 order_gateway_url 配置时构造;否则为 None,worker 不启动 worker_client: object | None = None # app.trading.worker.client.GatewayClient worker_local_db: object | None = None # app.trading.worker.local_db.LocalDB worker_evidence: object | None = None # app.trading.worker.evidence.EvidenceStore worker_runner: object | None = None # app.trading.worker.runner.WorkerRunner # 账号只读查询的第二条循环(docs/order-gateway.md §11)。与下单 worker 同生共死, # 但跑在各自的 asyncio 任务里:下单一跑几分钟,查询不该排在它后面。 query_runner: object | None = None # app.trading.worker.query_runner.QueryRunner