1
0
forked from erp-dev/erp
Files
erpnew/notifier/services.py

161 lines
5.7 KiB
Python

import logging
from django.conf import settings
from django.db.models import Case, IntegerField, Q, Value, When
from django.template.loader import render_to_string
from notifier.models import Notifier, NotifierRoute
from notifier.registry import get_notifier_backend
logger = logging.getLogger(__name__)
def render_notification_content(*, notifier: Notifier, payload: dict) -> str:
context = {
**(payload or {}),
"notifier_config": notifier.config or {},
"notifier_config_url": notifier.get_config_value("url", "") or "",
"notifier_config_image_url": notifier.get_config_value("image_url", "") or "",
"default_news_image_url": getattr(settings, "MESSAGE_API_DEFAULT_NEWS_IMAGE_URL", "") or "",
"notifier": notifier,
}
content = render_to_string(notifier.get_template_name(), context).strip()
if not content:
raise ValueError("通知模板渲染结果不能为空")
return content
def send_notification_with_notifier(*, notifier: Notifier, payload: dict) -> dict:
content = render_notification_content(notifier=notifier, payload=payload)
backend = get_notifier_backend(notifier.channel)
backend_result = backend.notify(notifier=notifier, content=content, context=payload)
result = {
"notifier_id": notifier.id,
"notifier_name": notifier.name,
"channel": notifier.channel,
"template_key": notifier.template_key,
"status": "sent",
**backend_result,
}
logger.info("[notifier.services] notification sent: %s", result)
return result
def _match_notifier_routes(*, event_key: str, merchant_id: int, payload: dict | None = None) -> list[NotifierRoute]:
payload = payload or {}
category_id = payload.get("category_id")
queryset = NotifierRoute.objects.filter(
merchant_id=merchant_id,
event_key=event_key,
is_enabled=True,
notifier__is_enabled=True,
).select_related("notifier", "mission_category")
if category_id is not None:
queryset = queryset.filter(Q(mission_category_id=category_id) | Q(mission_category__isnull=True)).annotate(
route_priority=Case(
When(mission_category_id=category_id, then=Value(0)),
default=Value(1),
output_field=IntegerField(),
)
)
else:
queryset = queryset.filter(mission_category__isnull=True).annotate(
route_priority=Value(0, output_field=IntegerField())
)
queryset = queryset.order_by("notifier_id", "route_priority", "id")
matched_by_notifier_id = {}
for route in queryset:
matched_by_notifier_id.setdefault(route.notifier_id, route)
return list(matched_by_notifier_id.values())
def dispatch_notification_event(*, event_key: str, merchant_id: int, payload: dict | None = None) -> list[dict]:
payload = payload or {}
routes = _match_notifier_routes(
event_key=event_key,
merchant_id=merchant_id,
payload=payload,
)
if not routes:
logger.info(
"[notifier.services] no enabled notifier route matched: event_key=%s merchant_id=%s category_id=%s",
event_key,
merchant_id,
payload.get("category_id"),
)
return []
results = []
for route in routes:
notifier = route.notifier
try:
item = send_notification_with_notifier(notifier=notifier, payload=payload)
item.update(
{
"event_key": event_key,
"route_id": route.id,
"route_event_key": route.event_key,
"route_mission_category_id": route.mission_category_id,
}
)
results.append(item)
logger.info(
"[notifier.services] notification routed: route_id=%s notifier_id=%s event_key=%s merchant_id=%s route_category_id=%s",
route.id,
notifier.id,
event_key,
merchant_id,
route.mission_category_id,
)
except Exception as exc:
logger.exception(
"[notifier.services] notification failed: route_id=%s notifier_id=%s event_key=%s merchant_id=%s",
route.id,
notifier.id,
event_key,
merchant_id,
)
results.append(
{
"notifier_id": notifier.id,
"notifier_name": notifier.name,
"event_key": event_key,
"channel": notifier.channel,
"template_key": notifier.template_key,
"route_id": route.id,
"route_event_key": route.event_key,
"route_mission_category_id": route.mission_category_id,
"status": "failed",
"error": str(exc),
}
)
return results
def enqueue_notification_event(*, event_key: str, merchant_id: int, payload: dict | None = None) -> str | None:
from notifier.tasks import dispatch_notification_event_task
try:
async_result = dispatch_notification_event_task.delay(
event_key=event_key,
merchant_id=merchant_id,
payload=payload or {},
)
except Exception:
logger.exception(
"[notifier.services] queue notification task failed: event_key=%s merchant_id=%s",
event_key,
merchant_id,
)
return None
logger.info(
"[notifier.services] queued notification task: event_key=%s merchant_id=%s task_id=%s",
event_key,
merchant_id,
async_result.id,
)
return async_result.id