3、消息接收与响应:接收普通文本消息、XML消息格式解析、被动回复用户消息

好,咱们进入实战环节。

前面两章我们把环境搭好了,也拿到了开发者权限。现在要干正事了——让我们的服务器能听懂用户说了什么,并且能回复。

说白了,微信服务器就是个「传话筒」。用户发一条消息给公众号,微信服务器把它转成一段XML,POST到你的服务器上。你的服务器解析完,再返回一段XML,微信服务器再转发给用户。整个过程,你想想看,是不是很像一个中间人?

3.1 接收普通文本消息

用户发来的消息,最常见的就是文本。比如用户说「你好」,微信服务器会发来这样一段XML:

<xml>
  <ToUserName><![CDATA[gh_xxxxx]]></ToUserName>
  <FromUserName><![CDATA[oXXXX_xxxx]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>

嗯,这里要注意几个关键字段:

  • ToUserName:开发者微信号(也就是你的公众号原始ID)
  • FromUserName:用户的OpenID,每个用户唯一
  • CreateTime:消息时间戳
  • MsgType:消息类型,文本就是text
  • Content:用户发的文本内容
  • MsgId:消息ID,用于去重

我在项目中遇到过一个问题:用户连续发两条一模一样的消息,MsgId是不同的。所以千万别用Content去重,要用MsgId。

3.2 XML消息格式解析

收到XML后,第一件事就是解析。我个人习惯用Python的xml.etree.ElementTree,轻量又好用。当然你也可以用lxml,看团队技术栈。

解析的核心代码长这样:

import xml.etree.ElementTree as ET

def parse_xml(xml_data):
    root = ET.fromstring(xml_data)
    msg_dict = {}
    for child in root:
        msg_dict[child.tag] = child.text
    return msg_dict

# 使用示例
xml_data = request.body.decode('utf-8')
msg = parse_xml(xml_data)
msg_type = msg.get('MsgType')
content = msg.get('Content')

这里有个坑,我踩过。CDATA段里的内容,ElementTree解析后会自动去掉CDATA标记,直接拿到纯文本。但如果你用某些框架的解析器,可能会保留CDATA标签。所以拿到数据后,最好打印出来看一眼。

注意:微信服务器发来的XML编码是UTF-8,但CDATA里的内容可能包含特殊字符。解析时一定要指定编码,否则中文会乱码。

3.3 被动回复用户消息

解析完消息,就该回复了。被动回复,说白了就是「用户发一条,你回一条」。你不能主动给用户发消息,只能响应。

回复的XML格式和接收的类似,只是ToUserName和FromUserName要互换:

<xml>
  <ToUserName><![CDATA[oXXXX_xxxx]]></ToUserName>
  <FromUserName><![CDATA[gh_xxxxx]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好,欢迎使用智能客服!]]></Content>
</xml>

回复文本消息的代码示例:

def build_text_response(to_user, from_user, content):
    response = f"""<xml>
  <ToUserName><![CDATA[{to_user}]]></ToUserName>
  <FromUserName><![CDATA[{from_user}]]></FromUserName>
  <CreateTime>{int(time.time())}</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[{content}]]></Content>
</xml>"""
    return response

# 在视图函数中
def wechat_handler(request):
    if request.method == 'POST':
        xml_data = request.body.decode('utf-8')
        msg = parse_xml(xml_data)
        
        to_user = msg.get('FromUserName')
        from_user = msg.get('ToUserName')
        content = msg.get('Content', '')
        
        reply_content = f"你说了:{content}"
        response_xml = build_text_response(to_user, from_user, reply_content)
        
        return HttpResponse(response_xml, content_type='application/xml')
小技巧:回复的XML必须严格符合微信规范,不能有多余的空格或换行。我建议用字符串模板拼接,别用XML库生成,容易出格式问题。

3.4 避坑指南

我曾经在线上环境遇到过一个诡异的问题:用户发消息,服务器明明返回了200,但用户收不到回复。查了半天,发现是返回的Content-Type写成了text/html,微信不认。必须写成application/xml

还有一次,我同事把ToUserName和FromUserName写反了,结果用户收到了别人的回复。嗯,这种低级错误,调试时一定要仔细核对。

核心要点:
  • 接收消息:解析XML,提取关键字段
  • 回复消息:构造XML,注意字段互换
  • 响应状态:必须返回200,Content-Type为application/xml
  • 超时限制:微信服务器等待5秒,超时则重试3次

好了,这一章的内容就这些。说白了就是「收XML -> 解析 -> 构造XML -> 返回」。下一章我们会聊到图文消息和菜单事件的处理,到时候会更复杂一些,但核心逻辑是一样的。

公众号:蓝海资料掘金营,微信deep3321