加 /api/cart/* 接口;加购链路完全归 trading
trading 新增 4 条购物车接口(POST /api/cart/{add,status,clear,remove}),
全部 Bearer 鉴权、走 SiteInteractor(Playwright + storage_state)。同步把
SiteInteractor 从 gateway URL 解耦——lifespan 总是构造与启停,container
字段 worker_site → site,加 asyncio.Lock 让 HTTP 与 worker 共用同一把锁
(同账号串行硬约束)。clear/remove 用 UI 点击 button[aria-label="削除"],
探针回报这是稳定 selector;真账号实测前先用此路径。
抽 ichiba 加购字段解析到 app/shared/purchase_contract.py(常量 +
inventory_flag_for + basket_domain_of + base_form_fields),原本 scraping
与 trading 重复实现同一段 __INITIAL_STATE__.purchase 解析。进一步发现
README 写的「purchase 块是两服务契约」实际未落地——trading 必须 Playwright
开页(httpx 被 TLS 指纹拦死),本地抽比再调 /api/item_detail 更快更新鲜。
删除 scraping 端 PurchaseInfo/PurchaseOption/PurchaseOptionValue 模型、
各站 _purchase_info 函数、tests/test_purchase.py。ItemDetailData 保留
purchase_condition / is_sold_out / purchase_unit / sku 等商品状态字段。
README「加购与下单」段重写。
328 测试全绿(含架构测试守住三方互不 import)。
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -347,56 +347,15 @@ class Breadcrumb(BaseModel):
|
||||
url: str = ""
|
||||
|
||||
|
||||
class PurchaseOptionValue(BaseModel):
|
||||
"""商品选项的一个可选值"""
|
||||
|
||||
value_id: str = ""
|
||||
name: str = ""
|
||||
|
||||
|
||||
class PurchaseOption(BaseModel):
|
||||
"""商品选项(選択肢),如「名入れ」「ラッピング」
|
||||
|
||||
加购时需要按 `名称:取值` 的形式拼进 options_field 指定的字段。
|
||||
"""
|
||||
|
||||
option_id: str = ""
|
||||
name: str = ""
|
||||
type: str = "" # select 单选 / check 多选 / text 自由文本
|
||||
is_required: bool = False
|
||||
values: list[PurchaseOptionValue] = Field(default_factory=list) # type=text 时为空
|
||||
|
||||
|
||||
class PurchaseInfo(BaseModel):
|
||||
"""构造加购请求所需的信息
|
||||
|
||||
本服务只提供数据、不执行加购——加购需要已登录的乐天账号会话,由持有登录态的
|
||||
下游负责。四个来源的加购端点与字段名各不相同,因此这里不写死字段,而是把
|
||||
「提交到哪里、固定字段是什么、数量/规格/选项各该用哪个字段名」显式描述出来:
|
||||
|
||||
payload = {**form_fields}
|
||||
payload[quantity_field] = 数量 # quantity_field 为空表示不支持指定数量
|
||||
payload[variant_field] = 选中的 sku.variants[].variant_id # variant_field 为空表示无规格
|
||||
payload[options_field] = ["选项名:取值", ...] # options_field 为空表示无选项
|
||||
|
||||
然后以 cart_method 提交到 cart_url。
|
||||
"""
|
||||
|
||||
cart_url: str = ""
|
||||
cart_method: str = "POST"
|
||||
form_fields: dict[str, str] = Field(default_factory=dict)
|
||||
quantity_field: str = ""
|
||||
variant_field: str = ""
|
||||
options_field: str = ""
|
||||
options: list[PurchaseOption] = Field(default_factory=list)
|
||||
has_required_options: bool = False
|
||||
|
||||
|
||||
class ItemDetailData(BaseModel):
|
||||
"""商品详情数据
|
||||
|
||||
部分乐天官方店的商品页会跳转到独立子站,各子站页面结构不同、可提供的字段也
|
||||
不同。source 标明这条数据由哪个站点解析而来,字段覆盖差异见 README。
|
||||
|
||||
加购(构造 cart 请求)不在本服务范围内——加购需要已登录的乐天账号会话,
|
||||
归 trading 服务(app.trading)。本响应只描述「商品状态」:能不能买
|
||||
(purchase_condition / is_sold_out)、规格(sku.variants)、起订单位等。
|
||||
"""
|
||||
|
||||
source: str = "ichiba" # ichiba / books / brandavenue / biccamera
|
||||
@@ -421,7 +380,6 @@ class ItemDetailData(BaseModel):
|
||||
breadcrumbs: list[Breadcrumb] = Field(default_factory=list)
|
||||
shipping: ShippingInfo = Field(default_factory=ShippingInfo)
|
||||
sku: SkuInfo = Field(default_factory=SkuInfo)
|
||||
purchase: PurchaseInfo = Field(default_factory=PurchaseInfo)
|
||||
|
||||
|
||||
class HealthData(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user