type: 收敛测试 schemas 与协议层类型

This commit is contained in:
yml2213
2026-08-30 20:35:08 +08:00
parent 92dc461e52
commit c891ac982e
26 changed files with 1846 additions and 882 deletions
+193 -73
View File
@@ -1,16 +1,32 @@
"""账号管理路由"""
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy import func, or_
from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session, defer, joinedload
from ..database import get_db
from ..models import User, Account, AuditLog, LoginTask, DouyuTask, DouyuWorkbenchAccount
from ..schemas import AccountBulkSelection, AccountBulkTag, AccountImport, AccountAssign, AccountTag, AccountOut, BatchAssign
from ..models import (
User,
Account,
AuditLog,
LoginTask,
DouyuTask,
DouyuWorkbenchAccount,
)
from ..schemas import (
AccountBulkSelection,
AccountBulkTag,
AccountImport,
AccountAssign,
AccountTag,
AccountOut,
BatchAssign,
)
from ..deps import get_current_user, require_permission
from ..permissions import user_has_permission
from ..services.account_service import (
cookie_account_ids_query, parse_and_build_accounts,
cookie_account_ids_query,
parse_and_build_accounts,
)
router = APIRouter(prefix="/api/accounts", tags=["账号管理"])
@@ -46,15 +62,19 @@ def _filter_accounts_query(
search_text = (search or "").strip()
if search_text:
pattern = f"%{search_text}%"
query = query.filter(or_(
Account.username.ilike(pattern),
Account.tag.ilike(pattern),
Account.remark.ilike(pattern),
))
query = query.filter(
or_(
Account.username.ilike(pattern),
Account.tag.ilike(pattern),
Account.remark.ilike(pattern),
)
)
return query
def _selected_account_ids(db: Session, current: User, req: AccountBulkSelection) -> list[int]:
def _selected_account_ids(
db: Session, current: User, req: AccountBulkSelection
) -> list[int]:
"""解析批量操作目标:当前筛选全部或显式选择的 ID。"""
if req.all_matching:
rows = (
@@ -71,7 +91,7 @@ def _selected_account_ids(db: Session, current: User, req: AccountBulkSelection)
.order_by(None)
.all()
)
return [account_id for account_id, in rows]
return [account_id for (account_id,) in rows]
seen = set()
ids = []
@@ -88,7 +108,7 @@ def _selected_account_ids(db: Session, current: User, req: AccountBulkSelection)
.order_by(None)
.all()
)
allowed = {account_id for account_id, in rows}
allowed = {account_id for (account_id,) in rows}
return [account_id for account_id in ids if account_id in allowed]
@@ -131,19 +151,25 @@ def list_accounts(
if page is not None:
query = query.offset((page - 1) * page_size).limit(page_size)
can_include_sensitive = include_sensitive and user_has_permission(current, "account:view_full")
can_include_sensitive = include_sensitive and user_has_permission(
current, "account:view_full"
)
options = [joinedload(Account.assigned_user)]
if not can_include_sensitive:
options.extend([
defer(Account.password),
defer(Account.email),
defer(Account.email_password),
])
options.extend(
[
defer(Account.password),
defer(Account.email),
defer(Account.email_password),
]
)
accounts = query.options(*options).all()
result = []
for acc in accounts:
item = AccountOut(
id=acc.id, username=acc.username, remark=acc.remark or "",
id=acc.id,
username=acc.username,
remark=acc.remark or "",
tag=acc.tag or "",
assigned_to=acc.assigned_to,
assigned_username=acc.assigned_user.username if acc.assigned_user else None,
@@ -156,7 +182,12 @@ def list_accounts(
item.email_password = acc.email_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
@@ -209,8 +240,14 @@ def import_accounts(
if accounts:
db.add_all(accounts)
db.add(AuditLog(user_id=current.id, username=current.username,
action="account:import", target=f"导入{len(accounts)}"))
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:import",
target=f"导入{len(accounts)}",
)
)
db.commit()
duplicated_note = f",重复跳过 {duplicated}" if duplicated else ""
@@ -240,17 +277,27 @@ def assign_account(
raise HTTPException(status_code=400, detail="只能分配给客服角色")
# 只能分配已成功登录过的账号(有cookie)
has_success = db.query(LoginTask).filter(
LoginTask.account_id == account_id,
LoginTask.status == 'success',
LoginTask.cookie != '',
).first()
has_success = (
db.query(LoginTask)
.filter(
LoginTask.account_id == account_id,
LoginTask.status == "success",
LoginTask.cookie != "",
)
.first()
)
if not has_success:
raise HTTPException(status_code=400, detail="该账号尚未成功登录,无法分配")
acc.assigned_to = req.assigned_to
db.add(AuditLog(user_id=current.id, username=current.username,
action="account:assign", target=acc.username))
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:assign",
target=acc.username,
)
)
db.commit()
return {"message": "已分配", "success": True}
@@ -274,31 +321,52 @@ def batch_assign_accounts(
raise HTTPException(status_code=400, detail="只能分配给客服角色")
# 只能分配已成功登录过的账号(有cookie)
cookie_ids_query = cookie_account_ids_query(db).subquery()
invalid_ids = db.query(Account.id).filter(
Account.id.in_(req.account_ids),
Account.id.notin_(cookie_ids_query),
).all()
cookie_ids_query = select(LoginTask.account_id).where(
LoginTask.status == "success",
LoginTask.cookie != "",
LoginTask.cookie.isnot(None),
)
invalid_ids = (
db.query(Account.id)
.filter(
Account.id.in_(req.account_ids),
Account.id.notin_(cookie_ids_query),
)
.all()
)
if invalid_ids:
names = db.query(Account.username).filter(Account.id.in_([i[0] for i in invalid_ids])).all()
name_list = ', '.join([n[0] for n in names[:5]])
suffix = '...' if len(invalid_ids) > 5 else ''
names = (
db.query(Account.username)
.filter(Account.id.in_([i[0] for i in invalid_ids]))
.all()
)
name_list = ", ".join([n[0] for n in names[:5]])
suffix = "..." if len(invalid_ids) > 5 else ""
raise HTTPException(
status_code=400,
detail=f"以下账号尚未成功登录,无法分配:{name_list}{suffix}",
)
count = db.query(Account).filter(Account.id.in_(req.account_ids)).update(
{Account.assigned_to: req.assigned_to}, synchronize_session=False
count = (
db.query(Account)
.filter(Account.id.in_(req.account_ids))
.update({Account.assigned_to: req.assigned_to}, synchronize_session=False)
)
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:assign",
target=f"批量{'分配' if req.assigned_to else '取消分配'}{count}个账号",
)
)
db.add(AuditLog(
user_id=current.id, username=current.username,
action="account:assign",
target=f"批量{'分配' if req.assigned_to else '取消分配'}{count}个账号"
))
db.commit()
action = "分配" if req.assigned_to else "取消分配"
return {"message": f"已批量{action} {count} 个账号", "success": True, "count": count}
return {
"message": f"已批量{action} {count} 个账号",
"success": True,
"count": count,
}
@router.get("/assignments/summary")
@@ -307,11 +375,17 @@ def assignments_summary(
current: User = Depends(require_permission("account:assign")),
):
"""分配概览:每个客服分配了多少账号(仅统计已成功登录的账号)。"""
cookie_subq = cookie_account_ids_query(db).subquery()
cookie_subq = select(LoginTask.account_id).where(
LoginTask.status == "success",
LoginTask.cookie != "",
LoginTask.cookie.isnot(None),
)
cookie_accounts = db.query(Account).filter(Account.id.in_(cookie_subq)).subquery()
results = (
db.query(User.id, User.username, func.count(cookie_accounts.c.id).label("count"))
db.query(
User.id, User.username, func.count(cookie_accounts.c.id).label("count")
)
.outerjoin(cookie_accounts, cookie_accounts.c.assigned_to == User.id)
.filter(User.role == "support")
.group_by(User.id, User.username)
@@ -345,8 +419,15 @@ def set_account_tag(
if not acc:
raise HTTPException(status_code=404, detail="账号不存在")
acc.tag = (req.tag or "").strip()
db.add(AuditLog(user_id=current.id, username=current.username,
action="account:tag", target=acc.username, detail=acc.tag))
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:tag",
target=acc.username,
detail=acc.tag,
)
)
db.commit()
return {"message": "标签已更新", "success": True}
@@ -362,8 +443,10 @@ def batch_tag(
if not req.account_ids:
raise HTTPException(status_code=400, detail="请选择账号")
tag = (req.tag or "").strip()
count = _visible_accounts_query(db, current).filter(Account.id.in_(req.account_ids)).update(
{Account.tag: tag}, synchronize_session=False
count = (
_visible_accounts_query(db, current)
.filter(Account.id.in_(req.account_ids))
.update({Account.tag: tag}, synchronize_session=False)
)
db.commit()
return {"message": f"已为 {count} 个账号设置标签", "success": True}
@@ -388,7 +471,11 @@ def batch_tag_selection(
)
db.commit()
scope = "当前筛选下" if req.all_matching else "选中的"
return {"message": f"已为{scope} {count} 个账号设置标签", "success": True, "count": count}
return {
"message": f"已为{scope} {count} 个账号设置标签",
"success": True,
"count": count,
}
@router.get("/tags/list")
@@ -422,17 +509,29 @@ def batch_delete_accounts(
raise HTTPException(status_code=400, detail="无效的账号ID")
# 先删除关联的登录任务
db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(synchronize_session=False)
db.query(DouyuTask).filter(DouyuTask.account_id.in_(ids)).delete(synchronize_session=False)
db.query(DouyuWorkbenchAccount).filter(DouyuWorkbenchAccount.account_id.in_(ids)).delete(synchronize_session=False)
db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(
synchronize_session=False
)
db.query(DouyuTask).filter(DouyuTask.account_id.in_(ids)).delete(
synchronize_session=False
)
db.query(DouyuWorkbenchAccount).filter(
DouyuWorkbenchAccount.account_id.in_(ids)
).delete(synchronize_session=False)
# 删除账号
deleted = db.query(Account).filter(Account.id.in_(ids)).delete(synchronize_session=False)
deleted = (
db.query(Account).filter(Account.id.in_(ids)).delete(synchronize_session=False)
)
db.add(AuditLog(
user_id=current.id, username=current.username,
action="account:delete", target=f"批量删除{deleted}个账号"
))
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:delete",
target=f"批量删除{deleted}个账号",
)
)
db.commit()
return {"message": f"已删除 {deleted} 个账号", "deleted": deleted, "success": True}
@@ -448,19 +547,28 @@ def batch_delete_accounts_selection(
if not ids:
raise HTTPException(status_code=400, detail="请选择账号")
db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(synchronize_session=False)
db.query(DouyuTask).filter(DouyuTask.account_id.in_(ids)).delete(synchronize_session=False)
db.query(DouyuWorkbenchAccount).filter(DouyuWorkbenchAccount.account_id.in_(ids)).delete(synchronize_session=False)
db.query(LoginTask).filter(LoginTask.account_id.in_(ids)).delete(
synchronize_session=False
)
db.query(DouyuTask).filter(DouyuTask.account_id.in_(ids)).delete(
synchronize_session=False
)
db.query(DouyuWorkbenchAccount).filter(
DouyuWorkbenchAccount.account_id.in_(ids)
).delete(synchronize_session=False)
deleted = (
_visible_accounts_query(db, current)
.filter(Account.id.in_(ids))
.delete(synchronize_session=False)
)
db.add(AuditLog(
user_id=current.id, username=current.username,
action="account:delete",
target=f"{'按筛选' if req.all_matching else '批量'}删除{deleted}个账号",
))
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:delete",
target=f"{'按筛选' if req.all_matching else '批量'}删除{deleted}个账号",
)
)
db.commit()
return {"message": f"已删除 {deleted} 个账号", "deleted": deleted, "success": True}
@@ -476,12 +584,24 @@ def delete_account(
raise HTTPException(status_code=404, detail="账号不存在")
# 先删除关联的登录任务,避免外键约束失败
db.query(LoginTask).filter(LoginTask.account_id == account_id).delete(synchronize_session=False)
db.query(DouyuTask).filter(DouyuTask.account_id == account_id).delete(synchronize_session=False)
db.query(DouyuWorkbenchAccount).filter(DouyuWorkbenchAccount.account_id == account_id).delete(synchronize_session=False)
db.query(LoginTask).filter(LoginTask.account_id == account_id).delete(
synchronize_session=False
)
db.query(DouyuTask).filter(DouyuTask.account_id == account_id).delete(
synchronize_session=False
)
db.query(DouyuWorkbenchAccount).filter(
DouyuWorkbenchAccount.account_id == account_id
).delete(synchronize_session=False)
db.add(AuditLog(user_id=current.id, username=current.username,
action="account:delete", target=acc.username))
db.add(
AuditLog(
user_id=current.id,
username=current.username,
action="account:delete",
target=acc.username,
)
)
db.delete(acc)
db.commit()
return {"message": "已删除", "success": True}
+148 -77
View File
@@ -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 = ""
File diff suppressed because it is too large Load Diff