feat(alerts): 飞书通知支持 Web 界面配置(数据库存储+热更新)
1. 新增 notification_config 模型,飞书配置存数据库(单行id=1) 2. feishu_service 改为从数据库读配置,支持热更新,Web保存即生效 3. alerts.py 新增接口: - GET /alerts/notify/feishu/config 读配置(secret只返回是否已设) - PUT /alerts/notify/feishu/config 保存配置+热更新 (status/test 同步改为走数据库配置) 4. 前端告警中心增加「飞书通知设置」弹窗: - 开关/Webhook/签名密钥/最低级别/类型白名单 - 保存配置、发送测试按钮 5. alert_service.create_alert 推送时按数据库配置判断 配置无需改文件/重启,前端界面直接保存即生效。
This commit is contained in:
@@ -13,15 +13,9 @@ from app.models.network import Network, IPAddress
|
||||
from app.models.snmp import NetworkDevice, ARPEntry
|
||||
|
||||
# 飞书告警通知(延迟导入避免循环依赖)
|
||||
_feishu_service = None
|
||||
|
||||
|
||||
def _get_feishu_service():
|
||||
global _feishu_service
|
||||
if _feishu_service is None:
|
||||
from app.services.feishu_service import get_feishu_service
|
||||
_feishu_service = get_feishu_service()
|
||||
return _feishu_service
|
||||
def _get_feishu_service(db=None):
|
||||
from app.services.feishu_service import get_feishu_service
|
||||
return get_feishu_service(db)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -85,7 +79,7 @@ class AlertService:
|
||||
|
||||
# 飞书告警通知(仅新创建的告警推送;existing 去重返回的不会走到这里,避免刷屏)
|
||||
try:
|
||||
_get_feishu_service().send_alert({
|
||||
_get_feishu_service(db).send_alert({
|
||||
"alert_type": alert_type.value if hasattr(alert_type, "value") else str(alert_type),
|
||||
"severity": severity.value if hasattr(severity, "value") else str(severity),
|
||||
"title": title,
|
||||
|
||||
@@ -200,19 +200,59 @@ class FeishuService:
|
||||
_service: Optional[FeishuService] = None
|
||||
|
||||
|
||||
def get_feishu_service() -> FeishuService:
|
||||
"""获取(缓存的)飞书服务实例"""
|
||||
def _build_from_values(webhook_url="", secret="", enabled=False,
|
||||
min_severity="warning", alert_types_str="") -> FeishuService:
|
||||
"""用一组值构造 FeishuService(api 层用)"""
|
||||
if _service is not None:
|
||||
# 热更新:复用同一实例,更新内部配置
|
||||
_service.webhook_url = webhook_url
|
||||
_service.secret = secret
|
||||
_service.enabled = bool(enabled)
|
||||
_service.min_severity = min_severity or "warning"
|
||||
_service.alert_types = [t.strip() for t in (alert_types_str or "").split(",") if t.strip()]
|
||||
return _service
|
||||
return FeishuService(
|
||||
webhook_url=webhook_url,
|
||||
secret=secret,
|
||||
enabled=bool(enabled),
|
||||
min_severity=min_severity or "warning",
|
||||
alert_types=[t.strip() for t in (alert_types_str or "").split(",") if t.strip()],
|
||||
)
|
||||
|
||||
|
||||
def get_feishu_service(db=None) -> FeishuService:
|
||||
"""获取(缓存的)飞书服务实例。
|
||||
优先从数据库 notification_config 读取配置(Web 界面可热更新);
|
||||
若无数据库配置则回退到 .env / settings 默认值。
|
||||
"""
|
||||
global _service
|
||||
|
||||
# 优先从数据库加载(Web 配置优先)
|
||||
try:
|
||||
if db is not None:
|
||||
from app.models.notification import NotificationConfig
|
||||
row = db.query(NotificationConfig).filter(NotificationConfig.id == 1).first()
|
||||
if row is not None:
|
||||
return _build_from_values(
|
||||
webhook_url=row.feishu_webhook_url or "",
|
||||
secret=row.feishu_secret or "",
|
||||
enabled=row.feishu_enabled or False,
|
||||
min_severity=row.feishu_min_severity or "warning",
|
||||
alert_types_str=row.feishu_alert_types or "",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"从数据库读取飞书配置失败,回退 .env: {e}")
|
||||
|
||||
# 回退到 settings(.env)
|
||||
if _service is None:
|
||||
try:
|
||||
from app.core.config import settings
|
||||
alert_types = [t.strip() for t in (settings.FEISHU_ALERT_TYPES or "").split(",") if t.strip()]
|
||||
_service = FeishuService(
|
||||
_service = _build_from_values(
|
||||
webhook_url=settings.FEISHU_WEBHOOK_URL,
|
||||
secret=settings.FEISHU_SECRET,
|
||||
enabled=settings.FEISHU_ENABLED,
|
||||
min_severity=settings.FEISHU_MIN_SEVERITY or "warning",
|
||||
alert_types=alert_types,
|
||||
alert_types_str=settings.FEISHU_ALERT_TYPES or "",
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"初始化飞书服务失败: {e}")
|
||||
@@ -220,6 +260,25 @@ def get_feishu_service() -> FeishuService:
|
||||
return _service
|
||||
|
||||
|
||||
def send_alert_notification(alert_data: Dict[str, Any]) -> bool:
|
||||
def reload_feishu_service(db) -> FeishuService:
|
||||
"""强制从数据库重新加载飞书配置(Web 界面保存配置后调用)"""
|
||||
global _service
|
||||
from app.models.notification import NotificationConfig
|
||||
row = db.query(NotificationConfig).filter(NotificationConfig.id == 1).first()
|
||||
if row is None:
|
||||
row = NotificationConfig.get_singleton(db)
|
||||
svc = _build_from_values(
|
||||
webhook_url=row.feishu_webhook_url or "",
|
||||
secret=row.feishu_secret or "",
|
||||
enabled=row.feishu_enabled or False,
|
||||
min_severity=row.feishu_min_severity or "warning",
|
||||
alert_types_str=row.feishu_alert_types or "",
|
||||
)
|
||||
global _service
|
||||
_service = svc
|
||||
return svc
|
||||
|
||||
|
||||
def send_alert_notification(alert_data: Dict[str, Any], db=None) -> bool:
|
||||
"""便捷函数:发送一条告警通知"""
|
||||
return get_feishu_service().send_alert(alert_data)
|
||||
return get_feishu_service(db).send_alert(alert_data)
|
||||
|
||||
Reference in New Issue
Block a user