ForcePilot/backend/server/routers/external_systems/dashboard_router.py
Kris aabde54f2e refactor(routers): 统一完善所有外部系统接口的参数校验和类型约束
1. 为所有查询参数添加max_length长度限制,规范参数输入范围
2. 使用Literal类型替换普通字符串参数,限定合法取值范围
3. 为路径参数添加Path校验,确保ID参数合法有效
4. 优化请求体参数的声明,补充缺失的Body注解和校验规则
5. 统一分页参数的offset/limit使用方式,替换旧的page/page_size模式
2026-07-04 00:16:45 +08:00

149 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Dashboard 子域 Router。
外部系统限界上下文的监控仪表板 API覆盖全局总览 / 系统维度 /
执行维度 / 告警维度 / 配额维度 / 健康维度 / 活动维度的跨子域聚合统计。
所有端点通过 ``create_use_cases_from_db`` 装配 use_cases
经 ``dashboard_service`` 端口调用用例。
路径顺序约束:本 router 所有端点均为静态路径(无动态路径参数),无路由匹配歧义。
端点按业务维度组织(总览 → 系统 → 执行 → 告警 → 配额 → 健康 → 活动),
与上文子域覆盖顺序保持一致。
认证策略:
- 所有端点均为只读查询,使用 get_required_user支持 JWT + API Key 双模认证)
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from fastapi import APIRouter, Depends, Query
from sqlalchemy.ext.asyncio import AsyncSession
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
from yuxi.external_systems.use_cases.dto.dashboard import (
GetActivitiesStatsInput,
GetAlertsStatsInput,
GetExecutionsStatsInput,
GetHealthStatsInput,
GetOverviewInput,
GetQuotasStatsInput,
GetSystemsStatsInput,
)
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_db, get_required_user
dashboard_router = APIRouter(prefix="/dashboard", tags=["external-systems-dashboard"])
# ---------------- Endpoints ----------------
@dashboard_router.get("/overview", response_model=dict)
async def get_overview(
start_time: datetime | None = Query(None, description="执行统计起始时间"),
end_time: datetime | None = Query(None, description="执行统计结束时间"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""全局总览。聚合 5 个子域的关键指标。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetOverviewInput(start=start_time, end=end_time)
output = await use_cases.dashboard_service.get_overview(input_dto)
return {"success": True, "data": output.model_dump()}
@dashboard_router.get("/systems", response_model=dict)
async def get_systems_stats(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""系统维度统计。含启用状态/适配器类型/分类分组 + 熔断器状态。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetSystemsStatsInput()
output = await use_cases.dashboard_service.get_systems_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@dashboard_router.get("/executions", response_model=dict)
async def get_executions_stats(
start_time: datetime | None = Query(None, description="执行统计起始时间"),
end_time: datetime | None = Query(None, description="执行统计结束时间"),
tool_limit: int = Query(20, ge=1, le=100, description="Top 工具数量"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""执行维度统计。含按状态/工具分组。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetExecutionsStatsInput(
start=start_time,
end=end_time,
tool_limit=tool_limit,
)
output = await use_cases.dashboard_service.get_executions_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@dashboard_router.get("/alerts", response_model=dict)
async def get_alerts_stats(
severity_status: Literal["firing", "acknowledged", "resolved", "suppressed"] = Query(
"firing",
description="count_by_severity 的状态过滤告警状态firing/acknowledged/resolved/suppressed",
),
top_limit: int = Query(10, ge=1, le=50, description="Top 告警去重键数量"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""告警维度统计。含按状态/严重程度分组 + Top 告警去重键。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetAlertsStatsInput(
severity_status=severity_status,
top_limit=top_limit,
)
output = await use_cases.dashboard_service.get_alerts_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@dashboard_router.get("/quotas", response_model=dict)
async def get_quotas_stats(
warning_limit: int = Query(20, ge=1, le=100, description="预警配额明细数量"),
critical_limit: int = Query(20, ge=1, le=100, description="临界配额明细数量"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""配额维度统计。含按窗口分组 + 预警/临界配额明细。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetQuotasStatsInput(
warning_limit=warning_limit,
critical_limit=critical_limit,
)
output = await use_cases.dashboard_service.get_quotas_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@dashboard_router.get("/health", response_model=dict)
async def get_health_stats(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""健康维度统计。含按健康状态分组。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetHealthStatsInput()
output = await use_cases.dashboard_service.get_health_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@dashboard_router.get("/activities", response_model=dict)
async def get_activities_stats(
start_time: datetime | None = Query(None, description="活动统计起始时间"),
end_time: datetime | None = Query(None, description="活动统计结束时间"),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""活动维度统计。含按操作类型分组。"""
use_cases = create_use_cases_from_db(db)
input_dto = GetActivitiesStatsInput(start=start_time, end=end_time)
output = await use_cases.dashboard_service.get_activities_stats(input_dto)
return {"success": True, "data": output.model_dump()}