使用 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;