基础前端移动端和小程序

微信公众号开发——自动回复用户消息

2020-02-18  本文已影响0人  CondorHero

前言:微信服务器会发送两种消息到开发者服务器:

但是无论是 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服务,发送消息测试是否有错误。

上一篇 下一篇

猜你喜欢

热点阅读