修复虎牙注册续跑逻辑:默认从停止处继续,不重试已失败项

「继续未完成」只跑 pending/stopped;另提供「重试失败」可选重跑 error。避免大批量中途停止后把失败号全部再跑一遍。
This commit is contained in:
yml2213
2026-07-12 20:08:03 +08:00
parent e18b2c6e0c
commit 851d8c122b
5 changed files with 142 additions and 60 deletions
+60 -29
View File
@@ -1,4 +1,4 @@
"""虎牙自动注册批次执行器(支持持久化与失败续跑)。"""
"""虎牙自动注册批次执行器(支持持久化与从停止处继续)。"""
from __future__ import annotations
@@ -24,10 +24,15 @@ from ..models import (
from .huya_service import upsert_huya_cookie
# 运行中状态:服务中断后视为可续跑
# 运行中状态:服务中断后视为未完成,可继续
RUNNING_ITEM_STATUSES = frozenset({"sending", "waiting", "changing", "logging"})
# 可重试状态:失败 / 停止 / 中断后的运行态 / 尚未开始
RETRYABLE_ITEM_STATUSES = frozenset({"error", "stopped", "pending"} | RUNNING_ITEM_STATUSES)
# 续跑(continue):只跑「还没跑完」的,不重试已明确失败(error)、不重跑成功(success)
# - pending: 队列里还没轮到
# - stopped: 点停止时尚未开始或被中断
# - running 中间态: 进程中断时卡在半路
RESUME_ITEM_STATUSES = frozenset({"pending", "stopped"} | RUNNING_ITEM_STATUSES)
# 仅重试明确失败(可选模式)
RETRY_FAILED_ITEM_STATUSES = frozenset({"error"})
TERMINAL_BATCH_STATUSES = frozenset({"finished", "stopped", "error", "interrupted"})
@@ -216,16 +221,16 @@ def load_batch_snapshot(batch_id: str, *, recover_interrupted: bool = True) -> d
if recover_interrupted and batch_row.status in {"running", "pending"}:
now = _now()
batch_row.status = "interrupted"
batch_row.message = "服务中断或未真正启动,可从失败/未完成项续跑"
batch_row.message = "服务中断或未真正启动,可继续未完成项"
batch_row.finished_at = now
for item in item_rows:
if item.status in RUNNING_ITEM_STATUSES:
item.status = "error"
item.message = "服务中断,可续跑"
# 记为 stopped 而非 error,以便「继续未完成」能接着跑
item.status = "stopped"
item.message = "服务中断,可继续"
item.finished_at = now
elif item.status == "pending":
# 保留 pending 供续跑,仅更新提示
item.message = item.message or "等待续跑"
item.message = item.message or "等待继续"
_refresh_batch_counts(batch_row, item_rows)
db.commit()
db.refresh(batch_row)
@@ -259,7 +264,7 @@ def list_batch_summaries(limit: int = 50, live_batch_ids: set[str] | None = None
# 无内存 runner 且状态仍是 running/pending,展示为 interrupted
if status in {"running", "pending"} and row.batch_id not in live:
status = "interrupted"
message = message or "服务中断或未真正启动,可从失败/未完成项续跑"
message = message or "服务中断或未真正启动,可继续未完成项"
running_count = 0
if status == "running":
running_count = max(
@@ -855,6 +860,7 @@ class HuyaRegisterRegistry:
batch_id: str,
proxy_config: Optional[ProxyConfigModel] = None,
*,
mode: str = "continue",
concurrency: int | None = None,
wait_seconds: float | None = None,
poll_interval: float | None = None,
@@ -862,12 +868,36 @@ class HuyaRegisterRegistry:
fixed_password: str | None = None,
use_proxy: bool | None = None,
) -> HuyaRegisterRunner:
"""从失败/停止/未完成的条目续跑;成功项跳过。"""
"""继续批次。
mode:
- continue(默认):从停止处往下跑,只处理 pending/stopped/中断中,
**不重试已失败(error)**,不重跑已成功。
- retry_failed:只重试 error。
- all_unfinishederror + pending + stopped + 中断中(旧行为)。
"""
with self._lock:
existing = self._runners.get(batch_id)
if existing and existing.batch.status == "running":
raise RuntimeError("批次正在运行中,请先停止或等待完成")
mode = (mode or "continue").strip().lower()
if mode not in {"continue", "retry_failed", "all_unfinished"}:
raise ValueError("mode 仅支持 continue / retry_failed / all_unfinished")
if mode == "continue":
target_statuses = RESUME_ITEM_STATUSES
empty_msg = "没有未完成条目可继续(已成功/已失败的不会自动重跑)"
run_label = "继续未完成"
elif mode == "retry_failed":
target_statuses = RETRY_FAILED_ITEM_STATUSES
empty_msg = "没有失败条目可重试"
run_label = "重试失败"
else:
target_statuses = RESUME_ITEM_STATUSES | RETRY_FAILED_ITEM_STATUSES
empty_msg = "没有可继续的未完成/失败条目"
run_label = "续跑未完成"
db = SessionLocal()
try:
batch_row = (
@@ -878,9 +908,9 @@ class HuyaRegisterRegistry:
if not batch_row:
raise ValueError("批次不存在")
if batch_row.status == "running":
# 无内存 runner 的 running 视为中断,允许续
# 无内存 runner 的 running 视为中断,允许
batch_row.status = "interrupted"
batch_row.message = "服务中断,准备续"
batch_row.message = "服务中断,准备"
db.commit()
item_rows = (
@@ -908,23 +938,26 @@ class HuyaRegisterRegistry:
retry_indices: list[int] = []
for idx, row in enumerate(item_rows):
if (row.status or "pending") in RETRYABLE_ITEM_STATUSES and row.status != "success":
row.status = "pending"
row.message = "等待续跑"
row.finished_at = None
row.started_at = None
row.code = ""
row.change_code = ""
row.attempts = 0
row.change_attempts = 0
# 保留历史密码等成功字段不覆盖;失败项本来也没有
retry_indices.append(idx)
status = row.status or "pending"
if status == "success":
continue
if status not in target_statuses:
continue
row.status = "pending"
row.message = "等待继续" if mode == "continue" else "等待重试"
row.finished_at = None
row.started_at = None
row.code = ""
row.change_code = ""
row.attempts = 0
row.change_attempts = 0
retry_indices.append(idx)
if not retry_indices:
raise ValueError("没有可续跑的失败/未完成条目")
raise ValueError(empty_msg)
batch_row.status = "running"
batch_row.message = f"续跑中{len(retry_indices)} 条)"
batch_row.message = f"{run_label}{len(retry_indices)} 条)"
batch_row.started_at = batch_row.started_at or _now()
batch_row.finished_at = None
_refresh_batch_counts(batch_row, item_rows)
@@ -941,7 +974,6 @@ class HuyaRegisterRegistry:
if use_proxy is False:
proxy_config = None
elif batch.use_proxy and proxy_config is None:
# 由调用方传入;若未传则 runner 内会报代理未配置
pass
runner = HuyaRegisterRunner(
@@ -950,9 +982,8 @@ class HuyaRegisterRegistry:
proxy_config=proxy_config if batch.use_proxy else None,
item_indices=retry_indices,
)
# 内存侧也保持 running,与 DB 一致
runner.batch.status = "running"
runner.batch.message = f"续跑中{len(retry_indices)} 条)"
runner.batch.message = f"{run_label}{len(retry_indices)} 条)"
with self._lock:
self._runners[batch_id] = runner
return runner