159 lines
6.0 KiB
Python
159 lines
6.0 KiB
Python
"""交易侧 API 数据模型:登录态、(后续的)加购、下单与订单监控
|
|
|
|
与抓取侧模型(app/scraping/models/scrape.py)分开的理由和服务本身拆开的理由
|
|
一致:抓取模型描述的是「站点上有什么」,这里描述的是「我们对某个账号做了什么、
|
|
现在处于哪一步」——后者有生命周期、有状态迁移,会持久化,不是一次请求的产物。
|
|
|
|
响应信封 `ApiResponse` 与抓取侧共用,在 app/shared/api.py。
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from enum import StrEnum
|
|
from typing import Any
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class AuthSite(StrEnum):
|
|
"""支持登录的站点
|
|
|
|
交易服务只管理乐天市场的购物车购买、支付与订单监控;ラクマ 不进入交易链路。
|
|
"""
|
|
|
|
RAKUTEN = "rakuten"
|
|
|
|
|
|
class AuthStatusRequest(BaseModel):
|
|
"""登录态查询请求"""
|
|
|
|
site: AuthSite | None = Field(default=None, description="站点标识;不传则返回所有已配置站点")
|
|
refresh: bool = Field(default=True, description="是否真实打一次请求探测;false 时只读缓存")
|
|
|
|
|
|
class AuthSiteStatus(BaseModel):
|
|
"""单站登录态"""
|
|
|
|
site: str = Field(description="站点标识(rakuten)")
|
|
state_file_exists: bool = Field(description="是否已跑过 scripts/login.py(本地登录态文件存在)")
|
|
logged_in: bool | None = Field(description="是否已登录;null 表示尚未探测")
|
|
checked_age_seconds: float | None = Field(default=None, description="上次探测距现在的秒数;未探测为 null")
|
|
detail: str = Field(default="", description="补充说明(如未登录原因)")
|
|
|
|
|
|
class AuthStatusData(BaseModel):
|
|
"""登录态查询响应"""
|
|
|
|
sites: list[AuthSiteStatus] = Field(default_factory=list, description="各站点登录态列表")
|
|
|
|
|
|
class AuthReloadRequest(BaseModel):
|
|
"""重新加载登录态请求(人工登录完成后调用,免重启服务)"""
|
|
|
|
site: AuthSite | None = Field(default=None, description="站点标识;不传则重载所有已配置站点")
|
|
|
|
|
|
class AuthReloadData(BaseModel):
|
|
"""重新加载登录态响应"""
|
|
|
|
reloaded: dict[str, int] = Field(default_factory=dict, description="重载结果:站点 -> cookie 条数")
|
|
sites: list[AuthSiteStatus] = Field(default_factory=list, description="重载后的各站点登录态")
|
|
|
|
|
|
class AuthLoginRequest(BaseModel):
|
|
"""触发自动登录请求"""
|
|
|
|
site: AuthSite | None = Field(default=None, description="站点标识;不传则对所有已配置站点各跑一次")
|
|
|
|
|
|
class AuthLoginData(BaseModel):
|
|
"""触发自动登录响应
|
|
|
|
logged_in 是本次动作的最终结论(所有涉及站点都登录着才为 true);
|
|
relogin_attempted 标出哪些站点真的跑了登录流程——已登录的站点直接跳过,
|
|
不会白起一次浏览器。
|
|
"""
|
|
|
|
logged_in: bool = Field(description="本次动作的最终结论:所有涉及站点都登录着才为 true")
|
|
relogin_attempted: dict[str, bool] = Field(
|
|
default_factory=dict, description="各站点是否真的跑了登录流程;已登录的站点直接跳过"
|
|
)
|
|
sites: list[AuthSiteStatus] = Field(default_factory=list, description="各站点登录态")
|
|
|
|
|
|
class TradingHealthData(BaseModel):
|
|
"""交易服务健康检查响应数据
|
|
|
|
只读缓存的登录态,不触发网络探测——健康检查会被高频轮询,实时结果请用
|
|
POST /api/auth/status。
|
|
"""
|
|
|
|
status: str = Field(description="服务状态,健康为 ok")
|
|
auth: dict[str, Any] = Field(default_factory=dict, description="缓存的各站点登录态(不触发网络探测)")
|
|
|
|
|
|
# ---- 购物车接口(/api/cart/*)----
|
|
|
|
|
|
class CartAddRequest(BaseModel):
|
|
"""加购请求
|
|
|
|
item_url 必填;variant_id / choice 不传时由 SiteInteractor 按商品页
|
|
__INITIAL_STATE__.purchase 自动选(多规格选第一个非售罄,必填选项选第一个候选值)。
|
|
choice 接受字符串("颜色:赤")或字符串列表(["颜色:赤", "サイズ:M"])。
|
|
"""
|
|
|
|
item_url: str = Field(description="商品页 URL")
|
|
quantity: int = Field(default=1, description="加购数量")
|
|
variant_id: str | None = Field(
|
|
default=None, description="多规格商品的规格 ID;不传时自动选第一个非售罄规格"
|
|
)
|
|
choice: str | list[str] | None = Field(
|
|
default=None,
|
|
description="商品选项,如 \"颜色:赤\";可传字符串或字符串列表,不传时必填选项自动选第一个候选值",
|
|
)
|
|
|
|
|
|
class CartStatusRequest(BaseModel):
|
|
"""购物车状态查询请求(无入参,保留结构对称)"""
|
|
|
|
|
|
class CartClearRequest(BaseModel):
|
|
"""清空购物车请求(无入参)"""
|
|
|
|
|
|
class CartRemoveRequest(BaseModel):
|
|
"""删除指定商品请求"""
|
|
|
|
item_id: str = Field(description="要删除的商品 ID")
|
|
|
|
|
|
class CartAddData(BaseModel):
|
|
"""加购响应数据"""
|
|
|
|
added: bool = Field(default=True, description="是否加购成功")
|
|
item_id: str = Field(description="加购的商品 ID")
|
|
shop_bid: str = Field(description="店铺 bid")
|
|
cart_count: int = Field(description="加购后的购物车商品件数;-1 表示加购成功但末尾 count 查询失败")
|
|
|
|
|
|
class CartStatusData(BaseModel):
|
|
"""购物车状态响应数据"""
|
|
|
|
logged_in: bool = Field(description="当前是否已登录")
|
|
count: int = Field(description="购物车商品件数(空车为 0)")
|
|
raw_status: str = Field(description="站点 cart count API 的状态码字符串,\"100\" 表示正常、\"101\" 表示空车")
|
|
|
|
|
|
class CartClearData(BaseModel):
|
|
"""清空购物车响应数据"""
|
|
|
|
removed_count: int = Field(description="实际点击「削除」按钮的次数")
|
|
cart_count: int = Field(description="清空后的购物车商品件数;-1 表示末尾 count 查询失败")
|
|
|
|
|
|
class CartRemoveData(BaseModel):
|
|
"""删除指定商品响应数据"""
|
|
|
|
removed: bool = Field(description="末尾 cart HTML 已不含该 item_id 时为 true")
|
|
item_id: str = Field(description="被删除的商品 ID")
|