fix(login): 删除任务不再丢 CK —— 只删不持有 CK 的记录【数据安全】
事故:斗鱼账号的 CK **只存在 `login_tasks.cookie`**(`accounts` 表没有 cookie 列),删掉记录即永久丢失登录态。而列表显示的「状态」是最近一次尝试 (`attempt_status`),于是一条**显示「失败」但实际仍持有有效 CK** 的记录 (status=success + cookie + 上次尝试失败)会被「清空失败」当成垃圾删掉。 修复: - 批量删除只删空 cookie 的记录,持有 CK 的一律跳过,并在响应里报告跳过数; - 单条删除遇到持有 CK 的记录直接 409,提示先处理该账号的 CK; - 前端确认弹窗写明「持有 CK 的记录会跳过」。 顺带修统计口径:分页后 success/failed 只统计当前页,却和「共 N 个任务」 并排显示(曾出现「共 8508,成功 48」)。改走 /tasks/summary 的全局统计。 回归测试补 5 条,专门盯「显示失败但持有 CK」这个事故场景。
This commit is contained in:
@@ -255,16 +255,36 @@ def tasks_summary(
|
||||
return _login_task_summary(query)
|
||||
|
||||
|
||||
def _holds_ck_clause():
|
||||
"""判定登录记录是否仍**持有 CK**(与 `cookie_holding_filter` 同口径)。
|
||||
|
||||
⚠️ 删除登录记录会连带删掉它持有的 CK —— 斗鱼账号的 CK **只存在
|
||||
`login_tasks.cookie` 里**(`accounts` 表没有 cookie 列)。删掉就再也找不回来,
|
||||
所以删除接口一律只允许删「空 cookie」的历史记录。
|
||||
"""
|
||||
return and_(LoginTask.cookie.isnot(None), LoginTask.cookie != "")
|
||||
|
||||
|
||||
def _deletable_clause():
|
||||
"""可安全删除的记录:没有持有 CK。"""
|
||||
return or_(LoginTask.cookie.is_(None), LoginTask.cookie == "")
|
||||
|
||||
|
||||
@router.delete("/tasks/{task_id}")
|
||||
def delete_task(
|
||||
task_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current: User = Depends(require_permission("login:batch")),
|
||||
):
|
||||
"""删除单个登录任务。"""
|
||||
"""删除单个登录任务(**持有 CK 的记录不允许删除**)。"""
|
||||
task = db.query(LoginTask).filter(LoginTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
if task.cookie:
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="该记录仍持有 CK,删除会丢失账号的登录态;如需清理请先处理该账号的 CK",
|
||||
)
|
||||
db.delete(task)
|
||||
db.commit()
|
||||
return {"message": "已删除", "success": True}
|
||||
@@ -285,8 +305,14 @@ def delete_tasks(
|
||||
2. ``all_matching=true``:删当前筛选条件下的**全部**(跨页全选用);
|
||||
3. 两者都没给 → 400,绝不默认全删。
|
||||
|
||||
**进行中(pending/running)的任务一律跳过**:删掉会让批次线程找不到记录,
|
||||
而停止批次应该走 ``/stop/{batch_id}``。
|
||||
两条护栏(都会在响应里报告跳过数量):
|
||||
|
||||
- **持有 CK 的记录一律不删**:斗鱼账号的 CK 只存在 `login_tasks.cookie`,
|
||||
删掉即永久丢失登录态。列表显示的「状态」是最近一次尝试
|
||||
(``attempt_status``),一条显示「失败」的记录完全可能仍持有有效 CK ——
|
||||
曾因此丢过一批 CK,所以这里按 cookie 而不是按显示状态判断。
|
||||
- **进行中(pending/running)的记录不删**:删掉会让批次线程找不到记录;
|
||||
停批次请走 ``/stop/{batch_id}``。
|
||||
"""
|
||||
# 护栏:不删在途任务,避免批次线程与数据库状态打架。
|
||||
in_flight = ("pending", "running")
|
||||
@@ -321,19 +347,26 @@ def delete_tasks(
|
||||
raise HTTPException(status_code=400, detail="请指定任务ID或 all_matching=true")
|
||||
|
||||
if not ids:
|
||||
return {"message": "没有符合条件的任务", "deleted": 0, "success": True}
|
||||
return {
|
||||
"message": "没有符合条件的任务",
|
||||
"deleted": 0,
|
||||
"skipped": 0,
|
||||
"success": True,
|
||||
}
|
||||
|
||||
target_ids = [
|
||||
row[0]
|
||||
for row in db.query(LoginTask.id)
|
||||
rows = (
|
||||
db.query(LoginTask.id, LoginTask.cookie, not_in_flight.label("ok_to_delete"))
|
||||
.filter(LoginTask.id.in_(ids))
|
||||
.filter(not_in_flight)
|
||||
.all()
|
||||
]
|
||||
)
|
||||
target_ids = [r[0] for r in rows if r[2] and not r[1]]
|
||||
skipped = len(rows) - len(target_ids)
|
||||
|
||||
if not target_ids:
|
||||
return {
|
||||
"message": "没有可删除的任务(在途任务已跳过)",
|
||||
"message": f"没有可删除的记录({skipped} 条持有 CK 或在途,已跳过)",
|
||||
"deleted": 0,
|
||||
"skipped": skipped,
|
||||
"success": True,
|
||||
}
|
||||
|
||||
@@ -343,7 +376,11 @@ def delete_tasks(
|
||||
.delete(synchronize_session=False)
|
||||
)
|
||||
db.commit()
|
||||
return {"message": f"已删除 {deleted} 个任务", "deleted": deleted, "success": True}
|
||||
|
||||
message = f"已删除 {deleted} 个任务"
|
||||
if skipped:
|
||||
message += f"({skipped} 条持有 CK 或在途,已跳过)"
|
||||
return {"message": message, "deleted": deleted, "skipped": skipped, "success": True}
|
||||
|
||||
|
||||
@router.post("/stop/{batch_id}")
|
||||
|
||||
Reference in New Issue
Block a user