微信向第三方服务器发送请求时会降 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;