Commit Graph
27 Commits
Author SHA1 Message Date
yml2213 00125fbfc4 refactor(account): S2 读写一次切换 —— CK/健康/重登彻底搬到账号,登录记录退化为历史
S1 加好了列,本提交把**读与写一起切**过去。这是设计里「同一份数据的读与写必须
在同一个提交里切」的要求:先切读或先切写都会出现「写账号、读记录」的中间态,
跑起来不报错,只是默默用错数据。

## 核心改动

**判定「谁有 CK」不再借道登录记录**
- `cookie_holding_filter()` / `cookie_operable_filter()` / `cookie_holding_account_ids_query()`
  / `visible_douyu_task_accounts()` 全部改为作用于 `Account`;
- `cookie_operable_filter()` 的「在途排除」仍查 `login_tasks`(那是**某次尝试**的属性),
  用 `NOT EXISTS` 子查询 + 迁移 0042 新增的 `(account_id, attempt_status)` 索引;
- 删除死代码 `latest_cookie_login_task()`;`latest_cookie()` 改为直接读账号。

**CK 写入**
- `_write_login_success()` 写 `Account.cookie / cookie_updated_at / cookie_source`;
- `_mark_attempt_failed()` 改看**账号**有没有 CK(决定是否沿用旧的失败语义);
- 检测模式的 `task.cookie = ""` **不再清空账号的 CK**(见下「行为变更」)。

**自动化作业按账号调度**
- `ck_health`:`_select_tasks` → `_select_accounts`,`db.query(Account)`;新增
  `count_remaining()`(刻意不带 LIMIT,否则积压数恒等于批次上限);
- `ck_relogin`:同样按账号选行;结算改为「账号 + 本轮尝试记录」,
  退避进度写账号;`settle_fresh_results(batch_id)` 按 `attempt_batch_id` 配对,
  `run_ck_relogin` 把 **account_ids** 交给 runner;
- 顺带让**没有任何登录历史的账号**也能被巡检/重登(以前它们永远进不了队列)。

**登录批次**
- `LoginBatchRunner` 去掉 `relogin_task_ids`,重登与登录统一走 `account_ids`;
- 批次行复用逻辑重写:只保证「同一账号不并发两行」,不再关心「哪一行持有 CK」。
  失败行复用同时看 `attempt_status`(账号有 CK 时 `_mark_attempt_failed` 刻意不动
  `status`,只看 status 会永远命不中 → 每次失败刷一行)。

**删除护栏语义变更(用户可感知)**
- 去掉「持有 CK 的记录不可删」——CK 在账号上,删历史不再影响登录态,历史终于可清理;
- **保留**「在途尝试不可删」(批次线程正拿着那条记录)。

## 行为变更(需评审)

1. **账号检测不再清空 CK**。旧代码在 check 模式写 `task.cookie = ""`,而那时记录的
   cookie 就是账号唯一凭证 —— 一次「批量检测」会把账号登录态删掉。解耦后
   「检测账号凭据」与「CK 是否有效」本就是两件事(后者归 `ck_health` 判),
   继续清空等于凭空制造不可逆的数据丢失(重登当前成功率极低)。
2. **Cookie 页的记录单位从「登录记录」变成「账号」**,所有接口的 id 入参/出参
   变成 `account_id`;`DELETE /cookies/{id}` 清的是该账号的 CK,**必写审计**。
3. 重复检测从「同一账号多行持 CK」改为「**同一个 CK 出现在多个账号上**」——
   后者才是真风险(两个账号共用一个登录态)。用 sha256 指纹分组:
   CK 是 AES-GCM + 随机 nonce 加密,**同一明文每次密文都不同**,比密文永远比不出重复。

## 修掉一个真实并发缺陷

`_write_login_success` 原先被传入 `self.db`(runner 主会话),而它是在**工作线程**里
用 `worker_db` 执行的。`emit()` 会往 `account_events` 插一行,其 `account_id` 外键对
`accounts` 那一行加**共享锁**;这把锁加在另一个会话上,紧接着本会话的
`UPDATE accounts SET cookie=...` 就被自己人挡住 —— 实测 **50 秒后
`Lock wait timeout exceeded`,登录明明成功却写不回 CK**(改造前 UPDATE 打在
`login_tasks` 上所以没撞上)。改为传 `worker_db`:事件本就该和状态变更同事务提交,
顺带修掉跨线程共用一个 Session 的隐患。

## 验证

- 全量 pytest **368 passed / 6 skipped**;迁移链 3 passed;
- 前端 `tsc -b && vite build` 通过;
- `ruff check` / `ruff format --check` 全绿(顺带修掉 main 上遗留的 F821);
- 新增/改写的关键回归:**删掉全部登录历史后账号的 CK 与健康状态仍在**、
  **无登录历史的账号也能进巡检队列**、结算不碰 CK、删除护栏改为在途语义。

