使用 nodeJs 開發微信公衆號(設置自動回覆消息)

微信向第三方服務器發送請求時會降 signature 、timestamp、 nonce 、 openid(用戶標識),發送內容會以 xml 的形式附加在請求中git

回覆消息前提咱們得拿到用戶id , 用戶發送內容等信息,用戶發送內容格式參考微信官方文檔:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453express

想要獲取用戶發送信息,須要從請求中得到 xml ,所以須要用到 raw-body(得到原生請求體)npm

npm install raw-body --save

接下來須要將xml從請求中分離而且格式化成jsonjson

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 (文本)ide

event類型有:subscribe,unsubscribe,LOCATION,CLICK,SCANui

根據 content中發送的內容,咱們能夠進行判斷,返回自定義消息回覆this

微信規定咱們返回的數據必須是xml格式的,格式參考:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140543url

所以在返回信息前須要拼接內容成指定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;
相關文章
相關標籤/搜索