項目結構html
>config/wechat.json 微信公衆號的配置文件node
>controllers/oauth.js 微信網頁受權接口(下一篇再細講講)git
>controllers/wechat.js 微信公衆號接口(包括接入接口和其餘調用微信api的接口)github
>wechat/access_token.json 請求微信api接口以前都須要使用的access_token數據庫
>wechat/crytoGraphy.js 加密解密文件(這裏使用的是明文方式,未用到)json
>wechat/menus.json 微信公衆號的自定義菜單api
>wechat/wechat.js 調用微信api的方法緩存
項目的架構說明和使用步驟能夠參考前面一篇的‘node.js 接口調用示例’:http://www.javashuo.com/article/p-speryjjv-ba.html服務器
一些說明點微信
一、公衆號的接入接口和接收消息接口
兩個接口的地址是一致的,區別是:
> 接入接口是GET請求,須要的是對get的接口參數進行解析驗證,而後按需返回就能夠了
> 接收消息接口是POST請求,須要先解析微信發送過來的消息,而後根據狀況決定是否返回消息
WeChat.prototype.handleMsg = async function (ctx) { return new Promise((resolve, reject) => { // let req = ctx.request; // let res = ctx.response; let req = ctx.req; var buffer = [], that = this; //實例微信消息加解密 // var cryptoGraphy = new CryptoGraphy(that.config,ctx.request); //監聽 data 事件 用於接收數據 req.on('data', function (data) { // logger.info("on data", data); buffer.push(data); }); req.on('end', function () { // logger.info("on end"); var msgXml = Buffer.concat(buffer).toString('utf-8'); parseString(msgXml, { explicitArray: false }, function (err, result) { // logger.info("on result", result); result = result.xml; resolve(result) }) }); }) },
* 接收消息使用的是Promise函數封裝,目的是將微信的收發消息兩塊作隔離
* 涉及到node.js 接收post參數的方式,須要ctx.req.on('data',function(){})方法接受參數,並在ctx.req.on(‘end’,function(){})函數中接受最終的獲取參數
* 由於微信發過來的消息是xml格式,因此在node.js 中須要xml2js模塊(非node.js內置模塊,須要先安裝依賴)將xml文件解析
/** * 微信消息回覆 * @param {ctx} context 對象 * @param {result} 微信消息 */ WeChat.prototype.responseMsg = function (ctx, result) { var toUser = result.ToUserName; //接收方微信 var fromUser = result.FromUserName; //發送仿微信 var reportMsg = ""; //聲明回覆消息的變量 if (result.MsgType.toLowerCase() === "event") { //判斷事件類型 switch (result.Event.toLowerCase()) { case 'subscribe': //回覆消息 var content = "歡迎關注 線上隨訪 公衆號\n"; content += "咱們致力於幫助出院康復病人與醫生創建便捷的溝通渠道~\n"; reportMsg = msg.txtMsg(fromUser, toUser, content); break; case 'click': var contentArr = [{ Title: "Node.js 微信自定義菜單", Description: "使用Node.js實現自定義微信菜單", PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast", Url: "http://blog.csdn.net/hvkcoder/article/details/72868520" }, { Title: "Node.js access_token的獲取、存儲及更新", Description: "Node.js access_token的獲取、存儲及更新", PicUrl: "http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast", Url: "http://blog.csdn.net/hvkcoder/article/details/72783631" }, { Title: "Node.js 接入微信公衆平臺開發", Description: "Node.js 接入微信公衆平臺開發", PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast", Url: "http://blog.csdn.net/hvkcoder/article/details/72765279" } ]; //回覆圖文消息 reportMsg = msg.graphicMsg(fromUser, toUser, contentArr); break; } } else { //判斷消息類型爲 文本消息 if (result.MsgType.toLowerCase() === "text") { switch (result.Content) { case '1': reportMsg = msg.txtMsg(fromUser, toUser, 'Hello ,線上隨訪公衆號開通了,快來使用吧……'); break; case '2': reportMsg = msg.txtMsg(fromUser, toUser, 'Ha Ha,我還有更多的功能待發掘呢,敬請期待吧……'); break; case '文章': var contentArr = [{ Title: "Node.js 微信自定義菜單", Description: "使用Node.js實現自定義微信菜單", PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast", Url: "http://blog.csdn.net/hvkcoder/article/details/72868520" }, { Title: "Node.js access_token的獲取、存儲及更新", Description: "Node.js access_token的獲取、存儲及更新", PicUrl: "http://img.blog.csdn.net/20170528151333883?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast", Url: "http://blog.csdn.net/hvkcoder/article/details/72783631" }, { Title: "Node.js 接入微信公衆平臺開發", Description: "Node.js 接入微信公衆平臺開發", PicUrl: "http://img.blog.csdn.net/20170605162832842?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHZrQ29kZXI=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast", Url: "http://blog.csdn.net/hvkcoder/article/details/72765279" } ]; //回覆圖文消息 reportMsg = msg.graphicMsg(fromUser, toUser, contentArr); break; default: reportMsg = msg.txtMsg(fromUser, toUser, '沒有這個選項哦'); break; } // logger.info("on reportMsg", reportMsg); } } //返回給微信服務器 ctx.body = reportMsg },
# 主要任務是根據微信消息的類型‘MsgType’,來執行不一樣的動做
# 事件推送消息(MsgType==‘event’)
* 關注取消事件 subscribe,unsubscribe
此時能夠作數據庫接入錄入訂閱者信息或者更新訂閱者信息
* 掃描帶參數二維碼事件
用戶未關注時:qrscene_ +二維碼參數值
用戶已關注時:scan
* 上報地理位置事件 LOCATION
* 自定義菜單事件 CLICK
# 普通消息推送 (MsgType=='text')
可分爲 文本消息、圖片消息、語音消息、視頻消息、小視頻消息、地理位置消息、連接消息
二、有參考的node.js 發送get、post請求的兩個方法
/** * 用於處理 https Get請求方法 * @param {String} url 請求地址 */ this.requestGet = function (url) { return new Promise(function (resolve, reject) { https.get(url, function (res) { var buffer = [], result = ""; //監聽 data 事件 res.on('data', function (data) { buffer.push(data); }); //監聽 數據傳輸完成事件 res.on('end', function () { result = Buffer.concat(buffer).toString('utf-8'); //將最後結果返回 resolve(result); }); }).on('error', function (err) { reject(err); }); }); }
/** * 用於處理 https Post請求方法 * @param {String} url 請求地址 * @param {JSON} data 提交的數據 */ this.requestPost = function (url, data) { return new Promise(function (resolve, reject) { //解析 url 地址 var urlData = urltil.parse(url); //設置 https.request options 傳入的參數對象 var options = { //目標主機地址 hostname: urlData.hostname, //目標地址 path: urlData.path, //請求方法 method: 'POST', //頭部協議 headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(data, 'utf-8') } }; var req = https.request(options, function (res) { var buffer = [], result = ''; //用於監聽 data 事件 接收數據 res.on('data', function (data) { buffer.push(data); }); //用於監聽 end 事件 完成數據的接收 res.on('end', function () { result = Buffer.concat(buffer).toString('utf-8'); resolve(result); }) }) //監聽錯誤事件 .on('error', function (err) { console.log(err); reject(err); }); //傳入數據 req.write(data); req.end(); }); } }
注:使用前須要引用內置的https中間件
三、關於access_token
調用微信接口的時候都須要發送微信接口憑證access_token(除微信網頁受權中獲取的access_token並非這個access_token),所以請求微信接口的第一步都是要先獲取這個access_oken
/** * 獲取微信 access_token */ WeChat.prototype.getAccessToken = function () { var that = this; return new Promise(function (resolve, reject) { //獲取當前時間 var currentTime = new Date().getTime(); //格式化請求地址 var url = util.format(that.apiURL.accessTokenApi, that.apiDomain, that.appID, that.appScrect); //判斷 本地存儲的 access_token 是否有效 if (accessTokenJson.access_token === "" || accessTokenJson.expires_time < currentTime) { that.requestGet(url).then(function (data) { var result = JSON.parse(data); if (data.indexOf("errcode") < 0) { accessTokenJson.access_token = result.access_token; accessTokenJson.expires_time = new Date().getTime() + (parseInt(result.expires_in) - 200) * 1000; //更新本地存儲的 fs.writeFile('./wechat/access_token.json', JSON.stringify(accessTokenJson)); //將獲取後的 access_token 返回 resolve(accessTokenJson.access_token); } else { //將錯誤返回 resolve(result); } }); } else { //將本地存儲的 access_token 返回 resolve(accessTokenJson.access_token); } }); }
var url = util.format(that.apiURL.accessTokenApi, that.apiDomain, that.appID, that.appScrect);
url參數依次爲:
that.apiURL.accessTokenApi:"%scgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
that.apiDomain:"https://api.weixin.qq.com/"
that.appID:公衆號的appID
that.appScrect:公衆號的密匙
請求後獲取的accee_token 有效時間爲7200s,在此時間內,再次請求微信接口均可以使用已經緩存的accee_token
四、關於菜單
最好是在請求接口的時候加入請求密匙(如oauth2認證),尤爲是更新和刪除