"""微信服务号模板消息:OA 审批/抄送通知。""" from __future__ import annotations import logging from typing import Any, Dict import httpx from app.core.config import settings from app.services.wechat_mp_auth import WechatMpAuthError, get_mp_access_token logger = logging.getLogger(__name__) _WX_TEMPLATE_SEND = "https://api.weixin.qq.com/cgi-bin/message/template/send" # 微信 thing 类字段建议约 20 汉字;按字符保守截断 _THING_MAX_CHARS = 20 def _truncate_thing(value: str, max_chars: int = _THING_MAX_CHARS) -> str: s = (value or "").strip() if len(s) <= max_chars: return s return s[:max_chars] def _build_oa_data( *, title: str, applicant: str, node_name: str, time_text: str, ) -> Dict[str, Dict[str, str]]: return { settings.WX_MP_TPL_FIELD_TITLE: {"value": _truncate_thing(title)}, settings.WX_MP_TPL_FIELD_APPLICANT: {"value": _truncate_thing(applicant)}, settings.WX_MP_TPL_FIELD_NODE: {"value": _truncate_thing(node_name)}, settings.WX_MP_TPL_FIELD_TIME: {"value": (time_text or "").strip()}, } def _send_template( *, mp_openid: str, template_id: str, data: Dict[str, Any], pagepath: str, ) -> Dict[str, Any]: oid = (mp_openid or "").strip() if not oid: return {"success": False, "error": "missing_mp_openid"} tid = (template_id or "").strip() if not tid: return {"success": False, "error": "missing_template_id"} mini_appid = (settings.WXPAY_MINI_APPID or "").strip() if not mini_appid: return {"success": False, "error": "missing_mini_appid"} try: access_token = get_mp_access_token() except WechatMpAuthError as exc: logger.warning("mp template send aborted: %s", exc) return {"success": False, "error": str(exc)} payload = { "touser": oid, "template_id": tid, "data": data, "miniprogram": { "appid": mini_appid, "pagepath": (pagepath or "").lstrip("/"), }, } try: with httpx.Client(timeout=15.0) as client: r = client.post( f"{_WX_TEMPLATE_SEND}?access_token={access_token}", json=payload, ) r.raise_for_status() result = r.json() except Exception as exc: logger.exception("mp template send request failed") return {"success": False, "error": str(exc)} if result.get("errcode") in (0, None): logger.info( "mp template send ok: openid=%s msgid=%s", oid, result.get("msgid"), ) return {"success": True, "msgid": result.get("msgid")} logger.error("mp template send failed: %s", result) return { "success": False, "error": result.get("errmsg") or "send_failed", "errcode": result.get("errcode"), } def send_oa_approval_notify( *, mp_openid: str, pagepath: str, title: str, applicant: str, node_name: str, time_text: str, ) -> dict: return _send_template( mp_openid=mp_openid, template_id=settings.WX_MP_TEMPLATE_APPROVAL, data=_build_oa_data( title=title, applicant=applicant, node_name=node_name, time_text=time_text, ), pagepath=pagepath, ) def send_oa_cc_notify( *, mp_openid: str, pagepath: str, title: str, applicant: str, node_name: str, time_text: str, ) -> dict: return _send_template( mp_openid=mp_openid, template_id=settings.WX_MP_TEMPLATE_CC, data=_build_oa_data( title=title, applicant=applicant, node_name=node_name, time_text=time_text, ), pagepath=pagepath, )