1
0
forked from erp-dev/erp

feat: wecom robot markdown message support

This commit is contained in:
2026-01-23 15:47:37 +08:00
parent 7fd44c64aa
commit 88688f9481
6 changed files with 297 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
from django.core.management.base import BaseCommand, CommandError
from api_v1.utils.wecom_webhook import send_wecom_webhook_message
class Command(BaseCommand):
help = "发送企业微信机器人 webhook 文本消息(用于测试)"
def add_arguments(self, parser):
parser.add_argument("text", type=str, help="要发送的文本内容")
parser.add_argument(
"--msgtype",
type=str,
choices=["text", "markdown"],
default="text",
help="消息类型(默认 text",
)
parser.add_argument("--key", type=str, default=None, help="可选:临时覆盖 settings.WECOM_WEBHOOK_KEY")
parser.add_argument(
"--timeout",
type=float,
default=10.0,
help="HTTP 超时(秒)",
)
def handle(self, *args, **options):
text = options["text"]
msgtype = options["msgtype"]
key = options["key"]
timeout = options["timeout"]
try:
resp = send_wecom_webhook_message(content=text, msgtype=msgtype, key=key, timeout_seconds=timeout)
except Exception as exc:
raise CommandError(str(exc)) from exc
# 企业微信成功时 errcode=0
if not resp.ok:
raise CommandError(f"WeCom webhook 返回失败errcode={resp.errcode}, errmsg={resp.errmsg}, raw={resp.raw}")
self.stdout.write(self.style.SUCCESS(f"OK: {resp.raw}"))

View File

@@ -0,0 +1,116 @@
"""
WeCom (企业微信) robot webhook helpers.
Only text/markdown messages are supported for now.
API:
POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxx
Content-Type: application/json
{
"msgtype": "text",
"text": {"content": "hello world"}
}
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from typing import Any
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
from django.conf import settings
@dataclass(frozen=True)
class WeComWebhookResponse:
errcode: int
errmsg: str
raw: dict[str, Any]
@property
def ok(self) -> bool:
return int(self.errcode) == 0
def send_wecom_webhook_message(
*,
content: str,
msgtype: str = "text",
key: str | None = None,
timeout_seconds: float = 10.0,
) -> WeComWebhookResponse:
"""
Send a message to WeCom robot webhook.
Supported msgtype:
- text: {"msgtype":"text","text":{"content": "..."}}
- markdown: {"msgtype":"markdown","markdown":{"content": "..."}}
"""
t = (msgtype or "text").strip().lower()
if t not in {"text", "markdown"}:
raise ValueError("msgtype 仅支持 text 或 markdown")
content = (content or "").strip()
if not content:
raise ValueError("content 不能为空")
base_url = getattr(settings, "WECOM_WEBHOOK_BASE_URL", "").strip()
if not base_url:
base_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send"
k = (key or getattr(settings, "WECOM_WEBHOOK_KEY", "") or "").strip()
if not k:
raise ValueError("WeCom webhook key 未配置settings.WECOM_WEBHOOK_KEY")
url = f"{base_url}?{urlencode({'key': k})}"
if t == "markdown":
payload = {"msgtype": "markdown", "markdown": {"content": content}}
else:
payload = {"msgtype": "text", "text": {"content": content}}
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = Request(
url=url,
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
try:
with urlopen(req, timeout=float(timeout_seconds)) as resp:
body = resp.read().decode("utf-8", errors="replace")
except HTTPError as exc:
body = ""
try:
body = exc.read().decode("utf-8", errors="replace")
except Exception:
pass
raise RuntimeError(f"WeCom webhook HTTPError: status={exc.code}, body={body}") from exc
except URLError as exc:
raise RuntimeError(f"WeCom webhook URLError: {exc}") from exc
try:
raw = json.loads(body) if body else {}
except Exception as exc:
raise RuntimeError(f"WeCom webhook 响应不是合法 JSON: {body}") from exc
errcode = int(raw.get("errcode") or 0)
errmsg = str(raw.get("errmsg") or "")
return WeComWebhookResponse(errcode=errcode, errmsg=errmsg, raw=raw)
def send_wecom_webhook_text(
*,
content: str,
key: str | None = None,
timeout_seconds: float = 10.0,
) -> WeComWebhookResponse:
"""
Backward-compatible wrapper for sending text.
"""
return send_wecom_webhook_message(content=content, msgtype="text", key=key, timeout_seconds=timeout_seconds)