87 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""通知渠道封装。
对 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):
"""通用 WebhookPOST 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})