yml2213
|
9ac98dad89
|
fix(automation): 补全作业改用界面上的「同一账号多久重检」—— 之前那个字段对它无效
用户问「同一账号多久重检是啥意思」,顺着查发现**我的作业没在用它**。
## 「同一账号多久重检」是什么
**每个账号的体检周期**:一个账号这轮被查了,多久之后才重新进入候选。
批量扫描类作业的通用参数,配合另外两个决定"扫不扫得过来":
需要的能力 = 总账号数 ÷ 重检间隔
实际的能力 = 每轮条数 ÷ 运行间隔
**实际 < 需要 → 永远扫不完**(队列越来越长)。
按这个口径核对「CK 自动巡检」的默认值(生产 14,574 个持有 CK 的账号):
需要 14,574 ÷ 6 小时 = 2,429 个/小时
实际 200 条 ÷ 5 分钟 = 2,400 个/小时
**几乎精确匹配** —— 那两个默认值是校准过的,不用动。
## 但我的作业读的是 config 里的 refresh_days
于是界面上那个「同一账号多久重检」**对本作业是死的**(改了没反应)——
正是用户上一条批评过的同一类缺陷:「看起来能配、其实配的是不相干的东西」。
我在同一个问题上连着栽了两次(上一次是拿 `interval_seconds` 当调度频率算吞吐)。
改成 `job.interval_seconds`,与其它批量作业**同一套口径**;
默认 7 天(查过的账号 7 天后再查)。config 里的 `refresh_days` 整个去掉。
## 顺带
- 注册处显式写明两个参数:`interval_seconds=7 天`(重检)+ `run_interval_seconds=5 分钟`(调度)
- 用例改名并加注释,钉住"重检间隔必须来自界面那个字段"
## 验证
TZ=Asia/Shanghai **639 passed / 6 skipped**;ruff 全绿;前端 build 通过。
|
2026-10-04 21:33:50 +08:00 |
|
yml2213
|
0d60cfbec9
|
perf(automation): 账号资料补全提速到 ~3.2 小时(原默认值要 5 天多)
用户反馈「几个小时补充完成就行吧,几天也太慢了」——**对,是我把调度设得太保守**。
## 算一下原默认值有多离谱
30 个 / 30 分钟 = 60 个/小时 → 7,784 个要 **130 小时(5 天多)**
(我上一条说"3 天"还算乐观了。)
而**试水实测的吞吐**是 200 个 / 131 秒(并发 3)= **1.53 个/秒 ≈ 5,500 个/小时**。
**瓶颈根本不在查询速度,在我把调度频率设低了。**
## 新默认值:10 分钟一轮 × 400 个
400 个 × 0.65 秒 ≈ 4.4 分钟/轮
10 分钟一轮 → 2,400 个/小时 → 7,784 个约 **3.2 小时**
## ⚠️ 为什么不是更激进
查调度器源码发现一条**架构约束**(`run_due_jobs` 的注释写明):
作业串行执行 —— 目前只有 CK 巡检,单轮约 20~40 秒,远小于 tick
也就是**我的作业跑着的时候,CK 巡检/重登都得等着**(tick 5 分钟)。
所以一轮时长必须**明显小于间隔** —— 4.4 分钟 / 10 分钟是刻意留的余量。
一轮跑 6.5 分钟以上会把其他自动化作业饿住。
想更快有两条路(都需要你定):
1. 调大 `batch_size` —— 但**跑得越快越容易被风控**,而风控是全部账号一起受影响
2. 让调度器**并发跑作业**(目前是串行)—— 属另一处改动
## 验证
TZ=Asia/Shanghai **632 passed / 6 skipped**;ruff 全绿。
|
2026-10-04 21:17:02 +08:00 |
|
yml2213
|
fd578683b6
|
feat(automation): 新增「账号资料定时补全」作业 —— 分批慢跑,不撞风控
## 为什么不能一次性跑全量
生产约 **8,000 个**「CK 有效但没查过」的账号。一次性发起 ≈ **8 万次斗鱼请求**,
很容易触发风控 —— 而风控一旦触发是**所有账号一起受影响**。试水 200 个时
0.65 秒/账号看着很香(全量 86 分钟),但那正是危险的地方:**跑得越快越容易被封**。
## 新作业 `account_overview_refresh`
照「自动巡检」(`ck_health`)那套做,并复用它的两道保护。
| 项 | 默认 | 说明 |
| --- | --- | --- |
| `interval_seconds` | 30 分钟 | 多久一轮 |
| `batch_size` | 30 | 每轮查多少(约 20 秒跑完)|
| `concurrency` | 3 | 并发 |
| `refresh_days` | 7(config 可调)| 查过的多久后再查 |
| `enabled` | **False** | 默认关,由运维手动开 |
按默认值:8,000 个约 **3 天**补完,全程不打满斗鱼接口。
## 两类账号**分开**(用户明确要求)
- **没查过的**(`overview_queried_at IS NULL`)**优先**
- 查过的只在超过 `refresh_days` 后重新入选
否则"周期性刷新老账号"会永远挤掉"从没查过的新账号"。
MySQL 没有 `NULLS FIRST`,用 `ORDER BY overview_queried_at IS NULL DESC` 等价实现。
## 只查有效 CK
`ck_check_status == 'valid'`。失效的查了也是白查(只会多一条失败记录),
交给「CK 检测与重登」处理。另外沿用 `cookie_operable_filter()`(有 CK 且无在途尝试)、
排除人工状态(停用/归档)与「不参与自动化」的账号。
## 风控熔断(沿用巡检口径)
一轮里失败率 > 50%(且样本 ≥ 10)就**自己把自己关掉**,交给人确认。
消息里写明可能原因,否则运维只看到"失败率高"会一头雾水:
失败率过高,已自动关闭作业(大面积失败通常是**被风控**或接口抖动,请先确认再重开)
样本 < 10 不熔断 —— 1 个失败就是 100%,比例没意义。
## 查询记录
每轮都建真实的 `douyu_tasks` 行(payload 标 `source=scheduled_refresh`),
「哪个账号、什么时候、查出来什么」都有据可查,也能在任务列表里
区分「定时巡检」与「人工点查询」。账号上的 `overview_queried_at`
则是"下次该不该再查"的依据。
## 验证
- **镜像库实测**:`本轮查 3 个:成功 3 / 失败 0(剩 7784 个待查)`
- 本地 `TZ=Asia/Shanghai` **632 passed / 6 skipped**;ruff 全绿
- 新增 11 条用例:选择口径(只查有效 CK、没查过优先、refresh_days 可调、
人工状态排除、batch_size 限流)+ 熔断(高失败率关闭、正常不关、小样本不熔断)
+ 一条回归:计数不能走带 LIMIT 的查询(作业第一版就是这么挂的)
|
2026-10-04 21:14:16 +08:00 |
|
yml2213
|
0b1fc1dee0
|
feat(events): 补齐资产【出项】—— 开通金额、鱼翅消耗、积分到期(C 最全)
审「哪些操作该进账号生命周期」时发现:**进项有账、出项没账** ——
充值/积分到账都有金额,而开通(花积分)、送礼(花鱼翅)几乎没记,
于是「一共充了多少」算得出来,「花哪儿了」算不出来。
按确认的范围一次做完(应用宝充值**不动** —— 它按游戏角色记账、且未启用)。
## 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`,标记被清、重跑不再重复
## 现在的资产流水(斗鱼侧)
| 方向 | 覆盖 |
| --- | --- |
| 进项 | 充值(二维码/供应商/回调三路径去重)✅、积分到账 ✅ |
| 出项 | 兑换 ✅、**开通 ✅(新补金额)**、**送礼鱼翅 ✅(新)**、**积分到期 ✅(新,标预计)** |
| 未动 | 应用宝充值(独立系统、按游戏角色记、当前未启用) |
|
2026-10-04 18:10:46 +08:00 |
|
yml2213
|
6e729e7a80
|
feat(account): S5 业务事实直发事件 + 删掉收集器 + 任务日志保留策略
时间线从此覆盖**全部**业务事实,`douyu_tasks` 也有了可控的增长边界。
## 一、删掉收集器(它必然漏数据)
`jobs/account_events_collect.py` 连同 `_task_to_event` / `COLLECTED_TASK_TYPES` /
水位线一起删除,迁移 `20261004_0043` 清掉它的作业行。删除理由是实测出来的:
- **靠 allowlist 认业务类型,新增类型就静默丢**:原有名单只有 5 个,
而 `exchange_esports_goods`(电竞兑换)、`exchange_xpd_goods`(和平小店兑换)、
`pay_locked_order`(锁单支付)、`donate_esports_*`(电竞礼物积分)**从来没进过时间线**;
- **「任务 success」≠「业务真的发生了」**(见下);
- **字段路径靠抽样猜**:二维码充值的 `result` 里根本没有金额,收集器恒写
`amount=None`,`recharge_total` 系统性少计;
- 作业默认 `enabled=False`,生产上一直没跑。
历史**不回填**(既有结论不变):需要历史走斗鱼侧权威接口
(`query_exchange_records` / `query_xpd_purchase_records` / `query_points` 都是现成任务类型)。
## 二、业务事实直发:命名构造函数
新增 `services/business_events.py`:`record_recharge` / `record_exchange` /
`record_points_credit` / `record_bind`。每类事实的字段、标题、金额符号只定义一次
(与 S4 的 `record_account_action`、`on_ck_verdict` 同一套路)。
**9 处成功点接线**:`exchange_goods` 真兑换分支、`exchange_esports_goods`、
`exchange_xpd_goods`、`pay_locked_order`、`create_gold_qr`(二维码 + 供应商轮询 +
**供应商回调**三条路径)、`donate_elite_gift`、`donate_esports_gift`、
`confirm_bind`、`confirm_esports_bind`。
**只记真实事实**(按你的决定):`exchange_goods` 里「预约到货 / 已预兑 / 预兑确认」
三条 success 分支**不发事件** —— 它们不消耗积分也没换到东西,旧收集器只按 status
收集,于是每预约一次就生成一条金额为空的 `exchange` 把 `exchange_count` 灌大。
`confirm_bind` 里「检测到账号本来就已绑定」那条也跳过。
## 三、修掉两个真 bug(都是钱账)
1. **到账判据 `!=` → `>`**(`douyu_runner_gold.py`):余额**下降**也算「鱼翅已到账」。
批内并发(最高 10)时同账号送礼会消耗鱼翅,余额一变就被充值任务读成到账,
**往台账记一笔根本没发生的充值**。
2. **供应商回调晚于 runner 超时**:原来守卫是「任务状态没被改过才更新」,
于是 runner 先判 failed、回调随后说成功的那笔真金白银**永远不进时间线**。
改成按**回调自己的终态**判断,并补发事件。
## 四、发射顺序:一个会静默丢事件的坑
`_mark_task` 内部 `db.commit()`,而 `emit` 用 `db.begin_nested()` **只 RELEASE
SAVEPOINT、不提交外层事务**;runner 的 `finally` 只 `close()`(未提交即回滚)。
所以**在 `_mark_task` 之后发事件会被静默丢弃,且 `emit` 仍返回 True**。
所有接线一律紧贴 `_mark_task(success)` **之前**,并加了源码扫描用例钉住
(已反向验证:故意挪到之后,用例立刻报出 file:line)。
## 五、`douyu_tasks` 保留策略(新增作业,默认关闭)
`jobs/douyu_tasks_retention.py`,两档:
| 档 | 保留 |
| --- | --- |
| 噪声查询(只读看一眼就走,且前端不拿它派生账号状态) | **7 天** |
| 其余一切(业务事实 + 状态派生依赖的查询 + **以后新增的类型**) | **90 天** |
长保留档用 `task_type NOT IN (噪声名单)` 表达 → **新增类型默认长保留**,不会因为
「忘了加名单」被误删(正是收集器翻车的反面)。在途任务(planned/pending/running)
**绝不删**;分批删避免长锁;两个窗口都能在作业 `config` 里改,不必发版。
⚠️ 「前端从任务列表反推账号当前状态」那几类查询(`query_xpd_bind_info` /
`query_esports_game_name` 等)**故意留在长保留档** —— 删早了工作台账号行会变
「未查」,那是把**当前显示**搞坏。根治要靠 §3.6 的状态列拆解(后续阶段)。
作业默认 `enabled=False`,由运维决定何时打开。
## 验证
- `TZ=Asia/Shanghai`:**450 passed / 6 skipped**(S4 后 426 → 净增 24)
- `TZ=UTC`(相关文件)101 passed;ruff check/format 全绿
- 前端 build 通过、lint 0 errors
- 真实开发库:迁移已应用(head `20261004_0043`,孤儿作业行已清);
保留作业实跑确认「作业行默认关闭、SQL 在 MySQL 上正确、近期行不误删」
- **两处反向验证**:把 `record_bind` 挪到 `_mark_task` 之后 → 顺序用例报红;
修掉 `registered_jobs()` 那个**假绿用例**(它返回 dict,原来的集合推导式
遍历空字典恒成立),并在断言里加了反向确认
未做(后续):S6 账号页重构 + `bind_status` 三列拆解(做完才能把状态派生类查询
也纳入短保留)、S7 账号检测改账号维度、S8 丢 `login_tasks` 旧凭据列。
|
2026-10-04 16:00:42 +08:00 |
|
yml2213
|
834bfdd1d0
|
feat(account-events): 账号详情页与账号事件历史全链路支持
1. 新增账号事件数据表 AccountEvent 与迁移版本 20261002_0040_account_events,支持审计 Cookie 变更、自动重登、健康检查及任务执行事件;
2. 后端新增 account_events_service 并扩展 /api/accounts 路由,支持事件查询、过滤与上报;
3. 前端新增 AccountDetailPage 账号详情页组件,支持账号元信息展示、事件历史瀑布流与快捷重登操作;
4. 修复 CK 重登与自动化定时任务中事件关联逻辑,补充 test_account_events 单元测试;
5. 新增生产服务器高负载排查分析报告及相关设计文档。
|
2026-10-03 12:58:58 +08:00 |
|
yml2213
|
8fc8e623b9
|
feat(automation): CK 自动重登(P1b)+ 失败分型与待人工队列
P1a 的巡检只产出「谁失效了」的清单,不兑现修复:1,356 个失效账号仍要人工逐个
选集→点重登→过滑块。线上实测 20 个失效账号重登 20/20 成功(2 分 13 秒、零极验),
证实失效是会话级过期而非凭证问题,可救量约 1,350。
后端:
- 迁移 20261002_0038:login_tasks 加 relogin_failure_kind(枚举),供作业决定
「继续退避重试」还是「转人工」;不用 attempt_message 做字符串匹配(文案一改
就失效,页面也要按类型筛选);
- jobs/ck_relogin.py:
- 候选 = 巡检判定 invalid + 允许自动管理 + 无在途尝试 + 退避到期 + 未超每日上限
+ **未转人工**(极验/凭证问题不再自动重登);
- 复用 LoginBatchRunner(mode="relogin"),成功才替换 CK(沿用既有安全语义)。
自动化身份取最早的超管做 created_by,权限给 login:view_all 以免受归属限制;
总时长上限压到 240 秒,避免长时间占住调度线程;
- settle_results() 独立成纯函数便于单测:成功清空退避状态;失败按分型分流 ——
captcha/credential 转人工(不排期)、email_quota 退避到次日零点+随机抖动、
email_code/interrupted/network 走 1h→3h→12h→24h 阶梯;次数用尽也停下等人工;
- 未知文案默认按可重试处理(有每日上限兜底),别把可能只是抖动的账号推给人工;
- 注册第二个作业「CK 自动重登」(batch 10 / 并发 3,**默认关闭**);
- 新增 GET /api/automation/manual-queue:按原因分组列出待人工处理的账号。可重试
类型但已无下次排期的(次数用尽)单独成组 —— 否则会混进「验证码失败」里,客服
看不出这个已经不会再自动试了。
前端:
- /automation 页新增「待人工处理」卡片,按分型分组展示(极验要过滑块、凭证要核对、
次数用尽要人工确认),各带账号与最近失败原因。
测试:新增 26 条(分型顺序陷阱「上限」优先于「验证码」、退避阶梯、配额退避次日、
候选筛选、MySQL 方言编译、结算、待人工队列分组)。
提交前全量 297 通过、ruff 全绿、前端 tsc 通过。
注意:提交时另一路「测试改用 MySQL」的改造正在同一工作区进行,本次刻意只暂存
本功能相关文件,未触碰 tests/conftest.py、tests/support/ 及对方已转换的测试文件。
|
2026-10-02 20:51:19 +08:00 |
|
yml2213
|
7aaaeebe2b
|
fix(automation): 修正调度口径——作业每 5 分钟跑一轮,interval_seconds 只是单行重检间隔
P1a 把两个「间隔」混成了一个:next_run_at 用的是 job.interval_seconds,于是
作业 6 小时才跑一轮、每轮 200 条 = 800 条/天,全库 12,881 条要 16 天才能过
一遍 —— 等于没巡检,与规划里「账号 6 小时内被查一次」的目标差了两个数量级。
按规划原意修正:
- TICK_SECONDS 60 → 300:作业每个 tick(5 分钟)跑一轮;
- next_run_at 改为 finished + TICK_SECONDS,不再用 interval_seconds;
- interval_seconds 只作为「同一行多久重新检测」的选行 cutoff(选行逻辑本来就
是这么用的,无需改);
- 容量口径随之更新:5 分钟一轮 × 200 条 = 40 条/分钟 ≥ 需要的 36 条/分钟,
冷启动约 5.4 小时覆盖全库,稳态每轮一百多条;
- 前端标签由「巡检周期(小时)」改为「单账号重检间隔(小时)」,说明文案写清
「每 5 分钟跑一轮」,避免运维误以为是作业周期。
同步更新规划文档 §7.2/§7.3(含两个间隔不能混的说明)与附录 C 的三段上线
步骤(立即跑一次 → 每轮 50 观察 → 每轮 200 启用)。
|
2026-10-02 19:54:31 +08:00 |
|
yml2213
|
f745a61df1
|
feat(automation): CK 自动巡检(P1a),只检测不改 CK
后台原本靠人工逐批点「CK 检测」发现失效账号,1.2 万个账号根本盯不过来。
P1a 把「哪些账号失效了」自动化:自动挑久未检测的账号、调现成检测接口、
把结果写回 ck_check_*。**本作业不替换任何 CK**,重登仍由人工点,符合规划里
「先做只读/只提示,确认无误再做自动动作」的渐进上线原则。
实现:
- 迁移 20261002_0037:automation_jobs / automation_runs 两张表,
login_tasks 加 relogin_attempts / relogin_next_at / relogin_last_error
(为 P1b 备好),accounts 加 auto_managed(特殊账号不参与自动化);
- services/automation/scheduler.py:进程内调度线程,60s tick,状态全部落库
(重启不丢调度进度、不重复跑轮);AUTOMATION_ENABLED 默认关闭,作业开关
也默认关闭;ensure_jobs 只补缺失行、不覆盖运维改过的配置;
作业异常只记 failed,不拖垮调度线程;单容器 --workers 1,无需分布式锁;
- services/automation/jobs/ck_health.py:选行口径与人工检测一致
(cookie_holding_filter:持有 CK 且无在途尝试),叠加久未检测、
auto_managed、batch_size,按 ck_checked_at 升序(最久没查的优先);
只写 ck_check_status / ck_check_result / ck_checked_at;
- routers/automation.py:GET /jobs、PATCH /jobs/{key}、POST /jobs/{key}/run、
GET /runs;新增权限点 automation:view(运营可看)与 automation:manage
(仅超管),改配置与手动触发都写审计;
- 顺带补 POST /api/login/batch 的审计(此前批次是谁发起、多少账号无据可查);
- 前端 /automation 页:作业卡片(开关/周期/单轮条数/并发)、运行历史、
「立即跑一次」;总开关未打开、调度线程未运行时给明确提示。
验证:
- 22 条新测试覆盖调度器行为(只跑到点的、异常不抛出、下轮时间、配置不被重置)、
选行条件(在途尝试/auto_managed/间隔/batch_size)、接口与权限,其中一条
专门断言巡检后 cookie/status/message/attempt_status 一字未动;
- 全量 257 条通过;前端 tsc 与构建通过;
- 端到端用 3 个真实账号跑真实斗鱼接口:巡检结果与直接调 check_douyu_cookie
逐条一致(3/3 valid),CK 与尝试状态全程未变。
顺带修掉一处会炸生产的坑:DB 时间列读出来是 naive UTC,直接与
datetime.now(UTC) 比较会 TypeError,现统一走 _as_utc() 补时区。
|
2026-10-02 19:47:41 +08:00 |
|