87 lines
2.8 KiB
Python
87 lines
2.8 KiB
Python
"""通知渠道封装。
|
||
|
||
对 email / webhook / dingtalk / wechat 等渠道做统一封装;真实发送通过
|
||
urllib 完成(或按需扩展 SDK),默认 InMemorySender 记录发送历史,便于测试。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import threading
|
||
import urllib.request
|
||
from typing import Any, Dict, List, Optional
|
||
|
||
from .models import Alert
|
||
|
||
|
||
class ChannelSender:
|
||
"""通知渠道发送器接口。"""
|
||
|
||
def send(self, alert: Alert, target: Optional[str] = None) -> bool:
|
||
raise NotImplementedError
|
||
|
||
|
||
class InMemorySender(ChannelSender):
|
||
"""记录发送历史,不真正外呼。"""
|
||
|
||
def __init__(self, channel: str) -> None:
|
||
self.channel = channel
|
||
self.sent: List[Dict[str, Any]] = []
|
||
self._lock = threading.Lock()
|
||
|
||
def send(self, alert: Alert, target: Optional[str] = None) -> bool:
|
||
with self._lock:
|
||
self.sent.append({"channel": self.channel, "target": target, "alert_id": alert.alert_id})
|
||
return True
|
||
|
||
|
||
class WebhookSender(ChannelSender):
|
||
"""通用 Webhook(POST JSON)。"""
|
||
|
||
def __init__(self, url: str) -> None:
|
||
self.url = url
|
||
|
||
def send(self, alert: Alert, target: Optional[str] = None) -> bool:
|
||
payload = json.dumps(alert.to_dict()).encode("utf-8")
|
||
req = urllib.request.Request(
|
||
target or self.url,
|
||
data=payload,
|
||
headers={"Content-Type": "application/json"},
|
||
method="POST",
|
||
)
|
||
with urllib.request.urlopen(req, timeout=5) as resp: # noqa: S310
|
||
return 200 <= resp.status < 300
|
||
|
||
|
||
class Notifier:
|
||
"""通知分发器:按告警渠道列表投递。"""
|
||
|
||
def __init__(self, senders: Optional[Dict[str, ChannelSender]] = None) -> None:
|
||
self.senders = senders or {}
|
||
self.sent: List[Dict[str, Any]] = []
|
||
self._lock = threading.Lock()
|
||
|
||
def register(self, channel: str, sender: ChannelSender) -> None:
|
||
self.senders[channel] = sender
|
||
|
||
def notify(self, alert: Alert, channels: Optional[List[str]] = None) -> List[str]:
|
||
"""发送告警,返回成功渠道列表。"""
|
||
channels = channels or list(self.senders.keys())
|
||
delivered: List[str] = []
|
||
for channel in channels:
|
||
sender = self.senders.get(channel)
|
||
if sender is None:
|
||
continue
|
||
ok = sender.send(alert)
|
||
with self._lock:
|
||
self.sent.append({"channel": channel, "alert_id": alert.alert_id, "ok": ok})
|
||
if ok:
|
||
delivered.append(channel)
|
||
return delivered
|
||
|
||
@classmethod
|
||
def memory(cls, channels: Optional[List[str]] = None) -> "Notifier":
|
||
"""构造内存版 Notifier,便于本地运行/测试。"""
|
||
channels = channels or ["email", "webhook", "dingtalk", "wechat"]
|
||
return cls({c: InMemorySender(c) for c in channels})
|