美文网首页基础前端移动端和小程序
微信公众号开发——自动回复用户消息

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

作者: CondorHero | 来源:发表于2020-02-18 21:24 被阅读0次

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

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

    相关文章

      网友评论

        本文标题:微信公众号开发——自动回复用户消息

        本文链接:https://www.haomeiwen.com/subject/faowoctx.html