updates
This commit is contained in:
parent
052c72eb5a
commit
43561548fd
|
|
@ -29,14 +29,20 @@ async def verify_callback(
|
||||||
|
|
||||||
|
|
||||||
@router.post("/webhook")
|
@router.post("/webhook")
|
||||||
async def receive_callback(request: Request, db: AsyncSession = Depends(get_db)):
|
async def receive_callback(
|
||||||
"""接收消息回调:解密 XML → 拉取消息 → 入库"""
|
request: Request,
|
||||||
|
msg_signature: str = Query("", alias="msg_signature"),
|
||||||
|
timestamp: str = Query(""),
|
||||||
|
nonce: str = Query(""),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""接收消息回调:验证签名 → 解密 XML → 拉取消息 → 入库"""
|
||||||
try:
|
try:
|
||||||
xml_body = await request.body()
|
xml_body = await request.body()
|
||||||
xml_str = xml_body.decode("utf-8")
|
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:
|
if not token or not open_kfid:
|
||||||
logger.warning("解密后 Token 或 OpenKfId 为空")
|
logger.warning("解密后 Token 或 OpenKfId 为空")
|
||||||
return PlainTextResponse("fail")
|
return PlainTextResponse("fail")
|
||||||
|
|
|
||||||
|
|
@ -66,22 +66,33 @@ def verify_url(msg_signature: str, timestamp: str, nonce: str, echostr: str) ->
|
||||||
return decrypt(echostr)
|
return decrypt(echostr)
|
||||||
|
|
||||||
|
|
||||||
def decrypt_message(xml_body: str) -> tuple[str, str]:
|
def decrypt_message(xml_body: str, msg_signature: str = "",
|
||||||
"""POST 请求:解密 XML 消息,返回 (token, open_kfid)
|
timestamp: str = "", nonce: str = "") -> tuple[str, str]:
|
||||||
|
"""POST 请求:验证签名 + 解密 XML 消息,返回 (token, open_kfid)
|
||||||
|
|
||||||
注意:解密后的 XML 包含 <Token> 和 <OpenKfId> 等字段
|
解密后的 XML 包含 <Token> 和 <OpenKfId> 等字段
|
||||||
"""
|
"""
|
||||||
root = ET.fromstring(xml_body)
|
root = ET.fromstring(xml_body)
|
||||||
encrypt_elem = root.find("Encrypt")
|
encrypt_elem = root.find("Encrypt")
|
||||||
if encrypt_elem is None or encrypt_elem.text is None:
|
if encrypt_elem is None or encrypt_elem.text is None:
|
||||||
raise ValueError("XML 中缺少 Encrypt 字段")
|
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)
|
plain_text = decrypt(encrypt_str)
|
||||||
# 解析解密后的 XML
|
|
||||||
plain_root = ET.fromstring(plain_text)
|
|
||||||
|
|
||||||
|
# 解密后的内容可能是 XML 或 JSON(取决于微信客服的配置)
|
||||||
|
# 先尝试 XML 解析
|
||||||
|
try:
|
||||||
|
plain_root = ET.fromstring(plain_text)
|
||||||
token = ""
|
token = ""
|
||||||
open_kfid = ""
|
open_kfid = ""
|
||||||
token_elem = plain_root.find("Token")
|
token_elem = plain_root.find("Token")
|
||||||
|
|
@ -90,5 +101,11 @@ def decrypt_message(xml_body: str) -> tuple[str, str]:
|
||||||
token = token_elem.text or ""
|
token = token_elem.text or ""
|
||||||
if kfid_elem is not None:
|
if kfid_elem is not None:
|
||||||
open_kfid = kfid_elem.text or ""
|
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
|
return token, open_kfid
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue