NodeJS实现微信公众号关注后自动回复功能


Posted in NodeJs onMay 31, 2017

一 实先自动回复功能的逻辑步骤

1 处理POST类型的控制逻辑,接收XML的数据包;

2 解析XML数据包(获得数据包的消息类型或者是事件类型);

3 拼装我们定义好的消息;

4 包装成XML格式;

5 在5秒内返回回去

二 代码实操

本节代码参照上节课继续修改和完善,目录结构跟之前相同,新引入的模块raw-body使用npm install安装一下即可,app.js启动文件和util.js不做变动,主要修改一下generator.js文件,以及在generator.js同级目录下新建wechat.js文件和tools.js文件。

wechat.js文件是将前一节generator.js文件中票据部分的代码抽离出来单独放在一个文件中,具体代码如下:

'use strict';
// 引入模块
var Promise = require('bluebird');
var request = Promise.promisify(require('request'));

//增加url配置项
var prefix = 'https://api.weixin.qq.com/cgi-bin/';
var api = {
  accessToken: prefix + 'token?grant_type=client_credential'
};

//利用构造函数生成实例 完成票据存储逻辑
function weChat(opts) {
  var that = this;
  this.appID = opts.appID;
  this.appSecret = opts.appSecret;
  this.getAccessToken = opts.getAccessToken;
  this.saveAccessToken = opts.saveAccessToken;
  //获取票据的方法
  this.getAccessToken()
    .then(function(data) {
      //从静态文件获取票据,JSON化数据,如果有异常,则尝试更新票据
      try {
        data = JSON.parse(data);
      } catch (e) {
        return that.updateAccessToken();
      }
      //判断票据是否在有效期内,如果合法,向下传递票据,如果不合法,更新票据
      if (that.isValidAccessToken(data)) {
        Promise.resolve(data);
      } else {
        return that.updateAccessToken();
      }
    })
    //将拿到的票据信息和有效期信息存储起来
    .then(function(data) {
      //console.log(data);
      that.access_token = data.access_token;
      that.expires_in = data.expires_in;

      that.saveAccessToken(data);
    })
};

//在weChat的原型链上增加验证有效期的方法
weChat.prototype.isValidAccessToken = function(data) {
  //进行判断,如果票据不合法,返回false
  if (!data || !data.access_token || !data.expires_in) {
    return false;
  }
  //拿到票据和过期时间的数据
  var access_token = data.access_token;
  var expires_in = data.expires_in;
  //获取当前时间
  var now = (new Date().getTime());
  //如果当前时间小于票据过期时间,返回true,否则返回false
  if (now < expires_in) {
    return true;
  } else {
    return false;
  };
};

//在weChat的原型链上增加更新票据的方法
weChat.prototype.updateAccessToken = function() {
  var appID = this.appID;
  var appSecret = this.appSecret;
  var url = api.accessToken + '&appid=' + appID + '&secret=' + appSecret;

  return new Promise(function(resolve, reject) {
    //使用request发起请求
    request({
      url: url,
      json: true
    }).then(function(response) {
      var data = response.body;
      var now = (new Date().getTime());
      var expires_in = now + (data.expires_in - 20) * 1000;
      //把新票据的有效时间赋值给data
      data.expires_in = expires_in;
      resolve(data);
    })
  })
};

//向外暴露weChat
module.exports = weChat;

generator.js文件进行精简后,添加判断对xml数据的格式化方法以及判断事件,添加关注事件测试信息,具体代码如下:

'use strict';
// 引入模块
var sha1 = require('sha1');
var getRawBody = require('raw-body');
var weChat = require('./wechat');
var tools = require('./tools');

