feat: add scoped cookie operations for support

This commit is contained in:
yml2213
2026-08-13 16:17:06 +08:00
parent cf3fe68de3
commit 47ded2bc3e
15 changed files with 806 additions and 14 deletions
+2
View File
@@ -23,6 +23,7 @@ PERMISSIONS = {
"login:view_assigned": "查看自己账号的登录任务",
# Cookie
"cookie:view": "查看 Cookie",
"cookie:operate": "检测/重登已分配账号 Cookie(不含查看)",
"cookie:export": "导出 Cookie",
# 斗鱼活动
"douyu:task": "斗鱼任务管理",
@@ -88,6 +89,7 @@ ROLE_PERMISSIONS = {
],
"support": [
"account:view_assigned",
"cookie:operate",
"douyu:task",
"yyb:session",
"yyb:history",
+124 -13
View File
@@ -12,7 +12,7 @@ import io
import csv
from ..database import get_db, SessionLocal
from ..models import User, LoginTask, Account, ProxyConfig as ProxyConfigModel
from ..models import User, LoginTask, Account, AuditLog, ProxyConfig as ProxyConfigModel
from ..deps import get_current_user, require_permission
from ..permissions import user_has_permission, get_user_permissions
from ..schemas import CookieReloginRequest
@@ -30,6 +30,15 @@ def _fmt_dt(dt) -> str | None:
router = APIRouter(prefix="/api/cookies", tags=["Cookie管理"])
def _require_cookie_operation_perm(current: User) -> None:
"""允许脱敏的 CK 检测与重登,不授予 CK 查看或导出能力。"""
if not (
user_has_permission(current, "cookie:operate")
or user_has_permission(current, "cookie:view")
):
raise HTTPException(status_code=403, detail="无权限: cookie:operate")
def _parse_account_names(raw_names: str) -> list[str]:
"""解析前端粘贴的 Excel 账号名,每行一个并去重。"""
names = []
@@ -162,6 +171,8 @@ def list_cookies(
current: User = Depends(get_current_user),
):
"""查看登录成功的 Cookie 列表。"""
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))
else:
@@ -248,6 +259,8 @@ def cookies_summary(
current: User = Depends(get_current_user),
):
"""Cookie 管理统计,避免前端为了卡片统计拉全量 Cookie。"""
if not user_has_permission(current, "cookie:view"):
raise HTTPException(status_code=403, detail="无权限: cookie:view")
query = _visible_cookie_tasks_query(db, current)
if user_has_permission(current, "login:view_all"):
query = query.join(Account, LoginTask.account_id == Account.id)
@@ -260,6 +273,47 @@ def cookies_summary(
}
@router.get("/operations")
def list_cookie_operations(
search: str = Query(""),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=200),
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""脱敏的 CK 操作列表,只用于检测与重登。"""
_require_cookie_operation_perm(current)
query = _visible_cookie_tasks_query(db, current)
if user_has_permission(current, "login:view_all"):
query = query.join(Account, LoginTask.account_id == Account.id)
search_text = (search or "").strip()
if search_text:
query = query.filter(Account.username.ilike(f"%{search_text}%"))
total = query.order_by(None).count()
tasks = (
query.order_by(LoginTask.finished_at.desc(), LoginTask.id.desc())
.offset((page - 1) * page_size)
.limit(page_size)
.all()
)
return {
"items": [
{
"id": task.id,
"account_username": task.account.username if task.account else "",
"ck_check_status": task.ck_check_status or "",
"ck_checked_at": _fmt_dt(task.ck_checked_at),
"created_at": _fmt_dt(task.finished_at),
}
for task in tasks
],
"total": total,
"page": page,
"page_size": page_size,
}
@router.get("/duplicates")
def find_duplicate_cookies(
db: Session = Depends(get_db),
@@ -390,13 +444,8 @@ def export_cookies(
)
@router.post("/check")
def check_cookies(
ids: str = "",
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""批量检测斗鱼 Cookie 有效性(鱼丸接口),返回每条有效性与鱼丸数/昵称。"""
def _check_cookies(ids: str, db: Session, current: User, *, detailed: bool) -> dict:
"""执行 CK 检测;客服操作页仅返回状态,不返回账号衍生信息。"""
if not ids:
raise HTTPException(status_code=400, detail="请指定记录ID")
id_list = [int(x) for x in ids.split(",") if x.strip().isdigit()]
@@ -439,16 +488,48 @@ def check_cookies(
"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.commit()
if not detailed:
results = [
{"id": item["id"], "valid": item["valid"], "checked_at": item["checked_at"]}
for item in results
]
return {"results": results, "success": True}
@router.post("/relogin")
def relogin_cookies(
req: CookieReloginRequest,
@router.post("/check")
def check_cookies(
ids: str = "",
db: Session = Depends(get_db),
current: User = Depends(require_permission("login:batch")),
current: User = Depends(get_current_user),
):
"""批量检测斗鱼 Cookie 有效性,供 Cookie 管理页查看详细结果。"""
if not user_has_permission(current, "cookie:view"):
raise HTTPException(status_code=403, detail="无权限: cookie:view")
return _check_cookies(ids, db, current, detailed=True)
@router.post("/operations/check")
def check_cookie_operations(
ids: str = "",
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""脱敏 CK 检测,只返回有效性与检测时间。"""
_require_cookie_operation_perm(current)
return _check_cookies(ids, db, current, detailed=False)
def _start_relogin(
req: CookieReloginRequest,
db: Session,
current: User,
):
"""重新登录失效的 Cookie:用账号信息重新登录,成功后替换旧 Cookie。
@@ -498,6 +579,13 @@ def relogin_cookies(
thread = threading.Thread(target=runner.run, daemon=True)
thread.start()
db.add(AuditLog(
user_id=current.id,
username=current.username,
action="cookie:relogin",
target=f"重登 {len(task_ids)} 条已分配账号 CK",
))
db.commit()
return {
"batch_id": batch_id,
"count": len(task_ids),
@@ -506,6 +594,27 @@ def relogin_cookies(
}
@router.post("/relogin")
def relogin_cookies(
req: CookieReloginRequest,
db: Session = Depends(get_db),
current: User = Depends(require_permission("login:batch")),
):
"""Cookie 管理页的重登入口,保留既有 login:batch 权限。"""
return _start_relogin(req, db, current)
@router.post("/operations/relogin")
def relogin_cookie_operations(
req: CookieReloginRequest,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""脱敏 CK 操作页的重登入口。"""
_require_cookie_operation_perm(current)
return _start_relogin(req, db, current)
@router.get("/{task_id}")
def get_cookie(
task_id: int,
@@ -513,6 +622,8 @@ def get_cookie(
current: User = Depends(get_current_user),
):
"""获取单条 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()
if not task:
raise HTTPException(status_code=404, detail="记录不存在")