您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Koa2微信公眾號開發之消息管理的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
接收消息
當普通微信用戶向公眾賬號發消息時,微信服務器將POST消息的XML數據包到開發者填寫的URL上。
接收普通消息數據格式
XML的結構基本固定,不同的消息類型略有不同。
用戶發送文本消息時,微信公眾賬號接收到的XML數據格式如下所示:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>createTime</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml>
用戶發送圖片消息時,微信公眾賬號接收到的XML數據格式如下所示:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[image]]></MsgType> <PicUrl><![CDATA[this is a url]]></PicUrl> <MediaId><![CDATA[media_id]]></MediaId> <MsgId>1234567890123456</MsgId> </xml>
其他消息消息類型的結構請查閱【微信公眾平臺開發文檔】
對于POST請求的處理,koa2沒有封裝獲取參數的方法,需要通過自己解析上下文context中的原生node.js請求對象request。我們將用到row-body這個模塊來拿到數據。
先來優化之前的代碼
這一節的代碼緊接著上一屆實現的代碼,在上一屆的基礎上輕微改動了下。
'use strict' const Koa = require('koa') const app = new Koa() const crypto = require('crypto') // 將配置文件獨立到config.js const config = require('./config') app.use(async ctx => { // GET 驗證服務器 if (ctx.method === 'GET') { const { signature, timestamp, nonce, echostr } = ctx.query const TOKEN = config.wechat.token let hash = crypto.createHash('sha1') const arr = [TOKEN, timestamp, nonce].sort() hash.update(arr.join('')) const shasum = hash.digest('hex') if (shasum === signature) { return ctx.body = echostr } ctx.status = 401 ctx.body = 'Invalid signature' } else if (ctx.method === 'POST') { // POST接收數據 // TODO } }); app.listen(7001);
這兒我們在只在GET中驗證了簽名值是否合法,實際上我們在POST中也應該驗證簽名。
將簽名驗證寫成一個函數
function getSignature (timestamp, nonce, token) { let hash = crypto.createHash('sha1') const arr = [token, timestamp, nonce].sort() hash.update(arr.join('')) return hash.digest('hex') }
優化代碼,再POST中也加入驗證
... app.use(async ctx => { const { signature, timestamp, nonce, echostr } = ctx.query const TOKEN = config.wechat.token if (ctx.method === 'GET') { if (signature === getSignature(timestamp, nonce, TOKEN)) { return ctx.body = echostr } ctx.status = 401 ctx.body = 'Invalid signature' }else if (ctx.method === 'POST') { if (signature !== getSignature(timestamp, nonce, TOKEN)) { ctx.status = 401 return ctx.body = 'Invalid signature' } // TODO } }); ...
到這兒我們都沒有開始實現接受XML數據包的功能,而是在修改之前的代碼。這是為了演示在實際開發中的過程,寫任何代碼都不是一步到位的,好的代碼都是改出來的。
2.3 接收公眾號普通消息的XML數據包
現在開始進入本節的重點,接受XML數據包并轉為JSON
$ npm install raw-body --save
... const getRawBody = require('raw-body') ... // TODO // 取原始數據 const xml = await getRawBody(ctx.req, { length: ctx.request.length, limit: '1mb', encoding: ctx.request.charset || 'utf-8' }); console.log(xml) return ctx.body = 'success' // 直接回復success,微信服務器不會對此作任何處理
給你的測試號發送文本消息,你可以在命令行看見打印出如下數據
<xml> <ToUserName><![CDATA[gh_9d2d49e7e006]]></ToUserName> <FromUserName><![CDATA[oBp2T0wK8lM4vIkmMTJfFpk6Owlo]]></FromUserName> <CreateTime>1516940059</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[JavaScript之禪]]></Content> <MsgId>6515207943908059832</MsgId> </xml>
恭喜,到此你已經可以接收到XML數據了。? 但是我們還需要將XML轉為JSON方便我們的使用,我們將用到xml2js這個包
$ npm install xml2js --save
我們需要寫一個解析XML的異步函數,返回一個Promise對象
function parseXML(xml) { return new Promise((resolve, reject) => { xml2js.parseString(xml, { trim: true, explicitArray: false, ignoreAttrs: true }, function (err, result) { if (err) { return reject(err) } resolve(result.xml) }) }) }
接著調用parseXML方法,并打印出結果
... const formatted = await parseXML(xml) console.log(formatted) return ctx.body = 'success'
一切正常的話*(實際開發中你可能會遇到各種問題)*,命令行將打印出如下JSON數據
{ ToUserName: 'gh_9d2d49e7e006', FromUserName: 'oBp2T0wK8lM4vIkmMTJfFpk6Owlo', CreateTime: '1516941086', MsgType: 'text', Content: 'JavaScript之禪', MsgId: '6515212354839473910' }
到此,我們就能處理微信接收到的消息了,你可以自己測試關注、取消關注、發送各種類型的消息看看這個類型的消息所對應的XML數據格式都是怎么樣的
回復消息
當用戶發送消息給公眾號時(或某些特定的用戶操作引發的事件推送時),會產生一個POST請求,開發者可以在響應包(Get)中返回特定XML結構,來對該消息進行響應(現支持回復文本、圖片、圖文、語音、視頻、音樂)。嚴格來說,發送被動響應消息其實并不是一種接口,而是對微信服務器發過來消息的一次回復。
被動回復用戶消息數據格式
前面說了交互的數據格式為XML,接收消息是XML的,我們回復回去也應該是XML。
微信公眾賬號回復用戶文本消息時的XML數據格式如下所示:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[你好]]></Content> </xml>
微信公眾賬號回復用戶圖片消息時的XML數據格式如下所示:
<xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[image]]></MsgType> <Image><MediaId><![CDATA[media_id]]></MediaId></Image> </xml>
篇幅所限就不一一列舉了,請查閱【微信公眾平臺開發文檔】
前面的代碼都是直接回復success,不做任何處理。先來擼一個自動回復吧。收到消息后就回復這兒是JavaScript之禪
// return ctx.body = 'success' // 直接success ctx.type = 'application/xml' return ctx.body = `<xml> <ToUserName><![CDATA[${formatted.FromUserName}]]></ToUserName> <FromUserName><![CDATA[${formatted.ToUserName}]]></FromUserName> <CreateTime>${new Date().getTime()}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[這兒是JavaScript之禪]]></Content> </xml>`
使用ejs模板引擎處理回復內容
從這一小段代碼中可以看出,被動回復消息就是把你想要回復的內容按照約定的XML格式返回即可。但是一段一段的拼XML那多麻煩。我們來加個模板引擎方便我們處理XML。模板引擎有很多,ejs 是其中一種,它使用起來十分簡單
首先下載并引入ejs
$ npm install ejs --save
如果你之前沒用過現在只需要記住下面這幾個語法,以及ejs.compile()方法
<% code %>:運行 JavaScript 代碼,不輸出
<%= code %>:顯示轉義后的 HTML內容
<%- code %>:顯示原始 HTML 內容
可以先看看這個ejs的小demo:
const ejs = require('ejs') let tpl = ` <xml> <ToUserName><![CDATA[<%-toUsername%>]]></ToUserName> <FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName> <CreateTime><%=createTime%></CreateTime> <MsgType><![CDATA[<%=msgType%>]]></MsgType> <Content><![CDATA[<%-content%>]]></Content> </xml> ` const compiled = ejs.compile(tpl) let mess = compiled({ toUsername: '1234', fromUsername: '12345', createTime: new Date().getTime(), msgType: 'text', content: 'JavaScript之禪', }) console.log(mess) /* 將打印出如下信息 *================ <xml> <ToUserName><![CDATA[1234]]></ToUserName> <FromUserName><![CDATA[12345]]></FromUserName> <CreateTime>1517037564494</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[JavaScript之禪]]></Content> </xml> */
現在來編寫被動回復消息的模板,各種if else,這兒就直接貼代碼了
<xml> <ToUserName><![CDATA[<%-toUsername%>]]></ToUserName> <FromUserName><![CDATA[<%-fromUsername%>]]></FromUserName> <CreateTime><%=createTime%></CreateTime> <MsgType><![CDATA[<%=msgType%>]]></MsgType> <% if (msgType === 'news') { %> <ArticleCount><%=content.length%></ArticleCount> <Articles> <% content.forEach(function(item){ %> <item> <Title><![CDATA[<%-item.title%>]]></Title> <Description><![CDATA[<%-item.description%>]]></Description> <PicUrl><![CDATA[<%-item.picUrl || item.picurl || item.pic || item.thumb_url %>]]></PicUrl> <Url><![CDATA[<%-item.url%>]]></Url> </item> <% }); %> </Articles> <% } else if (msgType === 'music') { %> <Music> <Title><![CDATA[<%-content.title%>]]></Title> <Description><![CDATA[<%-content.description%>]]></Description> <MusicUrl><![CDATA[<%-content.musicUrl || content.url %>]]></MusicUrl> <HQMusicUrl><![CDATA[<%-content.hqMusicUrl || content.hqUrl %>]]></HQMusicUrl> </Music> <% } else if (msgType === 'voice') { %> <Voice> <MediaId><![CDATA[<%-content.mediaId%>]]></MediaId> </Voice> <% } else if (msgType === 'image') { %> <Image> <MediaId><![CDATA[<%-content.mediaId%>]]></MediaId> </Image> <% } else if (msgType === 'video') { %> <Video> <MediaId><![CDATA[<%-content.mediaId%>]]></MediaId> <Title><![CDATA[<%-content.title%>]]></Title> <Description><![CDATA[<%-content.description%>]]></Description> </Video> <% } else { %> <Content><![CDATA[<%-content%>]]></Content> <% } %> </xml>
現在就可以使用我們寫好的模板回復XML消息了
... const formatted = await parseXML(xml) console.log(formatted) let info = {} let type = 'text' info.msgType = type info.createTime = new Date().getTime() info.toUsername = formatted.FromUserName info.fromUsername = formatted.ToUserName info.content = 'JavaScript之禪' return ctx.body = compiled(info)
我們可以把這個回復消息的功能寫成一個函數
function reply (content, fromUsername, toUsername) { var info = {} var type = 'text' info.content = content || '' // 判斷消息類型 if (Array.isArray(content)) { type = 'news' } else if (typeof content === 'object') { if (content.hasOwnProperty('type')) { type = content.type info.content = content.content } else { type = 'music' } } info.msgType = type info.createTime = new Date().getTime() info.toUsername = toUsername info.fromUsername = fromUsername return compiled(info) }
在回復消息的時候直接調用這個方法即可
... const formatted = await parseXML(xml) console.log(formatted) const content = 'JavaScript之禪' const replyMessageXml = reply(content, formatted.ToUserName, formatted.FromUserName) return ctx.body = replyMessageXml
現在為了測試我們所寫的這個功能,來實現一個【學我說話】的功能:
回復音樂將返回一個音樂類型的消息,回復文本圖片,語音,公眾號將返回同樣的內容,當然了你可以在這個基礎上進行各種發揮。
.... const formatted = await parseXML(xml) console.log(formatted) let content = '' if (formatted.Content === '音樂') { content = { type: 'music', content: { title: 'Lemon Tree', description: 'Lemon Tree', musicUrl: 'http://mp3.com/xx.mp3' }, } } else if (formatted.MsgType === 'text') { content = formatted.Content } else if (formatted.MsgType === 'image') { content = { type: 'image', content: { mediaId: formatted.MediaId }, } } else if (formatted.MsgType === 'voice') { content = { type: 'voice', content: { mediaId: formatted.MediaId }, } } else { content = 'JavaScript之禪' } const replyMessageXml = reply(content, formatted.ToUserName, formatted.FromUserName) console.log(replyMessageXml) ctx.type = 'application/xml' return ctx.body = replyMessageXml
nice,到此時我們的測試號已經能夠根據我們的消息做出相應的回應了
關于“Koa2微信公眾號開發之消息管理的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。