概述
工作内容的关系,需要开发一个微信消息接收和处理机制。就此需求进行了相关的探索和实践,有一些收获和问题觉得需要记录一下,遂有了本文。
基本原理和流程
研究后发现,微信消息的接收原理其实比较简单,就是当微信用户在公众号界面中,将公众号作为一个特殊的用户对象发送信息的时候,如果公众号开发者在后台配置了相关的机制(集成的应用系统),则微信系统会转发这个信息,然后由应用系统来进行处理。关键的是,这个转发机制也是通用的HTTP(S)协议的方式,应用系统提供一个HTTP端口,由微信系统来进行请求。
虽然看起来就是简单的HTTP协议请求响应的机制,但其实这里的处理机制是非常灵活的,HTTP协议只是其承载方式,处理方式其实可以是异步的,笔者简单总结了一下,可以包括一下三种模式:
- 即时响应
就跟一般的HTTP请求响应模式一样,用户发送消息,微信会封装消息内容来请求应用系统端点,应用系统处理后返回处理结果,然后微信会将结果作为消息立即反馈给用户界面。这种情况下,微信的系统就是一个代理,负责转发消息和响应信息。这个过程是实时同步发生的。
- 异步响应
当微信系统转发了用户信息的时候,应用系统可以选择不立刻处理这个用户请求。而是进入异步处理程序,处理完成之后,再以响应消息的方式,反馈给用户。
这个过程中,其实应用系统需要要做两个操作。首先需要即时响应给微信的HTTP请求,一般就是简单的响应文本"success";然后是处理后,发送一个客服消息给请求的用户。由于微信系统已经知道这个客服消息,可能是用于处理此前用户的请求的,所以它是允许在一定的条件下进行直接向用户发送的。
- 异构响应
这个模式的前半个阶段和异步响应是一样的。但其实,应用系统是可以选择对用户消息进行响应,甚至是不响应的。响应的方式,不见得一定是通过微信的平台,还可以基于其他的系统。
比如我们可以构想一个使用场景,就是用户发送一个"空调开机"的指令,应用系统接收到这个指令,可以通过其他的工业管理系统来执行这个开机指令,用户就可以在现实场景中,看到空调的开机情况。这就是笔者所谓的异构响应,真正实现了异构系统之间的集成。
这里还可以再扩展一下,这里的消息,不仅仅支持用户发送的消息,还可以是用户在公众号界面上其他操作的行为,比如点击菜单项目,微信同样会将其封装成为时间转发到应用系统服务接口之上。
实践和实现
下面笔者来说明一下这个具体操作的过程和可能需要注意的问题,包括一些实现的细节。
服务器准备
在这个体系中,最终提供给微信系统的,其实就只有一个HTTP端点,所有的请求都直接由这个端点来承载和处理,请求的内容由请求体来确定,开发者不需要对路径进行规划和管理。
当然,开发者对微信请求的处理方式是可以逐渐完善的,但首先需要处理的就是微信在配置阶段,提供一个服务器认证的操作,这需要开发者准备好。当用户在微信平台上进行服务器的配置的时候,微信平台会向这个端点发起一个带有特定参数的HTTP GET请求,而这时如果能够正确的响应预先约定的内容,微信系统就认为此端点和配置通过了认证。当然,为了提高安全性,开发者可选的可以对这个请求进行验证,我们先看一下相关的JS参考实现代码,然后简单解释这个认证的机制和原理。
js
// 接收并解析请求
const params = new URL(req.url).searchParams;
// console.log(params);
// 构造验证内容
let hstr = [WXSRV.token, params.get("timestamp"), params.get("nonce")].sort().join("");
let hash = createHash("SHA1").update(hstr).digest("hex");
// 签名验证
let sig = params.get("signature");
let rstr = (hash === sig) ? params.get("echostr") : "false";
return new Response(rstr);
说明如下:
- 请求的参数形式是url searchParams
- 形如?nonce=xxx×tamp=yyy&echostr=zzz&signature=www
- 解析后的参数项目包括timestamp(时间戳),nonce(随机信息),echostr(响应内容),signature(签名信息)等
- 如果响应echostr,就代表成功验证;响应false,代表验证失败;所以其实这个验证过程,是可以选择的
- 具体验证方式是将timestamp,nonce和token的文本内容进行排序,连接后进行SHA1摘要,以hex形式输出结果,并和signature的内容进行比较,匹配后作为成功验证
- 这里的token是一个预配置的随机信息(3~32个字符),可以理解成共享密钥,后面会在微信公众平台的服务器管理中配置进去
可能是实现的时间比较古早的问题(其实也没有太早,2012年左右),笔者觉得这个验证机制的设计实在是有点"简陋"。看起来复杂,但其实并不高明,不像是科班有密码学基础的开发者设计的。理由如下:
- 对Hash进行混淆,一般不使用随机字符串拼接,而是使用HMAC,安全性更好
- SHA1本身问题不大,但应该选择更好的SHA256,也就是HMAC-SHA256
- 随机信息其实一个就够了,timestamp和nonce可以拼装,这样就不用去做那个画蛇添足的参数解析和排序计算
- 可能更好的验证逻辑是,微信端使用nonce进行请求,由应用端使用token(其实是ShareSec)作为密钥对nonce进行签名,然后将signature响应给微信端,让微信端来验证这个服务器的端点是否配置正确
- 因为本质上的基本逻辑是微信平台是验证方,应用系统是被验证方,它需要证明自己具有相关的配置信息(服务器地址和密钥)
- 最安全的做法,应该提供微信方公钥签名验证的选项,确保信息来源确实来自微信平台
当然,相对而言,这样的设计,在应用开发端比较容易理解和实现。另外在微信端,其实也可以"偷懒",只需要记录和比较ehcostr就可以了(其实有点不负责任)。笔者还想起好像以前在做支付宝应用集成的时候,也有类似的设计,他们真的是在相互借鉴。
服务器配置
当端点和验证机制准备好之后,就可以考虑在公众号管理界面中先进行服务器的配置了,后续的功能可以逐步开发和完善。这个操作可以在公众号管理中的设置与开发-开发接口管理-服务器配置板块中完成(下图):

