• node.js 微信开发2-消息回复、token获取、自定义菜单


    项目结构

    >config/wechat.json 微信公众号的配置文件

    >controllers/oauth.js 微信网页授权接口(下一篇再细讲讲)

    >controllers/wechat.js 微信公众号接口(包括接入接口和其他调用微信api的接口)

    >wechat/access_token.json 请求微信api接口之前都需要使用的access_token

    >wechat/crytoGraphy.js 加密解密文件(这里使用的是明文方式,未用到)

    >wechat/menus.json 微信公众号的自定义菜单

    >wechat/wechat.js 调用微信api的方法

    项目的架构说明和使用步骤可以参考前面一篇的‘node.js 接口调用示例’: https://www.cnblogs.com/eye-like/p/11743744.html

    一些说明点

    1、公众号的接入接口和接收消息接口

    两个接口的地址是一致的,区别是:

    > 接入接口是GET请求,需要的是对get的接口参数进行解析验证,然后按需返回就可以了

    > 接收消息接口是POST请求,需要先解析微信发送过来的消息,然后根据情况决定是否返回消息

    WeChat.prototype.handleMsg = async function (ctx) {
                return new Promise((resolve, reject) => {
    
                    // let req = ctx.request;
                    // let res = ctx.response;
                    let req = ctx.req;
                    var buffer = [],
                        that = this;
    
                    //实例微信消息加解密
                    // var cryptoGraphy = new CryptoGraphy(that.config,ctx.request);
                    //监听 data 事件 用于接收数据
                    req.on('data', function (data) {
                        // logger.info("on data", data);
                        buffer.push(data);
                    });
                    req.on('end', function () {
                        // logger.info("on end");
                        var msgXml = Buffer.concat(buffer).toString('utf-8');
    
                        parseString(msgXml, {
                            explicitArray: false
                        }, function (err, result) {
                            // logger.info("on result", result);
                            result = result.xml;
                            resolve(result)
                        })
                    });
                })
    
            },
    接收微信消息

    * 接收消息使用的是Promise函数封装,目的是将微信的收发消息两块做隔离

    * 涉及到node.js 接收post参数的方式,需要ctx.req.on('data',function(){})方法接受参数,并在ctx.req.on(‘end’,function(){})函数中接受最终的获取参数

    * 因为微信发过来的消息是xml格式,所以在node.js 中需要xml2js模块(非node.js内置模块,需要先安装依赖)将xml文件解析

    /**
     * 微信消息回复
     * @param {ctx} context 对象
     * @param {result} 微信消息
     */
    WeChat.prototype.responseMsg = function (ctx, result) {
            var toUser = result.ToUserName; //接收方微信
            var fromUser = result.FromUserName; //发送仿微信
            var reportMsg = ""; //声明回复消息的变量   
            if (result.MsgType.toLowerCase() === "event") {
                //判断事件类型
                switch (result.Event.toLowerCase()) {
                    case 'subscribe':
                        //回复消息
                        var content = "欢迎关注 线上随访 公众号
    ";
                        content += "我们致力于帮助出院康复病人与医生建立便捷的沟通渠道~
    ";
                        reportMsg = msg.txtMsg(fromUser, toUser, content);
                        break;
                    case 'click':
                        var contentArr = [{
                                Title: "Node.js 微信自定义菜单",
                                Description: "使用Node.js实现自定义微信菜单",
                                PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",
                                Url: "http://blog.csdn.net/hvkcoder/article/details/72868520"
                            },
                            {
                                Title: "Node.js access_token的获取、存储及更新",
                                Description: "Node.js access_token的获取、存储及更新",
                                PicUrl: "http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",
                                Url: "http://blog.csdn.net/hvkcoder/article/details/72783631"
                            },
                            {
                                Title: "Node.js 接入微信公众平台开发",
                                Description: "Node.js 接入微信公众平台开发",
                                PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",
                                Url: "http://blog.csdn.net/hvkcoder/article/details/72765279"
                            }
                        ];
                        //回复图文消息
                        reportMsg = msg.graphicMsg(fromUser, toUser, contentArr);
                        break;
                }
            } else {
                //判断消息类型为 文本消息
                if (result.MsgType.toLowerCase() === "text") {
                    switch (result.Content) {
                        case '1':
                            reportMsg = msg.txtMsg(fromUser, toUser, 'Hello ,线上随访公众号开通了,快来使用吧……');
                            break;
                        case '2':
                            reportMsg = msg.txtMsg(fromUser, toUser, 'Ha Ha,我还有更多的功能待发掘呢,敬请期待吧……');
                            break;
                        case '文章':
                            var contentArr = [{
                                    Title: "Node.js 微信自定义菜单",
                                    Description: "使用Node.js实现自定义微信菜单",
                                    PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",
                                    Url: "http://blog.csdn.net/hvkcoder/article/details/72868520"
                                },
                                {
                                    Title: "Node.js access_token的获取、存储及更新",
                                    Description: "Node.js access_token的获取、存储及更新",
                                    PicUrl: "http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",
                                    Url: "http://blog.csdn.net/hvkcoder/article/details/72783631"
                                },
                                {
                                    Title: "Node.js 接入微信公众平台开发",
                                    Description: "Node.js 接入微信公众平台开发",
                                    PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast",
                                    Url: "http://blog.csdn.net/hvkcoder/article/details/72765279"
                                }
                            ];
                            //回复图文消息
                            reportMsg = msg.graphicMsg(fromUser, toUser, contentArr);
                            break;
                        default:
                            reportMsg = msg.txtMsg(fromUser, toUser, '没有这个选项哦');
                            break;
                    }
                    // logger.info("on reportMsg", reportMsg);
                }
            }
            //返回给微信服务器
            ctx.body = reportMsg
        },
    回复微信消息

    # 主要任务是根据微信消息的类型‘MsgType’,来执行不同的动作

    #  事件推送消息(MsgType==‘event’)

    * 关注取消事件 subscribe,unsubscribe

      此时可以做数据库接入录入订阅者信息或者更新订阅者信息

    * 扫描带参数二维码事件

      用户未关注时:qrscene_ +二维码参数值

      用户已关注时:scan

    * 上报地理位置事件 LOCATION

    * 自定义菜单事件 CLICK

    # 普通消息推送 (MsgType=='text')

      可分为 文本消息、图片消息、语音消息、视频消息、小视频消息、地理位置消息、链接消息

    2、有参考的node.js 发送get、post请求的两个方法

    /**
         * 用于处理 https Get请求方法
         * @param {String} url 请求地址 
         */
        this.requestGet = function (url) {
            return new Promise(function (resolve, reject) {
                https.get(url, function (res) {
                    var buffer = [],
                        result = "";
                    //监听 data 事件
                    res.on('data', function (data) {
                        buffer.push(data);
                    });
                    //监听 数据传输完成事件
                    res.on('end', function () {
                        result = Buffer.concat(buffer).toString('utf-8');
                        //将最后结果返回
                        resolve(result);
                    });
                }).on('error', function (err) {
                    reject(err);
                });
            });
        }
    node.js 发送get请求
        /**
         * 用于处理 https Post请求方法
         * @param {String} url  请求地址
         * @param {JSON} data 提交的数据
         */
        this.requestPost = function (url, data) {
            return new Promise(function (resolve, reject) {
                //解析 url 地址
                var urlData = urltil.parse(url);
                //设置 https.request  options 传入的参数对象
                var options = {
                    //目标主机地址
                    hostname: urlData.hostname,
                    //目标地址 
                    path: urlData.path,
                    //请求方法
                    method: 'POST',
                    //头部协议
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded',
                        'Content-Length': Buffer.byteLength(data, 'utf-8')
                    }
                };
                var req = https.request(options, function (res) {
                        var buffer = [],
                            result = '';
                        //用于监听 data 事件 接收数据
                        res.on('data', function (data) {
                            buffer.push(data);
                        });
                        //用于监听 end 事件 完成数据的接收
                        res.on('end', function () {
                            result = Buffer.concat(buffer).toString('utf-8');
                            resolve(result);
                        })
                    })
                    //监听错误事件
                    .on('error', function (err) {
                        console.log(err);
                        reject(err);
                    });
                //传入数据
                req.write(data);
                req.end();
            });
        }
    }
    node.js 发送post请求

      注:使用前需要引用内置的https中间件

    3、关于access_token

      调用微信接口的时候都需要发送微信接口凭证access_token(除微信网页授权中获取的access_token并不是这个access_token),因此请求微信接口的第一步都是要先获取这个access_oken

    /**
     * 获取微信 access_token
     */
    WeChat.prototype.getAccessToken = function () {
        var that = this;
        return new Promise(function (resolve, reject) {
            //获取当前时间 
            var currentTime = new Date().getTime();
            //格式化请求地址
            var url = util.format(that.apiURL.accessTokenApi, that.apiDomain, that.appID, that.appScrect);
            //判断 本地存储的 access_token 是否有效
            if (accessTokenJson.access_token === "" || accessTokenJson.expires_time < currentTime) {
                that.requestGet(url).then(function (data) {
                    var result = JSON.parse(data);
                    if (data.indexOf("errcode") < 0) {
                        accessTokenJson.access_token = result.access_token;
                        accessTokenJson.expires_time = new Date().getTime() + (parseInt(result.expires_in) - 200) * 1000;
                        //更新本地存储的
                        fs.writeFile('./wechat/access_token.json', JSON.stringify(accessTokenJson));
                        //将获取后的 access_token 返回
                        resolve(accessTokenJson.access_token);
                    } else {
                        //将错误返回
                        resolve(result);
                    }
                });
            } else {
                //将本地存储的 access_token 返回
                resolve(accessTokenJson.access_token);
            }
        });
    }
    获取access_token
    var url = util.format(that.apiURL.accessTokenApi, that.apiDomain, that.appID, that.appScrect);

    url参数依次为:

    that.apiURL.accessTokenApi:"%scgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
    that.apiDomain:"https://api.weixin.qq.com/"
    that.appID:公众号的appID
    that.appScrect:公众号的密匙

    请求后获取的accee_token 有效时间为7200s,在此时间内,再次请求微信接口都可以使用已经缓存的accee_token

    4、关于菜单

      最好是在请求接口的时候加入请求密匙(如oauth2认证),尤其是更新和删除

    git:https://github.com/wuyongxian20/wechat-api

  • 相关阅读:
    静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?
    编写多线程程序有几种实现方式?
    文件拷贝
    如何实现对象克隆?
    c#如何保存richtextbox的rtf格式
    C#实现文件与二进制互转并存入数据库
    c#中绝对路径和相对路径
    C#实现MySQL数据库中的blob数据存储
    CSS控制文字,超出显示省略号
    ES6 语句判断简写
  • 原文地址:https://www.cnblogs.com/eye-like/p/11782610.html
Copyright © 2020-2023  润新知