上游要的不只是网关记的任务状态镜像,还有「已登录账号在站点上的真实订单」,
但账号只在 NAT 后本地机上,只能经网关队列走。新增独立查询通道(§11):
- gateway 单开 account_queries 表 + QueryStatus 状态机,接口
POST /api/account/queries(幂等)/ lease / {id}/result / {id}
- 不复用下单任务队列:查询是只读,租约过期可安全重投(与下单「绝不自动
重投」相反),且不该被全局并发度 1 堵死、task_reports 是订单镜像不能污染
- 本地交易服务起第二条常驻循环 query_runner,领到即调 SiteInteractor 真读:
order_list 复用已实测的 list_recent_orders(规范化字段 + 站点
orderListData 原文),order_detail 复用 fetch_order_detail(配送阶段 +
页面 __INITIAL_STATE__ 原样透传,结构未经真实样本,不抽字段)
- 账号级串行仍由 SiteInteractor 的锁保证;每次执行套超时按失败回报
- 错误码 6005/6006(查询通道,可重试只读区别于 6001-6004);/health 暴露
queued_query_count;结果体积上限先丢原始 JSON
openapi.json 重导,docs/order-gateway.md §11、README、.env.example 补全
配置与实测边界。全量测试 404→454 通过。
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
46 lines
1.5 KiB
Python
46 lines
1.5 KiB
Python
"""worker 侧数据模型:不引用网关的 Pydantic 模型
|
|
|
|
worker 通过 HTTP 与网关交互,看到的只有 JSON。这里定义 worker 自己用来表达
|
|
「领到的任务」「核对结论」的数据类——结构上对应网关响应,但**不 import**
|
|
app.gateway 的任何东西,保证架构边界(worker → shared,禁止 worker → gateway)。
|
|
|
|
OrderState / TaskStatus 枚举在 app.shared.task_state,两侧共用同一份取值表。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class LeaseTask:
|
|
"""worker 从网关领到的任务
|
|
|
|
对应网关 GET /api/orders/lease 的 LeaseData,但用 dataclass 表达——worker
|
|
内部不依赖网关侧的 Pydantic 模型。
|
|
"""
|
|
|
|
task_id: str
|
|
site: str
|
|
intent: dict[str, Any] = field(default_factory=dict)
|
|
lease_expires_at: str = ""
|
|
lease_count: int = 1
|
|
known_state: str | None = None
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class QueryTask:
|
|
"""worker 从网关领到的账号只读查询单
|
|
|
|
对应网关 GET /api/account/queries/lease 的 QueryLeaseData。没有 known_state
|
|
之类的字段——查询是一次性只读动作,`attempt > 1` 只说明上一轮超时被重投了,
|
|
worker 不需要因此改变行为(重跑安全,这正是它与下单任务的根本差别)。
|
|
"""
|
|
|
|
query_id: str
|
|
site: str
|
|
kind: str
|
|
params: dict[str, Any] = field(default_factory=dict)
|
|
lease_expires_at: str = ""
|
|
attempt: int = 1
|