Files
ipam/backend/app/api/v1/alerts.py
T
Your Name 717fa31456 feat(alerts): 告警中心增加飞书机器人告警推送功能
1. 新增 feishu_service.py: 飞书自定义机器人 webhook 推送
   - 支持卡片消息(interactive),按告警级别着色
   - 支持签名校验(HMAC-SHA256)
   - 支持最低级别过滤 + 告警类型白名单
2. alert_service.create_alert 创建新告警时自动推飞书
   - 仅新告警推送,去重告警不刷屏
   - 推送失败不影响告警创建
3. alerts.py 新增两个接口:
   - GET /alerts/notify/feishu/status 查看配置状态
   - POST /alerts/notify/feishu/test 发送测试告警
4. config.py + .env.example 增加 FEISHU_* 配置项
   (WEBHOOK_URL / SECRET / ENABLED / MIN_SEVERITY / ALERT_TYPES)
2026-08-12 13:10:08 +08:00

226 lines
7.6 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from typing import Optional
from app.core.database import get_db
from app.core.security import get_current_user
from app.services.alert_service import AlertService
from app.schemas._tz_util import serialize_dt_fields, serialize_dt_list, to_business_iso
router = APIRouter(
prefix="/alerts",
tags=["告警管理"],
dependencies=[Depends(get_current_user)],
)
@router.get("", summary="获取告警列表")
def get_alerts(
status: Optional[str] = None,
severity: Optional[str] = None,
alert_type: Optional[str] = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
"""获取告警列表"""
from app.models.alert import Alert
query = db.query(Alert)
if status:
query = query.filter(Alert.status == status)
if severity:
query = query.filter(Alert.severity == severity)
if alert_type:
query = query.filter(Alert.alert_type == alert_type)
total = query.count()
items = query.order_by(Alert.created_at.desc()).offset(skip).limit(limit).all()
return {"total": total, "items": serialize_dt_list(items)}
@router.get("/{alert_id}", summary="获取告警详情")
def get_alert(alert_id: int, db: Session = Depends(get_db)):
from app.models.alert import Alert
alert = db.query(Alert).filter(Alert.id == alert_id).first()
if not alert:
raise HTTPException(status_code=404, detail="告警不存在")
return serialize_dt_fields(alert)
@router.post("/{alert_id}/acknowledge", summary="确认告警")
def acknowledge_alert(alert_id: int, acknowledged_by: str = "system", db: Session = Depends(get_db)):
"""确认告警"""
alert = AlertService.acknowledge_alert(db, alert_id, acknowledged_by)
if not alert:
raise HTTPException(status_code=404, detail="告警不存在")
return {"message": "告警已确认", "alert": alert}
@router.post("/{alert_id}/resolve", summary="解决告警")
def resolve_alert(alert_id: int, resolved_by: str = "system", notes: str = "", db: Session = Depends(get_db)):
"""标记告警为已解决"""
alert = AlertService.resolve_alert(db, alert_id, resolved_by, notes)
if not alert:
raise HTTPException(status_code=404, detail="告警不存在")
return {"message": "告警已解决", "alert": alert}
@router.post("/{alert_id}/ignore", summary="忽略告警")
def ignore_alert(alert_id: int, db: Session = Depends(get_db)):
"""忽略告警"""
alert = AlertService.ignore_alert(db, alert_id)
if not alert:
raise HTTPException(status_code=404, detail="告警不存在")
return {"message": "告警已忽略", "alert": alert}
@router.post("/detect/run", summary="运行所有检测")
def run_detection(db: Session = Depends(get_db)):
"""立即运行所有告警检测"""
results = AlertService.run_all_detections(db)
return {"message": "检测完成", "results": results}
@router.get("/statistics/summary", summary="告警统计摘要")
def get_alert_statistics(db: Session = Depends(get_db)):
"""获取告警统计信息"""
stats = AlertService.get_statistics(db)
return stats
# ========== MAC 白名单管理 ==========
@router.get("/whitelist/macs", summary="获取 MAC 白名单")
def get_mac_whitelist(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
"""获取 MAC 地址白名单"""
total, items = AlertService.get_mac_whitelist(db, skip=skip, limit=limit)
return {"total": total, "items": items}
@router.post("/whitelist/macs", summary="添加 MAC 到白名单")
def add_mac_to_whitelist(
mac_address: str,
description: Optional[str] = None,
owner: Optional[str] = None,
db: Session = Depends(get_db)
):
"""添加 MAC 地址到白名单"""
whitelist_mac = AlertService.add_mac_to_whitelist(
db,
mac_address=mac_address,
description=description,
owner=owner
)
return {"message": "MAC 已添加到白名单", "item": whitelist_mac}
@router.delete("/whitelist/macs/{mac_id}", summary="从白名单移除 MAC")
def remove_mac_from_whitelist(mac_id: int, db: Session = Depends(get_db)):
"""从白名单移除 MAC 地址"""
success = AlertService.remove_mac_from_whitelist(db, mac_id)
if not success:
raise HTTPException(status_code=404, detail="MAC 不存在")
return {"message": "MAC 已从白名单移除"}
# ========== 单独检测接口 ==========
@router.post("/detect/ip-conflicts", summary="检测 IP 冲突")
def detect_ip_conflicts(db: Session = Depends(get_db)):
"""仅检测 IP 冲突"""
conflicts = AlertService.detect_ip_conflicts(db)
return {
"count": len(conflicts),
"conflicts": conflicts
}
@router.post("/detect/unauthorized", summary="检测未授权接入")
def detect_unauthorized_access(db: Session = Depends(get_db)):
"""仅检测未授权接入"""
unauthorized = AlertService.detect_unauthorized_access(db)
return {
"count": len(unauthorized),
"devices": unauthorized
}
@router.post("/detect/subnet-exhaustion", summary="检测网段耗尽")
def detect_subnet_exhaustion(db: Session = Depends(get_db)):
"""仅检测网段耗尽"""
exhaustion = AlertService.detect_subnet_exhaustion(db)
return {
"count": len(exhaustion),
"subnets": exhaustion
}
@router.post("/detect/new-devices", summary="检测新设备")
def detect_new_devices(db: Session = Depends(get_db)):
"""仅检测新发现的设备"""
new_devices = AlertService.detect_new_devices(db)
return {
"count": len(new_devices),
"devices": new_devices
}
@router.post("/detect/offline-devices", summary="检测离线设备")
def detect_offline_devices(db: Session = Depends(get_db)):
"""仅检测离线设备"""
offline_devices = AlertService.detect_device_offline(db)
return {
"count": len(offline_devices),
"devices": offline_devices
}
# ========== 飞书告警通知 ==========
@router.get("/notify/feishu/status", summary="获取飞书通知配置状态")
def get_feishu_status():
"""查看飞书通知是否已配置(不返回密钥等敏感信息)"""
from app.services.feishu_service import get_feishu_service
svc = get_feishu_service()
return {
"enabled": svc.enabled,
"configured": bool(svc.webhook_url),
"webhook_set": bool(svc.webhook_url),
"secret_set": bool(svc.secret),
"min_severity": svc.min_severity,
"alert_types": svc.alert_types,
"cani_push": svc.is_configured(),
}
@router.post("/notify/feishu/test", summary="发送飞书测试告警")
def test_feishu_notification():
"""发送一条测试告警到飞书,验证 webhook 配置是否正确"""
from app.services.feishu_service import get_feishu_service
svc = get_feishu_service()
if not svc.is_configured():
raise HTTPException(
status_code=400,
detail="飞书通知未启用或未配置 webhook。请在 backend/.env 设置 FEISHU_ENABLED=true 和 FEISHU_WEBHOOK_URL"
)
ok = svc.send_alert({
"alert_type": "new_device_detected",
"severity": "warning",
"title": "飞书通知测试",
"message": "这是一条测试告警,用于验证飞书机器人 webhook 配置是否正常。",
"ip_address_str": "172.16.0.1",
"mac_address": "00:11:22:33:44:55",
})
if ok:
return {"message": "飞书测试通知发送成功", "success": True}
raise HTTPException(status_code=500, detail="飞书测试通知发送失败,请检查 webhook 地址和日志")