1
0
forked from erp-dev/erp

fix: appversions

This commit is contained in:
2026-07-11 00:05:05 +08:00
parent 48e4782e1e
commit e91d06e4b6
26 changed files with 2582 additions and 19 deletions

View File

@@ -2,6 +2,7 @@ import logging
import json
from api_v1.utils.wecom_webhook import send_wecom_webhook_message
from notifier.jpush import send_jpush_payload
from notifier.models import NotifierChannelEnum
from notifier.message_api import send_message_api_news_to_agents, send_message_api_text_message
from notifier.serializers import (
@@ -132,3 +133,144 @@ class MessageAPINotifierBackend(BaseNotifierBackend):
}
logger.info("[notifier.backends] message api news sent: notifier_id=%s result=%s", notifier.id, result)
return result
class JPushNotifierBackend(BaseNotifierBackend):
channel = NotifierChannelEnum.JPUSH
def _load_rendered_payload(self, *, content: str) -> dict:
content = (content or "").strip()
if not content:
raise ValueError("极光推送模板渲染结果不能为空")
try:
payload = json.loads(content)
except json.JSONDecodeError as exc:
raise ValueError(f"极光推送模板渲染结果不是合法 JSON: {exc}") from exc
if not isinstance(payload, dict):
raise ValueError("极光推送模板渲染结果必须是 JSON 对象")
return payload
def _resolve_aliases(self, *, notifier, rendered_payload: dict, context: dict) -> list[str]:
aliases = rendered_payload.get("aliases") or notifier.get_config_value("aliases")
if aliases:
return self._normalize_aliases(aliases)
recipient_source = str(
rendered_payload.get("recipient_source")
or notifier.get_config_value("recipient_source", "participants")
or "participants"
)
employee_ids = []
if recipient_source == "participants":
employee_ids = context.get("participant_ids") or []
elif recipient_source == "creator":
employee_ids = [context.get("creator_id")]
elif recipient_source == "participants_and_creator":
employee_ids = [*(context.get("participant_ids") or []), context.get("creator_id")]
else:
raise ValueError(f"不支持的极光接收人来源: {recipient_source}")
alias_prefix = str(notifier.get_config_value("alias_prefix", "emp_") or "emp_")
resolved = []
for employee_id in employee_ids:
if employee_id is None or employee_id == "":
continue
resolved.append(f"{alias_prefix}{int(employee_id)}")
return self._normalize_aliases(resolved)
def _normalize_aliases(self, values) -> list[str]:
aliases = []
for value in values or []:
alias = str(value or "").strip()
if not alias:
raise ValueError("极光 aliases 中不能包含空值")
if len(alias.encode("utf-8")) > 40:
raise ValueError(f"极光 alias 超过 40 字节限制: {alias}")
aliases.append(alias)
aliases = list(dict.fromkeys(aliases))
if not aliases:
raise ValueError("极光推送接收 alias 不能为空")
if len(aliases) > 1000:
raise ValueError("极光单次推送 alias 不能超过 1000 个")
return aliases
def _build_payload(self, *, notifier, rendered_payload: dict, context: dict) -> dict:
aliases = self._resolve_aliases(
notifier=notifier,
rendered_payload=rendered_payload,
context=context,
)
platform = rendered_payload.get("platform") or notifier.get_config_value("platform", "all")
title = str(rendered_payload.get("title") or notifier.get_config_value("title", "任务提醒")).strip()
alert = str(rendered_payload.get("alert") or rendered_payload.get("content") or "").strip()
if not alert:
raise ValueError("极光推送 alert 不能为空")
extras = rendered_payload.get("extras") or {}
if not isinstance(extras, dict):
raise ValueError("极光推送 extras 必须是 JSON 对象")
notification = {
"alert": alert,
"android": {
"alert": alert,
"title": title,
"extras": extras,
},
"ios": {
"alert": alert,
"sound": rendered_payload.get("ios_sound") or notifier.get_config_value("ios_sound", "default"),
"extras": extras,
},
}
android_channel_id = rendered_payload.get("android_channel_id") or notifier.get_config_value("android_channel_id")
if android_channel_id:
notification["android"]["channel_id"] = str(android_channel_id)
android_intent = rendered_payload.get("android_intent") or notifier.get_config_value("android_intent")
if android_intent:
notification["android"]["intent"] = {"url": str(android_intent)}
android_category = rendered_payload.get("android_category") or notifier.get_config_value("android_category")
if android_category:
notification["android"]["category"] = str(android_category)
payload = {
"platform": platform,
"audience": {"alias": aliases},
"notification": notification,
"options": {
"apns_production": bool(notifier.get_config_value("apns_production", False)),
},
}
time_to_live = rendered_payload.get("time_to_live", notifier.get_config_value("time_to_live"))
if time_to_live is not None:
payload["options"]["time_to_live"] = int(time_to_live)
message = rendered_payload.get("message")
if message:
if not isinstance(message, dict):
raise ValueError("极光推送 message 必须是 JSON 对象")
payload["message"] = message
return payload
def notify(self, *, notifier, content: str, context: dict) -> dict:
rendered_payload = self._load_rendered_payload(content=content)
payload = self._build_payload(
notifier=notifier,
rendered_payload=rendered_payload,
context=context,
)
response = send_jpush_payload(
app_key=notifier.get_config_value("app_key"),
master_secret=notifier.get_config_value("master_secret"),
payload=payload,
timeout_seconds=float(notifier.get_config_value("timeout_seconds", 10.0) or 10.0),
)
result = {
"channel": self.channel,
"aliases": payload["audience"]["alias"],
"msg_id": response.raw.get("msg_id"),
"sendno": response.raw.get("sendno"),
"raw": response.raw,
}
logger.info("[notifier.backends] jpush sent: notifier_id=%s result=%s", notifier.id, result)
return result