type: 收敛测试 schemas 与协议层类型
This commit is contained in:
+148
-77
@@ -27,6 +27,7 @@ def _fmt_dt(dt) -> str | None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt.isoformat()
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/cookies", tags=["Cookie管理"])
|
||||
cookie_relogin_registry = BatchRegistry()
|
||||
|
||||
@@ -44,7 +45,9 @@ def _parse_account_names(raw_names: str) -> list[str]:
|
||||
"""解析前端粘贴的 Excel 账号名,每行一个并去重。"""
|
||||
names = []
|
||||
seen = set()
|
||||
for value in (raw_names or "").replace("\r\n", "\n").replace("\r", "\n").split("\n"):
|
||||
for value in (
|
||||
(raw_names or "").replace("\r\n", "\n").replace("\r", "\n").split("\n")
|
||||
):
|
||||
name = value.strip()
|
||||
if name and name not in seen:
|
||||
names.append(name)
|
||||
@@ -60,7 +63,9 @@ def _order_cookie_tasks(query, selected_names: list[str]):
|
||||
value=Account.username,
|
||||
else_=len(selected_names),
|
||||
)
|
||||
return query.order_by(input_order, LoginTask.finished_at.desc(), LoginTask.id.desc())
|
||||
return query.order_by(
|
||||
input_order, LoginTask.finished_at.desc(), LoginTask.id.desc()
|
||||
)
|
||||
return query.order_by(LoginTask.finished_at.desc(), LoginTask.id.desc())
|
||||
|
||||
|
||||
@@ -88,7 +93,9 @@ def _visible_cookie_operation_tasks_query(db: Session, current: User):
|
||||
"""返回可检测/重登的 Cookie 记录,重登中或失败时仍保留在操作列表。"""
|
||||
query = db.query(LoginTask).filter(
|
||||
LoginTask.cookie != "",
|
||||
LoginTask.status.in_(("success", "relogin_pending", "relogin_running", "relogin_failed")),
|
||||
LoginTask.status.in_(
|
||||
("success", "relogin_pending", "relogin_running", "relogin_failed")
|
||||
),
|
||||
)
|
||||
if not user_has_permission(current, "login:view_all"):
|
||||
query = query.join(Account, LoginTask.account_id == Account.id).filter(
|
||||
@@ -112,7 +119,9 @@ def list_cookies(
|
||||
if not user_has_permission(current, "cookie:view"):
|
||||
raise HTTPException(status_code=403, detail="无权限: cookie:view")
|
||||
if not include_cookie:
|
||||
query = _visible_cookie_tasks_query(db, current).options(defer(LoginTask.cookie))
|
||||
query = _visible_cookie_tasks_query(db, current).options(
|
||||
defer(LoginTask.cookie)
|
||||
)
|
||||
else:
|
||||
query = _visible_cookie_tasks_query(db, current).options(
|
||||
joinedload(LoginTask.account).joinedload(Account.assigned_user),
|
||||
@@ -137,15 +146,19 @@ def list_cookies(
|
||||
if not account_joined:
|
||||
query = query.join(Account, LoginTask.account_id == Account.id)
|
||||
account_joined = True
|
||||
query = query.outerjoin(User, Account.assigned_to == User.id).filter(or_(
|
||||
Account.username.ilike(pattern),
|
||||
Account.tag.ilike(pattern),
|
||||
User.username.ilike(pattern),
|
||||
))
|
||||
query = query.outerjoin(User, Account.assigned_to == User.id).filter(
|
||||
or_(
|
||||
Account.username.ilike(pattern),
|
||||
Account.tag.ilike(pattern),
|
||||
User.username.ilike(pattern),
|
||||
)
|
||||
)
|
||||
|
||||
total = None
|
||||
if page is not None:
|
||||
total = query.order_by(None).with_entities(func.count(LoginTask.id)).scalar() or 0
|
||||
total = (
|
||||
query.order_by(None).with_entities(func.count(LoginTask.id)).scalar() or 0
|
||||
)
|
||||
query = _order_cookie_tasks(query, selected_names)
|
||||
if page is not None:
|
||||
query = query.offset((page - 1) * page_size).limit(page_size)
|
||||
@@ -175,8 +188,10 @@ def list_cookies(
|
||||
"batch_id": t.batch_id,
|
||||
"account_id": t.account_id,
|
||||
"account_username": acc.username if acc else "",
|
||||
"assigned_to": acc.assigned_to,
|
||||
"assigned_username": acc.assigned_user.username if acc and acc.assigned_user else None,
|
||||
"assigned_to": acc.assigned_to if acc else None,
|
||||
"assigned_username": acc.assigned_user.username
|
||||
if acc and acc.assigned_user
|
||||
else None,
|
||||
"created_at": _fmt_dt(t.finished_at),
|
||||
"ck_check_status": t.ck_check_status or "",
|
||||
"ck_check_result": t.ck_check_result,
|
||||
@@ -194,7 +209,12 @@ def list_cookies(
|
||||
item["account_password"] = ""
|
||||
result.append(item)
|
||||
if page is not None:
|
||||
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
|
||||
return {
|
||||
"items": result,
|
||||
"total": total or 0,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@@ -242,10 +262,12 @@ def list_cookie_operations(
|
||||
if tag_value:
|
||||
query = query.filter(Account.tag == tag_value)
|
||||
if search_text:
|
||||
query = query.filter(or_(
|
||||
Account.username.ilike(f"%{search_text}%"),
|
||||
Account.tag.ilike(f"%{search_text}%"),
|
||||
))
|
||||
query = query.filter(
|
||||
or_(
|
||||
Account.username.ilike(f"%{search_text}%"),
|
||||
Account.tag.ilike(f"%{search_text}%"),
|
||||
)
|
||||
)
|
||||
|
||||
total = query.order_by(None).with_entities(func.count(LoginTask.id)).scalar() or 0
|
||||
tasks = (
|
||||
@@ -265,7 +287,9 @@ def list_cookie_operations(
|
||||
"created_at": _fmt_dt(task.finished_at),
|
||||
"relogin_status": task.status if task.status != "success" else "",
|
||||
"relogin_message": task.message or "",
|
||||
"relogin_batch_id": task.batch_id if task.status in {"relogin_pending", "relogin_running"} else "",
|
||||
"relogin_batch_id": task.batch_id
|
||||
if task.status in {"relogin_pending", "relogin_running"}
|
||||
else "",
|
||||
}
|
||||
for task in tasks
|
||||
],
|
||||
@@ -292,7 +316,7 @@ def list_cookie_operation_tags(
|
||||
.order_by(Account.tag.asc())
|
||||
.all()
|
||||
)
|
||||
return [tag for tag, in rows if tag]
|
||||
return [tag for (tag,) in rows if tag]
|
||||
|
||||
|
||||
@router.get("/duplicates")
|
||||
@@ -317,7 +341,9 @@ def find_duplicate_cookies(
|
||||
if not user_has_permission(current, "login:view_all"):
|
||||
query = query.filter(Account.assigned_to == current.id)
|
||||
|
||||
rows = query.order_by(Account.username.asc(), LoginTask.finished_at.desc(), LoginTask.id.desc()).all()
|
||||
rows = query.order_by(
|
||||
Account.username.asc(), LoginTask.finished_at.desc(), LoginTask.id.desc()
|
||||
).all()
|
||||
grouped: dict[str, dict] = {}
|
||||
for row in rows:
|
||||
username = (row.username or "").strip()
|
||||
@@ -337,26 +363,30 @@ def find_duplicate_cookies(
|
||||
group["account_names"].add(username)
|
||||
group["account_ids"].add(row.account_id)
|
||||
group["cookie_ids"].append(row.cookie_id)
|
||||
group["records"].append({
|
||||
"id": row.cookie_id,
|
||||
"account_id": row.account_id,
|
||||
"batch_id": row.batch_id,
|
||||
"finished_at": _fmt_dt(row.finished_at),
|
||||
})
|
||||
group["records"].append(
|
||||
{
|
||||
"id": row.cookie_id,
|
||||
"account_id": row.account_id,
|
||||
"batch_id": row.batch_id,
|
||||
"finished_at": _fmt_dt(row.finished_at),
|
||||
}
|
||||
)
|
||||
|
||||
duplicate_groups = []
|
||||
for group in grouped.values():
|
||||
if len(group["cookie_ids"]) < 2:
|
||||
continue
|
||||
duplicate_groups.append({
|
||||
"account_key": group["account_key"],
|
||||
"account_names": sorted(group["account_names"]),
|
||||
"cookie_count": len(group["cookie_ids"]),
|
||||
"account_count": len(group["account_ids"]),
|
||||
"cookie_ids": group["cookie_ids"],
|
||||
"account_ids": sorted(group["account_ids"]),
|
||||
"records": group["records"],
|
||||
})
|
||||
duplicate_groups.append(
|
||||
{
|
||||
"account_key": group["account_key"],
|
||||
"account_names": sorted(group["account_names"]),
|
||||
"cookie_count": len(group["cookie_ids"]),
|
||||
"account_count": len(group["account_ids"]),
|
||||
"cookie_ids": group["cookie_ids"],
|
||||
"account_ids": sorted(group["account_ids"]),
|
||||
"records": group["records"],
|
||||
}
|
||||
)
|
||||
duplicate_groups.sort(key=lambda item: (-item["cookie_count"], item["account_key"]))
|
||||
|
||||
return {
|
||||
@@ -432,10 +462,16 @@ def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> d
|
||||
id_list = [int(x) for x in ids.split(",") if x.strip().isdigit()]
|
||||
if not id_list:
|
||||
raise HTTPException(status_code=400, detail="无效的ID")
|
||||
base_query = _visible_cookie_tasks_query(db, current) if detailed else _visible_cookie_operation_tasks_query(db, current)
|
||||
tasks = base_query.filter(LoginTask.id.in_(id_list)).filter(
|
||||
LoginTask.status.in_(("success", "relogin_failed"))
|
||||
).all()
|
||||
base_query = (
|
||||
_visible_cookie_tasks_query(db, current)
|
||||
if detailed
|
||||
else _visible_cookie_operation_tasks_query(db, current)
|
||||
)
|
||||
tasks = (
|
||||
base_query.filter(LoginTask.id.in_(id_list))
|
||||
.filter(LoginTask.status.in_(("success", "relogin_failed")))
|
||||
.all()
|
||||
)
|
||||
if not tasks:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
|
||||
@@ -447,15 +483,17 @@ def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> d
|
||||
results.append(future.result())
|
||||
except Exception as exc:
|
||||
task = futures[future]
|
||||
results.append({
|
||||
"id": task.id,
|
||||
"valid": False,
|
||||
"message": f"检测异常: {exc}",
|
||||
"fish_ball": None,
|
||||
"nickname": None,
|
||||
"level": None,
|
||||
"checked_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"id": task.id,
|
||||
"valid": False,
|
||||
"message": f"检测异常: {exc}",
|
||||
"fish_ball": None,
|
||||
"nickname": None,
|
||||
"level": None,
|
||||
"checked_at": datetime.now(timezone.utc).isoformat(),
|
||||
}
|
||||
)
|
||||
results.sort(key=lambda item: item["id"])
|
||||
|
||||
# 持久化检测结果,刷新/翻页不丢失
|
||||
@@ -472,12 +510,14 @@ def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> d
|
||||
"message": item.get("message", ""),
|
||||
}
|
||||
task.ck_checked_at = datetime.now(timezone.utc)
|
||||
db.add(AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="cookie:check",
|
||||
target=f"检测 {len(results)} 条已分配账号 CK",
|
||||
))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="cookie:check",
|
||||
target=f"检测 {len(results)} 条已分配账号 CK",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
if not detailed:
|
||||
results = [
|
||||
@@ -510,7 +550,13 @@ def check_cookie_operations(
|
||||
return _check_cookies(ids, db, current, detailed=False)
|
||||
|
||||
|
||||
def _start_relogin_tasks(tasks: list[LoginTask], db: Session, current: User, *, action: str = "cookie:relogin"):
|
||||
def _start_relogin_tasks(
|
||||
tasks: list[LoginTask],
|
||||
db: Session,
|
||||
current: User,
|
||||
*,
|
||||
action: str = "cookie:relogin",
|
||||
):
|
||||
"""启动重登批次:旧 Cookie 保留到新登录成功后才替换。"""
|
||||
if not tasks:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
@@ -538,7 +584,9 @@ def _start_relogin_tasks(tasks: list[LoginTask], db: Session, current: User, *,
|
||||
|
||||
if not task_ids:
|
||||
db.commit()
|
||||
raise HTTPException(status_code=400, detail="没有可重登的账号,请检查账号凭据或当前重登状态")
|
||||
raise HTTPException(
|
||||
status_code=400, detail="没有可重登的账号,请检查账号凭据或当前重登状态"
|
||||
)
|
||||
|
||||
proxy = db.query(ProxyConfigModel).first()
|
||||
thread_db = SessionLocal()
|
||||
@@ -558,13 +606,15 @@ def _start_relogin_tasks(tasks: list[LoginTask], db: Session, current: User, *,
|
||||
relogin_task_ids=task_ids,
|
||||
)
|
||||
batch_id = runner.batch_id
|
||||
db.add(AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action=action,
|
||||
target=f"重登 {len(task_ids)} 条账号 CK",
|
||||
detail="旧 Cookie 将在新登录成功后替换",
|
||||
))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action=action,
|
||||
target=f"重登 {len(task_ids)} 条账号 CK",
|
||||
detail="旧 Cookie 将在新登录成功后替换",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
cookie_relogin_registry.register(batch_id, None, None, runner, owner_id=current.id)
|
||||
|
||||
@@ -599,19 +649,28 @@ def stop_cookie_relogin(
|
||||
_require_cookie_operation_perm(current)
|
||||
batch = cookie_relogin_registry.get(batch_id)
|
||||
if not batch:
|
||||
raise HTTPException(status_code=404, detail="重登批次不存在、已结束或服务已重启")
|
||||
if batch.get("owner_id") != current.id and not user_has_permission(current, "login:view_all"):
|
||||
raise HTTPException(
|
||||
status_code=404, detail="重登批次不存在、已结束或服务已重启"
|
||||
)
|
||||
if batch.get("owner_id") != current.id and not user_has_permission(
|
||||
current, "login:view_all"
|
||||
):
|
||||
raise HTTPException(status_code=403, detail="无权限停止该重登批次")
|
||||
|
||||
batch["runner"].stop()
|
||||
db.add(AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="cookie:relogin_stop",
|
||||
target=f"停止 CK 重登批次 {batch_id}",
|
||||
))
|
||||
db.add(
|
||||
AuditLog(
|
||||
user_id=current.id,
|
||||
username=current.username,
|
||||
action="cookie:relogin_stop",
|
||||
target=f"停止 CK 重登批次 {batch_id}",
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
return {"message": "已发送停止信号,正在登录的账号会在当前请求结束后停止", "success": True}
|
||||
return {
|
||||
"message": "已发送停止信号,正在登录的账号会在当前请求结束后停止",
|
||||
"success": True,
|
||||
}
|
||||
|
||||
|
||||
def _start_relogin(req: CookieReloginRequest, db: Session, current: User):
|
||||
@@ -667,7 +726,9 @@ def relogin_invalid_cookie_operations(
|
||||
query = query.filter(Account.tag == tag.strip())
|
||||
if search.strip():
|
||||
pattern = f"%{search.strip()}%"
|
||||
query = query.filter(or_(Account.username.ilike(pattern), Account.tag.ilike(pattern)))
|
||||
query = query.filter(
|
||||
or_(Account.username.ilike(pattern), Account.tag.ilike(pattern))
|
||||
)
|
||||
tasks = query.order_by(LoginTask.id.asc()).all()
|
||||
return _start_relogin_tasks(tasks, db, current, action="cookie:relogin_invalid")
|
||||
|
||||
@@ -681,7 +742,9 @@ def get_cookie(
|
||||
"""获取单条 Cookie 详情,供复制操作按需读取完整敏感字段。"""
|
||||
if not user_has_permission(current, "cookie:view"):
|
||||
raise HTTPException(status_code=403, detail="无权限: cookie:view")
|
||||
task = _visible_cookie_tasks_query(db, current).filter(LoginTask.id == task_id).first()
|
||||
task = (
|
||||
_visible_cookie_tasks_query(db, current).filter(LoginTask.id == task_id).first()
|
||||
)
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
acc = db.query(Account).filter(Account.id == task.account_id).first()
|
||||
@@ -691,7 +754,9 @@ def get_cookie(
|
||||
"account_id": task.account_id,
|
||||
"account_username": acc.username if acc else "",
|
||||
"assigned_to": acc.assigned_to if acc else None,
|
||||
"assigned_username": acc.assigned_user.username if acc and acc.assigned_user else None,
|
||||
"assigned_username": acc.assigned_user.username
|
||||
if acc and acc.assigned_user
|
||||
else None,
|
||||
"created_at": _fmt_dt(task.finished_at),
|
||||
"ck_check_status": task.ck_check_status or "",
|
||||
"ck_check_result": task.ck_check_result,
|
||||
@@ -726,7 +791,11 @@ def delete_cookies_batch(
|
||||
t.status = "failed"
|
||||
t.message = "Cookie已清除"
|
||||
db.commit()
|
||||
return {"message": f"已删除 {len(tasks)} 条", "deleted": len(tasks), "success": True}
|
||||
return {
|
||||
"message": f"已删除 {len(tasks)} 条",
|
||||
"deleted": len(tasks),
|
||||
"success": True,
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{task_id}")
|
||||
@@ -736,7 +805,9 @@ def delete_cookie(
|
||||
current: User = Depends(require_permission("cookie:export")),
|
||||
):
|
||||
"""删除一条 Cookie 记录。"""
|
||||
task = _visible_cookie_tasks_query(db, current).filter(LoginTask.id == task_id).first()
|
||||
task = (
|
||||
_visible_cookie_tasks_query(db, current).filter(LoginTask.id == task_id).first()
|
||||
)
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="记录不存在")
|
||||
task.cookie = ""
|
||||
|
||||
Reference in New Issue
Block a user