yml2213
|
d285196287
|
feat(account): 账号「一键查询」(异步 + 批量)—— 含一次重要的判定修正
按 `docs/设计-账号一键查询.md` 实现。异步任务、支持批量(后端每账号一条任务)。
## 功能
- 新任务类型 `query_account_overview`:**只读**聚合查询,不改账号状态、不发事件
(纯查询不是「事实」,按既有设计例行查询不进时间线,否则会刷屏)
- 一次拉全:鱼翅 / 钱包兑换余额 / 积分(含到期)/ 精英宝典 / 电竞手册 /
**官方兑换记录** / 限兑商品,并**重点标出「兔兔白日梦」「狂战萌兔」是否已获得**
- **单项失败不整体失败**:账号没绑电竞手册是常态,失败只写进 `errors` 并在 UI
**如实展示** —— 避免「没查到」被读成「没有」
- 批量:`create_douyu_planned_tasks` 本来就是每账号一条任务,前端加「批量一键查询」
- 前端:详情页一键查询卡片 + 结果面板(两个套装单独高亮);任务页结果自动弹面板;
三个工作台的快捷操作/类型集合都加了
## ⚠️ 修正:判定「是否已获得」用错了依据
设计文档第一版说「`storeStatus == 0` → 已拥有」,依据是
「账号 558(兑换过)count=0,账号 15407(没兑换)count=96982」。
**这是错的。** 那两条记录分别取自 **2026-08-01** 和 **2026-10-04**,相隔两个月 ——
我把**时间差异**当成了**账号差异**。
**同一时刻**同时查 558 / 607(都兑换过)和 15507(没有),
商品项的**所有字段完全一致**,`preExchange.isExists` 也都是 0;
`count` 的取值分布是 0/98746/103350/109832…,**随时间变化 = 全局库存**。
用它判「这个账号有没有」必然给错结论 —— 而对高价值资产给错结论是本功能最不能接受的。
**正确依据:`exchange_records(manual_id)`**(斗鱼官方的**账号级**兑换记录)。
真实斗鱼实测:
| 账号 | 官方兑换记录 | 判定 |
| --- | --- | --- |
| 558 | 破片手榴弹-兔兔白日梦 | **已获得 ✅** |
| 607 | 破片手榴弹-兔兔白日梦 | **已获得 ✅** |
| 15507 | (0 条) | 未获得 ✅ |
同时刻三者看到的兔兔白日梦库存**都是 96956**(全局),兑换记录却区分得干干净净。
顺带发现:同一件东西有多个商品名(`套装-兔兔白日梦`、`破片手榴弹-兔兔白日梦`),
所以按**核心词子串**匹配整个名字家族。
商品列表的 `score`/`count`/活动时间仍展示,但明确是**全局**信息
(「现在还能不能换、要多少分」),与「该账号是否已获得」分开。
## 测试
`tests/test_account_overview.py` 22 条,含**回归用例**:
`test_global_stock_never_implies_owned`(全局售罄不能推出账号拥有)——
正是第一版踩的坑。另有失败隔离、只读性(不发事件/不改状态)、
批量每账号一条任务、`missing` 不被静默丢弃。
## 验证
- `TZ=Asia/Shanghai` **574 passed / 6 skipped**;`TZ=UTC` 27 passed
- ruff 全绿;前端 build 通过、lint 0 errors
- **真实斗鱼端到端**:三个账号各跑一次真查询,8 项全部成功(`errors: []`),
已获得/未获得判定全部正确
|
2026-10-04 19:07:35 +08:00 |
|
yml2213
|
64dc9a9116
|
docs(query): 账号「一键查询」设计 —— 数据源已用生产镜像实证
决策已定:两个套装属精英宝典(manual_id/rid 用后台配置)、异步任务。
## 关键实证(镜像库真实数据)
「是否已拥有某套装」**能直接从斗鱼官方查**:
query_limited_goods 的商品项里 count / foreverCount / storeStatus 是**账号维度**的。
实测「套装-兔兔白日梦」—— 已兑换的账号 558 是 count=0/storeStatus=0,
未兑换的 15407 是 count=96982/storeStatus=1。配合 countLimited=1(每账号限兑 1 次),
判定规则就是 storeStatus==0 → 已拥有。
⚠️ 同时记下风险:storeStatus=0 也可能是全局售罄,UI 要写「不可再兑」并显示 count。
## 另一个坑
任务类型 query_exchange_records **在生产从未运行过**(0 行);
真正的官方兑换记录接口是客户端的 exchange_records()(activity_client.py:950),
**没有被任何任务接线**。一键查询要用它。
## 文档内容
数据源矩阵(8 个客户端方法 + 参数来源)、任务设计(新类型
query_account_overview,只读、单项失败不整体失败、errors 如实展示)、
结果 JSON 结构、后端/前端/测试实现清单、判定规则、镜像库复现样本、风险。
注:本次只落设计不写实现 —— 上下文不足以一次干净做完,
半成品比未开始更糟。实现按本文档一次完成。
|
2026-10-04 18:56:01 +08:00 |
|
yml2213
|
4732c02c09
|
feat(ops): 本地生产镜像同步脚本 + 上线清单补充说明
生产已部署完成(main@2f610a5,迁移 20261004_0046)。为了让后续测试能在本地跑,
把生产库同步成本地镜像,并把同步流程固化成脚本。
## 本次建立的本地镜像
`douyu_login_prod` —— 与生产逐项一致:15,182 账号 / 14,323 CK / 15,169 登录记录 /
**253,424 条 douyu_tasks(含 837MB JSON,可实测保留策略)** / 迁移 0046 / 907MB。
**已验证本地 `.env` 的 `APP_ENCRYPTION_KEY` 与生产一致** ——
抽样解密正常(CK 前缀 `dy_accounts_main`,长度 1659–1702),
所以镜像里的 CK 可用,CK 相关测试能跑。这一点不成立的话镜像基本没用。
## 脚本里规避的两个坑
1. **不要把 `mysqldump` 串进 SSH 管道**:实测 ~49KB/s(MySQL 读 + gzip + 网络互相拖),
改成「服务器本地 dump 成文件 + rsync」后 ~8MB/s,**快两个数量级**。
下载那步用 **rsync 而非 scp**:可断点续传,网断了不用重来。
2. 恢复前先 `DROP DATABASE`,避免旧数据残留造成「看起来同步了其实没有」。
## 文档
`docs/上线清单-S1-S7部署.md` 追加「把生产库同步到本地」一节:用法、
镜像内容、两个前提(密钥必须一致;**不要在镜像库上跑测试套件** ——
测试用独立的 `douyu_login_test`),以及上面两个坑。
|
2026-10-04 18:46:26 +08:00 |
|
yml2213
|
2f610a53e3
|
feat(ops): S1–S7 上线清单 + 回滚前置脚本(已在生产克隆库完整演练)
生产还停在 `20261003_0040`,S1–S7 一条都没上。本次在**生产克隆库**上把
「迁移 → 校验 → 回滚」完整走了一遍,并把可执行的清单落成文档。
## 演练结果(克隆库,与生产逐项同构)
| 步骤 | 结果 |
| --- | --- |
| 克隆生产库 | 15,182 账号 / 14,324 CK / 版本 `0040` / 27 张表,与生产一致 |
| **迁移全链 0041→0046** | 6 个迁移,**9 秒**,无错误(含 S1 的 fail-closed 自校验) |
| 账号数 | 15,182 ✓ 不变 |
| **CK 搬运** | 账号侧 14,323 = 登录记录侧**去重账号数 14,323**(精确相等)|
| 精英/电竞映射 | bound 410 / pending 75 / unbound 14;电竞 **unbound 26**(修复生效)|
| `esports_opened` | **4** ✓(抢救生效)|
| `source_ref` 可空 | `YES` ✓ |
| **回滚降级** | 6 个迁移,**2 秒**;`bind_status` 原值完好、两列回到 `varchar(32)` |
## 🔴 演练查出的关键风险(不演练绝对发现不了)
S2 之后 CK **只写 `accounts.cookie`、刻意不双写** `login_tasks.cookie`。
而降级会 **drop 掉 `accounts.cookie`** —— 也就是:
> **部署期间新拿到的 CK,会在回滚时全部丢失。**
CK 丢失是本项目唯一不能承受的损失(重登成功率≈0、全量重登≈2.7 天)。
于是新增 `scripts/backfill_login_tasks_from_accounts.py`(**回滚前置**):
把账号侧 CK / 健康 / 重登进度搬回旧列;没有历史行的账号补一条合成行
(否则旧代码靠 `login_tasks` 找不到它的 CK);没有 CK 的账号清掉旧行残留
(否则旧代码以为它还持有 CK)。自带 `--dry-run` 与**自检**
(账号侧 ≤ 登录记录侧去重数,不通过就别降级)。
**同样在克隆库上验证过**:模拟「部署期间新拿到一个 CK」→ 跑回填 → 降级 →
**该 CK 完整幸存**(task#100 / 账号 168)。不跑回填的话它就没了。
## 补齐了之前做不到的那项验证
本地没有生产的加密密钥,所以之前只验证了 CK「搬过去了」、没验证「能解密」。
这次**在生产上只读实测**:抽样 5 条 `login_tasks.cookie`,**5/5 解密正常**
(长度 1613–1660)。这是清单里第 1 节的部署前基线。
## 交付物
- `docs/上线清单-S1-S7部署.md`:部署前检查 / 执行顺序(**先停应用→迁移→再起代码**)/
七项逐条校验 / 回滚预案(含正确顺序)/ 已知限制 / 演练证据
- `scripts/backfill_login_tasks_from_accounts.py` + 2 条用例
(搬回已有行、无历史行时补合成行)
## 验证
`TZ=Asia/Shanghai`:**552 passed / 6 skipped**;ruff 全绿。
⚠️ 清单里写明:**S8(删旧列)本次不做** —— 要等稳定运行 + 逻辑备份之后。
|
2026-10-04 18:18:30 +08:00 |
|
yml2213
|
5ae5d92978
|
feat(account): 兑换记录列 + 修掉 source_ref NOT NULL 导致的事件静默丢失
三件事合成一次提交(按反馈不再一改一提交)。
## ① 账号列表加「兑换记录」列
次数 + 最近一次(商品名 + 日期),填上列表右侧的空档。
数据源是**账号自己的事件流**(`account_events` 的 `exchange`),不是 `douyu_tasks`
的 JSON —— 后者是执行日志,S5 起业务事实已经在事件里了。
性能上刻意**每页只跑两条聚合查询**(不是每行一条 N+1):
一条分组算 `count` + `max(occurred_at)`(走 `(account_id, event_type, occurred_at)`
复合索引),一条用窗口函数 `ROW_NUMBER()` 取每个账号最新那条的商品名。
实测 6 个账号 3 条兑换事件,聚合与「最近」都正确。
「兑换 」前缀在前端剥掉(列头已经叫「兑换记录」,再带前缀是重复)。
## ② 修掉一个静默丢事件的 bug(排查中发现的)
**现象**:开发库上「状态变迁 / 分配 / 标签 / 备注 / CK 清除」这些事件**一条都没有**,
而带幂等键的事件(业务事实、`ck_invalid`、`relogin_*`)照常写入 ——
时间线看起来有数据,只是恰好缺了某一类,**极难发现**。
**根因**:`source_ref` 是**可选**幂等键(模型与迁移文件都是 `nullable=True`,
`emit()` 对没有天然幂等键的事件就传 `None`)。但早期那版迁移
(重编号成 `0041` 之前的 `0040`)建的是 **NOT NULL**,而我后来只改了
**迁移文件**、没管**已建好的库** —— Alembic 只记版本号,不会重放已应用的脚本。
`__pycache__` 里那个 `20261002_0040_*.pyc` 就是旧版留下的痕迹。
**为什么静默**:`emit()` 按设计吞掉任何异常(事件是旁路,不能影响主流程),
只记一条 warning,调用方也从不检查返回值。已实测复现:
`emit(...)` 返回 `False` + warning,实际落库 `0` 条。
**修法**:迁移 `20261004_0045` 改回可空(`MODIFY`,**唯一索引保留**,幂等,
生产若跑过旧版也一并修好)。修复后实测:`emit` 返回 `True`、落库 1 条。
## ③ 补一条能防「未来」同类问题的守卫
`tests/test_migrations.py` 原本只比对「表/列/索引在不在」,**不比对可空性**。
已加:迁移建出的列**不允许比模型更严**(模型说可空、库里 NOT NULL)。
只查这个**危险方向**:反方向(模型 NOT NULL、库里允许 NULL)是无害的宽松,
历史遗留里有 28 处,一并报出来只会制造噪声、逼人修没收益的东西。
> 这条守卫**有边界**,已在代码注释与设计文档 §3.5.1 写明:
> 它在**全新库**上跑整链,只能防「未来的迁移建错」,**防不了已有库的历史漂移**。
> 已验证:临时禁用 `0045`,该用例仍通过(全新链本来就是对的)。
## 验证
- `TZ=Asia/Shanghai`:**503 passed / 6 skipped**(+3 条兑换列用例)
- `TZ=UTC`(迁移 + 列表相关)45 passed;ruff 全绿;前端 build 通过、lint 0 errors
- 真实开发库:迁移已应用(head `20261004_0045`),
`source_ref` 现在 `IS_NULLABLE=YES` 且唯一索引仍在;
不带幂等键的事件确实能落库;兑换聚合返回值正确
注:验证时在开发库那个有 CK 的账号上留了 3 条 `exchange` 演示事件
(兔兔白日梦 / 冠军鸡腿 / 烟花),是为了让列有数据可看,随时可删。
|
2026-10-04 17:25:40 +08:00 |
|
yml2213
|
885a780bd3
|
feat(account): S6 账号页重构 —— 列表关键列 + 详情页生命周期状态卡
S6 的展示层部分(数据模型部分见上一个提交)。目标:客服打开账号页要能一眼回答
「这账号能不能用、要不要我处理、账上还有多少」。
## 账号列表:加关键列 + 三个筛选
新增四列,**全部直接来自 `accounts` 行**(不 JOIN、不聚合 —— 列表是全站最热接口):
| 列 | 内容 |
| --- | --- |
| **CK** | 有无 CK / 正常 / 已失效 / 检测异常 + 最近检测时间 |
| **绑定** | 精/电/店 三个作用域各一个小标签(✓ / … / ✗ / ?) |
| **积分 / 鱼翅** | 两行小字 |
新增筛选:**CK 健康**、**角色绑定**(三个作用域任一命中即算)。
`AccountOut` 相应补齐 `has_cookie` / `ck_check_status` / `ck_checked_at` /
`points` / `gold_balance` / 三组绑定状态与标签。
## 账号详情页:生命周期主页
顶部新增**生命周期状态卡**,把之前散落的信息聚成一眼可读的几格:
- **生命周期状态**:状态标签 + 不参与自动化标记 + 变更时间与原因
- **CK**:有无 / 健康 / 最近检测时间
- **角色绑定**:精/电/店三格
- **账上还有多少**:积分 / 鱼翅 / 兑换余额
- **归属与标注**:分配客服(下拉)+ 标签(就地编辑)—— 之前详情页只能看不能改
业务台账补上**登录成功次数**与**最近登录**(`stats` 里本来就有,只是没展示)。
时间线已有分类筛选,生命周期操作入口(停用/归档/恢复/自动化)S3 已加。
## ⚠️ 顺带修掉一个静默 bug
`bind_payload()` 的字段名按**作用域名**拼成了 `elite_bind_status` / `peace_bind_status`,
而 schema 声明的是 `role_bind_status` / `xpd_bind_status`。Pydantic 默认
`extra='ignore'`,于是多出来的键**被静默丢弃**、声明过的字段保持默认值 ——
**接口永远返回 unknown 而不报任何错**(这正是它躲过前面几轮测试的原因)。
修法:前缀改为由**列名**推导(`SCOPE_PAYLOAD_PREFIX`),并在模块导入时自检
「载荷前缀 == 列名前缀」。另加两条用例:`bind_payload` 的键必须都被
`DouyuTaskAccountOut` / `AccountOut` 声明(这是能抓到这类错的那种用例)。
顺带发现我自己之前那条 payload 用例**把错误的前缀当成了预期**,一并修正。
## 自己踩的坑
详情页加客服列表时又把 `useEffect` 写在了早期 `return` 之后 ——
和本次会话前面修 `LoginTasksPage` 的是同一个错。eslint(`react-hooks/rules-of-hooks`)
当场抓住,已上移。**`tsc` 与 `vite build` 都不会报这个**。
另外:列表关键列那处编辑我曾以为已生效(脚本打印了成功),实际没落到文件里 ——
后面是靠「读回文件确认」和用例失败才发现。已改为每次都读回核对。
## 验证
- `TZ=Asia/Shanghai`:**497 passed / 6 skipped**(S6 前半 490 → +7)
- `TZ=UTC`(相关文件)通过;ruff 全绿;前端 build 通过、lint **0 errors**
- **真实开发库端到端**:列表与工作台接口都返回了真实值
(`has_cookie=True` / `ck_check_status=valid` / `role_bind_label=未知`),
修 bug 前这两处返回的是被丢弃后的默认值
未做:`bind_status` 旧列待 S8 drop;保留策略里「状态派生类查询」已可下探到 7 天档(待确认)。
|
2026-10-04 16:45:39 +08:00 |
|
yml2213
|
a64380fbcc
|
feat(account): S6 bind_status 三列拆解 —— 四值枚举、唯一写入口、前端改读账号
`accounts.bind_status` 是个**语义垃圾场**:一个自由文本列,6 个文件、**29 处写入**、
20 多种取值,混了三种东西 —— 真状态(`bind_confirmed`)、动作痕迹(`goods_locked`)、
纯查询痕迹(`points_queried`)。它是**覆盖式**的,所以 `bind_confirmed` 会被之后
任意一次查询盖掉 —— 「这个账号到底绑没绑」根本查不出来。
`esports_bind_status` / `xpd_bind_status` 是同一个病。
## 收敛成四值 + 三列一义
`unknown` / `unbound` / `pending` / `bound`,每个作用域一列:
| 列 | 作用域 |
| --- | --- |
| `accounts.role_bind_status`(**新增**) | 精英宝典角色 |
| `accounts.esports_bind_status`(收敛) | 电竞手册角色 |
| `accounts.xpd_bind_status`(收敛) | 和平小店角色 |
- 新增 `services/account_binding.py`:唯一写入口 `set_bind_status()`,
传旧自由文本直接 `ValueError`(否则垃圾会被写回去);`map_legacy_bind_status()` 负责旧值映射。
- **29 处自由文本写入全部删除或改走枚举**:真状态走枚举,动作/查询痕迹**不再写状态列**
(动作已由 S5 的直发事件记录,查询痕迹本来就不该是状态)。
- `accounts.bind_status` 保留原值便于比对,**已无任何写入方**,待 S8 drop。
- 一条**源码扫描用例**禁止任何地方再出现 `account.bind_status = "..."` 这类写入。
## 回填不猜(设计文档 §5.5)
只映射**明确陈述了绑定关系**的取值(`bind_confirmed`/`esports_bound`/`xpd_bound` → bound,
`*_qr_*`/`*_ready` → pending,`game_not_bound`/`xpd_not_bound` → unbound),
**其余一律 unknown**。因为列是覆盖式的,「最后一件事是查询」并不代表没绑定。
迁移 SQL 与 Python 映射有一致性用例:**解析迁移源码里的 CASE 逐条比对**
(已反向验证:故意改错一条映射,用例立刻红)。
## ⚠️ 顺带救回一个差点被我自己丢掉的状态
「**电竞手册是否已开通**」以前**只寄生在 `esports_bind_status` 的 `esports_opened`
取值里**(`elite_opened` 有独立布尔列,电竞没有)。归一化会把那个取值抹成 `unknown`
→ **静默丢状态**。所以:
- 新增 `accounts.esports_opened` 布尔列(与 `elite_opened` 对称),开通任务成功时置位;
- 迁移里**必须先 `save_esports_opened()` 再归一化**,顺序用例钉住;
- 详情页「电竞手册」开通状态改读 `esports_opened`。
## 前端不再从任务日志反推账号状态
工作台原来从**当前加载的那一页任务**里挑每个账号最新的一条来推绑定状态
(`latestConfirmBindTaskByAccount` 等)。两个问题:① 支撑任务一旦翻出窗口,
**显示的状态就是错的**;② 这让任务日志变成「账号状态库」,于是它永远不敢清理。
现在状态读账号字段,任务日志只补「扫码流程中的瞬时信息」(如确认前的
`pending_role_name`)。删掉了两个不再需要的派生与对应的 `clearPending` 绕行逻辑。
## 踩到并修掉的东西
- **模型与迁移不一致**:我把模型收窄成 `String(16)`,迁移却留着 `String(32)` ——
测试库按模型建、生产按迁移建,两边列宽不同。已让迁移也收窄,且**收窄必须在
归一化之后**(旧取值最长 19 字符,先收窄会截断成废值)。downgrade 也补齐了反向操作,
已实测 `downgrade -1 && upgrade head` 干净。
- **parity 用例第一版是抄了一份 SQL**(迁移改了用例也不会红,等于没测)——
改成解析迁移源码;后来又因为列已收窄、19 字符的旧值塞不进测试库,
干脆改成源码级解析比对(不造数据)。
## 验证
- `TZ=Asia/Shanghai`:**490 passed / 6 skipped**(S5 后 450 → 净增 40)
- `TZ=UTC`(相关文件)106 passed;迁移链 3 passed;ruff 全绿
- 前端 `tsc -b && vite build` 通过、`npm run lint` **0 errors**
- 真实开发库:迁移重放后三列均为 `varchar(16) NOT NULL DEFAULT 'unknown'`;
12 个账号的旧值 `elite_status_queried`(纯查询痕迹)→ **unknown**(没有猜成 bound)
未做(后续):S6 的**账号页重构**(列表关键列 / 生命周期主页)保留;
`bind_status` 旧列待 S8 drop;保留策略里「状态派生类查询」现在技术上可下探到 7 天档,
保守起见留待确认(影响的是在用页面显示,不是历史)。
|
2026-10-04 16:28:06 +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
|
253571730a
|
feat(account): 补上改备注接口,remark_changed 事件从此有发射点
`remark_changed` 此前是「定义了事件类型、但压根没有改备注的接口」,于是永远不发。
按 S4 的原则(留着一个永远不发的类型比删掉更糟 —— 看代码的人会以为它有数据),
补接口而不是删类型。
## 接口
`PUT /api/accounts/{id}/remark`,权限点 `account:remark`(授予 super_admin /
operation / support,与 `account:tag` 一致)。
- 与改标签一样走 `record_account_action()`:**审计 + 账号时间线一次写完**,
审计带 `account_id`,事件 detail 带 `{from, to}`;
- **只做单个、不做批量**:备注是自由文本,给一堆账号写同一句备注没有实际意义
(标签是短枚举才有批量);
- 内容 trim 后截断到 256(与 `accounts.remark` 列宽一致),清空也记一条事件;
- 备注没变化时直接返回、**不写事件**:否则时间线会被「点开保存了一下但没改」刷满。
## 权限点新增前的安全检查
新权限点会被 `custom_permissions` 覆盖,所以先查了库:
`admin` / `kf1_test` 的 `custom_permissions` 是 **JSON null**(不是 SQL NULL),
Python 侧取出来是 `None` → `get_user_permissions` 走角色默认。
也就是说**没有任何用户在用自定义权限列表**,新增权限点不会把谁的能力锁掉。
(这一点很容易看错:`IS NULL` 判出来是「有自定义权限」,`JSON_LENGTH` 还是 1。)
## 前端
- 详情页「备注」一行改为可编辑(铅笔 → 输入框 → 保存/取消,回车即存);
- `EVENT_STYLE` 补 `remark_changed`,时间线里能看到备注的每次变更。
## 一个自己踩到又修掉的坑
第一版把两个 `useState` 写在了 `if (!accountId) return <Empty/>` **之后** ——
正是本次会话刚在 LoginTasksPage 修掉的那类 hooks 顺序违规(`react-hooks/rules-of-hooks`
直接报错)。已上移到早期 return 之前的 state 区块。lint 抓到,构建不报,属于
「能跑但迟早炸」的那类问题。
## 验证
- `TZ=Asia/Shanghai`:**426 passed / 6 skipped**(S4 后 420 → 新增 6 条)
- `TZ=UTC`(相关文件)77 passed;ruff check/format 全绿
- 前端 `tsc -b && vite build` 通过、`npm run lint` 0 errors
- 新增用例:写入并带 from/to 事件与审计关联、未变化不写事件、清空也记一条、
trim + 截断到 256、账号不存在 404、**新权限点确实在三个角色的默认权限里**
(防止「加了接口但没人能用」)
|
2026-10-04 15:21:48 +08:00 |
|
yml2213
|
0a922ce56d
|
feat(account): S4 事件发射点补齐 —— 审计与时间线打通、批量事件、ck_invalid 只认转变
S3 让 `status` 可信,S4 让**时间线完整**:账号上发生的事,时间线里都能查到。
## 补齐「定义了却从没发过」的事件
`assigned` / `tag_changed` 这两个类型早就定义了、前端 `EVENT_STYLE` 也渲染了,
但**全仓没有任何代码发过**(`grep` 只有定义处)。本次接上:
- 单个分配 / 批量分配 → `assigned`(detail 带 assigned_to / assigned_username)
- 单个改标签 / 批量改标签 / 按筛选批量改标签 → `tag_changed`(detail 带 from/to)
## 新增 `ck_cleared`,并修掉 `ck_invalid` 的刷屏
- **`ck_cleared`**:清 CK 是**不可逆**操作(重登当前成功率极低),时间线里却不留痕。
现在单个清除与批量清除共用 `account_lifecycle.on_ck_cleared()`。
- **`ck_invalid` 改为只在「由好变坏」时发**。旧实现每轮巡检只要判定 invalid 就发一条,
源码注释写着「避免每轮刷屏」,**但代码并没有做到** —— 6 小时一轮、失效一周就是
28 条一模一样的事件。判定失效是**一次状态转变**,不是每轮读数。`error`(查不到)
同样不改变结论、不发事件。
## 审计与时间线一次写完(防漏一边)
新增 `audit_service.record_account_action()`:**审计 + 账号事件一起写**,并把
`account_id` 落到审计上。这两件事必须成对出现 —— 只写审计,时间线就缺「谁改了什么」;
只发事件,合规审计就缺一条。分成两次调用写,迟早有人漏一边(`assigned` 就是这么漏的)。
顺带把批量删除的审计补上被删账号 id(`detail.account_ids`,截前 200 条):
删账号会**连带删掉它的事件时间线**(外键),审计是唯一痕迹 —— 这正是 10-03
那次事故要留的东西。
## 批量操作不再逐条开 SAVEPOINT
批量分配/改标签可能覆盖几千个账号,而每个账号的时间线都该各留一条。
逐条走 `emit()` 会给每条开一个 SAVEPOINT,几千条时请求明显变慢。
新增 `emit_bulk()`:一次 executemany,整批包在**一个** SAVEPOINT 里,
与 `emit()` 一样**绝不抛异常**(整批失败就整批丢弃 + warning,不影响业务写入)。
## 检测入口收口:4 处 → 1 个 `on_ck_verdict()`
`ck_health` / 人工检测 / 工作台执行前检测 / 重登后复检 原本各自写
`ck_check_status` + `ck_check_result` + `ck_checked_at` + 发事件 + 调 `sync_status`,
口径容易分叉(`ck_invalid` 的刷屏就是这么来的)。现在统一走
`account_lifecycle.on_ck_verdict()`,顺序固定:**写结论 → 由好变坏记事件 → 重算状态**。
至此 `account_lifecycle` 成为**唯一**写生命周期字段的模块,并加了一条**源码扫描用例**
把它钉死(任何地方直接 `acc.status = ...` 都会让它红,并指出 file:line)——
比写在文档里的约定可靠。虎牙的 `huya_accounts.status` 是同一个病,但属另一套模型,
已在用例里显式排除并注明。
## 接口与前端
- 时间线支持 `?category=` 筛选(lifecycle / auth / business / ops);
响应补 `category` / `subject_*` / `actor_*`
- 前端 `EVENT_STYLE` 补上 `status_changed`(**S3 我漏的**:加了事件类型却没加样式,
页面上显示成原始英文)与 `ck_cleared`;时间线加「分类」筛选
## 验证
- `TZ=Asia/Shanghai`:**420 passed / 6 skipped**(S3 后 404 → 新增 16 条)
- 迁移链 3 passed;ruff check/format 全绿;前端 `tsc -b && vite build` 与
`npm run lint`(0 errors)均通过
- **两条关键用例都做过「反向验证」**(先注入旧行为确认会红):
- 把 `ck_invalid` 改回「每轮都发」→ 用例报 `3 == 1`;
- 在路由里直接写 `acc.status = ...` → 不变量用例给出精确 file:line。
- 新用例还覆盖:批量分配/改标签逐账号发事件、清 CK 事件与审计关联、
`emit_bulk` 的跳过与容错、时间线分类筛选、批量删除的 id 留痕。
未做(后续):S5 业务事实直发事件 + `douyu_tasks` 保留策略、S6 账号页重构 +
`bind_status` 三列拆解。`remark_changed` 目前没有改备注的接口,故无发射点(已在文档标注)。
|
2026-10-04 15:09:46 +08:00 |
|
yml2213
|
1a8710834a
|
feat(account): S3 生命周期状态收口 —— 6 状态状态机、唯一推导入口、停用/归档闭环
`status` 列从「空壳」变成可信状态:S1 建了列、S2 让账号独立,本提交让状态真正
反映账号处境,并把「哪些账号要人处理」变成可筛选的事实。
## 状态机(7 → 6 个状态)
`imported` / `active` / `invalid` / `manual` / `disabled` / `archived`
**砍掉 `relogin`**:它完全等价于 `invalid + relogin_next_at is not None`,
独立成状态就多出一个必须与 `relogin_next_at` 同步的字段 —— 正是要消灭的「双份真相」。
## 唯一推导入口(防「双份真相」)
新增 `services/account_lifecycle.py`:
- `derive_status(account)` —— **纯函数**,按 cookie / ck_check_status / relogin_*
推导状态;人工态(disabled/archived)优先级最高,不被覆盖;
- `sync_status(db, account)` —— 推导 + 变了才写,**幂等**(没变则不写库、不发事件,
否则巡检每轮都往时间线刷一条);
- `set_manual_state(db, account, state)` —— 停用/归档/`auto`(恢复自动推导);
- `on_login_success(db, account)` —— **「登录成功对账号意味着什么」只定义一次**:
作废本轮重登退避痕迹 + 重算状态。于是**「待人工」的账号被人工登录救回来时状态
会自动恢复**,而不是卡在 manual。
- `MANUAL_KINDS` 从 `jobs/ck_relogin.py` 挪到这里(service 不该反向依赖 job)。
**9 处底层判据写入点全部接线**:登录成功、CK 清除(单个/批量)、ck_health 巡检、
人工 CK 检测、工作台执行前检测、重登复检、重登结算(成功/失败退避)。
## 消费端:补上一个真缺口 + 删掉两处临时派生
- ⚠️ **缺口**:`ck_health` / `ck_relogin` / `count_pending_relogin` 此前只按底层判据
选行,**没排除 disabled/archived** —— S2 提交里已标注,状态一上线就会变成
「停用的账号照样被自动重登」。现已全部排除。
- `automation.manual_queue` 原本用 4 个条件自己拼「需要人工」,现在一句
`status == 'manual'`。同一判据从「3 处各写一遍」收敛成「1 处推导」。
## 接口与前端
- `PUT /api/accounts/{id}/lifecycle`(disabled/archived/auto + 原因,写审计)
- `PUT /api/accounts/{id}/automation`(**接上死开关**:`auto_managed` 此前没有任何
接口/前端能改,永远是默认 True)
- 新增权限点 `account:manage`(运营默认拥有)
- 列表支持 `?status=` 筛选;**归档默认不出现**(要看需显式筛选)
- `AccountOut` / 概览接口补 status / status_label / status_reason / auto_managed
- 前端:账号列表加「状态」列与状态筛选;详情页状态卡 + 停用/归档/恢复/自动化开关
## 两个自查出来的问题(都已修)
1. **幂等键反而丢事件**:状态事件一开始用 `status:{id}:{target}:{秒级时间戳}` 作
`source_ref`,结果同一账号在同一秒内两次切到同一状态就撞唯一键,`emit` 静默吞掉
—— **状态改了、时间线却少一条**,正是本模块要消灭的「悄悄不一致」。
改为不传 `source_ref`:`sync_status` 本身已幂等,不存在重复写入路径。已加用例。
2. **迁移与运行时规则分叉**:迁移 0042 的状态回填 CASE 原本会写出已被砍掉的
`relogin`,且判据与 `derive_status` 不一致(次数用尽的会回填成 invalid 而非 manual)。
已改成逐字对应,并加一条用例**真跑那段 SQL 与纯函数逐条比对**(已验证:
临时改回旧 CASE 该用例立刻红)。
## 验证
- `TZ=Asia/Shanghai`:**404 passed / 6 skipped**(S2 后 373 → 新增 31 条)
- 迁移链 3 passed;ruff check/format 全绿;前端 `tsc -b && vite build` 通过
- 真实开发库核对:落库状态分布 active 4 / imported 8,**与 derive_status 逐账号比对无漂移**
- 新增用例覆盖:derive 真值表(每状态入口 + 人工态优先)、sync 幂等、
人工态切换、**全生命周期走查**(导入→登录→失效→重登→转人工→人工救回→清 CK→停用)、
自动化排除 disabled/archived、列表状态筛选、接口
未做(后续阶段):S4 事件发射点补齐、S5 业务事实直发事件、S6 账号页重构。
|
2026-10-04 14:48:22 +08:00 |
|
yml2213
|
94eace50c1
|
fix(events): S2.1 修掉 emit() 的 naive 时区偏移,并打通台账列与审计关联
外部审计对 S1+S2 提出 3 条,全部复核成立,本提交一并修掉。第 1 条是真缺陷,
且已经写进数据了。
## 1. ⚠️ emit() 把 naive datetime 当成「本地时间」(数据正确性)
`emit()` 原来写 `(occurred_at or now).astimezone(UTC)`。对 **naive** 值,Python 会
把它当作**服务器本地时间**再换算 —— 本机 UTC+8,事件时间整整早 8 小时。
naive 的来源很具体:收集器用**裸 SQL** 读 `douyu_tasks`,绕过 `UTCDateTime`
装饰器,拿到的就是 naive。而这条路正是「充值/兑换/积分/开通」台账。
仓库早有既定约定(写在 `database.UTCDateTime` 里):**naive → 视为已是 UTC,原样用**。
`scheduler._as_utc` / `yyb_service._as_utc` / `audit.py` 都遵守了,**只有 emit() 漏了**。
现抽成 `account_events_service.to_naive_utc()` 统一收口,并补齐文档。
**数据库铁证**(修复前,本机开发库):
```
事件 occurred_at 源 douyu_tasks.finished_at 相差
2026-09-30 20:23:36 → 2026-10-01 04:23:36 8 小时
```
已用 `UPDATE ... JOIN douyu_tasks` 把那 3 条按源时间对齐(相差 0 秒),
修复 SQL 记在账号事件设计文档 §11.2 第 5 条,供曾开启过收集器的环境使用。
该作业默认 `enabled=false`、需人工开启,故生产大概率无受影响数据。
## 2. 回归用例:这个 bug 在 UTC 机器上**看不见**
`astimezone(UTC)` 在 TZ=UTC 下恰好是恒等变换 —— 旧实现与正确实现结果相同。
也就是说单写一条普通用例,在 CI/容器里会**假绿**,只在开发机现形。
所以用例显式把进程时区切到 UTC+8(`_force_non_utc_timezone`)。已验证:
**在 TZ=UTC 下临时还原旧实现,该用例照样红**。另补一条 aware 路径的用例,
防止「为了修 naive 把换算弄坏」。
顺带补了收集器的端到端用例:**事件 occurred_at 必须等于源任务 finished_at**。
旧用例只断言 written/watermark 数量、从不看时间,这正是 bug 一路绿灯的原因。
## 3. emit() 支持 S1 建的台账列(此前是「空壳」)
迁移 0042 给 `account_events` 加了 `category` / `subject_type` / `subject_key` /
`actor_type` / `actor_id` 与复合索引 `ix_account_events_ledger`,但 `emit()` 一直没
暴露它们 —— 列建好了没有写入方。现补齐参数与 INSERT,并加用例锁住「能写」。
S3 的 `status_changed`(`category="lifecycle"`)与 S5 的台账查询都要靠它。
## 4. 清 CK 的审计补 account_id
`DELETE /cookies/{id}` 与 `/cookies/batch` 的审计只写了 `target`(自由文本,
账号改名/重名就关联不上)。S1 已给 `audit_logs` 加了 `account_id`,这里补上写入
(批量时仅在只涉及一个账号时填),并加用例断言审计能反查到账号。
这是 S6「审计进账号时间线」的前置。
## 验证
- 新增/修改 5 条用例,先红后绿;**已验证在 TZ=UTC 下旧实现照样被这些用例抓到**
- `TZ=Asia/Shanghai`:**373 passed / 6 skipped**(原 368 + 5 条新用例)
- `TZ=UTC`:369 passed / 4 failed —— 那 4 条是 `test_huya_web_cookie_fields.py`
的 qimei 用例,**在 main 上同样失败**(qimei 按 SDK 语义编码本地时间,
与本次改动无关,也未修)
- ruff check / format 全绿;前端 `vite build` 通过(本次未动前端)
|
2026-10-04 14:30:02 +08:00 |
|
yml2213
|
3a227ffec1
|
docs(account): 斗鱼账号全生命周期全新设计,取代旧的目标模型
围绕账号做一套全新设计,不再参照虎牙,并丢掉旧结构的包袱。
推翻的直接证据:
1. 三个「状态」都找不到家 —— 凭证在 login_tasks.cookie、健康与重登在 login_tasks.ck_check_*/relogin_*、
生命周期状态根本不存在(只能靠「有没有持 CK 的记录」反推);
2. accounts.bind_status 一列塞了 36 种取值,混了三种性质(真绑定状态/一次性动作痕迹/业务结果)
且互相覆盖 —— 查一次精英宝典状态就把 bind_confirmed 冲掉,账号详情页还拿它显示「绑定状态」;
3. 开通/积分/兑换/充值这些账号事实全埋在 douyu_tasks.result(22 万行 / 687MB,绝大多数是轮询噪声);
4. 账号检测页粘贴文本检测、结果不落库、连 account_id 字段都没有,与库内账号完全脱节。
设计要点:
- 账号为聚合根;当前状态在账号上、发生过什么在 account_events,两者严格分离;
- 登录态(CK/健康/重登)内联 accounts(否决独立表:多会话与懒加载风险,且内联切换面最小),
并说明 1:1 场景下 defer() 已能解决列表查询拖大字段的问题;
- 新增显式生命周期状态机 imported/active/invalid/relogin/manual/disabled/archived,
写入收口到唯一服务,禁止各处直接赋值(bind_status 崩掉的直接教训);
- account_events 扩展 category/subject_type/subject_key/actor_* 与台账复合索引,
一张表同时承载「时间线 + 开通/积分/兑换/充值台账」,支撑 365 天同皮肤规则;
有流程的兑换工单仍独立建表 —— 流程归工单、事实归事件流;
- login_tasks 瘦身为纯登录尝试历史,douyu_tasks 降级为执行日志并加保留策略;
- 迁移沿用 CK 解耦文档的回填机制(分块/幂等/自校验 fail closed/值级校验),
历史业务记录不回填 douyu_tasks,需要时走斗鱼侧权威接口按需同步;
- 落地分 S1~S8,S1→S2 为关键路径,S8(drop 旧列)不可逆且前置每日备份。
同时更新 CK 解耦文档头部,标注其目标模型已被取代、迁移机制仍然有效。
|
2026-10-04 13:37:07 +08:00 |
|
yml2213
|
44a1a2bc95
|
docs(account): 逐行核对 CK 解耦设计,并补账号全生命周期信息模型
在动手前把那份「待评审」的设计与当前代码逐行对了一遍:
1. 方向成立(CK 确实只存在于 login_tasks.cookie),但原文 §5 漏了 4 处耦合点:
- services/account_service.py:31 账号列表的 CK 筛选
- services/douyu_service.py:283 业务任务页的账号可见性
- routers/accounts.py:629-643 账号详情概览的裸 SQL(账号事件功能新增,原文写作时还没有)
- routers/login.py:259 与 cookie_holding_filter 同口径的重复实现
2. latest_cookie() 是死代码,原文列为改造目标,实际直接删即可;
3. 修正漂移的行号(accounts.py 285/324/374 → 295/338/391);
4. 补 §13「账号全生命周期信息模型」:现状把账号事实散在 5 张表 7 个页面,
指出「账号没独立」在数据/接口/UI 三层的具体表现,以虎牙账号(已解耦的样板)为参照,
给出四层信息模型、生命周期阶段缺口表与 S1~S6 落地顺序。
|
2026-10-04 13:31:39 +08:00 |
|
yml2213
|
98a9f0e0bc
|
chore(merge): 账号事件迁移改号 0041,并去重排查报告
dev 与 main 从 294c6cd 分叉后,各自新增了一个 0040 迁移(都从 20261002_0039 起链):
- main:20261003_0040 douyu_tasks.account_assigned_to(生产已应用)
- dev: 20261002_0040 account_events(未上生产)
两者合并即两个 alembic head,upgrade head 会直接失败。因为账号事件迁移从未被生产库
应用过,改号是安全的:
1. account_events 迁移改名为 20261003_0041,down_revision 指向 20261003_0040;
2. tests/test_migrations.py 的 HEAD_REVISION 同步改 20261003_0041;
3. 迁移保持幂等,本机开发库(alembic_version 已是 20261003_0040、account_events 表已存在)
实跑 upgrade 通过,仅推进版本号、数据未动;
4. 顺带修正规划/解耦设计里「下一个迁移编号」的过期取值(现为 20261003_0042)。
排查报告去重:两个会话各自写了一份《生产服务器高负载排查…》报告,内容重叠且互相矛盾
(一处写 8 核/16GB,另一处有 nproc 原始输出为 4 vCPU/7.1GiB)。合并为单一文档
docs/生产服务器高负载排查与优化报告.md,以带原始命令输出的事实为准,删除重复的那份,
并修正引用它的迁移方案文档。
|
2026-10-04 13:20:36 +08:00 |
|
yml2213
|
e88c41385b
|
Merge branch 'main' into dev
# Conflicts:
# tests/test_migrations.py
|
2026-10-04 13:17:43 +08:00 |
|
yml2213
|
b69c9ff6b6
|
docs(perf): 沉淀生产服务器高负载排查与MySQL任务排序优化实证报告
1. 记录Postgres JIT导致CPU飙升197%的排查与禁用优化;
2. 记录MySQL任务列表跨表排序引发Using filesort与Out of sort memory的根因诊断;
3. 记录新增account_assigned_to与复合索引落地、24万历史任务回填及线上实测87倍提速实证。
|
2026-10-03 13:26:19 +08:00 |
|
yml2213
|
7790be4e04
|
perf(douyu-tasks): 为斗鱼任务增加归属客服索引字段,彻底消除全表 filesort 慢查询
1. 数据库新增迁移 20261003_0040,在 douyu_tasks 表增加 account_assigned_to 字段,并创建 (account_assigned_to, handbook_scope, id) 与 (account_assigned_to, id) 复合覆盖索引;
2. 迁移脚本自动回填历史 24 万条任务的所属客服 ID;
3. 任务服务与账号路由联动:创建任务时自动继承 account_assigned_to,修改账号分配时同步更新关联任务归属;
4. 斗鱼任务查询路由 _visible_tasks_query 改为通过 account_assigned_to 复合索引过滤,消除与 accounts 表的跨表关联与临时表全量文件排序;
5. 新增 test_douyu_task_assigned_optimization 单元测试并通过整链迁移与回归测试。
|
2026-10-03 13:10:59 +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
|
294c6cd058
|
docs: 登录任务与 CK 解耦设计(待评审)
根因:login_tasks 一张表同时扛四件事——CK 本体、CK 健康状态、重登调度、
登录历史。前三个是「账号当前状态」,第四个是流水,生命周期不同,于是
清理流水必然伤到状态(2026-10-03 因此丢了 5019 个 CK)。临时护栏只是兜底。
方案:照 huya_accounts 的既有先例,把 CK 与状态挂到 accounts,
login_tasks 退化为纯流水。
关键结论:
- **不做双写**:两处真相会悄悄发散(比报错更难发现),且旧路径会掩盖新
路径的 bug;而它买到的回退保险本来就有——accounts.cookie 是权威副本,
随时能反向回填出旧列。改为代码一次切换、旧列延后 drop。
- 两条不可省项:迁移必须自校验(回填不全就中止启动,fail closed);
在途判定仍要查 login_tasks(实测 255 行在途),需补 (account_id,
attempt_status) 索引。
- 登录服务里「一行一账号」的复用逻辑是耦合产物(且已漏水),切换时其
第一段 cookie_holding_filter 查询必须一起重写,否则静默退化。
含迁移与回填 SQL、读写切换清单、7 个提交的提交计划与依赖关系、
验收基线(13264 个账号持 CK)、测试计划、回退方案,以及待确认决策点。
|
2026-10-03 11:40:05 +08:00 |
|
yml2213
|
cd60a48039
|
docs: 记录 CK 误删事故复盘与恢复方法,并补两条硬规则
事故:删除登录任务时误删了 5019 个仍持有有效 CK 的记录(显示状态是
attempt_status,与是否持有 CK 语义不同)。已用 binlog 全部恢复。
复盘含:影响范围、根因、修复、可复用的 binlog 恢复流程,以及途中踩到的
四个坑(MySQL 8.4 无 mysqlbinlog、解出的列名是 UNKNOWN_COL* 导致假阴性、
tail 吞进度、JSON bytes 键)。
|
2026-10-03 11:11:37 +08:00 |
|
yml2213
|
6ce988a3da
|
docs: 记录部署即跑迁移、时区口径与任务列表分页用法
|
2026-10-03 10:44:06 +08:00 |
|
yml2213
|
5995f16c39
|
docs(plan): 补续作入口与 P0 踩坑留档(外部表结构核实、uv.lock、容器验证姿势)
|
2026-10-02 22:54:36 +08:00 |
|
yml2213
|
d669552be8
|
docs(plan): P0 施工单与真机验证结果归档(迁移 0039)
|
2026-10-02 22:51:25 +08:00 |
|
yml2213
|
afa5c815f6
|
feat(orders): P0 订单读取适配层——连 order_site 只读库查订单(默认关闭)
让运营能按订单号查到订单,作为后续资料收集(P2)与皮肤准入(P3/P4)的输入。
order_site 零改动:只读直连、独立极小连接池、可一键降级。
上机核实推翻了规划两处假设(详见 §6.5.1):
- orders 表没有 order_no 列,只有 id 与 platform_order_id → 查询入口用后者;
- platform_order_id 非唯一:541 组重复,形态固定为「91kaquan 内部行(已发货)
+ 真实店铺行(无人管)」。按规划原文的单行判据,这 540 个已发货订单会被误判
成「没人管」而放行重复履约。改为**订单号维度聚合**:任一行已发货即 auto_fulfilled。
真机验证(只读账号 livehub_ro):三桶判定全对,重复组正确拦下并回显真实
店铺行的中文名(狂沙舞者,而非内部码 suit_sand_dancer),单次查询 3.3ms。
顺带修一个只在真机才暴露的 bug:_get_engine 先置 _INITIALIZED 再读 URL,
一次落空就把模块永久毒化成不可用,之后补上连接串也救不回来。
|
2026-10-02 22:50:20 +08:00 |
|
yml2213
|
c3fd299f3a
|
test(db): 测试改用真实 MySQL,方言差异不再漏到生产
生产是 MySQL 8、测试是 SQLite,两者的方言差异在本地完全测不出来。今天为此
付出了两次代价:
1. `ORDER BY ck_checked_at ASC NULLS FIRST` 在 SQLite 上完全正常,本地 258 条
全绿,一部署到生产第一次巡检就 1064 语法错误(MySQL 不支持 NULLS FIRST);
2. P1b 自动重登的结算复用调度器事务读取,在 SQLite 下看不出问题,但在 MySQL 的
REPEATABLE READ 下读到的是批次开始前的快照 —— 10 条实际成功的重登全部被记成
失败。SQLite 的读一致性与 MySQL 不同,这类问题跑一万遍也复现不出来。
改动:
- 新增 tests/support/db.py:测试专用引擎/会话工具,连接串从 .env 读取,
默认指向独立的 douyu_login_test 库;
- 新增 tests/conftest.py:会话级建好表结构,每个用例前后清空所有表做隔离;
- 根 conftest.py 接管连接串设置:它在 pytest 收集阶段最先执行,在这里把
DATABASE_URL 指向测试库,应用模块首次 import 拿到就是 MySQL,不依赖任何
import 顺序(原先这里是 sqlite:// 兜底,改造后会变成「先建 SQLite engine
再打补丁换成 MySQL」,顺序一变就静默跑错库);
- 21 个测试文件的 setup 样板统一改用共享测试引擎;
- 两个迁移测试改为在临时库 douyu_login_mig_<随机> 上跑完整 alembic upgrade head,
跑完 drop —— 迁移脚本本身也终于在 MySQL 上被验证;
- dev.sh 与 README 的过时说明一并更新(原先写着「不需要 MySQL / .env 配置」;
「测试各自建 SQLite 内存库」)。
安全与易用:
- 测试库名与开发库同名时**直接拒绝运行**(clear_tables 会 DELETE 所有表,
一旦 TEST_MYSQL_DATABASE 被指到 douyu_login 就是数据灾难);
- 连不上 MySQL 时给出可操作提示(先 ./dev.sh 起库 / 用 TEST_MYSQL_DATABASE 覆盖),
而不是抛一段 SQLAlchemy 原始堆栈。
验收:
- 全量 301 条通过,连跑两次结果一致(隔离有效),耗时约 12.5 秒(原 SQLite 约 3.6 秒);
- 单文件独立运行通过(不依赖全量执行顺序);
- `grep -rn sqlite tests/ conftest.py` 已无实际用法;
- **方言确实生效**:临时把一条查询改回 `NULLS FIRST`,13 条测试立刻变红(含运行期
失败),还原即恢复;这条由 test_generated_sql_is_mysql_compatible 长期守着。
代价(已在 README 与规划附录 C.2 记明):跑测试前必须先有 MySQL,pre-push 钩子
同理;CI 需要 service container。
|
2026-10-02 21:22:08 +08:00 |
|
yml2213
|
5645f19102
|
docs(plan): P1b 小规模实测完成——20/20 全部重登成功
按 §7.4.7 第一段跑实测,用现有手动重登链路(batch 65eda0142b4c)对 20 个
invalid 账号重登,两组各 10:A「还没在 attempt_* 时代重试过」、B「取码 bug
时代失败过」。
结果:
- 成功率 20/20 = 100%(历史推断只有 78%);耗时 2 分 13 秒(约 9 个/分钟);
- 极验滑块 0 次;邮箱取码全部正常 —— B 组全过,证实那 144 条「验证码失败」
确实是 20261001 修掉的取码 bug 造成的,修复在生产生效;
- 20 个 CK 全部替换成功,ck_check_status 由登录流程自动改回 valid。
可救量由此从估的 1,050 上调到约 1,350;失败分型仍要做但属少数路径。
实测速度与零极验说明当前风控容忍度比预想高,全量仍建议限速(每小时
100~200 个起步,观察极验率再决定是否加速)。
|
2026-10-02 20:46:17 +08:00 |
|
yml2213
|
9143a3afa0
|
docs(plan): 按线上实测重写 P1b 设计——失败分型,并把 P1b 提到 P1a 之后
线上数据把 P1b 的必要性和设计都改了:
- 巡检只产出失效清单、不兑现修复,1,356 个失效账号仍要人工逐个选集→重登→过
滑块,所以要把 P1b 提前(原计划等一到两周观察期,但检测准确性/误判封堵/
失效性质三个前提都已验证完);
- 更正一处误判:那 1,356 个失效账号不是「从未成功过」,而是「曾经成功、CK 放
了一两个月过期」。依据是 LoginTask 全项目只在一处创建(登录成功写回处),
导入账号不建 LoginTask;attempt_status='' 只说明成功发生在该字段出现之前;
- 失效应属会话级(斗鱼回「用户未登录」而非密码错误),用库里密码重登大概率
可救;按历史数据真实成功率约 78%,预计 1,356 个里可自动救回约 1,050;
- 新增失败分型 relogin_failure_kind(决策 11a):极验/凭证问题转人工,取码/
中断/网络自动重试,配额满退避次日。线上 415 个失败里 64% 实为可自动解决
(取码 bug 与重启中断),不分型会白费 265 个可救名额;
- 记录速率约束:13,618 账号对应 13,618 个独立邮箱(1:1),单账号配额满不影响
他人;瓶颈在 IP,建议走代理池 + 限速 30~60/小时;
- 排期改为 P1a → P1b → P0 → P2 → P3 → P4,P1b 估 2.5~3 人日;附录 A 补 P1b
迁移,上线按「小规模实测 → 限量开 → 全量」三段。
|
2026-10-02 20:41:25 +08:00 |
|
yml2213
|
f8c5e896d4
|
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 条通过。
|
2026-10-02 20:35:30 +08:00 |
|
yml2213
|
60f1a8d542
|
docs(plan): 记录 P1a 生产上线结果与首轮数据结论
按施工单三段走完并验证:调度器启动、手动 50 条观察无风控、启用后调度器自动
接管(12:27:26 跑完 200 条 / 22 秒 / next_run_at 按 tick 推进)。
记录两点供后续参考:
- 上线过程中抓到两个只有生产才暴露的问题(MySQL 不认 NULLS FIRST、compose 的
environment 是显式列表导致 .env 新增变量传不进容器),均已在 C.2 记档;
- 首轮 100% invalid 属正常且是价值所在:选行「从没检测过的优先」,而积压的
1,897 条多为 2026-08-04 批量导入、从未尝试登录、CK 已两个月的陈旧账号,
斗鱼侧统一回「用户未登录」,非风控。客服页会突然出现大批失效账号,属预期。
|
2026-10-02 20:34:00 +08:00 |
|
yml2213
|
3d5d0f551f
|
fix(automation): 去掉 MySQL 不认的 NULLS FIRST,排序改用可移植写法
生产 MySQL 上手动跑第一轮就报 1064:
You have an error in your SQL syntax ... near 'NULLS FIRST, login_tasks.id ASC'
NULLS FIRST 是 PostgreSQL/SQLite 语法,MySQL 不支持。本地测试全绿是因为跑在
SQLite 上(它认识这个语法),属于方言差异漏网 —— 生产是 MySQL、测试是 SQLite,
行为测试覆盖不到这类问题。
改成 `ck_checked_at IS NULL DESC, ck_checked_at ASC, id ASC`:三个方言下语义
一致(没检测过的优先)且都是合法 SQL。顺带把选行查询抽成 build_select_query,
便于按方言编译做回归。
新增回归测试:用 MySQL 方言 compile() 选行 SQL,断言不含 NULLS FIRST/LAST,
且「没检测过优先」用可移植写法表达。
规划附录 C.2 记下这条:写 SQL 前先确认方言;P0 连的是 order_site 的 PostgreSQL,
项目里已经出现 MySQL / SQLite / PG 三套方言。
|
2026-10-02 20:14:49 +08:00 |
|
yml2213
|
f8a4a0baff
|
fix(compose): 补齐自动化与 P0/P2 的环境变量透传,并放宽部署等待窗口
部署 P1a 时发现两个问题:
一、compose 的 environment 是显式列表(不是 env_file),只写进 .env 不会传进
容器。于是 .env 里新加的 AUTOMATION_ENABLED=true 容器查不到,日志照旧打印
「AUTOMATION_ENABLED 未开启,自动化调度器不启动」——调度器根本没起。早先
§15.1 写入的 ORDER_SITE_* 与 OSS_* 也是同样情况,P0/P2 会直接踩到。
补齐 9 个变量的透传:AUTOMATION_ENABLED、ORDER_SITE_DATABASE_URL /
LOOKUP_ENABLED / LOOKUP_TIMEOUT_SECONDS、OSS_ENDPOINT / BUCKET /
ACCESS_KEY_ID / ACCESS_KEY_SECRET / PREFIX,全部带默认值(缺省即关闭或空),
不配置也不会让容器起不来。
二、deploy.sh 只等 15×2=30 秒就判「服务启动超时」,而 mysql 一起重建时应用
就绪约 35 秒,于是部署实际成功却报失败。等待窗口放宽到 40×2=80 秒。
规划附录 C.2 记下这条约定:新增环境变量要同时改 .env(实值)、
docker-compose.yml(透传)、.env.example(留档)三处。
|
2026-10-02 20:12:24 +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
|
2379598800
|
docs(plan): 记录迁移编号不能改名的坑,并说明应用过后的对齐做法
P1a 的迁移先叫 20261002_0038,本地 dev 库应用过之后为保持链条连续改名成
0037,dev 库的 alembic_version 还记着旧编号,再启动就报
Can't locate revision identified by '20261002_0038'。
结论写进附录 C.1:编号要在被任何库应用之前定下来;已经应用过又必须改名时,
除改文件外还要把每个应用过的库 UPDATE alembic_version 对齐(前提是迁移内容
未变)。
|
2026-10-02 19:48:47 +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 |
|
yml2213
|
6a62bbe70f
|
docs(plan): 新增自动化与资料收集规划与皮肤准入映射分析
把「CK 全靠人工维护 / 资料靠人转述 / 兑换无台账」整理成分期规划,并基于两套
线上库的实测数据定下关键口径。
数据结论(2026-10-02 实测):
- order_site 271,134 单按履约路由二选一且完全不重叠:A 有 fulfillment_tasks
85,338 单(88.3% 已 delivered,是发货平台的活)、B 有 work_orders 96,755 单
(打手)、C 两者都无 89,542 单 —— 只有 C 是「没人管」,才是本系统的目标;
- 斗鱼可兑换商品只有 78 个(皮肤类 60)。C 桶皮肤类 923 个快手商品名映射过去:
62 个唯一命中(27.7% 件数)、14 个命中多个(12.4%,需用户选)、847 个斗鱼
兑不了(59.9%)—— 准入白名单以斗鱼商品目录为准,不匹配就不建单;
- order_items.spec_json 已带 sellerId/relItemId/relSkuId/skuNick,(itemId, skuId)
才是单订单的精确定位键(4,092 个数字 skuId 中 89% 唯一),接单平台就是按
sku_exact/sku_series 匹配的,本系统照此设计;
- CK 巡检规模 12,881 行(valid 10,079 / invalid 905 / 从未检测 1,897),按 6h
周期反推 batch_size 应为 200 而非 50,否则实际周期被拉长到 21 小时。
规划要点:
- 与 order_site 整合选只读直连 PG:order_site 零代码改动,跨库 SQL 只允许出现在
一个适配层文件里,另配降级开关与只读保护参数;
- P1 拆成 P1a(只检测、不改动任何 CK)与 P1b(自动重登,默认关闭),按「渐进
上线」原则逐步减少人工;
- 用户截图一律进私有 OSS 桶 + 后端鉴权代理读图,不落本地磁盘;
- P4 的规则/模板/状态机对齐 order_site 接单平台:资料模板固定为游戏编号 +
游戏昵称 + 主页截图,皮肤由订单号自动对应,仅多选类让用户选一件;
- 取消规则映射层(与 skin_aliases 职责重叠),规则直接挂 commodity_id,
连带去掉 rule_ambiguous 状态。
三轮复核:① 补齐结构与缺口(术语表、P0 章节、权限表、配置清单、保留策略、
文案、测试计划、风险回退、迁移与文件清单、施工单);② 逐条核对代码事实
(路径/符号/接口/字段/迁移 head/测试基线全部命中);③ 找出并修掉 8 处实现级
矛盾(选择结果无处存、365 天命中的状态缺失、draft 悬空、多选回链断点、P4 与
P1b 排期矛盾、映射层重叠、P2 能力错位、用户回看链接缺失)。
含:docs/自动化与资料收集规划.md、docs/皮肤准入映射分析.md,并更新文档索引。
|
2026-10-02 19:34:37 +08:00 |
|
yml2213
|
4518b726ac
|
feat(huya): 浏览器兑换三目标直达并收窄扩展注入范围
- 操作列改为「直播间 / 精英宝典 / 电竞宝典」三个直达按钮,点哪个就按哪个目标写入登录态并打开,去掉弹窗内选择
- 新增 services/huya_exchange_targets.py 统一三个目标地址,接口支持可选 body {"target": "..."}(不传=精英宝典)
- 扩展内容脚本从 <all_urls> 收窄到后台站点白名单,service worker 校验 sender.tab.url,拒绝其它来源的注入
- 注入改为 Cookie 写完后回执(ok/written/failed/error),前端按回执提示成功或失败,不再静默
- 扩展图标兜底改用 world:'MAIN' 读取;一个 Cookie 都没写成功时不打开目标页
- 兑换材料签发写审计 huya:exchange:kit(含账号与目标)
- 控制台脚本增加 huya.com 域校验与 Secure,与扩展路径保持一致
- 修复兑换记录轮询的跨账号串扰与卸载后 setState;兑换请求加 epoch 防止过期结果覆盖弹窗
- 新增 tests/test_huya_browser_exchange.py(13 项)并同步 docs/HUYA_BROWSER_EXCHANGE.md
|
2026-09-11 10:31:46 +08:00 |
|
 yml2213andCursor
|
e81119117f
|
feat(huya): 多选登录改单线程队列并拆分路由与工作台
默认同时只登一个号,失败两次跳下一个,号间留间隔,结束后可一键重试失败账号。
顺带拆分虎牙路由与斗鱼工作台页面,并补齐 raw_cookie 重建入口。
Co-authored-by: Cursor <cursoragent@cursor.com>
|
2026-09-10 11:31:50 +08:00 |
|
yml2213
|
90a0065918
|
虎牙菜单按角色显示并修复确认框暗色模式
客服在虎牙导航只显示浏览器兑换,管理员/运营保留完整入口;退出登录
与显示凭据确认框改用 App.useApp() 的 modal 实例以适配暗色主题;
版本号升至 0.3.0。
|
2026-09-05 19:21:59 +08:00 |
|
yml2213
|
178eba1eea
|
浏览器兑换独立成页面并按斗鱼模式优化导入
新增 /huya/browser-exchange 页面(精英宝典下方),支持导入账号与
执行兑换,客服仅见已分配账号;账号管理页移除该功能。导入弹窗对齐
斗鱼操作台:服务端分页筛选、可搜索标签、无 CK 不展示、导入本页/
一键导入全部/导入选中;新增 /accounts/ids 接口与 workbench
browser_exchange scope。
|
2026-09-05 18:30:29 +08:00 |
|
yml2213
|
230d6c1918
|
虎牙人工兑换改用本机浏览器并新增扩展自动注入登录态
移除服务器浏览器方案(容器/Xvfb/noVNC/CDP 会话管理),后端仅下发
兑换材料;新增内置 Chrome/Edge 扩展一键写入 huya.com 登录态,
未安装扩展时回退控制台脚本手动注入,兑换始终由用户手动完成。
|
2026-09-05 17:55:31 +08:00 |
|
yml2213
|
aaaa568916
|
修正活动WSS会话guid一致性
|
2026-09-02 18:07:31 +08:00 |
|
yml2213
|
92ba6cbc62
|
闭合QIMEI h38兼容键
|
2026-09-02 17:51:12 +08:00 |
|
yml2213
|
1f3773de28
|
修正QIMEI UUID42随机段生成
|
2026-09-02 17:15:06 +08:00 |
|
yml2213
|
17ee943753
|
复刻QIMEI UUID42并默认使用无头浏览器
|
2026-09-02 16:59:46 +08:00 |
|
yml2213
|
c62c025ced
|
整理虎牙文档并接入设备画像指纹
|
2026-09-02 13:42:32 +08:00 |
|
yml2213
|
0d21a3fa09
|
trace文档补充:逆向生成器全链路验证通过(含真实兑换成功)
|
2026-09-01 23:53:28 +08:00 |
|
yml2213
|
8fef04d0af
|
web_cookie_fields 用逆向算法替换随机:__yamid_new=UUIDv1时间戳复刻、game_did=35位63字符集、_yasids同源、_qimei_uuid42=1a前缀、guid回退加固定段
|
2026-09-01 23:48:00 +08:00 |
|
yml2213
|
8b24732c81
|
文档:网页设备字段溯源实证——5字段服务端下发/其余浏览器脚本生成,定位QIMEI SDK 0.6.3与guid固定段
|
2026-09-01 23:34:30 +08:00 |
|
yml2213
|
3ae1c3de1e
|
文档:浏览器注入ck未登录根因=domain需带前导点(.huya.com),host-only仅主域生效
|
2026-09-01 23:02:40 +08:00 |
|