前言:微信服务器会发送两种消息到开发者服务器:
- GET 请求(验证服务器)
- POST请求 (把用户发送的消息以POST发送到开发者服务器)
但是无论是 GET 请求还是 POST 请求微信服务器都会携带 query 让开发者服务器来进行验证。
一、获取测试号
开发者用来模拟测试用户发送消息,请到测试号网页扫码关注,添加用户:
二、接收来自公众号的消息
更改 auth.js 文件的内容:
//引入配置对象
const config = require('../config');
//引入sha1加密模块
const sha1 = require('sha1');
module.exports = () => {
return (req, res, next) => {
//接受微信服务器发送过来的请求参数
//获取参与加密的参数
const { signature, echostr, timestamp, nonce } = req.query;
const { token } = config;
// - 将三个参数拼接在一起,进行sha1加密
const sha1Str = sha1([timestamp, nonce, token].sort().join(''));
/*
微信服务器会发送两种消息到开发者服务器:
GET 请求(验证服务器)
POST请求 (把用户发送的消息以POST发送到开发者服务器)
*/
if(req.method === "GET"){
// - 将加密后生成字符串和微信签名进行对比,
if (sha1Str === signature) {
//说明成功,返回echostr给微信服务器
res.send(echostr);
} else {
//说明失败
res.send('');
}
}else if(req.method === "POST"){
// - 将加密后生成字符串和微信签名进行对比,
if (sha1Str === signature) {
//说明成功,返回空字符串防止微信服务器重复请求
res.send('');
//接收用户发送的消息
let str = "";
req.on("data",chunk=>{
str += chunk;
});
req.on("end",()=>{
console.log(str);
});
} else {
//说明失败
res.send('POST请求不是来自微信服务器。');
}
}else{
console.log("请求方式错误");
res.send("请求方式错误");
}
}
}
在第一步关注的测试号里面发送一段测试消息:
看看控制台打印的是什么东西:
<xml><ToUserName><![CDATA[gh_c11e26596ec7]]></ToUserName>
<FromUserName><![CDATA[o2r3sw8PqxLt6AiOs1PxpKdWwj88]]></FromUserName>
<CreateTime>1577860175</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[我是测试信息]]></Content>
<MsgId>22589525991718537</MsgId>
</xml>
微信服务器使用 POST 把用户发送的消息以 XML 数据包的形式发给了开发者。解释字段分别代表的意思:
<xml>
<ToUserName><![CDATA[gh_c11e26596ec7]]></ToUserName>//开发者的ID
<FromUserName><![CDATA[o2r3sw8PqxLt6AiOs1PxpKdWwj88]]></FromUserName>//用户的openid
<CreateTime>1577860175</CreateTime>//发送的时间戳
<MsgType><![CDATA[text]]></MsgType>//发送的消息类型这次是文本类型
<Content><![CDATA[我是测试信息]]></Content>//发送内容
<MsgId>22589525991718537</MsgId>//消息id,64位整型,微信服务器默认保留三天用户数据,通过此ID就能找到对应的用户数据。
</xml>
ToUserName 开发者ID就在:
ToUserName
需要注意的是,如果在接收来自微信服务器的POST请求的时候,开发者服务器如果没有正确返回空字符或echostr,微信服务器会询问三次我们的开发者服务器。
if(req.method === "POST"){
res.send("返回格式不对,触发三次");
}
手动重启node服务器,再次在测试公众号里面发送消息:
这次发送完消息微信公众号送给我们一句话:
该公众号提供的服务出现故障,请稍后再试
一旦遇到以下情况,微信都会在公众号会话中,向用户下发系统提示“该公众号暂时无法提供服务,请稍后再试”:
1、开发者在5秒内未回复任何内容 2、开发者回复了异常数据,比如JSON数据等
未回复给微信服务器消息,打印在控制台的 req.query
。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
可以看到微信服务器在没得到正确回复一共发送了三次请求。请求携带的内容少了个 echostr
但是多了个 openid ,这是微信公众号为了识别用户,每个用户针对每个公众号会产生一个安全的OpenID。获取这个用户的 OpenID
我们没有也不需要经过用户的同意。
三、转化和格式化用户数据
微信服务器发送的是 xml 格式的文件,你编程的话肯定是不能用的。需要来次转化,转化成 JS 对象。在处理这步之前我们先看优化下,把接收数据提出来变成一个函数就放在根目录下 utils 文件下里面的 tool.js 文件里面:
module.exports = {
getUserDataAsync(req){
return new Promise((resolve,reject)=>{
// 接收用户发送的消息
let xmlData = "";
req.on("data",chunk=>{
xmlData += chunk;
});
req.on("end",()=>{
resolve(xmlData);
});
req.on("error",(err)=>{
reject(err);
});
});
}
}
接下来的格式化函数也放在这个文件里面。在 auth.js 里面就可以使用了。
const { getUserDataAsync } = require("../utils/tool");
// - 将加密后生成字符串和微信签名进行对比,
if (sha1Str === signature) {
//接受post请求里面的数据
const xmlData = await getUserDataAsync(req);
//说明成功,返回空字符串防止微信服务器重复请求
res.send('');
}
接下里就要把 xml 格式的文件变成 js 对象,这个借助 npm 的一个包 xml2js :
npm i --save xml2js
在 tool.js 里面使用:
// 引入parseString把xml2js解析成对象形式
const { parseString } = require("xml2js");
parseXMLAsync(xmlData){
return new Promise((resolve,reject)=>{
parseString(xmlData,{trim:true},(err,data)=>{
if(!err){
resolve(data);
}else{
reject(`parseXMLAsync出错,错误为:${err}`);
}
});
});
}
在 auth.js 里面引入使用,打印出结果:
const { getUserDataAsync , parseXMLAsync} = require("../utils/tool");
// 将xmlData解析为js对象,以供编程使用
const jsData = await parseXMLAsync(xmlData);
console.log(jsData);
重启node服务,发送测试消息,得到的打印结果如下:
{
xml: {
ToUserName: [ 'gh_c11e26596ec7' ],
FromUserName: [ 'o2r3sw8PqxLt6AiOs1PxpKdWwj88' ],
CreateTime: [ '1577864116' ],
MsgType: [ 'text' ],
Content: [ '12' ],
MsgId: [ '22589582650201809' ]
}
}
得到的这个格式是可以进行编程了,但是还不是理想的代码格式,我们理想的它长成下面这样:
{
ToUserName: 'gh_c11e26596ec7',
FromUserName: 'o2r3sw8PqxLt6AiOs1PxpKdWwj88',
CreateTime: '1577864116',
MsgType: 'text',
Content: '12',
MsgId: '22589582650201809'
}
所以还需要在 tool.js 里面定义一个代码格式化函数(formatMessage):
formatMeassage(jsData = {}){
let message = {};
const {xml} = jsData;
for(let k in xml){
message[k] = xml[k][0];
}
return message;
}
在 auth.js 里面引入使用:
//引入tool模块
const { getUserDataAsync , parseXMLAsync , formatMeassage} = require("../utils/tool");
//接受post请求里面的数据
const xmlData = await getUserDataAsync(req);
// 将xmlData解析为js对象,以供编程使用
const jsData = await parseXMLAsync(xmlData);
// 代码格式化
const message = formatMeassage(jsData);
console.log(message);
//说明成功,返回空字符串防止微信服务器重复请求
res.send('');
重启node服务,发送测试消息,得到的打印结果如下:
{
ToUserName: 'gh_c11e26596ec7',
FromUserName: 'o2r3sw8PqxLt6AiOs1PxpKdWwj88',
CreateTime: '1577865574',
MsgType: 'text',
Content: '12',
MsgId: '22589601310172884'
}
四、简单回复
注意:回复的xml尖括号里面不能有空格,否则就会报错:该公众号暂时无法提供服务,请稍后再试。
参考:被动回复用户消息
auth.js 文件里面的简单回复功能代码:
//接受post请求里面的数据
const xmlData = await getUserDataAsync(req);
// 将xmlData解析为js对象,以供编程使用
const jsData = await parseXMLAsync(xmlData);
// 代码格式化
const message = formatMeassage(jsData);
let content = "";
if(message.MsgType === "text"){
if(message.Content.match(/a|o|a/gi)){
content = "I love AOA";
}else if(message.Content === "大吉大利"){
content = "今晚吃鸡";
}else if(Number(message.Content)){
content = "我输入的数字是 「" + (Number(message.Content) + 1) + " 」永远比你多一,哈哈哈。";
}else{
console.log(message.Content.match(/a/gi))
content = "你输入的什么我不懂";
}
};
repyMessage = `<xml>
<ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
<CreateTime>${Date.now()}</CreateTime>
<MsgType><![CDATA[${message.MsgType}]]></MsgType>
<Content><![CDATA[${content}]]></Content>
</xml>`;
//说明成功,要回复的消息返回给微信服务器
res.send(repyMessage);
测试回复效果:
测试回复结果
五、复杂回复格式
微信支持六种基本的用户消息回复,如下:
1 回复文本消息
2 回复图片消息
3 回复语音消息
4 回复视频消息
5 回复音乐消息
6 回复图文消息
可以点进入看看回复模板,接下来我们需要对回复内容进行一定的封装。首先把简单回复这个例子改造一下,单独提出来变成一个函数(template),文件的位置和 auth.js同级,文件名叫做 template.js,文件内容为:
module.exports = options =>{
// 定义回复模板的公共部分
let repymessage = `<xml>
<ToUserName><![CDATA[${options.FromUserName}]]></ToUserName>
<FromUserName><![CDATA[${options.ToUserName}]]></FromUserName>
<CreateTime>${Date.now()}</CreateTime>
<MsgType><![CDATA[${options.MsgType}]]></MsgType>`;
// 针对不同类型的消息回复不同的内容
if(options.MsgType === "text"){
if(options.Content.match(/a|o|a/gi)){
repymessage += "<Content><![CDATA[I love AOA]]></Content>";
}else if(options.Content === "大吉大利"){
content = "今晚吃鸡";
}else if(Number(options.Content)){
repymessage += `<Content><![CDATA[我输入的数字是 「${(Number(options.Content) + 1)} 」永远比你多一,哈哈哈。]]></Content>`;
content = "";
}else{
repymessage += "<Content><![CDATA[你输入的什么我不懂]]></Content>";
}
};
// 返回处理模板
return repymessage += "</xml>";
}
auth.js 引入并使用:
// 引入回复模板
const template = require("./template");
//<====验证来自微信服务器返回消息======>
//接受post请求里面的数据
const xmlData = await getUserDataAsync(req);
// 将xmlData解析为js对象,以供编程使用
const jsData = await parseXMLAsync(xmlData);
// 代码格式化
const message = formatMeassage(jsData);
// 回复模板
const repyMessage = template(message);
//说明成功,要回复的消息返回给微信服务器
res.send(repyMessage);
保存,重启node服务,发送消息测试是否有错误。
网友评论