微信公众号自定义菜单是用户与公众号交互的第一入口。菜单按钮类型中,最常用的两种是click和view:click点击后微信服务器会向开发者服务器推送一条XML事件消息,由服务端决定回复内容;view点击后则直接在微信内置浏览器中打开指定URL。理解这两种事件的接收与处理逻辑,是做好公众号开发的基础。

一、自定义菜单的创建与事件推送流程
在处理事件之前,必须先通过接口创建菜单。创建菜单调用的是https://api.weixin.qq.com/cgi-bin/menu/create接口,需要携带access_token,请求体为JSON结构。click类型的按钮带有key字段,view类型的按钮带有url字段,这个key就是后续事件推送中用来区分业务的关键标识。
创建成功后,用户点击click按钮时,微信服务器会向公众号后台配置的服务器地址(URL)以POST方式推送一条XML报文,MsgType为event,Event为CLICK,并携带EventKey。而点击view按钮时,微信同样会推送一条Event为VIEW的事件,但页面跳转由微信客户端自动完成,开发者只需在事件中记录用户行为即可,不需要回复消息。
需要注意,如果公众号启用了消息加密模式,推送的报文会包裹在Encrypt标签中,需要用EncodingAESKey解密后才能拿到明文XML,这一点在联调阶段经常被忽略。
二、click事件的接收与回复处理
click事件的处理核心是:接收XML、解析出EventKey、执行业务逻辑、按指定格式回复。微信要求服务端在5秒内响应,否则会显示该公众号暂时无法服务。如果业务处理较慢,可以先回复一个空串或success字符串,再通过客服消息接口异步推送结果。
下面是一段Node.js(Express)的实现示例,演示如何解析明文模式下的click事件并回复文本消息:
const express = require('express');
const { XMLParser, XMLBuilder } = require('fast-xml-parser');
const app = express();
const parser = new XMLParser();
const builder = new XMLBuilder({ format: true });
app.use(express.text({ type: 'text/xml' }));
app.post('/wechat', (req, res) => {
const data = parser.parse(req.body);
// 仅处理菜单点击事件
if (data.xml.MsgType === 'event' && data.xml.Event === 'CLICK') {
const key = data.xml.EventKey;
let reply = '您点击了未知菜单';
if (key === 'MENU_ABOUT') {
reply = '欢迎关注我们,本公众号提供技术分享服务';
} else if (key === 'MENU_NEWS') {
reply = '最新文章已整理,请稍后查看推送消息';
}
// 组装被动回复的XML报文
const result = builder.build({
xml: {
ToUserName: data.xml.FromUserName,
FromUserName: data.xml.ToUserName,
CreateTime: Math.floor(Date.now() / 1000),
MsgType: 'text',
Content: reply
}
});
res.set('Content-Type', 'text/xml');
return res.send(result);
}
// view事件或其他消息直接回空串,避免重试
return res.send('success');
});
app.listen(3000);这段代码有三个要点。第一,回复报文中ToUserName和FromUserName要与收到的报文对调,即回复给发送方。第二,非click事件务必返回success,否则微信会重试推送三次,造成重复消息。第三,业务逻辑不要阻塞响应,重量级操作放入消息队列处理。
三、view事件的处理与两种事件的对比
view事件的处理相对简单,因为跳转动作由微信客户端完成,服务端收到的事件报文主要用于埋点和用户行为分析。比如可以根据EventKey统计哪个入口的访问量更高,或者将用户的OpenID记录下来,用于后续网页授权时的身份关联。
两者的关键区别可以用下表概括:
| 对比项 | click事件 | view事件 |
|---|---|---|
| Event值 | CLICK | VIEW |
| 携带字段 | EventKey(自定义key) | EventKey(即跳转URL) |
| 是否需要回复 | 必须回复消息或success | 回复success即可 |
| 页面跳转 | 无,由开发者控制回复内容 | 自动打开内置浏览器 |
| 典型场景 | 查询、签到、领取资料 | 打开H5活动页、跳转小程序 |
需要注意的是,view事件的EventKey就是菜单中配置的URL本身,因此不能像click那样自定义业务标识,做埋点时可以直接根据URL区分入口。另外,view菜单还支持个性化菜单能力,可以针对不同标签的用户展示不同的URL入口,实现分群运营。
四、常见坑点与稳定性建议
第一个坑是重复推送。微信在服务端未及时响应或返回非success内容时会重试,导致用户收到多条相同消息。建议对每条报文的MsgId或时间戳做幂等校验,处理过的消息直接返回success。
第二个坑是加密模式下的报文解析。安全模式下推送的XML外层是Encrypt节点,需要先用msg_signature校验签名,再用AES-256-CBC解密。很多框架如weixin-java-tools已经封装好了,直接使用可以减少出错概率。
第三个坑是菜单更新不生效。菜单创建接口有覆盖性质,且客户端可能有缓存,修改后建议先取消关注再重新关注,或者进入公众号主页刷新。此外,自定义菜单每天有调用次数限制,开发阶段不要频繁重建。
综合来看,click事件考验的是服务端的消息处理与回复能力,view事件考验的是URL规划与行为埋点设计。把两者的接收逻辑统一封装成事件分发器,按Event类型路由到不同处理器,是保持代码可维护性的推荐做法。