审「哪些操作该进账号生命周期」时发现:**进项有账、出项没账** —— 充值/积分到账都有金额,而开通(花积分)、送礼(花鱼翅)几乎没记, 于是「一共充了多少」算得出来,「花哪儿了」算不出来。 按确认的范围一次做完(应用宝充值**不动** —— 它按游戏角色记账、且未启用)。 ## A. 开通类事件补金额 `elite_opened` / `esports_opened` 以前只记「开通后的积分余额」,不记**花了多少**。 现在补 `amount = -(开通前后积分差)`,并用**实测余额差**而不是"开通要多少分"的常量 (活动价、折扣会让实际扣减不同)。任一侧拿不到就不写金额,不编数字。 ## B. 鱼翅消耗事件(新 `gold_spent`) 送礼以前只发「积分到账」,**花掉的鱼翅没有任何记录**。现在: - 送礼**前后各刷一次**鱼翅(原来只在送礼后刷),取**实测余额差**; - 发 `gold_spent`,`amount` 用负数(与 `exchange` 一致); - 余额没减少或拿不到 → **不发**:宁可少一条,也不要记一笔没发生的消耗。 ## C. 积分到期(新 `points_expire`) 积分到期是资产真实减少,但**斗鱼不会通知** —— 只在积分查询里给 `willExpireTime`。 所以: - 新增 `accounts.points_expire_at`(迁移 `0046`),送礼流程学到就写下来; - 新增作业 `points_expire_check`:扫「已到期且积分未归零」的账号,补写事件并清标记 (不重复记);积分已为 0 则只清标记、不发事件(避免 `-0` 噪声)。 ⚠️ **金额的诚实边界**:到期那一刻**到底扣了多少,我们没亲眼看到**。 所以默认 `confirmed=False`、标题带「(预计)」,记的是「当时余额」这个**预计要归零**的数量, **不假装它是确认过的扣减**。作业刻意不发网络请求(纯 DB 可跑、不需要 CK), 要变成已确认只能由「到期后重查、真看到余额下降」的调用方补。 ## 顺带修掉一个会静默丢事件的坑 同一个任务可能产生**两条事件**(送礼:既「积分到账」又「消耗鱼翅」)。 `source_ref` 是唯一键 —— 共用 `task_ref(task_id)` 会让**后写的那条被静默丢弃**。 已让副事件带后缀(`douyu_task:{id}:gold`),主事件保持原形态(与旧收集器一致)。 有用例专门钉住「一个任务两条事件都不丢」。 ## 验证 - `TZ=Asia/Shanghai`:**550 passed / 6 skipped**(+18 条) - `TZ=UTC`(事件 + 迁移 + 绑定)95 passed;ruff 全绿;前端 build 通过、lint 0 errors - 真实开发库:迁移 `0046` 已应用;作业烟测 → 扫到 1 个到期账号, 写入「积分到期 -500(预计)」`confirmed=False`,标记被清、重跑不再重复 ## 现在的资产流水(斗鱼侧) | 方向 | 覆盖 | | --- | --- | | 进项 | 充值(二维码/供应商/回调三路径去重)✅、积分到账 ✅ | | 出项 | 兑换 ✅、**开通 ✅(新补金额)**、**送礼鱼翅 ✅(新)**、**积分到期 ✅(新,标预计)** | | 未动 | 应用宝充值(独立系统、按游戏角色记、当前未启用) |
108 lines
3.2 KiB
Python
108 lines
3.2 KiB
Python
"""自动化调度:作业注册、调度线程与各作业实现。
|
|
|
|
登记作业请放在 ``register_default_jobs()``,由 ``main.py`` 在启动时调用一次,
|
|
避免 import 顺序影响注册结果。
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from .jobs.ck_health import JOB_KEY as CK_HEALTH_KEY
|
|
from .jobs.ck_health import run_ck_health
|
|
from .jobs.ck_relogin import JOB_KEY as CK_RELOGIN_KEY
|
|
from .jobs.ck_relogin import run_ck_relogin
|
|
from .jobs.douyu_tasks_retention import (
|
|
JOB_KEY as DOUYU_TASKS_RETENTION_KEY,
|
|
)
|
|
from .jobs.douyu_tasks_retention import (
|
|
run_douyu_tasks_retention,
|
|
)
|
|
from .jobs.points_expire import JOB_KEY as POINTS_EXPIRE_KEY
|
|
from .jobs.points_expire import run_points_expire_check
|
|
from .scheduler import (
|
|
JobSpec,
|
|
automation_enabled,
|
|
ensure_jobs,
|
|
register_job,
|
|
registered_jobs,
|
|
reset_registry,
|
|
run_due_jobs,
|
|
run_job_now,
|
|
scheduler_status,
|
|
start_scheduler,
|
|
stop_scheduler,
|
|
)
|
|
|
|
__all__ = [
|
|
"CK_HEALTH_KEY",
|
|
"CK_RELOGIN_KEY",
|
|
"DOUYU_TASKS_RETENTION_KEY",
|
|
"POINTS_EXPIRE_KEY",
|
|
"JobSpec",
|
|
"automation_enabled",
|
|
"ensure_jobs",
|
|
"register_default_jobs",
|
|
"register_job",
|
|
"registered_jobs",
|
|
"reset_registry",
|
|
"run_ck_health",
|
|
"run_ck_relogin",
|
|
"run_douyu_tasks_retention",
|
|
"run_due_jobs",
|
|
"run_job_now",
|
|
"run_points_expire_check",
|
|
"scheduler_status",
|
|
"start_scheduler",
|
|
"stop_scheduler",
|
|
]
|
|
|
|
|
|
def register_default_jobs() -> None:
|
|
"""登记内置作业(幂等)。"""
|
|
register_job(
|
|
JobSpec(
|
|
key=POINTS_EXPIRE_KEY,
|
|
title="积分到期补记",
|
|
# 每天一次足够:到期时刻的精度是「天」级别,晚几小时记也不影响
|
|
interval_seconds=24 * 3600,
|
|
batch_size=200,
|
|
concurrency=1,
|
|
run=run_points_expire_check,
|
|
)
|
|
)
|
|
register_job(
|
|
JobSpec(
|
|
key=DOUYU_TASKS_RETENTION_KEY,
|
|
title="任务日志清理",
|
|
# 一天一次足够:这是**清理**,不是业务;跑太勤只是白扫索引。
|
|
interval_seconds=24 * 3600,
|
|
batch_size=2000,
|
|
concurrency=1,
|
|
run=run_douyu_tasks_retention,
|
|
)
|
|
)
|
|
register_job(
|
|
JobSpec(
|
|
key=CK_RELOGIN_KEY,
|
|
title="CK 自动重登",
|
|
# interval_seconds 同样是「同一行多久重试一次」的兜底(真实节奏由
|
|
# relogin_next_at 的退避表决定);批次小、跑得慢,单轮 10 条。
|
|
interval_seconds=6 * 3600,
|
|
batch_size=10,
|
|
concurrency=3,
|
|
run=run_ck_relogin,
|
|
)
|
|
)
|
|
register_job(
|
|
JobSpec(
|
|
key=CK_HEALTH_KEY,
|
|
title="CK 自动巡检",
|
|
# interval_seconds = 单账号多久重新检测一次(不是作业周期;
|
|
# 作业每个 tick 都跑)。全库 12,881 条按 5 分钟一轮、单轮 200 条
|
|
# 反推,吞吐 40 条/分钟,够 6 小时内覆盖一遍(规划 §7.3)。
|
|
interval_seconds=6 * 3600,
|
|
batch_size=200,
|
|
concurrency=5,
|
|
run=run_ck_health,
|
|
)
|
|
)
|