Files
zkxw/backend/app/services/wechat_mp_message.py
T

147 lines
3.8 KiB
Python

"""微信服务号模板消息: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,
)