ForcePilot/backend/server/routers/external_systems/alert_router.py

296 lines
10 KiB
Python
Raw Normal View History

"""Alert 子域 Router。
外部系统限界上下文的告警事件管理 API覆盖告警列表 / 详情 / 触发 /
确认 / 恢复 / 统计聚合 / 按链路查询 / 批量确认 / 批量恢复 / 告警上下文 /
通知状态所有端点通过 ``create_use_cases_from_db`` 装配 use_cases
``alert_service`` 端口调用用例
``list_alerts`` 支持 resource_type / resource_id / related_trace_id /
dedup_key 过滤参数Request Schema Input DTO 不共享类Router 内显式
构造 DTO确认 / 恢复操作的 ``acknowledged_by`` / ``user`` 字段由
``current_user.uid`` 填充确保审计链路可追溯操作人
"""
from __future__ import annotations
from datetime import datetime
from typing import Any
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel, ConfigDict, Field
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.alert import (
AcknowledgeAlertInput,
AlertStatsInput,
BatchAcknowledgeAlertsInput,
BatchResolveAlertsInput,
FireAlertInput,
GetAlertContextInput,
GetAlertInput,
GetAlertNotificationsInput,
ListAlertsByTraceInput,
ListAlertsInput,
ResolveAlertInput,
)
from yuxi.storage.postgres.models_business import User
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
alert_router = APIRouter(prefix="/alerts", tags=["external-systems-alert"])
# ---------------- Request Schemas ----------------
class FireAlertRequest(BaseModel):
"""触发告警请求体。字段对齐 ``FireAlertInput``。"""
model_config = ConfigDict(frozen=True)
system_id: int | None
alert_type: str
title: str
triggered_at: datetime
env_key: str | None = None
severity: str = "warning"
description: str | None = None
detail: dict[str, Any] = Field(default_factory=dict)
resource_type: str | None = None
resource_id: str | None = None
related_execution_id: str | None = None
related_trace_id: str | None = None
metric_snapshot: dict[str, Any] | None = None
dedup_key: str | None = None
class AcknowledgeAlertRequest(BaseModel):
"""确认告警请求体。``acknowledged_by`` 由当前管理员填充,仅暴露 ``note``。"""
model_config = ConfigDict(frozen=True)
note: str | None = None
class ResolveAlertRequest(BaseModel):
"""恢复告警请求体。字段对齐 ``ResolveAlertInput``(不含 id / user"""
model_config = ConfigDict(frozen=True)
resolution_note: str | None = None
class BatchAcknowledgeAlertsRequest(BaseModel):
"""批量确认告警请求体。字段对齐 ``BatchAcknowledgeAlertsInput``(不含 acknowledged_by"""
model_config = ConfigDict(frozen=True)
alert_ids: list[int]
note: str | None = None
class BatchResolveAlertsRequest(BaseModel):
"""批量恢复告警请求体。字段对齐 ``BatchResolveAlertsInput``(不含 user"""
model_config = ConfigDict(frozen=True)
alert_ids: list[int]
resolution_note: str | None = None
# ---------------- Endpoints ----------------
@alert_router.get("", response_model=dict)
async def list_alerts(
limit: int = Query(100, ge=1, le=500),
offset: int = Query(0, ge=0),
system_id: int | None = Query(None),
env_key: str | None = Query(None),
alert_type: str | None = Query(None),
severity: str | None = Query(None),
status: str | None = Query(None),
start_at: datetime | None = Query(None, description="ISO8601 开始时间"),
end_at: datetime | None = Query(None, description="ISO8601 结束时间"),
# 新增查询参数
resource_type: str | None = Query(None),
resource_id: str | None = Query(None),
related_trace_id: str | None = Query(None),
dedup_key: str | None = Query(None),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""分页列出告警事件(支持 resource_type / resource_id / related_trace_id / dedup_key 过滤)。"""
use_cases = create_use_cases_from_db(db)
input_dto = ListAlertsInput(
limit=limit,
offset=offset,
system_id=system_id,
env_key=env_key,
alert_type=alert_type,
severity=severity,
status=status,
start_at=start_at,
end_at=end_at,
resource_type=resource_type,
resource_id=resource_id,
related_trace_id=related_trace_id,
dedup_key=dedup_key,
)
output = await use_cases.alert_service.list_alerts(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.get("/stats", response_model=dict)
async def get_alert_stats(
system_id: int | None = Query(None),
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 = AlertStatsInput(system_id=system_id)
output = await use_cases.alert_service.get_alert_stats(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.get("/by-trace/{trace_id}", response_model=dict)
async def list_alerts_by_trace(
trace_id: str,
limit: int = Query(100, ge=1, le=500),
offset: int = Query(0, ge=0),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_required_user),
) -> dict[str, Any]:
"""按链路 ID 查询告警(通过 related_trace_id 关联,支持分页)。"""
use_cases = create_use_cases_from_db(db)
input_dto = ListAlertsByTraceInput(
trace_id=trace_id,
limit=limit,
offset=offset,
)
output = await use_cases.alert_service.list_alerts_by_trace(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.post("/batch-acknowledge", response_model=dict)
async def batch_acknowledge_alerts(
payload: BatchAcknowledgeAlertsRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""批量确认告警。``acknowledged_by`` 由当前管理员填充。"""
use_cases = create_use_cases_from_db(db)
input_dto = BatchAcknowledgeAlertsInput(
alert_ids=payload.alert_ids,
note=payload.note,
acknowledged_by=current_user.uid,
)
output = await use_cases.alert_service.batch_acknowledge_alerts(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.post("/batch-resolve", response_model=dict)
async def batch_resolve_alerts(
payload: BatchResolveAlertsRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""批量恢复告警。``user`` 由当前管理员填充,确保审计可追溯。"""
use_cases = create_use_cases_from_db(db)
input_dto = BatchResolveAlertsInput(
alert_ids=payload.alert_ids,
user=current_user.uid,
resolution_note=payload.resolution_note,
)
output = await use_cases.alert_service.batch_resolve_alerts(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.get("/{alert_id}", response_model=dict)
async def get_alert(
alert_id: int,
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 = GetAlertInput(id=alert_id)
output = await use_cases.alert_service.get_alert(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.post("", response_model=dict)
async def fire_alert(
payload: FireAlertRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""触发告警事件。"""
use_cases = create_use_cases_from_db(db)
input_dto = FireAlertInput(**payload.model_dump())
output = await use_cases.alert_service.fire_alert(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.post("/{alert_id}/acknowledge", response_model=dict)
async def acknowledge_alert(
alert_id: int,
payload: AcknowledgeAlertRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""确认告警。``acknowledged_by`` 由当前管理员 uid 填充,``note`` 可选。"""
use_cases = create_use_cases_from_db(db)
input_dto = AcknowledgeAlertInput(
id=alert_id,
acknowledged_by=current_user.uid,
note=payload.note,
)
output = await use_cases.alert_service.acknowledge_alert(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.post("/{alert_id}/resolve", response_model=dict)
async def resolve_alert(
alert_id: int,
payload: ResolveAlertRequest,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_admin_user),
) -> dict[str, Any]:
"""恢复告警。``user`` 由当前管理员 uid 填充,确保审计可追溯。"""
use_cases = create_use_cases_from_db(db)
input_dto = ResolveAlertInput(
id=alert_id,
user=current_user.uid,
resolution_note=payload.resolution_note,
)
output = await use_cases.alert_service.resolve_alert(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.get("/{alert_id}/context", response_model=dict)
async def get_alert_context(
alert_id: int,
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 = GetAlertContextInput(alert_id=alert_id)
output = await use_cases.alert_service.get_alert_context(input_dto)
return {"success": True, "data": output.model_dump()}
@alert_router.get("/{alert_id}/notifications", response_model=dict)
async def get_alert_notifications(
alert_id: int,
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 = GetAlertNotificationsInput(alert_id=alert_id)
output = await use_cases.alert_service.get_alert_notifications(input_dto)
return {"success": True, "data": output.model_dump()}