账号
This commit is contained in:
@@ -4,14 +4,18 @@
|
||||
而是分进程运行,原因:
|
||||
|
||||
- 抓取是**匿名**的,cookie 只用来过 Akamai 限速,丢了重新预热即可,无状态可言。
|
||||
- 加购下单必须**带账号**,cookie 一旦失效不能自动恢复——乐天与 ラクマ 登录都有
|
||||
reCAPTCHA 与设备验证,只能由人重新登录一次。因此这里的失效处理是「明确报错让
|
||||
上游停下」,而不是像抓取那样静默重试。
|
||||
- 加购下单必须**带账号**,cookie 一旦失效需要重登——乐天与 ラクマ 登录都有
|
||||
reCAPTCHA 与设备验证,全自动不可靠。本服务支持「失效时自动重登 + 撞验证码弹
|
||||
有头浏览器让人工接管」的降级路径(依赖 account.yaml 与 relogin_enabled)。
|
||||
- 这份登录态在整个系统里只能有一份。跟抓取同进程的话,抓取一扩容就会复制出 N 份
|
||||
登录态与 N 个订单轮询,同一个账号被并发操作。
|
||||
|
||||
登录态来源是 Playwright 的 storage_state:由 `scripts/login.py` 起一个有头浏览器
|
||||
让人工登录一次后落盘,本服务只读取、不生产。账号密码不经过本服务,也不写日志。
|
||||
登录态来源是 Playwright 的 storage_state:
|
||||
- 首次登录由 scripts/login.py 完成;
|
||||
- 运行时失效且 relogin_enabled=true 时,本模块自动触发 login_runner.login_one()
|
||||
重登(同账号串行,site 级 asyncio.Lock 防并发)。
|
||||
账号密码不在本模块持有——try_relogin() 时从 account.yaml 临时读取,调用结束即
|
||||
被 GC。日志只打 account.id,不打 username/password。
|
||||
|
||||
cookie 会被同时喂给两处:
|
||||
- httpx 客户端 —— 加购这类纯表单提交走 HTTP 更快
|
||||
@@ -80,6 +84,8 @@ class AuthSession:
|
||||
def __init__(self, settings: Settings):
|
||||
self._settings = settings
|
||||
self._sites: dict[str, _SiteAuth] = {}
|
||||
# 自动重登的 site 级互斥锁:同账号同时只能一个登录流程(user_data_dir 被锁)
|
||||
self._relogin_locks: dict[str, asyncio.Lock] = {}
|
||||
|
||||
# ---- 生命周期 ----
|
||||
|
||||
@@ -209,12 +215,94 @@ class AuthSession:
|
||||
async def require_logged_in(self, site: str) -> None:
|
||||
"""确保某站处于登录态,否则抛错
|
||||
|
||||
加购与下单前的统一入口。登录态失效时不做任何自动恢复尝试——两站登录都需要
|
||||
人工过验证码,只能让上游停下来重新跑一次 scripts/login.py。
|
||||
加购与下单前的统一入口。失效时的处理顺序:
|
||||
1. 检测到未登录 → 若 relogin_enabled,尝试 try_relogin(site)
|
||||
2. 重登成功 → 重新 check 一次,登录态转好即放行
|
||||
3. 重登失败 / 未启用 / account.yaml 缺失 → 抛 NotLoggedInError 让 worker 转 needs_human
|
||||
|
||||
重登只在这一层(任务前置检查)触发;任务执行过程中失效不重试,
|
||||
避免脏状态(cart 已提交但响应后 cookie 失效等场景)。
|
||||
"""
|
||||
status = await self.check(site)
|
||||
if not status.logged_in:
|
||||
raise NotLoggedInError(site=site, detail=status.detail)
|
||||
if status.logged_in:
|
||||
return
|
||||
|
||||
# 尝试自动重登(relogin_enabled=False 或无 account.yaml 时返回 False)
|
||||
if await self.try_relogin(site):
|
||||
status = await self.check(site)
|
||||
if status.logged_in:
|
||||
return
|
||||
detail = f"重登后仍判定未登录:{status.detail}"
|
||||
else:
|
||||
detail = status.detail
|
||||
raise NotLoggedInError(site=site, detail=detail)
|
||||
|
||||
# ---- 自动重登 ----
|
||||
|
||||
async def try_relogin(self, site: str) -> bool:
|
||||
"""触发自动重登:读 account.yaml 默认账号 → 跑 login_one → reload cookie
|
||||
|
||||
- relogin_enabled=False 直接返回 False(不抛错,让调用方走原降级路径)
|
||||
- account.yaml 缺失或该站没账号 → 返回 False,仅记日志
|
||||
- 同 site 串行(asyncio.Lock),并发请求只跑一次登录流程
|
||||
- 账号密码不进日志:只打 account.id;login_runner 内部完成填表
|
||||
|
||||
成功返回前已完成 self.reload(site),httpx 客户端拿到新 cookie;
|
||||
Playwright 侧(SiteInteractor)通过 storage_state 文件 mtime 自检刷新。
|
||||
"""
|
||||
if not self._settings.relogin_enabled:
|
||||
return False
|
||||
|
||||
# site 级锁:同账号同 user_data_dir,并发重登会撞锁
|
||||
lock = self._relogin_locks.setdefault(site, asyncio.Lock())
|
||||
async with lock:
|
||||
# 拿锁后再 check 一次——可能别的协程刚重登过
|
||||
status = self.status(site)
|
||||
if status.logged_in:
|
||||
return True
|
||||
|
||||
# 读 account.yaml(失败时降级,不抛错)
|
||||
try:
|
||||
from app.trading.services import login_runner
|
||||
accounts_by_site = login_runner.load_accounts()
|
||||
except FileNotFoundError as exc:
|
||||
logger.warning("自动重登跳过:%s", exc)
|
||||
return False
|
||||
except ValueError as exc:
|
||||
logger.warning("自动重登跳过:account.yaml 格式错误:%s", exc)
|
||||
return False
|
||||
|
||||
account = login_runner.default_account_for(site, accounts_by_site)
|
||||
if account is None:
|
||||
logger.warning("自动重登跳过:account.yaml 没有 %s 的账号", site)
|
||||
return False
|
||||
|
||||
logger.info(
|
||||
"自动重登启动:site=%s account_id=%s timeout=%ss",
|
||||
site, account.id, self._settings.relogin_timeout_seconds,
|
||||
)
|
||||
try:
|
||||
ok = await login_runner.login_one(
|
||||
account,
|
||||
self._settings,
|
||||
timeout_seconds=self._settings.relogin_timeout_seconds,
|
||||
progress=None, # 用 logger,不打到 stdout
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("自动重登异常:site=%s account_id=%s", site, account.id)
|
||||
return False
|
||||
|
||||
if not ok:
|
||||
logger.warning("自动重登失败:site=%s account_id=%s", site, account.id)
|
||||
return False
|
||||
|
||||
# 重登成功:reload cookie 到 httpx 客户端
|
||||
count = self.reload(site)
|
||||
logger.info(
|
||||
"自动重登成功:site=%s account_id=%s cookies=%s",
|
||||
site, account.id, count,
|
||||
)
|
||||
return True
|
||||
|
||||
# ---- 对外访问 ----
|
||||
|
||||
|
||||
Reference in New Issue
Block a user