// 建立中间件函数并暴露出去
module.exports = function(opts) {
  //实例化weChat()函数
  //var wechat = new weChat(opts);
  return function*(next) {
    //console.log(this.query);
    var that = this;
    var token = opts.token;
    var signature = this.query.signature;
    var nonce = this.query.nonce;
    var timestamp = this.query.timestamp;
    var echostr = this.query.echostr;
    // 进行字典排序
    var str = [token, timestamp, nonce].sort().join('');
    // 进行加密
    var sha = sha1(str);
    //使用this.method对请求方法进行判断
    if (this.method === 'GET') {
      // 如果是get请求 判断加密后的值是否等于签名值
      if (sha === signature) {
        this.body = echostr + '';
      } else {
        this.body = 'wrong';
      };
    } else if (this.method === 'POST') {
      //如果是post请求 也是先判断签名是否合法 如果不合法 直接返回wrong
      if (sha !== signature) {
        this.body = 'wrong';
        return false;
      };
      //通过raw-body模块 可以把把this上的request对象 也就是http模块中的request对象 去拼装它的数据 最终拿到一个buffer的xml数据
      //通过yield关键字 获取到post过来的原始的XML数据
      var data = yield getRawBody(this.req, {
        length: this.length,
        limit: '1mb',
        encoding: this.charset
      });
      //console.log(data.toString());打印XML数据(当微信公众号有操作的时候 终端可以看到返回的XML数据)
      //tools为处理XML数据的工具包 使用tools工具包的parseXMLAsync方法 把XML数据转化成数组对象
      var content = yield tools.parseXMLAsync(data);
      //console.log(content);打印转化后的数组对象
      //格式化content数据为json对象
      var message = tools.formatMessage(content.xml);
      console.log(message);
      //打印message
      //判断message的MsgType 如果是event 则是一个事件
      if (message.MsgType === 'event') {
        //如果是订阅事件
        if (message.Event === 'subscribe') {
          //获取当前时间戳
          var now = new Date().getTime();
          //设置回复状态是200
          that.status = 200;
          //设置回复的类型是xml格式
          that.type = 'application/xml';
          //设置回复的主体
          that.body = '<xml>' +
            '<ToUserName><![CDATA[' + message.FromUserName + ']]></ToUserName>' +
            '<FromUserName><![CDATA[' + message.ToUserName + ']]></FromUserName>' +
            '<CreateTime>' + now + '</CreateTime>' +
            '<MsgType><![CDATA[text]]></MsgType>' +
            '<Content><![CDATA[你好,同学!]]></Content>' +
            '</xml>';
          return;
        }
      }
    }

  }
};

tools.js是处理XML数据的工具文件:

'use strict';
//引入模块
var xml2js = require('xml2js');
var Promise = require('bluebird');
//导出解析XML的方法
exports.parseXMLAsync = function(xml) {
  return new Promise(function(resolve, reject) {
    xml2js.parseString(xml, { trim: true }, function(err, content) {
      if (err) {
        reject(err);
      } else {
        resolve(content);
      };
    });
  });
};
//因为value值可能是嵌套多层的 所以先对value值进行遍历
function formatMessage(result) {
  //声明空对象message
  var message = {};
  //对result类型进行判断
  if (typeof result === 'object') {
    //如果是object类型 通过Object.keys()方法拿到result所有的key 并存入keys变量中
    var keys = Object.keys(result);
    //对keys进行循环遍历
    for (var i = 0; i < keys.length; i++) {
      //拿到每个key对应的value值
      var item = result[keys[i]];
      //拿到key
      var key = keys[i];
      //判断item是否为数组或者长度是否为0
      if (!(item instanceof Array) || item.length === 0) {
        //如果item不是数组或者长度为0 则跳过继续向下解析
        continue;
      }
      //如果长度为1
      if (item.length === 1) {
        //拿到value值存入val变量
        var val = item[0];
        //判断val是否为对象
        if (typeof val === 'object') {
          //如果val为对象 则进一步进行遍历
          message[key] = formatMessage(val);
        } else {
          //如果不是对象 就把值赋给当前的key放入message里 并去除收尾空格
          message[key] = (val || '').trim();
        }
      }
      //如果item的长度既不是0也不是1 则说明它是一个数组
      else {
        //把message的key设置为空数组
        message[key] = [];
        //对数组进行遍历
        for (var j = 0, k = item.length; j < k; j++) {
          message[key].push(formatMessage(item[j]));
        }
      }
    }
  }
  return message;
}

exports.formatMessage = function(xml) {
  return new Promise(function(resolve, reject) {
    xml2js.parseString(xml, { trim: true }, function(err, content) {
      if (err) {
        reject(err);
      } else {
        resolve(content);
      };
    });
  });
};

exports.formatMessage = formatMessage;

完成这节的代码后,当关注微信测试公众号的时候,会自动回复『你好,同学!』的提示信息。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
nodejs实用示例 缩址还原
Dec 28 NodeJs
轻松创建nodejs服务器(10):处理上传图片
Dec 18 NodeJs
Nodejs如何复制文件
Mar 09 NodeJs
解析NodeJs的调试方法
Dec 11 NodeJs
NodeJs模拟登陆正方教务
Apr 28 NodeJs
详解Nodejs之静态资源处理
Jun 05 NodeJs
详解nodejs模板引擎制作
Jun 14 NodeJs
CentOS7中源码编译安装NodeJS的完整步骤
Oct 13 NodeJs
nodejs基础之buffer缓冲区用法分析
Dec 26 NodeJs
nodejs通过钉钉群机器人推送消息的实现代码
May 05 NodeJs
NodeJs实现简易WEB上传下载服务器
Aug 10 NodeJs
nodejs操作mysql实现增删改查的实例
May 28 #NodeJs
详解nodejs微信jssdk后端接口
May 25 #NodeJs
mac下的nodejs环境安装的步骤
May 24 #NodeJs
Nodejs搭建wss服务器教程
May 24 #NodeJs
Nodejs中Express 常用中间件 body-parser 实现解析
May 22 #NodeJs
深入理解nodejs中Express的中间件
May 19 #NodeJs
nodejs批量下载图片的实现方法
May 19 #NodeJs
You might like
利用PHP抓取百度阅读的方法示例
2016/12/18 PHP
php 判断IP为有效IP地址的方法
2018/01/28 PHP
Laravel 队列使用的实现
2019/01/08 PHP
Laravel5.5 手动分页和自定义分页样式的简单实现
2019/10/15 PHP
JS获取dom 对象 ajax操作 读写cookie函数
2009/11/18 Javascript
jQuery中绑定事件的命名空间详解
2011/04/05 Javascript
js使用DOM设置单选按钮、复选框及下拉菜单的方法
2015/01/20 Javascript
浅析javascript中的DOM
2015/03/01 Javascript
js轮播图代码分享
2016/07/14 Javascript
angularjs实现的前端分页控件示例
2017/02/10 Javascript
js使用generator函数同步执行ajax任务
2017/09/05 Javascript
javascript与PHP动态往类中添加方法对比
2018/03/21 Javascript
Layui 设置select下拉框自动选中某项的方法
2018/08/14 Javascript
详解vue路由篇(动态路由、路由嵌套)
2019/01/27 Javascript
微信小程序--特定区域滚动到顶部时固定的方法
2019/04/28 Javascript
了解JavaScript中的选择器
2019/05/24 Javascript
JavaScript从原型到原型链深入理解
2019/06/03 Javascript
mock.js模拟前后台交互
2019/07/25 Javascript
详解webpack打包vue项目之后生成的dist文件该怎么启动运行
2019/09/06 Javascript
p5.js临摹动态图形实现方法详解
2019/10/23 Javascript
解决vue中el-tab-pane切换的问题
2020/07/19 Javascript
jquery插件实现轮播图效果
2020/10/19 jQuery
Python BeautifulSoup中文乱码问题的2种解决方法
2014/04/22 Python
Python实现FTP上传文件或文件夹实例(递归)
2017/01/16 Python
python 实现得到当前时间偏移day天后的日期方法
2018/12/31 Python
基于python3 pyQt5 QtDesignner实现窗口化猜数字游戏功能
2019/07/15 Python
Django中间件拦截未登录url实例详解
2019/09/03 Python
python机器学习库xgboost的使用
2020/01/20 Python
为什么说python更适合树莓派编程
2020/07/20 Python
AVON雅芳官网:世界上最大的美容化妆品公司之一
2016/11/02 全球购物
惊艳的手工时装首饰:Migonne Gavigan
2018/02/23 全球购物
JAVA软件工程师测试题
2014/07/25 面试题
2015年领导干部廉洁自律工作总结
2015/05/26 职场文书
神州牡丹园的导游词
2019/11/20 职场文书
Python中可变和不可变对象的深入讲解
2021/08/02 Python
基于Python实现射击小游戏的制作
2022/04/06 Python