第七章:企业微信消息解密与完整回调链路打通

这一章是从“能收到请求”到“能理解消息”的关键跃迁。

上一章我们已经做到:

企业微信消息可以进入 FastAPI

但目前系统仍然是:

✔ 收到了请求
❌ 看不懂内容
❌ 没有解密
❌ 没有业务分发

这一章结束后,你的系统会变成:

企业微信 → 解析 → 解密 → 识别消息 → 分发处理

7.1 企业微信加密消息的真实结构

企业微信 POST 请求的核心内容如下:

<xml>
    <ToUserName><![CDATA[wwxxxxx]]></ToUserName>
    <Encrypt><![CDATA[encrypted_message]]></Encrypt>
</xml>

同时 HTTP 参数还有:

msg_signature=xxx
timestamp=xxx
nonce=xxx

📌 本质结构

HTTP层参数(签名)
XML外壳
Encrypt(真正消息体)
AES解密
明文XML

7.2 完整处理流程设计

本章完成后的标准链路:

POST /wecom/callback
1. 解析XML
2. 提取 Encrypt
3. 校验 msg_signature
4. AES解密
5. 得到明文XML
6. 解析消息内容
7. 进入Dispatcher

7.3 更新 wecom_crypto(核心升级)

📌 crypto.py(完整实现)

from wechatpy.crypto import WeChatCrypto
import os


class WeComCrypto:
    def __init__(self):
        self.token = os.getenv("WECOM_TOKEN")
        self.aes_key = os.getenv("WECOM_AES_KEY")
        self.corp_id = os.getenv("WECOM_CORP_ID")

        self.crypto = WeChatCrypto(
            self.token,
            self.aes_key,
            self.corp_id
        )

    def verify_signature(self, msg_signature, timestamp, nonce, data):
        """
        校验签名
        """
        return self.crypto.check_signature(
            msg_signature,
            timestamp,
            nonce,
            data
        )

    def decrypt_message(self, msg_signature, timestamp, nonce, xml):
        """
        解密企业微信消息
        """
        return self.crypto.decrypt_message(
            xml,
            msg_signature,
            timestamp,
            nonce
        )

7.4 XML解析增强

📌 parser.py(升级版)

import xml.etree.ElementTree as ET


def parse_encrypt_xml(xml_str: str):
    """
    解析外层XML(带Encrypt)
    """
    root = ET.fromstring(xml_str)

    data = {}

    for child in root:
        data[child.tag] = child.text

    return data


def parse_plain_xml(xml_str: str):
    """
    解析解密后的XML
    """
    root = ET.fromstring(xml_str)

    data = {}

    for child in root:
        data[child.tag] = child.text

    return data

7.5 callback.py(完整版本)

📌 POST入口升级

from fastapi import APIRouter, Request
from app.wecom.crypto import WeComCrypto
from app.wecom.parser import parse_encrypt_xml, parse_plain_xml

router = APIRouter()
crypto = WeComCrypto()


@router.post("/callback")
async def callback(request: Request):
    """
    企业微信消息入口
    """

    # 1️⃣ 获取URL参数
    params = dict(request.query_params)

    msg_signature = params.get("msg_signature")
    timestamp = params.get("timestamp")
    nonce = params.get("nonce")

    # 2️⃣ 读取XML
    body = await request.body()
    xml_str = body.decode("utf-8")

    # 3️⃣ 解析外层XML
    outer = parse_encrypt_xml(xml_str)
    encrypt = outer.get("Encrypt")

    print("收到Encrypt:", encrypt)

    # 4️⃣ 校验签名
    crypto.verify_signature(
        msg_signature,
        timestamp,
        nonce,
        encrypt
    )

    # 5️⃣ 解密消息
    plain_xml = crypto.decrypt_message(
        msg_signature,
        timestamp,
        nonce,
        xml_str
    )

    print("解密后XML:", plain_xml)

    # 6️⃣ 解析明文
    message = parse_plain_xml(plain_xml)

    print("结构化消息:", message)

    return "success"

7.6 明文消息结构

解密后通常是:

<xml>
    <ToUserName>wwxxx</ToUserName>
    <FromUserName>user123</FromUserName>
    <CreateTime>123456789</CreateTime>
    <MsgType>text</MsgType>
    <Content>你好</Content>
    <MsgId>123456</MsgId>
</xml>

📌 转换后的结构

{
    "ToUserName": "wwxxx",
    "FromUserName": "user123",
    "MsgType": "text",
    "Content": "你好",
    "MsgId": "123456"
}

7.7 消息类型识别(第一版)

在 callback 中加入:

msg_type = message.get("MsgType")

if msg_type == "text":
    print("文本消息:", message.get("Content"))

elif msg_type == "event":
    print("事件消息")

else:
    print("其他类型:", msg_type)

7.8 当前系统状态

✅ 已完成

✔ FastAPI回调入口
✔ XML解析(外层+内层)
✔ msg_signature校验
✔ AES解密
✔ 明文结构化
✔ 消息类型识别

🚧 当前系统能力

企业微信 → 你的系统

✔ 已能进入
✔ 已能解密
✔ 已能识别内容

但还没有:
❌ Redis队列
❌ AI处理
❌ 回复消息

7.9 本章核心成果

这一章完成后,你的系统已经具备:

👉 “理解用户消息”的能力

不再是HTTP服务,而是:

👉 企业微信消息处理系统

🚀 下一章预告(第八章)

第八章:消息分发与Dispatcher设计

下一章我们将完成:

消息路由
├── text → AI处理
├── event → 事件处理
├── image → 图片处理
└── file → 文件处理

并引入:

  • Dispatcher模式
  • Handler拆分
  • 可扩展消息架构

👉 到这一章结束:

系统将开始具备“业务处理能力”,真正进入AI链路入口。