diff --git a/docs/设计-登录任务与CK解耦.md b/docs/设计-登录任务与CK解耦.md index 271c917..93124ff 100644 --- a/docs/设计-登录任务与CK解耦.md +++ b/docs/设计-登录任务与CK解耦.md @@ -355,9 +355,55 @@ if task is None: | **D** | 是否顺带把历史做归档/保留策略 | ①加 `automation_runs` 那样的 90 天清理(规划 §11.4)②不做 | ①可顺带解决「任务列表 1.4 万条」的体量问题 | | **E** | 重登/巡检的调度粒度 | ①账号(推荐)②仍按最近一次登录行 | ①:账号才是状态的载体 | | ~~F~~ | ~~是否双写~~ | —— | ✅ **已定:不双写**(§6.1),代码一次切换、旧列延后 drop | -| **G** | 每日逻辑备份是否随本次一起做 | ①一起做(drop 列前必须有)②先不做 | ①(§11) | +| **G** | 每日逻辑备份是否随本次一起做 | ①一起做(drop 列前必须有)②先不做 | ①(§12、C7 前置) | -## 10. 工作量与风险 +## 10. 提交计划(7 个提交) + +拆分的核心约束:**同一份数据的读与写必须在同一个提交里切**。否则出现中间态 —— +比如「写已经写账号、读还在读记录行」,新登录拿到的 CK 在页面上看不见,巡检还会按旧值判定; +这类中间态跑起来不报错,只是**默默用错数据**,比崩溃更难发现。 + +另外三条边界: + +- **迁移单独一个提交**(纯加列 + 回填,无行为变化)—— 单独部署也安全; +- **用户可感知的语义变化各占一个提交**(Cookie 页单位、删除护栏),便于单独验证与回退; +- **drop 旧列隔离到下一次发版**(唯一不可逆动作)。 + +| # | 提交信息 | 改动文件 | 干什么 | 怎么验证 | +| --- | --- | --- | --- | --- | +| **C1** | `feat(db): accounts 增加 CK 与状态列,并从 login_tasks 回填` | `migrations/versions/20261002_0040_account_ck_state.py`(新)
`models.py`
`tests/test_migrations.py` | 加 8 列 + `(account_id, attempt_status)` 索引;分块回填;**自校验不过就抛错** | `pytest tests/test_migrations.py`;本地 dev 库实跑;回填后 `accounts.cookie<>''` == 记录级去重数 | +| **C2** | `refactor(cookie): 斗鱼 CK 本体改由账号承载` | `services/douyu_service.py`(`cookie_holding_filter` / `cookie_operable_filter` / `latest_cookie` / `latest_cookie_login_task`)
`services/login_service.py`(`_write_login_success`、清空、批次行复用)
`routers/cookies.py`(清除/删除 CK 的**写点**)
`services/douyu_runner.py` | **读+写一起切**到 `accounts.cookie`。批次行复用逻辑同步重写(§6.5 连带点,否则静默退化) | 新增 `tests/test_account_ck_storage.py`;改造 `test_login_attempt_isolation.py` | +| **C3** | `refactor(automation): CK 健康状态与重登状态迁到账号,作业按账号调度` | `services/automation/jobs/ck_health.py`
`services/automation/jobs/ck_relogin.py`
`routers/automation.py`
`routers/cookies.py`(检测写点)
`services/login_service.py`(重登后复检写点)
`services/douyu_runner.py` | `ck_check_*` / `relogin_*` 的**读+写+选行**一起切到 `Account`;`automation_runs.detail` 里的 task_id 改 account_id | 改造 `test_automation_ck_health.py` / `test_automation_ck_relogin.py`;新增「**没有历史行的账号也能被巡检/重登**」 | +| **C4** | `refactor(cookies): Cookie 页改为账号级` | `routers/cookies.py`
`routers/accounts.py`
`routers/douyu.py`
`routers/dashboard.py`
`web/frontend/src/pages/CookiePage.tsx`
`api/cookies.ts` / `api/types.ts` | 列表/汇总/重复/导出/检测/删除全部改为以**账号**为单位;`DELETE /cookies/{id}` 入参由 task_id 变 account_id | **风险最高**:前后端同一提交;专门用例覆盖「删除清的是入参那个账号」;上线后手工验证一次 | +| **C5** | `feat(login): 登录历史可自由删除(解除 CK 护栏)` | `routers/login.py`
`web/frontend/src/pages/LoginTasksPage.tsx`
`tests/test_login_tasks_pagination.py` | 去掉「持有 CK 不可删」,**保留**「在途不可删」;前端文案改回 | 验收核心:**删除全部历史后,账号 CK 与健康/重登状态仍在** | +| **C6** | `docs: 解耦落点与验收结果` | 本设计文档、`docs/自动化与资料收集规划.md`、`README.md` | 记录落点、基线对照、遗留项 | —— | +| **C7** | `chore(db): 删除 login_tasks 上已弃用的 CK/状态列`(**下一次发版**) | 迁移 `20261002_0041`、`models.py` | drop `cookie` / `ck_check_*` / `relogin_*`(若决策 B 选「留快照」则保留 `cookie`) | downgrade 可恢复结构;**执行前必须有每日备份**(决策 G) | + +### 10.1 提交顺序的依赖关系 + +``` +C1(加列+回填,无行为变化) + ├─> C2(CK 读写切账号)──┬─> C5(解除删除护栏) + │ └─> C4(Cookie 页账号级)──> C6(文档)──> C7(drop 列) + └─> C3(健康/重登状态 + 作业按账号)──┘ +``` + +- C2 与 C3 **可以并行**(CK 本体 vs 健康状态,互不依赖),但 C3 必须先于 C4(Cookie 页要展示 `ck_check_status`); +- **每个提交都能单独部署**:C1 部署后行为不变;C2 之后旧列虽不写但仍可回退; +- 每一步 pre-push 跑全量 pytest,迁移保持幂等,重复部署无害。 + +### 10.2 每个提交的「不能省」 + +| 提交 | 不能省的东西 | +| --- | --- | +| C1 | 自校验 + 分块回填 + 幂等;`HEAD_REVISION` 同步 | +| C2 | 读与写同提交;批次行复用逻辑一起改;`cookie_operable_filter` 的在途子查询 + 新索引 | +| C3 | 选行改账号后,「无历史行」的账号也要能被覆盖(否则等于漏掉一批账号) | +| C4 | 前后端同提交;删除接口的入参语义单独用例 | +| C5 | 在途护栏**保留**(语义从「保护 CK」变成「保护正在进行的尝试」) | +| C7 | 每日备份已就绪(不可逆) | + +## 11. 工作量与风险 - 涉及:1 个迁移、`models.py`、4 个 service、7 个 router、2 个自动化作业、前端 3 个页面 + 类型、约 10 个测试文件; - 粗估 **2~2.5 人日**(去掉双写后比原估的 2~3 天更少:少了双写与后续停写两轮改动); @@ -368,7 +414,7 @@ if task is None: 但**回填 SELECT 与切换之间存在的竞态**要注意:迁移在应用启动时执行,此时旧容器已停止 (compose 会先停后起),所以窗口极小;回填按 `finished_at` 取最新并写成幂等,必要时可重跑自愈。 -## 11. 与「备份」的关系 +## 12. 与「备份」的关系 这次改造本身是安全的(旧列保留、可反向回填),但**最后的 drop 列是不可逆的**。 叠加 2026-10-03 的事故(当时唯一退路是 binlog),建议把**每日逻辑备份**作为这个改造的