"""告警管理路由""" from typing import Optional, List from datetime import datetime, timedelta from fastapi import APIRouter, Depends, HTTPException, Query, BackgroundTasks from pydantic import BaseModel from sqlalchemy.orm import Session from sqlalchemy import desc, func from ..database import get_db from ..models.alert import AlertRule, AlertRecord, NotificationChannel from ..services.alert import AlertService from .auth import get_current_user, require_operator from ..models.user import User router = APIRouter(prefix="/alerts", tags=["告警管理"]) # ============= Schemas ============= class AlertRuleCreate(BaseModel): name: str description: Optional[str] = None rule_type: str scope_type: str = "global" scope_value: Optional[str] = None condition: dict notification_channels: Optional[List[dict]] = None cooldown_minutes: int = 30 max_alerts_per_day: int = 10 priority: str = "medium" class AlertRuleUpdate(BaseModel): name: Optional[str] = None description: Optional[str] = None condition: Optional[dict] = None notification_channels: Optional[List[dict]] = None cooldown_minutes: Optional[int] = None max_alerts_per_day: Optional[int] = None priority: Optional[str] = None status: Optional[int] = None class NotificationChannelCreate(BaseModel): name: str channel_type: str config: dict class NotificationChannelUpdate(BaseModel): name: Optional[str] = None config: Optional[dict] = None status: Optional[int] = None # ============= Alert Rules API ============= @router.get("/rules") async def list_alert_rules( page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100), rule_type: Optional[str] = None, status: Optional[int] = None, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取告警规则列表""" query = db.query(AlertRule) if rule_type: query = query.filter(AlertRule.rule_type == rule_type) if status is not None: query = query.filter(AlertRule.status == status) total = query.count() rules = query.order_by(desc(AlertRule.created_at)).offset((page - 1) * size).limit(size).all() return { "total": total, "page": page, "size": size, "items": [format_rule(r) for r in rules] } @router.get("/rules/{rule_id}") async def get_alert_rule( rule_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取告警规则详情""" rule = db.query(AlertRule).filter(AlertRule.id == rule_id).first() if not rule: raise HTTPException(status_code=404, detail="告警规则不存在") return format_rule(rule) @router.post("/rules") async def create_alert_rule( data: AlertRuleCreate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """创建告警规则""" rule = AlertRule( name=data.name, description=data.description, rule_type=data.rule_type, scope_type=data.scope_type, scope_value=data.scope_value, condition=data.condition, notification_channels=data.notification_channels, cooldown_minutes=data.cooldown_minutes, max_alerts_per_day=data.max_alerts_per_day, priority=data.priority, status=1 ) db.add(rule) db.commit() db.refresh(rule) return {"success": True, "id": rule.id} @router.put("/rules/{rule_id}") async def update_alert_rule( rule_id: int, data: AlertRuleUpdate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """更新告警规则""" rule = db.query(AlertRule).filter(AlertRule.id == rule_id).first() if not rule: raise HTTPException(status_code=404, detail="告警规则不存在") update_data = data.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(rule, key, value) db.commit() return {"success": True} @router.delete("/rules/{rule_id}") async def delete_alert_rule( rule_id: int, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """删除告警规则""" rule = db.query(AlertRule).filter(AlertRule.id == rule_id).first() if not rule: raise HTTPException(status_code=404, detail="告警规则不存在") db.delete(rule) db.commit() return {"success": True} # ============= Alert Records API ============= @router.get("/records") async def list_alert_records( page: int = Query(1, ge=1), size: int = Query(20, ge=1, le=100), status: Optional[str] = None, severity: Optional[str] = None, alert_type: Optional[str] = None, tenant_id: Optional[str] = None, start_date: Optional[str] = None, end_date: Optional[str] = None, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取告警记录列表""" query = db.query(AlertRecord) if status: query = query.filter(AlertRecord.status == status) if severity: query = query.filter(AlertRecord.severity == severity) if alert_type: query = query.filter(AlertRecord.alert_type == alert_type) if tenant_id: query = query.filter(AlertRecord.tenant_id == tenant_id) if start_date: query = query.filter(AlertRecord.created_at >= start_date) if end_date: query = query.filter(AlertRecord.created_at <= end_date + " 23:59:59") total = query.count() records = query.order_by(desc(AlertRecord.created_at)).offset((page - 1) * size).limit(size).all() return { "total": total, "page": page, "size": size, "items": [format_record(r) for r in records] } @router.get("/records/summary") async def get_alert_summary( user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取告警摘要统计""" today = datetime.now().date() week_start = today - timedelta(days=7) # 今日告警数 today_count = db.query(func.count(AlertRecord.id)).filter( func.date(AlertRecord.created_at) == today ).scalar() # 本周告警数 week_count = db.query(func.count(AlertRecord.id)).filter( func.date(AlertRecord.created_at) >= week_start ).scalar() # 活跃告警数 active_count = db.query(func.count(AlertRecord.id)).filter( AlertRecord.status == 'active' ).scalar() # 按严重程度统计 severity_stats = db.query( AlertRecord.severity, func.count(AlertRecord.id) ).filter( func.date(AlertRecord.created_at) >= week_start ).group_by(AlertRecord.severity).all() return { "today_count": today_count, "week_count": week_count, "active_count": active_count, "by_severity": {s: c for s, c in severity_stats} } @router.get("/records/{record_id}") async def get_alert_record( record_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取告警记录详情""" record = db.query(AlertRecord).filter(AlertRecord.id == record_id).first() if not record: raise HTTPException(status_code=404, detail="告警记录不存在") return format_record(record) @router.post("/records/{record_id}/acknowledge") async def acknowledge_alert( record_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """确认告警""" service = AlertService(db) record = service.acknowledge_alert(record_id, user.username) if not record: raise HTTPException(status_code=404, detail="告警记录不存在") return {"success": True} @router.post("/records/{record_id}/resolve") async def resolve_alert( record_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """解决告警""" service = AlertService(db) record = service.resolve_alert(record_id) if not record: raise HTTPException(status_code=404, detail="告警记录不存在") return {"success": True} # ============= Check Alerts API ============= @router.post("/check") async def trigger_alert_check( background_tasks: BackgroundTasks, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """手动触发告警检查""" service = AlertService(db) alerts = await service.check_all_rules() # 异步发送通知 for alert in alerts: rule = db.query(AlertRule).filter(AlertRule.id == alert.rule_id).first() if rule: background_tasks.add_task(service.send_notification, alert, rule) return { "success": True, "triggered_count": len(alerts), "alerts": [format_record(a) for a in alerts] } # ============= Notification Channels API ============= @router.get("/channels") async def list_notification_channels( user: User = Depends(get_current_user), db: Session = Depends(get_db) ): """获取通知渠道列表""" channels = db.query(NotificationChannel).order_by(desc(NotificationChannel.created_at)).all() return [format_channel(c) for c in channels] @router.post("/channels") async def create_notification_channel( data: NotificationChannelCreate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """创建通知渠道""" channel = NotificationChannel( name=data.name, channel_type=data.channel_type, config=data.config, status=1 ) db.add(channel) db.commit() db.refresh(channel) return {"success": True, "id": channel.id} @router.put("/channels/{channel_id}") async def update_notification_channel( channel_id: int, data: NotificationChannelUpdate, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """更新通知渠道""" channel = db.query(NotificationChannel).filter(NotificationChannel.id == channel_id).first() if not channel: raise HTTPException(status_code=404, detail="通知渠道不存在") update_data = data.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(channel, key, value) db.commit() return {"success": True} @router.delete("/channels/{channel_id}") async def delete_notification_channel( channel_id: int, user: User = Depends(require_operator), db: Session = Depends(get_db) ): """删除通知渠道""" channel = db.query(NotificationChannel).filter(NotificationChannel.id == channel_id).first() if not channel: raise HTTPException(status_code=404, detail="通知渠道不存在") db.delete(channel) db.commit() return {"success": True} # ============= Helper Functions ============= def format_rule(rule: AlertRule) -> dict: return { "id": rule.id, "name": rule.name, "description": rule.description, "rule_type": rule.rule_type, "scope_type": rule.scope_type, "scope_value": rule.scope_value, "condition": rule.condition, "notification_channels": rule.notification_channels, "cooldown_minutes": rule.cooldown_minutes, "max_alerts_per_day": rule.max_alerts_per_day, "priority": rule.priority, "status": rule.status, "created_at": rule.created_at, "updated_at": rule.updated_at } def format_record(record: AlertRecord) -> dict: return { "id": record.id, "rule_id": record.rule_id, "rule_name": record.rule_name, "alert_type": record.alert_type, "severity": record.severity, "title": record.title, "message": record.message, "tenant_id": record.tenant_id, "app_code": record.app_code, "metric_value": record.metric_value, "threshold_value": record.threshold_value, "notification_status": record.notification_status, "status": record.status, "acknowledged_by": record.acknowledged_by, "acknowledged_at": record.acknowledged_at, "resolved_at": record.resolved_at, "created_at": record.created_at } def format_channel(channel: NotificationChannel) -> dict: return { "id": channel.id, "name": channel.name, "channel_type": channel.channel_type, "config": channel.config, "status": channel.status, "created_at": channel.created_at, "updated_at": channel.updated_at }