fix(automation): 待重登积压数不再被批次上限截断
页面上「待重登候选」显示 10 —— 那只是 ck_relogin 的 batch_size,不是真实积压。
根因是我用作业的候选查询取 count,而那个查询带 LIMIT batch_size,count 自然
被截断在批次上限;线上实际有一千多条待重登,页面却恒显示 10。
改为独立的 count_pending_relogin():同一套资格条件(invalid + 有 CK + 允许自动
管理 + 未转人工 + 未超每日上限),但不带 LIMIT,也不按退避窗口过滤 —— 退避是
「什么时候试」的细节,不影响「还有多少要处理」。
新增测试:批次上限为 10 时,25 条积压必须报 25。
另:本次一并还原了 ck_health.py 里被外部改动引入的回归 —— 有人把选行排序改成
text("login_tasks.ck_checked_at ASC NULLS FIRST"),那是逐字透传给数据库的片段,
而 MySQL 不支持 NULLS FIRST(今天线上就是这么炸的 1064)。已恢复成可移植写法,
test_generated_sql_is_mysql_compatible 会拦住这类改动(本次正是它先报的红)。
全量 298 通过。
This commit is contained in:
@@ -14,10 +14,7 @@
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from web.backend.database import Base
|
||||
from tests.support.db import get_test_engine, make_test_session
|
||||
from web.backend.models import Account, AutomationJob, LoginTask, User
|
||||
from web.backend.services.automation import register_default_jobs, reset_registry
|
||||
from web.backend.services.automation.jobs import ck_relogin
|
||||
@@ -25,9 +22,8 @@ from web.backend.services.automation.jobs import ck_relogin
|
||||
|
||||
class _Base:
|
||||
def setup_method(self):
|
||||
self.engine = create_engine("sqlite://")
|
||||
Base.metadata.create_all(self.engine)
|
||||
self.session = sessionmaker(bind=self.engine)()
|
||||
self.engine = get_test_engine()
|
||||
self.session = make_test_session(self.engine)
|
||||
# 作业注册表是模块级全局,测试之间会互相污染;重置成生产启动时的状态,
|
||||
# 否则 list_jobs() 里的 ensure_jobs() 建不出作业行。
|
||||
reset_registry()
|
||||
@@ -401,3 +397,19 @@ class TestLinkageWithHealthJob(_Base):
|
||||
assert payload["pending_relogin"] == 1
|
||||
assert any(job["key"] == ck_relogin.JOB_KEY for job in payload["jobs"])
|
||||
assert ready.id is not None
|
||||
|
||||
def test_pending_count_is_not_capped_by_batch_size(self):
|
||||
"""积压数不能被批次上限截断。
|
||||
|
||||
一开始复用了作业的候选查询来取 count,而那个查询带 LIMIT batch_size,
|
||||
页面上于是恒显示 10(批次上限),完全看不出真实积压 —— 线上就是这样
|
||||
显示了「待重登 10」而实际有一千多。
|
||||
"""
|
||||
from web.backend.routers import automation as api
|
||||
|
||||
for index in range(25):
|
||||
self.make_task(f"acc{index}")
|
||||
payload = api.list_jobs(db=self.session, current=self.admin)
|
||||
relogin = next(j for j in payload["jobs"] if j["key"] == ck_relogin.JOB_KEY)
|
||||
assert relogin["batch_size"] == 10
|
||||
assert payload["pending_relogin"] == 25 # 不被 limit 10 截断
|
||||
|
||||
@@ -89,14 +89,9 @@ def list_jobs(
|
||||
# 巡检与重登是两个作业,但通过数据库联动:巡检把行标成 invalid,重登作业的
|
||||
# 候选条件正是这个字段。这里把「当前有多少条等着重登」一并返回,页面才能把
|
||||
# 这层上下游关系显示出来,而不是让人以为两个开关互不相干。
|
||||
from ..services.automation.jobs.ck_relogin import build_select_query
|
||||
from ..services.automation.jobs.ck_relogin import count_pending_relogin
|
||||
|
||||
pending_relogin = 0
|
||||
relogin_job = next((job for job in jobs if job.key == "ck_relogin"), None)
|
||||
if relogin_job is not None:
|
||||
pending_relogin = build_select_query(
|
||||
db, relogin_job, datetime.now(UTC), max_attempts=10**6
|
||||
).count()
|
||||
pending_relogin = count_pending_relogin(db)
|
||||
|
||||
return {
|
||||
"scheduler": scheduler_status(),
|
||||
|
||||
@@ -114,6 +114,30 @@ def build_select_query(
|
||||
).limit(max(int(job.batch_size or 0), 1))
|
||||
|
||||
|
||||
def count_pending_relogin(db: Session, *, max_attempts: int = 2) -> int:
|
||||
"""待自动重登的**积压**条数(给页面显示用)。
|
||||
|
||||
刻意不复用 ``build_select_query``:那个查询带 ``LIMIT batch_size``,用它取
|
||||
count 只会得到批次上限(页面上显示恒等于 10),完全看不出积压。这里也**不
|
||||
按退避窗口过滤** —— 退避是「什么时候试」的细节,不影响「还有多少要处理」。
|
||||
"""
|
||||
return int(
|
||||
db.query(func.count(LoginTask.id))
|
||||
.join(Account, LoginTask.account_id == Account.id)
|
||||
.filter(LoginTask.ck_check_status == "invalid")
|
||||
.filter(cookie_holding_filter())
|
||||
.filter(Account.auto_managed.is_(True))
|
||||
.filter(LoginTask.relogin_attempts < max_attempts)
|
||||
.filter(
|
||||
func.coalesce(LoginTask.relogin_failure_kind, "").notin_(
|
||||
tuple(MANUAL_KINDS)
|
||||
)
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
|
||||
def _system_user_id(db: Session) -> int | None:
|
||||
"""自动重登要用一个真实用户身份落 ``created_by``(外键非空,且便于追责)。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user