feat(runtime): 服务模式任务执行不再激活浏览器窗口

网页站点(顺心/百世/中通/韵达)执行下载流程时不再把浏览器窗口置顶,避免打扰用户当前工作;流程在后台静默跑。启动登录阶段与交互模式不受影响。

- RuntimeContext 加 foreground 标志:True=任务执行时置顶(交互调试),False=后台静默(服务模式)
- launch_and_prepare(foreground=True) 透传;server worker 以 foreground=False 启动
- _web_handler 按 ctx.foreground 决定单 page 置顶;顺心(list) 透传给 shunxin_download
- shunxin 两个 download 加 foreground 参数,逐账号 bring_to_front 加条件
- 启动登录/初始弹窗清理的 bring_to_front 不变(启动时窗口需可见)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Misaka
2026-07-27 23:17:10 +08:00
parent 29edcbfdbb
commit 82ab6d8be3
3 changed files with 25 additions and 8 deletions

View File

@@ -58,7 +58,7 @@ scheduler = BackgroundScheduler(daemon=True)
def _worker_loop():
"""worker 线程:启动 Playwright + 等就绪 + 任务循环(执行任务 + 心跳)。"""
try:
ctx = launch_and_prepare()
ctx = launch_and_prepare(foreground=False)
worker_state["ctx"] = ctx
worker_state["ready"] = True
# 【P1-2 重启自愈】worker 就绪后清理上轮遗留的 pending/running 僵尸任务

View File

@@ -179,6 +179,7 @@ class RuntimeContext:
sites_to_watch,
debug_mode,
debug_target,
foreground=True,
):
self.pw = pw
self.browser = browser
@@ -188,6 +189,8 @@ class RuntimeContext:
self.sites_to_watch = sites_to_watch
self.debug_mode = debug_mode
self.debug_target = debug_target
# True=任务执行时把 page 置顶交互调试False=后台静默不置顶(服务模式,避免抢焦点)
self.foreground = foreground
def stop(self):
"""关闭 browser + 安能 + Playwright。退出时调用。"""
@@ -236,11 +239,15 @@ def seed_legacy_config():
print(f">> [seed] 从 config.yaml 灌入站点配置: {', '.join(seeded)}")
def launch_and_prepare(debug_mode=False, debug_target=""):
def launch_and_prepare(debug_mode=False, debug_target="", foreground=True):
"""启动 Playwright + 各站 page + 就绪轮询 + 弹窗清理 + 心跳初值,返回 RuntimeContext。
必须在"持有 Playwright 的线程"调用(交互模式主线程 / 服务模式 worker 线程)。
阻塞至所有站点登录就绪才返回。
foregroundTrue=任务执行时把 page 置顶交互调试False=后台静默不置顶(服务模式,
避免抢用户焦点)。仅控制任务执行阶段的 bring_to_front启动登录/初始弹窗清理的置顶
不受影响(启动时窗口需对用户可见以便登录)。
"""
# 0. 状态库建表/迁移 + 从 config.yaml 灌入站点配置(须在 reset_login_states 等之前)
state_store.init_db()
@@ -397,6 +404,7 @@ def launch_and_prepare(debug_mode=False, debug_target=""):
sites_to_watch,
debug_mode,
debug_target,
foreground,
)
@@ -440,11 +448,18 @@ def _dismiss_initial_popups(pages_map):
def _web_handler(site, download_func):
"""构造网页站任务 handler:单 page 先 bring_to_front 再 download顺心(list) 直接传。"""
"""构造网页站任务 handler
foregroundctx控制任务执行时是否把 page 置顶:服务模式后台跑不置顶,避免抢用户
焦点;交互模式置顶便于调试。顺心是 page 列表,置顶标志透传给 shunxin_download。
"""
def handler(ctx):
pg = ctx.pages_map[site]
if not isinstance(pg, list):
if isinstance(pg, list):
# 顺心双账号:置顶与否交给 shunxin_download 在逐账号循环里按 foreground 决定
return download_func(pg, foreground=ctx.foreground)
if ctx.foreground:
pg.bring_to_front()
return download_func(pg)

View File

@@ -149,7 +149,7 @@ def shunxin_merge_final(kind, tags):
pass
def shunxin_expected_download(pages):
def shunxin_expected_download(pages, foreground=True):
"""顺心:应到货物数据下载(双账号/双归属地,内部含异常兜底重试与数据融合)。
pages 为该站点的 page 列表(双账号在同一窗口的各一个标签页)。
@@ -169,7 +169,8 @@ def shunxin_expected_download(pages):
)
for idx, (pg, tag) in enumerate(zip(pages, tags), start=1):
pg.bring_to_front()
if foreground:
pg.bring_to_front()
print(f"\n========== 顺心 · 账号{idx}{tag})应到数据下载 ==========")
ok = with_retry(
f"顺心-{tag}",
@@ -476,7 +477,7 @@ def shunxin_expected_download_impl(page, out_tag=""):
return False
def shunxin_actual_download(pages):
def shunxin_actual_download(pages, foreground=True):
"""顺心:实到货物数据下载(双账号/双归属地,内部含异常兜底重试与数据融合)。
与 shunxin_expected_download 同构:读归属地 → 去重校验 → 顺序各账号下载 →
@@ -494,7 +495,8 @@ def shunxin_actual_download(pages):
)
for idx, (pg, tag) in enumerate(zip(pages, tags), start=1):
pg.bring_to_front()
if foreground:
pg.bring_to_front()
print(f"\n========== 顺心 · 账号{idx}{tag})实到数据下载 ==========")
ok = with_retry(
f"顺心-{tag}",