优化首页统计和任务请求

This commit is contained in:
yml2213
2026-08-05 10:41:40 +08:00
parent 2fda47c631
commit 335f5cf57b
11 changed files with 421 additions and 160 deletions
+2 -1
View File
@@ -12,7 +12,7 @@ from fastapi.responses import FileResponse
from starlette.middleware.base import BaseHTTPMiddleware
from .database import init_db
from .routers import auth, users, accounts, account_check, login, proxy, cookies, huya, douyu
from .routers import auth, users, accounts, account_check, dashboard, login, proxy, cookies, huya, douyu
from .schemas import AppInfo
from .version import get_app_version
from utils import setup_logger
@@ -91,6 +91,7 @@ app.include_router(auth.router)
app.include_router(users.router)
app.include_router(accounts.router)
app.include_router(account_check.router)
app.include_router(dashboard.router)
app.include_router(login.router)
app.include_router(proxy.router)
app.include_router(cookies.router)
+123
View File
@@ -0,0 +1,123 @@
"""首页聚合统计路由。"""
from fastapi import APIRouter, Depends
from sqlalchemy import func
from sqlalchemy.orm import Session
from ..database import get_db
from ..deps import get_current_user
from ..models import (
Account,
HuyaAccount,
HuyaGoodsSnapshot,
HuyaRechargeGoodsSnapshot,
HuyaTask,
LoginTask,
User,
)
from ..permissions import user_has_permission
router = APIRouter(prefix="/api/dashboard", tags=["首页概览"])
def _task_summary(query, task_model) -> dict:
"""按状态汇总任务,避免首页读取任务列表。"""
rows = (
query.order_by(None)
.with_entities(task_model.status, func.count(task_model.id))
.group_by(task_model.status)
.all()
)
status_counts = {status or "": count for status, count in rows}
return {
"total": sum(status_counts.values()),
"success": status_counts.get("success", 0),
"failed": sum(status_counts.get(status, 0) for status in ("failed", "error")),
"status_counts": status_counts,
}
def _empty_task_summary() -> dict:
return {"total": 0, "success": 0, "failed": 0, "status_counts": {}}
def _can_view_huya_all(user: User) -> bool:
"""兼容旧 huya:account 全量权限。"""
return user_has_permission(user, "huya:view_all") or user_has_permission(user, "huya:account")
@router.get("/summary")
def dashboard_summary(
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
"""返回首页所需的全部轻量统计。"""
douyu_accounts = 0
if user_has_permission(current, "account:view_all"):
douyu_accounts = db.query(Account).count()
elif user_has_permission(current, "account:view_assigned"):
douyu_accounts = db.query(Account).filter(Account.assigned_to == current.id).count()
login_tasks = _empty_task_summary()
if any(user_has_permission(current, permission) for permission in (
"login:batch",
"login:view_all",
"login:view_assigned",
)):
login_query = db.query(LoginTask).join(Account, LoginTask.account_id == Account.id)
if not user_has_permission(current, "login:view_all"):
login_query = login_query.filter(Account.assigned_to == current.id)
login_tasks = _task_summary(login_query, LoginTask)
cookies = 0
if user_has_permission(current, "cookie:view"):
cookie_query = (
db.query(LoginTask)
.join(Account, LoginTask.account_id == Account.id)
.filter(LoginTask.status == "success")
)
if not user_has_permission(current, "login:view_all"):
cookie_query = cookie_query.filter(Account.assigned_to == current.id)
cookies = cookie_query.count()
huya_accounts = 0
can_view_huya_accounts = any(user_has_permission(current, permission) for permission in (
"huya:account",
"huya:view_all",
"huya:view_assigned",
))
if can_view_huya_accounts:
huya_account_query = db.query(HuyaAccount)
if not _can_view_huya_all(current):
huya_account_query = huya_account_query.filter(HuyaAccount.assigned_to == current.id)
huya_accounts = huya_account_query.count()
huya_tasks = _empty_task_summary()
huya_goods = 0
huya_recharge_goods = 0
if user_has_permission(current, "huya:task"):
huya_task_query = db.query(HuyaTask).join(HuyaAccount, HuyaTask.account_id == HuyaAccount.id)
if not _can_view_huya_all(current):
huya_task_query = huya_task_query.filter(HuyaAccount.assigned_to == current.id)
huya_tasks = _task_summary(huya_task_query, HuyaTask)
huya_goods = db.query(HuyaGoodsSnapshot).count()
huya_recharge_goods = db.query(HuyaRechargeGoodsSnapshot).count()
return {
"douyu": {
"accounts": douyu_accounts,
"tasks": login_tasks["total"],
"success": login_tasks["success"],
"failed": login_tasks["failed"],
"cookies": cookies,
},
"huya": {
"accounts": huya_accounts,
"tasks": huya_tasks["total"],
"success": huya_tasks["success"],
"failed": huya_tasks["failed"],
"goods": huya_goods,
"rechargeGoods": huya_recharge_goods,
},
}
+44 -11
View File
@@ -236,10 +236,12 @@ def task_types(current: User = Depends(require_permission("douyu:task"))):
return SUPPORTED_DOUYU_TASK_TYPES
@router.get("/accounts", response_model=list[DouyuTaskAccountOut])
@router.get("/accounts")
def list_task_accounts(
search: str = Query(""),
ids: str = Query(""),
page: int | None = Query(None, ge=1),
page_size: int = Query(50, ge=1, le=200),
db: Session = Depends(get_db),
current: User = Depends(get_current_user),
):
@@ -262,8 +264,18 @@ def list_task_accounts(
Account.game_name.ilike(pattern),
Account.esports_game_name.ilike(pattern),
))
rows = query.order_by(Account.id.desc()).limit(500).all()
return [_account_out(account) for account in rows]
total = None
if page is not None:
total = query.order_by(None).count()
query = query.order_by(Account.id.desc())
if page is not None:
query = query.offset((page - 1) * page_size).limit(page_size)
else:
query = query.limit(500)
result = [_account_out(account) for account in query.all()]
if page is not None:
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
return result
@router.get("/config", response_model=DouyuConfigOut)
@@ -366,25 +378,46 @@ async def create_task_batch(
return {"batch_id": batch_id, "count": count, "success": True}
@router.get("/tasks", response_model=list[DouyuTaskOut])
@router.get("/tasks")
def list_tasks(
batch_id: str | None = None,
include_detail: bool = Query(False, description="是否返回完整任务结果(默认否,轮询请保持 false)"),
page: int | None = Query(None, ge=1),
page_size: int = Query(100, ge=1, le=500),
db: Session = Depends(get_db),
current: User = Depends(require_permission("douyu:task")),
):
"""查看斗鱼任务记录。"""
cleanup_orphan_douyu_tasks(
"""查看斗鱼任务记录,默认只返回最近 100 条"""
query = _visible_tasks_query(db, current)
if batch_id:
query = query.filter(DouyuTask.batch_id == batch_id)
total = None
if page is not None:
total = query.enable_eagerloads(False).order_by(None).count()
query = query.order_by(DouyuTask.id.desc())
if page is not None:
query = query.offset((page - 1) * page_size).limit(page_size)
else:
query = query.limit(page_size)
result = [_task_out(task, include_detail=include_detail) for task in query.all()]
if page is not None:
return {"items": result, "total": total or 0, "page": page, "page_size": page_size}
return result
@router.post("/tasks/cleanup-orphans")
def cleanup_orphan_tasks(
db: Session = Depends(get_db),
current: User = Depends(require_permission("douyu:task")),
):
"""手动清理没有内存执行器接管的斗鱼任务。"""
cleaned = cleanup_orphan_douyu_tasks(
db,
active_batch_ids=douyu_batch_registry.active_ids(),
statuses=("pending", "running"),
message="任务已中断(无执行器接管)",
)
query = _visible_tasks_query(db, current)
if batch_id:
query = query.filter(DouyuTask.batch_id == batch_id)
rows = query.order_by(DouyuTask.id.desc()).limit(300).all()
return [_task_out(task, include_detail=include_detail) for task in rows]
return {"message": f"已清理 {cleaned} 个残留任务", "cleaned": cleaned, "success": True}
@router.get("/tasks/{task_id}", response_model=DouyuTaskOut)
+15 -6
View File
@@ -1384,12 +1384,6 @@ def list_tasks(
current: User = Depends(require_permission("huya:task")),
):
"""查看虎牙任务记录。"""
cleanup_orphan_huya_tasks(
db,
active_batch_ids=huya_batch_registry.active_ids(),
statuses=("pending", "running"),
message="任务已中断(无执行器接管)",
)
query = _visible_huya_tasks_query(db, current)
if batch_id:
query = query.filter(HuyaTask.batch_id == batch_id)
@@ -1397,6 +1391,21 @@ def list_tasks(
return [_task_out(task, include_images=include_images) for task in tasks]
@router.post("/tasks/cleanup-orphans")
def cleanup_orphan_tasks(
db: Session = Depends(get_db),
current: User = Depends(require_permission("huya:task")),
):
"""手动清理没有内存执行器接管的虎牙任务。"""
cleaned = cleanup_orphan_huya_tasks(
db,
active_batch_ids=huya_batch_registry.active_ids(),
statuses=("pending", "running"),
message="任务已中断(无执行器接管)",
)
return {"message": f"已清理 {cleaned} 个残留任务", "cleaned": cleaned, "success": True}
@router.get("/tasks/summary")
def tasks_summary(
db: Session = Depends(get_db),