fix(automation): 巡检区分「查不到」与「失效」,并在大面积失败时自动暂停
P1a 原本把「检测没通过」一律写成 invalid。但 check_douyu_cookie 的失败分两类: 斗鱼明确回「未登录/登陆异常」(CK 真失效),以及请求超时/响应异常(我们查不到)。 一视同仁的后果是:斗鱼接口抖动或被风控时,每 5 分钟 200 个、一小时就能把两千多个 好账号标成失效,客服会误判成「CK 大面积挂掉」。规划里的失败率熔断只给了 P1b, P1a 漏了。 改动: - 新增 classify_result():valid / invalid(命中斗鱼鉴权类说法)/ error(其余); - error 时**不覆盖该行原来的 ck_check_status**,只更新 ck_check_result(带 verdict 字段)与 ck_checked_at,并计入本轮 skipped; - 一轮 error 占比 > 30% 且条数 >= 20 时自动把作业 enabled 置 false,message 与 detail.paused 写明原因,避免继续污染; - 真实死号占比高**不触发**暂停 —— 首轮 100% invalid 是预期情况(选行「从没检测过 的优先」,积压的是两个月前批量导入、attempt_status 为空、从未登录的陈旧账号, 生产实测 200 条错误信息一致为斗鱼侧「用户未登录」)。 新增 6 条测试:分类正确性、error 保留原结论、异常不再标失效、大面积失败自动暂停、 真实死号不暂停。全量 271 条通过。
This commit is contained in:
@@ -23,6 +23,41 @@ from ...douyu_service import cookie_operable_filter
|
||||
JOB_KEY = "ck_health"
|
||||
FLUSH_EVERY = 25
|
||||
|
||||
# 斗鱼明确表示「这个 CK 不能用」的说法。只有命中这些才算真的失效。
|
||||
AUTH_FAILURE_MARKERS = (
|
||||
"登陆异常",
|
||||
"登录异常",
|
||||
"未登录",
|
||||
"登陆失效",
|
||||
"登录失效",
|
||||
"重新登陆",
|
||||
"重新登录",
|
||||
"账户登陆",
|
||||
)
|
||||
|
||||
# 非鉴权类失败(网络超时、响应异常、被判风控)占比超过它就自动暂停作业。
|
||||
# 这类失败说明「我们查不到」,不是「这个 CK 失效了」;如果不加区分地写
|
||||
# invalid,斗鱼接口抖动一小时就能把上千个好账号标成失效,客服会误判成
|
||||
# CK 大面积挂掉。
|
||||
ERROR_PAUSE_RATIO = 0.3
|
||||
ERROR_PAUSE_MIN_SCANNED = 20
|
||||
|
||||
|
||||
def classify_result(result: dict[str, Any]) -> str:
|
||||
"""把检测结果分成三类:valid / invalid(真失效)/ error(查不到)。
|
||||
|
||||
- ``valid``:斗鱼两个接口都通过;
|
||||
- ``invalid``:斗鱼明确回复「未登录 / 登陆异常」之类的鉴权失败;
|
||||
- ``error``:其余失败(请求失败、超时、响应异常、未知结构)——
|
||||
**不能当成失效**,也不覆盖该行原来的结论。
|
||||
"""
|
||||
if result.get("valid"):
|
||||
return "valid"
|
||||
message = str(result.get("message") or "")
|
||||
if any(marker in message for marker in AUTH_FAILURE_MARKERS):
|
||||
return "invalid"
|
||||
return "error"
|
||||
|
||||
|
||||
def build_select_query(db: Session, job: AutomationJob, now: datetime):
|
||||
"""构造本轮要检测记录的查询(与取行分开,便于按方言编译做回归)。
|
||||
@@ -88,22 +123,27 @@ def run_ck_health(db: Session, job: AutomationJob) -> dict[str, Any]:
|
||||
results[task.id] = future.result()
|
||||
|
||||
# 只写检测结果三件套;cookie / status / attempt_* 一律不动。
|
||||
valid = invalid = 0
|
||||
valid = invalid = errors = 0
|
||||
for index, task in enumerate(tasks, start=1):
|
||||
result = results.get(task.id)
|
||||
if result is None:
|
||||
continue
|
||||
is_valid = bool(result.get("valid"))
|
||||
if is_valid:
|
||||
verdict = classify_result(result)
|
||||
if verdict == "valid":
|
||||
valid += 1
|
||||
else:
|
||||
elif verdict == "invalid":
|
||||
invalid += 1
|
||||
task.ck_check_status = "valid" if is_valid else "invalid"
|
||||
else:
|
||||
errors += 1
|
||||
# error 时保留该行原来的结论:查不到不等于失效,宁可维持上一次判断。
|
||||
if verdict != "error":
|
||||
task.ck_check_status = verdict
|
||||
task.ck_check_result = {
|
||||
"fish_ball": result.get("fish_ball"),
|
||||
"nickname": result.get("nickname"),
|
||||
"level": result.get("level"),
|
||||
"message": result.get("message", ""),
|
||||
"verdict": verdict,
|
||||
}
|
||||
task.ck_checked_at = now
|
||||
if index % FLUSH_EVERY == 0:
|
||||
@@ -125,12 +165,30 @@ def run_ck_health(db: Session, job: AutomationJob) -> dict[str, Any]:
|
||||
)
|
||||
.count()
|
||||
)
|
||||
error_ratio = errors / len(tasks) if tasks else 0.0
|
||||
paused = False
|
||||
if errors >= ERROR_PAUSE_MIN_SCANNED and error_ratio > ERROR_PAUSE_RATIO:
|
||||
# 大面积「查不到」通常是斗鱼接口抖动或被风控,继续跑只会污染更多账号。
|
||||
job.enabled = False
|
||||
job.updated_at = datetime.now(UTC)
|
||||
paused = True
|
||||
|
||||
message = f"本轮检测 {len(tasks)} 条,剩余待检 {remaining} 条"
|
||||
if errors:
|
||||
message += f";非鉴权类失败 {errors} 条({error_ratio:.0%})"
|
||||
if paused:
|
||||
message += ";失败占比过高,已自动暂停作业"
|
||||
return {
|
||||
"scanned": len(tasks),
|
||||
"valid": valid,
|
||||
"invalid": invalid,
|
||||
"skipped": 0,
|
||||
"skipped": errors,
|
||||
"message": message,
|
||||
"detail": {"remaining": remaining, "concurrency": concurrency},
|
||||
"detail": {
|
||||
"remaining": remaining,
|
||||
"concurrency": concurrency,
|
||||
"errors": errors,
|
||||
"error_ratio": round(error_ratio, 4),
|
||||
"paused": paused,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user