仍未做(设计里排期的后续阶段):`account_lifecycle` 状态收口(S3)、事件发射点补齐(S4)、
业务事实直发事件(S5)、账号页重构(S6)。注意 `status` 目前只有模型默认值与迁移回填,
所以 `disabled/archived` 还没有任何写入方——S3 落地时要把自动化选行的状态过滤一起补上。
2026-10-04 14:10:13 +08:00
yml2213 6a25097412 fix(douyu): 替换 CK 是最后一步,只有登录成功才写回记录
上一版把可见性改成「记录里还有 CK」,账号不会再消失,但根因还在:批次启动
时就把复用的成功记录改成 pending(旧 CK 仍在),停止或失败后没人恢复状态,
记录一直显示成一次没完成的登录。

按「替换 CK 是最后一步」把两者拆开:

- login_tasks 新增 attempt_status / attempt_message / attempt_batch_id /
  attempt_at,批次只写 attempt_*,status/cookie/message 只在登录成功时更新;
- _begin_attempt 只登记尝试,_mark_attempt_failed 只记录失败(已有 CK 的记录
  原样不动),_write_login_success 是唯一替换旧 CK 的入口;
- 停止批次、代理不可用、登录异常、进程重启清理全部改为收敛 attempt_*,
  因此停止一个批次不可能再让账号失去 CK;
- 复用的记录改用「持有 CK 的记录」,不再创建第二条记录,失败记录在无 CK 时
  才复用;
- 登录任务列表/统计按 attempt_* 展示「这次登录怎么样了」,批次筛选同时匹配
  attempt_batch_id;CK 操作页的重登状态也从 attempt_* 映射;
- 迁移 20261001_0035 归一存量数据:持有 CK 的记录 status 统一为 success、
  历史状态搬进 attempt_*,无 CK 且卡在 pending/running 的收敛为 failed。

新增测试:批次失败/停止不碰旧 CK、成功后替换 CK、列表展示尝试状态、
迁移存量数据归一。
2026-10-01 18:29:49 +08:00
yml2213 64c399617a fix(douyu): 账号只要还持有 CK 就不再从工作台消失
线上事故:分配给客服的账号(如「远行者103」)在斗鱼工作台与 CK 列表里
查不到,只有账号管理里还能搜到。原因是可见性判定写成了
LoginTask.status == "success",而按设计保留旧 CK 的三种状态都不再是
success:

- 普通登录批次启动时会把复用的成功记录置为 pending/running(旧 CK 保留),
  批次被停止或失败后一直停在 pending,账号永久消失;
- CK 重登失败置为 relogin_failed(旧 CK 保留),账号从工作台消失;
- 普通登录失败置为 failed(复用记录的旧 CK 未清空),同样消失。

线上现状:12545 个账号持有 CK,但只有 11703 个能出现在工作台,842 个
(644 重登失败 + 176 排队中 + …)对客服和管理员都不可见。

改动:
- douyu_service 新增 cookie_holding_filter()/latest_cookie_login_task(),
  以「记录里还留着 Cookie」作为唯一判据,并抽出 LOGIN_IN_FLIGHT_STATUSES
  与 cookie_operable_filter() 供检测/重登入口复用;
- 斗鱼工作台账号列表、任务账号校验、账号管理 has_cookie 筛选、分配校验与
  概览、CK 管理/操作列表、首页 CK 统计统一改用该判据;
- 工作台取 CK 从 latest_success_login_task 换成 latest_cookie_login_task,
  保证重登失败/排队中的账号仍能被任务正常执行;
- 新增 cleanup_orphan_login_tasks():进程重启后把遗留的 pending/running
  收敛为 failed 并保留旧 CK,避免页面永久显示排队中。

新增回归测试覆盖上述状态下的可见性、可执行性与启动清理。
2026-10-01 18:18:02 +08:00
yml2213 09ab80e062 完成 Ruff 全量清理 2026-08-31 10:55:44 +08:00
yml2213 b58c6b4357 执行 Ruff 安全自动修复 2026-08-31 10:28:14 +08:00
yml2213 c891ac982e type: 收敛测试 schemas 与协议层类型 2026-08-30 20:35:08 +08:00
yml2213 9497e1ace6 perf: 优化 MySQL 查询并接入 pytest 2026-08-30 18:26:54 +08:00
yml2213 bf2f78aa70 优化客服CK重登流程 2026-08-14 14:43:21 +08:00
yml2213 a223cb726d feat: 为CK页面增加标签筛选 2026-08-13 16:24:35 +08:00
yml2213 47ded2bc3e feat: add scoped cookie operations for support 2026-08-13 16:17:06 +08:00
yml2213 7db3b2cd4f fix: preserve custom cookie input order 2026-08-09 18:00:42 +08:00
yml2213 b39780f26d 一键检测重复-仓库管理 2026-08-08 20:51:50 +08:00
yml2213 65271124c0 增加斗鱼自定义CK筛选功能 2026-08-08 19:03:23 +08:00
yml2213 39ea0d2184 feat(cookies): Cookie 失效后可账号重新登录替换, 并修复 Cookie 列空白过多 2026-08-08 01:39:02 +08:00
yml2213 b962f63bd2 feat(cookies): CK 有效性检测(双接口) + 结果持久化
- 检测: 鱼丸余额(fishBall) + 用户等级(userLevelDetail)双接口均通过才判有效,
  附带鱼丸数/昵称/等级, 失败时分别说明原因, 8 并发批量检测
