This commit is contained in:
zwt13703 2026-04-27 17:25:11 +08:00
parent 052c72eb5a
commit 43561548fd
2 changed files with 43 additions and 20 deletions

View File

@ -29,14 +29,20 @@ async def verify_callback(
@router.post("/webhook")
async def receive_callback(request: Request, db: AsyncSession = Depends(get_db)):
"""接收消息回调:解密 XML → 拉取消息 → 入库"""
async def receive_callback(
request: Request,
msg_signature: str = Query("", alias="msg_signature"),
timestamp: str = Query(""),
nonce: str = Query(""),
db: AsyncSession = Depends(get_db),
):
"""接收消息回调:验证签名 → 解密 XML → 拉取消息 → 入库"""
try:
xml_body = await request.body()
xml_str = xml_body.decode("utf-8")
logger.info(f"收到回调: {xml_str[:200]}")
logger.info(f"收到回调, encrypt 前100字符: {xml_str[:100]}")
token, open_kfid = decrypt_message(xml_str)
token, open_kfid = decrypt_message(xml_str, msg_signature, timestamp, nonce)
if not token or not open_kfid:
logger.warning("解密后 Token 或 OpenKfId 为空")
return PlainTextResponse("fail")

View File

@ -66,22 +66,33 @@ def verify_url(msg_signature: str, timestamp: str, nonce: str, echostr: str) ->
return decrypt(echostr)
def decrypt_message(xml_body: str) -> tuple[str, str]:
"""POST 请求:解密 XML 消息,返回 (token, open_kfid)
def decrypt_message(xml_body: str, msg_signature: str = "",
timestamp: str = "", nonce: str = "") -> tuple[str, str]:
"""POST 请求:验证签名 + 解密 XML 消息,返回 (token, open_kfid)
注意解密后的 XML 包含 <Token> <OpenKfId> 等字段
解密后的 XML 包含 <Token> <OpenKfId> 等字段
"""
root = ET.fromstring(xml_body)
encrypt_elem = root.find("Encrypt")
if encrypt_elem is None or encrypt_elem.text is None:
raise ValueError("XML 中缺少 Encrypt 字段")
encrypt_str = encrypt_elem.text
# 去除可能的空白/换行,确保 Base64 解码正确
encrypt_str = encrypt_elem.text.strip()
# 验证签名(如果提供了签名参数)
if msg_signature and timestamp and nonce:
if not verify_signature(settings.callback_token, timestamp, nonce,
encrypt_str, msg_signature):
raise ValueError("消息签名验证失败")
# 解密
plain_text = decrypt(encrypt_str)
# 解析解密后的 XML
plain_root = ET.fromstring(plain_text)
# 解密后的内容可能是 XML 或 JSON取决于微信客服的配置
# 先尝试 XML 解析
try:
plain_root = ET.fromstring(plain_text)
token = ""
open_kfid = ""
token_elem = plain_root.find("Token")
@ -90,5 +101,11 @@ def decrypt_message(xml_body: str) -> tuple[str, str]:
token = token_elem.text or ""
if kfid_elem is not None:
open_kfid = kfid_elem.text or ""
return token, open_kfid
except ET.ParseError:
# 可能是 JSON 格式
import json
data = json.loads(plain_text)
token = data.get("Token", "")
open_kfid = data.get("OpenKfId", "")
return token, open_kfid