1. 为渠道账户ID查询添加最小长度校验,统一分析模块常量引用 2. 新增扫码登录向导端点,完善文档说明 3. 优化配对统计接口,移除无效参数 4. 为出站箱接口添加批量上限与202状态码 5. 新增测试用例、访问规则、配额等模块的查询与校验参数 6. 新增适配器健康批量查询、健康检查触发接口 7. 统一告警、审计日志的错误处理方式 8. 新增插件配置账户ID支持,优化批量操作响应 9. 新增环境健康批量查询、Webhook限流与参数校验 10. 完善会话管理、审计日志的参数与文档说明 11. 修复导入模块的校验错误处理逻辑
211 lines
8.4 KiB
Python
211 lines
8.4 KiB
Python
"""Dashboard 子域 Router。
|
||
|
||
外部系统限界上下文的监控仪表板 API,覆盖全局总览 / 系统维度 /
|
||
执行维度 / 告警维度 / 配额维度 / 健康维度 / 活动维度的跨子域聚合统计。
|
||
所有端点通过 ``create_dashboard_service_from_db`` 装配轻量级 dashboard_service,
|
||
经 ``DashboardServicePort`` 端口调用用例(纯只读聚合,无需完整 use_cases 容器)。
|
||
|
||
路径顺序约束:本 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 fastapi.exceptions import RequestValidationError
|
||
from pydantic import BaseModel
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from yuxi.external_systems.infrastructure.container import create_dashboard_service_from_db
|
||
from yuxi.external_systems.use_cases.dto.dashboard import (
|
||
ActivitiesStatsOutput,
|
||
AlertsStatsOutput,
|
||
ExecutionsStatsOutput,
|
||
GetActivitiesStatsInput,
|
||
GetAlertsStatsInput,
|
||
GetExecutionsStatsInput,
|
||
GetHealthStatsInput,
|
||
GetOverviewInput,
|
||
GetQuotasStatsInput,
|
||
GetSystemsStatsInput,
|
||
GetTodosStatsInput,
|
||
HealthStatsOutput,
|
||
OverviewOutput,
|
||
QuotasStatsOutput,
|
||
SystemsStatsOutput,
|
||
TodosStatsOutput,
|
||
)
|
||
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"])
|
||
|
||
|
||
# ---------------- Response Helpers ----------------
|
||
|
||
|
||
def _build_response(output: BaseModel) -> dict[str, Any]:
|
||
"""构建统一成功响应,使用 JSON 模式序列化确保日期/枚举值可安全输出。"""
|
||
return {"success": True, "data": output.model_dump(mode="json")}
|
||
|
||
|
||
def _validate_time_range(
|
||
start: datetime | None,
|
||
end: datetime | None,
|
||
) -> None:
|
||
"""校验时间范围语义:start 不晚于 end。
|
||
|
||
若校验失败,抛出 ``RequestValidationError``,由全局
|
||
``request_validation_error_handler`` 统一映射为 422 + 项目统一错误格式。
|
||
"""
|
||
if start is not None and end is not None and start > end:
|
||
raise RequestValidationError(
|
||
errors=[
|
||
{
|
||
"loc": ("query", "start_time"),
|
||
"msg": "start_time 不能晚于 end_time",
|
||
"type": "value_error",
|
||
"input": {"start_time": start, "end_time": end},
|
||
}
|
||
]
|
||
)
|
||
|
||
|
||
# ---------------- 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 个子域的关键指标。"""
|
||
_validate_time_range(start_time, end_time)
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetOverviewInput(start=start_time, end=end_time)
|
||
output: OverviewOutput = await dashboard_service.get_overview(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@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]:
|
||
"""系统维度统计。含启用状态/适配器类型/分类分组 + 熔断器状态。"""
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetSystemsStatsInput()
|
||
output: SystemsStatsOutput = await dashboard_service.get_systems_stats(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@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]:
|
||
"""执行维度统计。含按状态/工具分组。"""
|
||
_validate_time_range(start_time, end_time)
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetExecutionsStatsInput(
|
||
start=start_time,
|
||
end=end_time,
|
||
tool_limit=tool_limit,
|
||
)
|
||
output: ExecutionsStatsOutput = await dashboard_service.get_executions_stats(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@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 告警去重键。"""
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetAlertsStatsInput(
|
||
severity_status=severity_status,
|
||
top_limit=top_limit,
|
||
)
|
||
output: AlertsStatsOutput = await dashboard_service.get_alerts_stats(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@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]:
|
||
"""配额维度统计。含按窗口分组 + 预警/临界配额明细。"""
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetQuotasStatsInput(
|
||
warning_limit=warning_limit,
|
||
critical_limit=critical_limit,
|
||
)
|
||
output: QuotasStatsOutput = await dashboard_service.get_quotas_stats(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@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]:
|
||
"""健康维度统计。含按健康状态分组。"""
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetHealthStatsInput()
|
||
output: HealthStatsOutput = await dashboard_service.get_health_stats(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@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]:
|
||
"""活动维度统计。含按操作类型分组。"""
|
||
_validate_time_range(start_time, end_time)
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetActivitiesStatsInput(start=start_time, end=end_time)
|
||
output: ActivitiesStatsOutput = await dashboard_service.get_activities_stats(input_dto)
|
||
return _build_response(output)
|
||
|
||
|
||
@dashboard_router.get("/todos", response_model=dict)
|
||
async def get_todos_stats(
|
||
expiring_token_days: int = Query(
|
||
7,
|
||
ge=1,
|
||
le=90,
|
||
description="Token 即将过期阈值(天)",
|
||
),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""待办统计。跨子域聚合影响导航健康灯/角标的关键计数。"""
|
||
dashboard_service = create_dashboard_service_from_db(db)
|
||
input_dto = GetTodosStatsInput(expiring_token_days=expiring_token_days)
|
||
output: TodosStatsOutput = await dashboard_service.get_todos_stats(input_dto)
|
||
return _build_response(output)
|