- 持久化: login_tasks 新增 ck_check_status/ck_check_result/ck_checked_at,
  刷新翻页不丢失; 列表与详情接口返回检测字段
- 前端: 新增"有效性"列(有效/无效+鱼丸昵称tooltip)与独立"检测时间"列,
  行内/选中批量检测按钮(cookie:view 权限), 加载时初始化历史检测结果
2026-08-06 20:58:23 +08:00
yml2213 72cf0e5fef fix: 修复斗鱼Cookie导出422路由冲突 & 优化登录密码错误提示
- cookies.py: 将 GET /export 路由移至 GET /{task_id} 之前,避免
  动态路径 task_id:int 误匹配 'export' 导致 422 校验错误
- LoginPage.tsx: 密码错误(401)时清空密码框并自动聚焦,方便快速重试
- database.py/migrations/env.py: 应用内嵌调用 alembic 时跳过 fileConfig,
  避免覆盖 uvicorn 日志配置导致启动日志丢失
- client.ts: 401 拦截排除 /auth/login,避免登录失败时误跳转吞掉错误提示
2026-07-30 13:04:40 +08:00
yml2213 f91c05c108 优化列表分页和数据加载 2026-07-24 13:25:27 +08:00
yml2213 f21f82cbed ck 复制格式优化,修复错误 2026-06-25 09:16:23 +08:00
yml2213 492360b097 Cookie 导出自定义格式:换行符改为 Windows 格式(CRLF) 2026-06-24 18:46:58 +08:00
yml2213 0d03b2c242 优化登录重试与配置清理 2026-06-24 10:20:33 +08:00
yml2213 4a07905edf fix: Cookie批量删除路由顺序修正(/batch必须在/{task_id}前)+ Statistic valueStyle改为styles.content 2026-06-23 22:37:26 +08:00
yml2213 527c0c8ff8 feat: Cookie管理页面增加批量删除功能 2026-06-23 22:31:51 +08:00
yml2213 453a637480 refactor: 修复9项中等架构问题
安全修复:
- WebSocket 端点添加认证(cookie/token),防止未授权窃听日志
- SPA serve_spa 添加路径遍历防护(resolve + relative_to 检查)
- Token 改用 httpOnly Cookie 存储,移除前端 localStorage token(防 XSS 窃取)
- 添加安全响应头中间件(X-Content-Type-Options/X-Frame-Options/Referrer-Policy)
- HTTP 请求日志脱敏请求体中的 password/secret/token 等敏感字段
- 权限检查统一使用 user_has_permission(考虑自定义权限,修复 has_permission 忽略 custom_permissions 的缺陷)

性能与稳定性:
- cookies.py 列表接口修复 N+1 查询(改为批量查询 Account)
- login_service.py run() 结束时关闭 DB Session(防止连接泄漏)
- _active_batches/_active_tests 全局字典添加 threading.Lock(防止并发竞态)

配置优化:
- CORS 源支持环境变量 CORS_ORIGINS 配置
- Uvicorn reload 支持环境变量 UVICORN_RELOAD 控制(生产环境默认关闭)
- Cookie 安全标志支持环境变量 COOKIE_SECURE 配置(HTTPS 部署时启用)
- logs.py 权限不足返回 HTTP 403(原来返回 200 + message)
2026-06-23 06:51:01 +08:00
yml2213andClaude Fable 5 1b20317b42 Cookie导出增加格式选择
- 后端: export接口增加 format 参数,支持 csv 和 custom 格式
- custom 格式: 账号----密码----ck,导出为 txt
- 后端: 导出用批量查 Account 替代逐条查询,消除 N+1
- 前端: 导出按钮改为下拉菜单可选格式
- 修复: blob 导出时不再取 .data(拦截器已提取)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 20:20:59 +08:00
yml2213andClaude Fable 5 72bcd1274c 优化登录任务界面 + 修复时间显示 + 增加删除功能
- 筛选栏改为单行横排,概览精简为一行文字
- 任务列表全宽展示,日志移到底部可折叠
- 页面使用百分比布局,一屏展示无外层滚动
- 登录任务增加单条删除和批量删除功能
- 后端: datetime.utcnow 替换为 datetime.now(timezone.utc)
- 后端: schemas 序列化统一输出带时区 ISO 格式
- 前端: 新增 utils/time.ts 统一时间格式化工具
- 前端: CookiePage/LoginTasksPage 时间列使用 formatTime()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 17:56:48 +08:00
yml2213 480dfa6085 Cookie 管理优化 2026-06-22 16:36:23 +08:00
yml2213 627f06d0a0 优化 ui 2026-06-22 14:21:39 +08:00