交易服务收敛为仅 rakuten:移除 rakuma 登录态与下单入口

trading 不再管理 ラクマ 的购物车购买、支付与订单监控。删除 auth_site /
auth_session / login_runner / models.AuthSite 中的 rakuma 分支与常量、
account.yaml.example 的 rakuma 段,并清理相关测试。scraping 侧的
ラクマ 抓取 API(/api/rakuma/*)保留不动。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-28 11:50:07 +08:00
co-authored by Claude Opus 4.6
parent 6247d68fb5
commit 65e3ed31f8
13 changed files with 63 additions and 121 deletions
+12 -21
View File
@@ -4,12 +4,15 @@
而是分进程运行,原因:
- 抓取是**匿名**的,cookie 只用来过 Akamai 限速,丢了重新预热即可,无状态可言。
- 加购下单必须**带账号**,cookie 一旦失效需要重登——乐天与 ラクマ 登录都有
reCAPTCHA 与设备验证,全自动不可靠。本服务支持「失效时自动重登 + 撞验证码弹
有头浏览器让人工接管」的降级路径(依赖 account.yaml 与 relogin_enabled)。
- 加购下单必须**带账号**,cookie 一旦失效需要重登——乐天登录有 reCAPTCHA 与设备
验证,全自动不可靠。本服务支持「失效时自动重登 + 撞验证码弹有头浏览器让人工接管」
的降级路径(依赖 account.yaml 与 relogin_enabled)。
- 这份登录态在整个系统里只能有一份。跟抓取同进程的话,抓取一扩容就会复制出 N 份
登录态与 N 个订单轮询,同一个账号被并发操作。
交易服务只管理乐天市场的购物车购买、支付与订单监控;ラクマ 抓取仍在抓取服务里
提供,但不进入交易链路。本模块只持有 rakuten 一个站点的登录态。
登录态来源是 Playwright 的 storage_state:
- 首次登录由 scripts/login.py 完成;
- 运行时失效且 relogin_enabled=true 时,本模块自动触发 login_runner.login_one()
@@ -75,7 +78,7 @@ class _SiteAuth:
class AuthSession:
"""持有两站登录态的会话
"""持有登录态的会话
职责边界:只负责「有没有登录态、cookie 是什么、还有效吗」,
具体加购/下单的业务请求由各自的 client 组装后借这里的 HTTP 客户端发出。
@@ -90,7 +93,7 @@ class AuthSession:
# ---- 生命周期 ----
async def start(self) -> None:
"""站创建带登录 cookie 的 HTTP 客户端
"""为站创建带登录 cookie 的 HTTP 客户端
登录态文件不存在时同样创建客户端(只是没有 cookie),这样 /health 与
/api/auth/status 能如实回报「未登录」,而不是整个服务起不来。
@@ -175,10 +178,9 @@ class AuthSession:
auth = self._require_site(site)
async with auth.lock:
try:
if site == "rakuten":
logged_in, detail = await self._check_rakuten(auth)
else:
logged_in, detail = await self._check_rakuma(auth)
if site != "rakuten":
raise ValueError(f"未知站点:{site}")
logged_in, detail = await self._check_rakuten(auth)
except httpx.HTTPError as exc:
raise UpstreamRequestError(
f"登录态探测请求失败:site={site} err={type(exc).__name__}: {exc}"
@@ -201,17 +203,6 @@ class AuthSession:
return False, "购物车页显示未登录"
return True, "购物车页未出现未登录标记"
async def _check_rakuma(self, auth: _SiteAuth) -> tuple[bool, str]:
"""/mypage 被重定向到 /users/sign_in 即未登录"""
response = await auth.client.get(auth_site.PROFILES["rakuma"].probe_url)
if response.status_code >= 400:
return False, f"mypage 返回 status {response.status_code}"
if not auth_site.is_logged_in(
"rakuma", final_url=str(response.url), body=response.text
):
return False, "mypage 被重定向至登录页"
return True, "mypage 正常返回"
async def require_logged_in(self, site: str) -> None:
"""确保某站处于登录态,否则抛错
@@ -338,7 +329,7 @@ class AuthSession:
)
def status_all(self) -> dict[str, dict[str, Any]]:
"""两站登录态快照,供健康检查展示"""
"""登录态快照,供健康检查展示"""
return {name: self.status(name).to_dict() for name in self._sites}
def _require_site(self, site: str) -> _SiteAuth: