使用 nodeJs 开发微信公众号(设置自动回复消息)

微信向第三方服务器发送请求时会降 signature 、timestamp、 nonce 、 openid(用户标识),发送内容会以 xml 的形式附加在请求中

回复消息前提我们得拿到用户id , 用户发送内容等信息,用户发送内容格式参考微信官方文档:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453

想要获取用户发送信息,需要从请求中获得 xml ,因此需要用到 raw-body(获得原生请求体)

npm install raw-body --save

接下来需要将xml从请求中分离并且格式化成json

var getRawBody = require('raw-body')
var contentType = require('content-type')
var data = getRawBody(req, { length: req.headers['content-length'], limit: '1mb', encoding: contentType.parse(req).parameters.charset }, function(err, buf) { utils.formatMessage(buf.toString()).then(message => { //判断消息,做出回应 }) } )

我将格式化 xml 的操作封装在 formatMessage

var xml2js = require('xml2js')

exports.formatMessage = function(xml) {
    return new Promise((resolve, reject) => {
        
        // 接收文本信息格式
        // <xml> <ToUserName><![CDATA[toUser]]></ToUserName>
        // <FromUserName><![CDATA[fromUser]]></FromUserName>
        // <CreateTime>1348831860</CreateTime>
        // <MsgType><![CDATA[text]]></MsgType>
        // <Content><![CDATA[this is a test]]></Content>
        // <MsgId>1234567890123456</MsgId></xml>

        xml2js.parseString(xml, function(err, content) {
            var result = content.xml
            var message = {};
            if (typeof result === 'object') {
                var keys = Object.keys(result);
                for (var i = 0; i < keys.length; i++) {
                    var key = keys[i];
                    var item = result[key];
                    if (!(item instanceof Array) || item.length === 0) continue;
                    if (item.length === 1) {
                        var val = item[0];
                        if (typeof val === 'object') message[key] = formatMessage(val);
                        else message[key] = (val || '').trim();
                    } else {
                        message[key] = [];
                        for (var j = 0, k = item.length; j < k; j++) message[key].push(formatMessage(item[j]));
                    }
                }
            }
            resolve(message)
        })
    })
}

解析完成后我们可以拿到 FromUserName、MsgType 和 Content

MsgType可能是 event(事件)或者是 text (文本)

event类型有:subscribe,unsubscribe,LOCATION,CLICK,SCAN

根据 content中发送的内容,我们可以进行判断,返回自定义消息回复

微信规定我们返回的数据必须是xml格式的,格式参考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543

因此在返回信息前需要拼接内容成指定xml格式,我将拼接方法封装在 template.js 文件中,使用时只要直接调用即可

lib/template.js:

exports.textMessage = function(message){
    var createTime = new Date().getTime()
    return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${createTime}</CreateTime>
    <MsgType><![CDATA[text]]></MsgType>
    <Content><![CDATA[${message.reply}]]></Content>
    </xml>`
}

exports.imageMessage = function(message){
    var createTime = new Date().getTime()
    return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${createTime}</CreateTime>
    <MsgType><![CDATA[image]]></MsgType>
    <Image>
        <MediaId><![CDATA[${message.mediaId}]]></MediaId>
    </Image>
    </xml>`
}

exports.voiceMessage = function(message){
    var createTime = new Date().getTime()
    return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${createTime}</CreateTime>
    <MsgType><![CDATA[voice]]></MsgType>
    <Voice>
        <MediaId><![CDATA[${message.mediaId}]]></MediaId>
    </Voice>
    </xml>`
}

exports.videoMessage = function(message){
    var createTime = new Date().getTime()
    return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${createTime}</CreateTime>
    <MsgType><![CDATA[video]]></MsgType>
    <Video>
        <MediaId><![CDATA[${message.mediaId}]]></MediaId>
        <Title><![CDATA[${message.title}]]></Title>
        <Description><![CDATA[${message.description}]]></Description>
    </Video>
    </xml>`
}

exports.articleMessage = function(message){
    var createTime = new Date().getTime()
    return `<xml>
    <ToUserName><![CDATA[${message.FromUserName}]]></ToUserName>
    <FromUserName><![CDATA[${message.ToUserName}]]></FromUserName>
    <CreateTime>${createTime}</CreateTime>
    <MsgType><![CDATA[news]]></MsgType>
    <ArticleCount>${message.articles.length}</ArticleCount>
    <Articles>
        ${message.articles.map(article => 
            `<item><Title><![CDATA[${article.title}]]></Title>
                <Description><![CDATA[${article.description}]]></Description>
                <PicUrl><![CDATA[${article.img}]]></PicUrl>
                <Url><![CDATA[${article.url}]]></Url></item>`
        ).join('')}
    </Articles>
    </xml>`
}

自动回复整体流程:收到微信请求->校验是否来自微信->获取access_token->解析请求体xml->根据类型以及内容作出相应

回复代码:

var express = require('express')
var router = express.Router()
var getRawBody = require('raw-body')
var contentType = require('content-type')
var utils = require('../lib/utils.js')
var template = require('../lib/template.js')

// 微信官方请求回调接口
router.all('/', function(req, res, next) {
    var data = getRawBody(req, {
        length: req.headers['content-length'],
        limit: '1mb',
        encoding: contentType.parse(req).parameters.charset
    }, function(err, buf) {
        if (err) return next(err)
        utils.formatMessage(buf.toString()).then(message => {
            if (message.MsgType == 'event') {
                if (message.Event === 'subscribe') {
                    if (message.EventKey) {
                        console.log('扫描二维码关注:' + message.EventKey + ' ' + message.ticket);
                    }
                    message.reply = '终于等到你,还好我没放弃';
                } else if (message.Event === 'unsubscribe') {
                    message.reply = '';
                    console.log(message.FromUserName + ' 悄悄地走了...');
                } else if (message.Event === 'LOCATION') {
                    message.reply = '您上报的地理位置是:' + message.Latitude + ',' + message.Longitude;
                } else if (message.Event === 'CLICK') {
                    message.reply = '您点击了菜单:' + message.EventKey;
                } else if (message.Event === 'SCAN') {
                    message.reply = '关注后扫描二维码:' + message.Ticket;
                }
                res.send(template.textMessage(message))
            } else if (message.MsgType === 'text') {
                var content = message.Content
                if (content === '1') {
                    message.reply = '终于等到你'
                    res.send(template.textMessage(message))
                } else if (content === '2') {
                    message.mediaId = '需要发送图片的媒体id'
                    res.send(template.imageMessage(message))
                } else if (content === '3') {
                    message.articles = [{
                        title: '标题',
                        description: '描述',
                        picUrl: '图片路径,不需要事先上传',
                        url: '素材路径,素材需要事先上传'
                    }]
                    res.send(template.articleMessage(message))
                } else {
                    message.reply = '你说的话:“' + content + '”,我听不懂呀'
                    res.send(template.textMessage(message))
                }
            }

        })
    })

});

module.exports = router;

 

posted @ 2018-12-29 15:41  吃饭睡觉打豆豆o  阅读(1439)  评论(0编辑  收藏  举报