点击修改配置后,进入编辑页面(下图):

简单说明如下:
- 服务器地址,就是一个提供给微信系统请求的网络URL,最好支持HTTPS方式,可以带有子路径
- 令牌:和端点验证代码中的相同
- 消息加解密密钥: 实际上是一个AES加密使用的Key
- 消息加解密: 提供了明文,兼容和安全三个选项
笔者认为,如果对安全性没那么高的要求,如果提供了HTTPS协议,明文模式就应该已经够用了,比较简单省事。如果是HTTP协议,则强烈建议实现消息加密,很多老的系统,是需要这个保证安全性的。
修改后,微信系统会首先使用配置信息,尝试访问其中的服务器地址进行检查验证,验证通过后,就会保存启动这个配置。此时配置还没有启用,只有点击启用后,微信系统才会真正的使用这个配置进行工作(转发消息和事件)。
实际上,这个服务器配置启用,还可能会影响到公众号现有的配置,主要是公众号的自定义菜单将会失效。要保留这个菜单,开发者需要使用程序通过配置接口来进行配置。笔者不是特别理解这样一个设定,因为其实额外增加了很多开发和维护的工作。但微信系统可能有我们不太了解的技术方面的限制和考虑,就暂时降低一下管理方面的体验。
即时响应
当所有的服务器配置和启动都完成之后,应用系统就可以准备好接收微信消息的请求,并作出相应的处理了。我们先来讨论一个最简单的同步即时相应的场景。大致的情况如下:
- 用户发送消息到公众号
- 微信会将这个消息封装,并作为内容来请求应用系统端点
- 请求的方法是POST,内容的格式是XML
- 应用系统处理请求,处理后响应数据,格式也是XML
- 微信端解析响应内容,并发送到用户界面(公众号)
基于上述流程,相关的参考实现代码如下:
js
// 依赖库引用
const { XMLParser } = require("fast-xml-parser"),
// 请求内容格式示例
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
// 请求内容处理
const body = await req.text();
const obody = new XMLParser().parse(body);
const msg = obody.xml;
// 响应其他内容
if (msg.MsgType !== "text") return new Response("success");;
let resData = [ msg.FromUserName, msg.ToUserName, 0 | Date.now()/ 1000, "text"];
if (edata.Content.endsWith("查询openid")) {
resData[4] = "openid:\n" + msg.FromUserName; // response message
} else {
resData[4] = "未知指令"; // response message
}
// XML构造,后详
resObject.xml = toXML(XTEMPLATE.text, resData);
// 响应 XML
return new Response(rObject.xml, { headers: { "Content-Type": "application/xml" } })
// 模板和XML填充操作
const XTEMPLATE = {
text:
`<xml>
<ToUserName><![CDATA[$0]]></ToUserName>
<FromUserName><![CDATA[$1]]></FromUserName>
<CreateTime>$2</CreateTime>
<MsgType><![CDATA[$3]]></MsgType>
<Content><![CDATA[$4]]></Content>
</xml>`
};
const toXML = (xtemp,data)=> data.reduce((c,v,i)=> c.replace("$"+i,v), xtemp );
这里先鄙视一下腾讯,在2025年好像仍然没有提供更高效的技术选项,还是使用XML格式。代码中的核心处理其实是在做JSON和XML的转换,这里使用了一个第三方库fast-xml-parse。转换后,请求体(json)的主要内容是:
- FromUserName: 用户在公众号的openid
- ToUserName: 公众号逻辑用户
- CreateTime: 时间戳
- MsgType: text是消息
- Content: 对应消息内容
应用端程序使用这些内容作为处理的依据,比如要响应或者发送消息的话,需要目标的openid。还有重要的业务信息,可能在消息内容中。
响应的内容,要求的格式同样是XML,所以还要将处理的结果封装转换一遍。示例中响应的结果也是简单的文本text,格式基本和请求消息相同。要注意目标用户是用户的openid,来源是公众号逻辑用户,它们可以从请求内容中获取。
以上就是即时响应的操作,简单而言,如果要响应具体信息,就处理后发送一个XML文本;否则应当简单的响应"success"文本。如果没有响应任何内容,微信系统会尝试重新请求应用服务端点。所以应当将响应success作为托底措施(好像响应空内容也可以,只要有响应就行)。
异步响应
异步响应,是指两个阶段的响应。首先是需要在微信系统请求的时候,给一个HTTP响应;然后是将这个请求内容,进行异步的处理,处理完成后,构造一个客服消息,发送给用户。本章节讨论的重点,就是后面这部分内容。
在微信中,客服消息是一类"有条件"发送的消息,它必须基于一个条件或者场景。用户发送消息,这就是一个条件场景,基于此场景,系统向用户发送客服消息,就是可以的。当然这些都有一些限制,这些场景和限制包括:
- 用户发送消息, 5条/48小时
- 点击自定义菜单, 3条/1分钟
- 关注公众号, 3条/1分钟
- 扫描二维码, 3条/1分钟
我们这里简化讨论一些,用户发送消息之后,应用系统可以选择在48小时内,向用户发送5条客服消息,都是可以的。也就是说,这个异步响应的限制就是48小时。
除此之外,客服消息和其他消息的发送方式,并没有太大的区别,都是POST请求微信消息的API地址,只是使用专门的路径。下面是简单的参考示例代码:
js
// 基础地址
const URL_CMSAG = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=`;
// 获取访问token
let token = "xxxtoken ";
// 构造请求数据
const qdata = {
touser: "用户OpenID",
msgtype : "text",
text: { content: "消息内容" },
send_ignore_reprint : 0
};
// 发起请求
const res = await fetch(url+token, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(qdata)
});
// 获取结果
if (!res.ok) {
throw new Error(`HTTP Error: ${res.status} ${res.statusText}`);
};
const result = await res.json();
{
errcode: 0,
errmessage: "ok",
....
}
简单说明如下:
- 请求时需要使用当前的access_token
- 请求接口url使用access_token作为参数,但方法是POST
- 接收JSON作为body
- 响应的标准格式是{ errcode, errmessage, content ...},为0时为正常,否则为异常并附带错误信息
由于时异步发送消息,所以不可控因素更多。影响消息发送失败的原因很多,包括openid错误/限制,内存限制,消息发送限制等等,可能都会体现在响应的错误信息当中,帮助开发者分析和解决问题。
关于异构响应,实际上在后半部分已经和微信消息本身没有关系了,其他其实是和异步响应机制是一样的,这里就不展开讨论了。
事件消息
前面的内容我们讨论自行编写应用程序接收和处理微信消息的情况。但实际上,微信平台不仅可以接收和转发用户在公众号中发送的文本消息,还可以转发用户在公众号中的一些操作事件。最常见的就是公众菜单的点击事件。
前面已经简单提到,当使用应用服务器来处理微信信息和事件时,管理平台中的"自定义菜单"功能其实是无法使用的,所有配置都需要通过程序和接口来进行。

使用接口进行菜单定义的细节在这里不展开讨论,我们主要关心菜单定义的数据如何影响后续的事件处理,下面就是定义的两种主要的菜单相关的定义方式:
js
// 菜单定义
{
"type": "view",
"name": "系统主页",
"url": "http://homepage.com/index"
},
{
"type":"click",
"name":"给我们点赞",
"key":"V1001_GOOD"
}
这些定义在微信公众号操作界面中的效果大致如下:

这些菜单项目虽然看起来一样,但其实后面的图标,展示了项目的类型。配置完成后,当用户点击这些菜单项目的时候,应用会收到事件消息,结构和内容如下(原始格式是XML,这里展示的是已转换成JSON的内容):
js
// 事件消息类型和内容
// 系统主页菜单
{
ToUserName: "公众号id",
FromUserName: "用户openid",
CreateTime: 1756952264,
MsgType: "event",
Event: "VIEW",
EventKey: "http://homepage.com/index",
MenuId: 435646991,
}
// 给我们点赞
{
ToUserName: "公众号id",
FromUserName: "用户openid",
CreateTime: 1756952217,
MsgType: "event",
Event: "CLICK",
EventKey: "V1001_GOOD",
}
应当容易理解,菜单链接类型的事件,消息类型是"event",事件是"VIEW",事件Key是定义的URL地址;而点击类型的消息,消息类型也是"event",但事件是"CLICK",而事件的Key是在定义的时候指定的EventKey内容。还记得前面提到过的用户发送信息的消息类型吗? 是"text",对应的内容主键是"content"。
有了这些信息,在服务程序端,可以获取这些消息和事件的类型,内容,和来源openid,这些都是后续进行消息处理的关键信息。后续的具体处理都是业务层面的事情,这里就不再展开了。
其他的消息类型,可能还包括如用户关注订阅,扫码等等,笔者还没有机会实际操作,但料想它们的基本原理都差不多,有机会再讨论。
小结
本文讨论了笔者在工作中遇到的如何实现在应用中集成微信消息接收和处理的问题。简单探讨了其实现原理和流程,具体操作步骤和需要注意的问题,并分别探讨了不同消息时间处理的模式和机制。