"""极光推送 JPush(无障碍保护掉线告警)。 调极光 Push REST v3 /v3/push 给指定 registration_id 推一条通知。鉴权复用极光 Basic Auth (appKey:masterSecret),与一键登录/短信同模式(见 integrations/jiguang.py、sms.py)。 凭证:settings.jpush_app_key / jpush_master_secret(JPUSH_* 留空时自动回退到 JG_*, 若推送与一键登录是同一极光应用)。客户端 push appkey 已知 = 966b451a8d9cfe12d173ea9d。 厂商通道(App 被杀也能到达)由极光后台 + 客户端插件负责,本服务只管调 push 接口。 """ from __future__ import annotations import base64 import logging import httpx from app.core.config import settings logger = logging.getLogger("shagua.jpush") class JPushError(Exception): """极光推送调用失败。""" class JPushNotConfiguredError(JPushError): """缺 appKey / masterSecret。""" def push_to_registration_ids( registration_ids: list[str], *, title: str, alert: str, extras: dict | None = None, ) -> dict: """给一批 registration_id 推送通知 + 透传消息。失败抛 JPushError。 Returns: 极光响应 JSON(含 sendno / msg_id)。 """ if not settings.jpush_configured: raise JPushNotConfiguredError( "JPush 未配置(缺 JPUSH_APP_KEY/JPUSH_MASTER_SECRET,且 JG_* 也为空)" ) reg_ids = [r for r in registration_ids if r] if not reg_ids: raise JPushError("registration_ids 为空") auth_b64 = base64.b64encode( f"{settings.jpush_app_key}:{settings.jpush_master_secret}".encode() ).decode() payload = { "platform": ["android"], "audience": {"registration_id": reg_ids}, "notification": { "android": { "alert": alert, "title": title, "priority": 1, "extras": extras or {}, }, }, "message": { "msg_content": alert, "title": title, "content_type": "text", "extras": extras or {}, }, "options": {"time_to_live": 86400, "apns_production": True}, } try: resp = httpx.post( settings.JPUSH_PUSH_ENDPOINT, json=payload, headers={ "Content-Type": "application/json", "Authorization": f"Basic {auth_b64}", }, timeout=settings.JG_REQUEST_TIMEOUT_SEC, ) except httpx.HTTPError as e: raise JPushError(f"jpush 网络错误: {e}") from e if resp.status_code != 200: body = resp.text[:300] logger.error("[JPush] http=%s body=%s", resp.status_code, body) raise JPushError(f"jpush http {resp.status_code}") data = resp.json() if not data.get("sendno") and not data.get("msg_id"): logger.error("[JPush] unexpected response: %s", data) raise JPushError(f"jpush 响应异常: {data}") return data