微信公众号开发之接收与回复消息(安全模式与明文模式)
Hi I'm Shendi
微信公众号接收消息接口
在第一篇的时候配置了服务器,并实现了get接口,当有消息时,微信将通过POST发送到接口
配置参考 微信公众号开发入门,配置与获取AccessToken
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
因为数据是 XML 格式的,且数据在请求体,如果使用的 node.js 可以查阅下面这两篇文章
Node.js的Express参数获取及获取POST请求的请求体
通过查阅文档,在 xml 中 MsgType 代表消息类型,通过这个参数做不同操作
例如文本消息的xml结构如下
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
<MsgDataId>xxxx</MsgDataId>
<Idx>xxxx</Idx>
</xml>
参数 | 描述 |
---|---|
ToUserName | 开发者微信号 |
FromUserName | 发送方账号(一个OpenID) |
CreateTime | 消息创建时间 (整型) |
MsgType | 消息类型,文本为text |
Content | 文本消息内容 |
MsgId | 消息id,64位整型 |
MsgDataId | 消息的数据ID(消息如果来自文章时才有) |
Idx | 多图文时第几篇文章,从1开始(消息如果来自文章时才有) |
本地测试可以使用花生壳之类的做个穿透,当发送消息,用户关注/取消关注,扫带参数二维码等,微信会发送消息到接口,这样接收就做好了,剩下的就是处理了
明文模式
nodejs简单的示例代码如下
var xmlreader = require('xmlreader');
router.post('/服务器配置的地址', (req, res) => {
let readData = req.read().toString();
console.log(readData);
// 解析xml字符串 这是同步的
xmlreader.read(readData, function (err, xmlData) {
if (err) {
console.error(err);
return;
}
// 是否响应数据,如果没有,在最后响应空字符串
let isResp = false;
// 根节点为 xml
let xml = xmlData.xml;
// 根据 MsgType 做不同操作
switch (xml.MsgType.text()) {
// 文本消息
case "text":
console.log("发送了文本");
break;
// 事件,事件通过Event做不同操作
case "event":
switch (xml.Event.text()) {
// 关注
case "subscribe":
console.log("关注了");
break;
// 取消关注
case "unsubscribe":
console.log("取消关注了");
break;
}
break;
}
});
// 没有响应则返回 success 或者空字符串,这样微信不会重复发送消息
if (!isResp) res.send("");
});
安全模式
数据将是加密的,消息的加解密参考:微信公众号开发之消息加解密
上面的代码将稍作更改,在 let xml = xmlData.xml;
后增加以下代码
let encData = xml.Encrypt.text(),
timestamp = req.query.timestamp,
nonce = req.query.nonce,
msgSignature = req.query.msg_signature;
res.statusCode = 404;
if (encData != null && timestamp != null && nonce != null && msgSignature != null
&& msgSignature == WxEncrypt.signature([encData, timestamp, nonce, TOKEN_VAL])) {
// 校验成功,开始解密
let dec = WxEncrypt.decode(encData);
console.log(dec);
xmlreader.read(dec, function (err, xmlData) {
if (err) {
console.error(err);
return;
}
xml = xmlData.xml;
});
res.statusCode = 200;
}
if (res.statusCode == 404) {
res.send();
return;
}
其中 TOKEN_VAL
是服务器配置的 Token
被动回复消息
这个地方有很多的坑...
当微信发消息给我们的接口,我们可以响应数据回去,例如用户发送消息,我们回复消息给用户,但需要确保在五秒内完成,如果不能保证,那就返回空字符串,后面使用客服接口去发送消息
微信服务器在将用户的消息发给公众号的开发者服务器地址后,微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。
1、直接回复success(推荐方式)
2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
明文模式
这里以回复文本消息为例,需要返回的数据格式如下
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好]]></Content>
</xml>
参数 | 是否必须 | 描述 |
---|---|---|
ToUserName | 是 | 接收方账号(收到的OpenID) |
FromUserName | 是 | 开发者微信号 |
CreateTime | 是 | 消息创建时间 (整型) |
MsgType | 是 | 消息类型,文本为text |
Content | 是 | 回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示) |
首先建议先以明文方式尝试,可以使用测试号,测试号只支持明文
第一个坑,微信发送的ToUserName是开发者微信号,FromUserName是发送方账号
而返回的数据中ToUserName是接收方账号,FromUserName是开发者微信号,是相反的
吃了没仔细看的亏,这个地方卡了我很久,以至于我发送消息给公众号,公众号只显示
当时可纳闷了,仔细对比数据结构、内容,用postman尝试,包括响应头都仔细检查了...
代码如下
// 文本消息,回复有坑-ToUserName与FromUserName是相反的
case "text":
let data = `<xml>
<ToUserName><![CDATA[${xml.FromUserName.text()}]]></ToUserName>
<FromUserName><![CDATA[${xml.ToUserName.text()}]]></FromUserName>
<CreateTime>${new Date().getTime()}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好呀]]></Content></xml>`;
res.send(data);
isResp = true;
break;
需要注意的是,其中的xml数据内容不能包含空格,这是官方上说的,这个坑我没有踩
安全模式
安全模式需要线上的公众号来进行测试,加解密参考上面那篇消息加解密的文章
加密消息的格式
<xml>
<Encrypt></Encrypt>
<MsgSignature></MsgSignature>
<TimeStamp></TimeStamp>
<Nonce></Nonce>
</xml>
其中,msg_encrypt=Base64_Encode(AES_Encrypt [random(16B)+ msg_len(4B) + msg + ])
random(16B)为16字节的随机字符串;msg_len为msg长度,占4个字节(网络字节序);
AESKey =Base64_Decode(EncodingAESKey + “=”),32个字节msg_signature=sha1(sort(Token、timestamp、nonce, msg_encrypt))timestamp、nonce回填请求中的值即可。
上面的引用是微信官方的原话,其中msg_encrypt代表 Encrypt
标签的内容,msg_signature代表 MsgSignature
,剩下两个参数是从url拿到
我这里也踩了个坑,以为MsgSignature就是url上的msg_signature,仔细看才发现所用的 msg_encrypt 为自己加密后的内容,而不是微信发送的加密消息
示例代码如下
// 文本消息,回复有坑-ToUserName与FromUserName是相反的
case "text":
let enc = `<xml>
<ToUserName><![CDATA[${xml.FromUserName.text()}]]></ToUserName>
<FromUserName><![CDATA[${xml.ToUserName.text()}]]></FromUserName>
<CreateTime>${new Date().getTime()}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[你好呀]]></Content></xml>`;
enc = WxEncrypt.encode(enc);
let data = `<xml>
<Encrypt>${enc}</Encrypt>
<MsgSignature>${WxEncrypt.signature([enc, req.query.timestamp, req.query.nonce, TOKEN_VAL])}</MsgSignature>
<TimeStamp>${req.query.timestamp}</TimeStamp>
<Nonce>${req.query.nonce}</Nonce>
</xml>`
res.send(data);
isResp = true;
break;
效果图
最后,别忘了将用户OpenID存起来,后面调用客服发送消息也需要使用到openid
END
本文链接:https://sdpro.top/blog/html/article/1101.html♥ 赞助 ♥
尽管去做,或许最终的结果不尽人意,但你不付出,他不付出,那怎会进步呢?