优化列表分页和数据加载

This commit is contained in:
yml2213
2026-07-24 13:25:27 +08:00
parent 6840728da6
commit f91c05c108
17 changed files with 647 additions and 161 deletions
+188 -9
View File
@@ -8,8 +8,8 @@ from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Query, WebSocket, WebSocketDisconnect
from fastapi.responses import StreamingResponse
from sqlalchemy import func
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import func, or_
from sqlalchemy.orm import Session, defer, joinedload
from core.huya import (
HuyaCredentialError,
@@ -134,6 +134,31 @@ def _account_out(account: HuyaAccount, include_cookie: bool = True) -> HuyaAccou
)
def _account_out_light(account: HuyaAccount, *, has_password: bool = False) -> HuyaAccountOut:
"""虎牙账号列表轻量输出,不读取加密字段。"""
return HuyaAccountOut(
id=account.id,
uid=account.uid or "",
yyuid=account.yyuid or "",
username=account.username or "",
has_password=has_password,
nickname=account.nickname or "",
cookie="",
cookie_preview="***",
tag=account.tag or "",
remark=account.remark or "",
status=account.status or "",
points=account.points,
game_name=account.game_name or "",
game_channel=account.game_channel or "",
game_phone=account.game_phone or "",
assigned_to=account.assigned_to,
assigned_username=account.assigned_user.username if account.assigned_user else None,
created_at=account.created_at,
updated_at=account.updated_at,
)
def _sanitize_task_result(result: dict | None, *, include_images: bool = False) -> dict | None:
"""列表接口默认剥离 base64 图片,避免轮询每次传 1MB+ 数据。"""
if not isinstance(result, dict):
@@ -196,17 +221,50 @@ def _cookie_out(account: HuyaAccount, include_cookie: bool) -> dict:
}
def _cookie_out_light(account: HuyaAccount) -> dict:
"""虎牙 Cookie 列表轻量输出,不读取完整加密 Cookie。"""
account_name = account.nickname or account.username or account.uid or str(account.id)
return {
"id": account.id,
"account_id": account.id,
"account_username": account_name,
"uid": account.uid or "",
"yyuid": account.yyuid or "",
"assigned_to": account.assigned_to,
"assigned_username": account.assigned_user.username if account.assigned_user else None,
"created_at": account.updated_at.isoformat() if account.updated_at else None,
"cookie": "",
"cookie_preview": "***",
}
def _huya_password_state_map(db: Session, account_ids: list[int]) -> dict[int, bool]:
"""不解密密码字段,仅判断密文字段是否非空。"""
if not account_ids:
return {}
rows = (
db.query(HuyaAccount.id, (HuyaAccount.account_password != "").label("has_password"))
.filter(HuyaAccount.id.in_(account_ids))
.all()
)
return {account_id: bool(has_password) for account_id, has_password in rows}
@router.get("/task-types")
def task_types(current: User = Depends(require_permission("huya:task"))):
"""返回当前规划的虎牙任务类型。"""
return SUPPORTED_TASK_TYPES
@router.get("/accounts", response_model=list[HuyaAccountOut])
@router.get("/accounts")
def list_accounts(
assigned_only: bool = Query(False),
tag: str | None = Query(None),
has_cookie: bool = Query(False),
search: str = Query(""),
page: int | None = Query(None, ge=1),
page_size: int = Query(20, ge=1, le=200),
include_cookie: bool | None = Query(None),
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
@@ -218,9 +276,71 @@ def list_accounts(
query = query.filter(HuyaAccount.tag == tag)
if has_cookie:
query = query.filter(HuyaAccount.cookie != "")
accounts = query.order_by(HuyaAccount.id.desc()).all()
include_cookie = _can_view_huya_cookie(current)
return [_account_out(account, include_cookie=include_cookie) for account in accounts]
search_text = (search or "").strip()
if search_text:
pattern = f"%{search_text}%"
query = query.filter(or_(
HuyaAccount.uid.ilike(pattern),
HuyaAccount.yyuid.ilike(pattern),
HuyaAccount.username.ilike(pattern),
HuyaAccount.nickname.ilike(pattern),
HuyaAccount.tag.ilike(pattern),
HuyaAccount.game_name.ilike(pattern),
HuyaAccount.game_phone.ilike(pattern),
))
total = None
if page is not None:
total = query.order_by(None).count()
should_include_cookie = _can_view_huya_cookie(current) if include_cookie is None else (include_cookie and _can_view_huya_cookie(current))
query = query.order_by(HuyaAccount.id.desc())
if page is not None:
query = query.offset((page - 1) * page_size).limit(page_size)
if not should_include_cookie:
query = query.options(defer(HuyaAccount.cookie), defer(HuyaAccount.account_password))
accounts = query.all()
if should_include_cookie:
result = [_account_out(account, include_cookie=True) for account in accounts]
else:
password_map = _huya_password_state_map(db, [account.id for account in accounts])
result = [_account_out_light(account, has_password=password_map.get(account.id, False)) for account in accounts]
if page is not None:
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
return result
@router.get("/accounts/summary")
def accounts_summary(
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""虎牙账号统计,避免前端为了卡片统计拉全量账号。"""
query = _visible_huya_accounts_query(db, current)
total = query.count()
assigned_count = query.filter(HuyaAccount.assigned_to.isnot(None)).count()
tag_count = (
query.filter(HuyaAccount.tag != "", HuyaAccount.tag.isnot(None))
.with_entities(HuyaAccount.tag)
.distinct()
.count()
)
password_ready_count = query.filter(HuyaAccount.account_password != "").count()
point_count = query.filter(HuyaAccount.points.isnot(None)).count()
bound_count = query.filter(or_(
HuyaAccount.game_name != "",
HuyaAccount.game_channel != "",
HuyaAccount.game_phone != "",
)).count()
return {
"total": total,
"assigned_count": assigned_count,
"unassigned_count": max(0, total - assigned_count),
"tag_count": tag_count,
"password_ready_count": password_ready_count,
"point_count": point_count,
"bound_count": bound_count,
}
@router.post("/accounts/import-cookies")
@@ -826,13 +946,59 @@ def list_tags(
@router.get("/cookies")
def list_cookies(
search: str = Query(""),
page: int | None = Query(None, ge=1),
page_size: int = Query(20, ge=1, le=200),
include_cookie: bool = Query(True),
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""查看当前用户可见的虎牙 Cookie。"""
include_cookie = _can_view_huya_cookie(current)
accounts = _visible_huya_accounts_query(db, current).filter(HuyaAccount.cookie != "").order_by(HuyaAccount.updated_at.desc()).all()
return [_cookie_out(account, include_cookie=include_cookie) for account in accounts]
should_include_cookie = include_cookie and _can_view_huya_cookie(current)
query = _visible_huya_accounts_query(db, current).filter(HuyaAccount.cookie != "")
search_text = (search or "").strip()
if search_text:
pattern = f"%{search_text}%"
query = query.outerjoin(User, HuyaAccount.assigned_to == User.id).filter(or_(
HuyaAccount.uid.ilike(pattern),
HuyaAccount.yyuid.ilike(pattern),
HuyaAccount.username.ilike(pattern),
HuyaAccount.nickname.ilike(pattern),
User.username.ilike(pattern),
))
total = None
if page is not None:
total = query.order_by(None).count()
query = query.order_by(HuyaAccount.updated_at.desc())
if page is not None:
query = query.offset((page - 1) * page_size).limit(page_size)
if not should_include_cookie:
query = query.options(defer(HuyaAccount.cookie), defer(HuyaAccount.account_password))
accounts = query.all()
result = (
[_cookie_out(account, include_cookie=True) for account in accounts]
if should_include_cookie
else [_cookie_out_light(account) for account in accounts]
)
if page is not None:
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
return result
@router.get("/cookies/summary")
def cookies_summary(
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""虎牙 Cookie 统计,避免前端为了卡片统计拉全量 Cookie。"""
query = _visible_huya_accounts_query(db, current).filter(HuyaAccount.cookie != "")
total = query.count()
assigned_count = query.filter(HuyaAccount.assigned_to.isnot(None)).count()
return {
"total": total,
"assigned_count": assigned_count,
"unassigned_count": max(0, total - assigned_count),
}
@router.get("/cookies/export")
@@ -875,6 +1041,19 @@ def export_cookies(
)
@router.get("/cookies/{account_id}")
def get_cookie(
account_id: int,
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""获取单条虎牙 Cookie 详情,供复制操作按需读取完整敏感字段。"""
account = _visible_huya_accounts_query(db, current).filter(HuyaAccount.id == account_id, HuyaAccount.cookie != "").first()
if not account:
raise HTTPException(status_code=404, detail="记录不存在")
return _cookie_out(account, include_cookie=_can_view_huya_cookie(current))
@router.delete("/cookies/batch")
def delete_cookies_batch(
account_ids: str = "",