首先須要確認一點,一旦接入第三方服務器,微信就認爲你已經具有了開發能力,像自動回覆、關鍵詞回覆、自定義菜單這些功能,微信公衆平臺就再也不提供了(須要開發者調用相關接口),停用服務器以後,這些功能也就恢復了,兩者是互斥的。javascript
說明一下,本文的例子是node+express搭建服務,消息加解密方式爲明文模式,請酌情參考。java
1、搭建本地調試環境,須要將本地服務穿透出去,便於外網訪問,能夠用花生殼或者ngrok等,能穿透內網就能夠,這裏就很少說了。node
2、服務器配置及校驗express
如今咱們已經有了一個可供外網訪問的本地服務,接下來講一下微信公衆平臺的相關配置:json
登陸微信公衆平臺,找到基本配置數組
能夠看到服務器配置一項(我這裏是已經啓用過的),點擊右側的修改配置服務器
先說一下大體流程,信息編輯完畢以後點擊提交,微信服務器會向你所填寫的URL發送一條get請求,你的服務器必需要能接到這條請求,而後拿微信服務器帶來的參數進行驗證,驗證完畢以後,返回驗證結果給微信,微信拿到想要的結果以後(至於具體返回什麼,後面會說),你的服務器就算是在微信服務器「備案」成功了,接下來點擊啓用就能夠了,啓用以後,微信服務器一旦收到消息,就會向你所填寫的URL發送一條post請求(確保你的服務器在5秒內作出響應,否則會發生一些錯誤,具體錯誤可查看微信文檔),請求攜帶的參數是xml格式的,注意配置一下,不要以json的形式去接收,解析xml,能拿到信息發送者、接收者、信息內容、事件類型等數據,而後就能夠根據事件類型、信息等作出相應的處理。微信
填寫URL:支持https和http,格式爲http://xxxxxx.com+接口路徑,例如https://www.baidu.com/authorize,https://www.baidu.com是你的服務地址,authotize是你的接口路徑。app
填寫Token:這兒的token是驗證服務器的令牌,是你本身定義的,符合格式要求就行,後面會用到(注意區別access_token,二者不是一回事)。微信公衆平臺
填寫EncodingAESKey:能夠隨機生成,也能夠本身定義,符合格式要求就好,當設置消息加解密方式爲加密模式時會用來解密消息(本文采用明文模式)。
選擇消息加解密方式:本文選擇明文模式。
服務器驗證:
// 服務器驗證 // /authorize爲接口路徑, router.get('/authorize', (req, res) => { //接收到微信服務器的請求後,取出參數signature,timestamp,echostr,nonce let signature = req.query.signature; let timestamp = req.query.timestamp; let echostr = req.query.echostr; let nonce = req.query.nonce; // 把token、timestamp、nonce進行字典排序,CONFIG.token換爲你本身的token就好 let arr = [CONFIG.token, timestamp, nonce].sort(); // sha1加密 let str = arr.join(''); let hashCode = crypto.createHash('sha1'); let result = hashCode.update(str).digest('hex'); // 與signature對比後返回結果 if (result === signature) { // 驗證正確以後,把echostr原封不動返回給微信就好了 res.send(echostr); } else { // 驗證錯誤的話也要返回信息,告訴微信不要再嘗試請求了,微信官方建議直接返回success字符串,固然返回空也是能夠的 res.send('success'); } });
必定要處理好服務器驗證邏輯以後再點擊提交按鈕,不然是提交不成功的。
提交成功以後,就算是接入服務器了,可是點擊啓用按鈕,服務器才能起做用。
接下來是消息處理邏輯:
微信服務器在接收到用戶消息以後,就會向你的服務器發送請求,URL和驗證服務器的URL同樣,只不過請求方式爲post
// 消息處理 // 用xml2js模塊來處理xml let parseString = require('xml2js').parseString; router.post('/authorize', (req, res) => { try { let buffer = []; // 監聽data事件,用於接收數據,用req.body是拿不到數據的 req.on('data', (data) => { buffer.push(data); }); // 監聽end事件,用於處理接收完成的數據 req.on('end', () => { parseString(Buffer.concat(buffer).toString('utf-8'), { explicitArray: false }, (err, result) => { // 處理錯誤 if (err) { console.log('解析微信服務器發來的消息出錯了:'); console.log(err); res.send('success'); return false; } if (!result || !result.xml) { // 未接收到有效消息,告訴微信服務器不要再嘗試鏈接 res.send('success'); return console.log('未接收到任何消息也未發生任何事件'); } result = result.xml; // 接收方微信(注意接收方和發送方的轉換) let toUser = result.FromUserName; // 發送方微信 let fromUser = result.ToUserName; let userMessage = result.Content; console.log('-----------------------開始處理消息-----------------------'); if (result.Event == 'subscribe') { // 若是是用戶關注 console.log('--------------------有用戶關注了---------------------------'); handleAutoReply(res, toUser, fromUser, 'subscribe'); } else { // 其餘消息 if (result.MsgType != 'text') { res.send('success'); console.log('------------------不是文本類型的消息暫不處理----------------------'); return false; } // 文本消息 // 這裏能夠處理一些特殊回覆,好比發送編碼查詢等 // 處理關鍵詞自動回覆 console.log('-----------------------如今處理關鍵詞回覆------------------------'); handleAutoReply(res, toUser, fromUser, userMessage); } }); }); } catch(err) { console.log(err); res.send('success'); } });
/** * [handleAutoReply description] * @param {Object} res [response對象] * @param {String} toUser [接收方] * @param {String} fromUser [發送方] * @param {String} keyword [關鍵詞] * @return {String} xmlContent [消息模板] */ function handleAutoReply(res, toUser, fromUser, keyword) { // messageMap是含有關鍵詞回覆key-value的json,根據不一樣的關鍵詞,向用戶發送不一樣消息 let messageMap = JSON.parse(JSON.stringify(messageJson)); let content = messageMap[keyword]; if (!content) { res.send('success'); return false; } let xml = returnText(toUser, fromUser, content); res.send(xml); }
/** * [returnText description] * @param {String} toUser [接收方] * @param {String} fromUser [發送方] * @param {String} content [消息內容] * @return {String} xmlContent [消息模板] */ function returnText(toUser, fromUser, content) { let xmlContent = `<xml><ToUserName><![CDATA[${toUser}]]></ToUserName> <FromUserName><![CDATA[${fromUser}]]></FromUserName> <CreateTime>${new Date().getTime()}</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[${content}]]></Content></xml>`; return xmlContent; }
必定要注意錯誤處理,微信收不到正確響應時,會嘗試從新請求,因此一旦程序發生未知錯誤,要及時處理,而且通知微信不要再嘗試發送請求了(發送success字符串便可),不然微信會提示用戶接入的服務器異常。
至此,消息回覆的邏輯已經處理完了。可是接入本身的服務器以後,以前在微信公衆平臺設置的自定義菜單也沒了,須要咱們調用接口去配置;
配置自定義菜單:
打開微信接口調試頁面:https://mp.weixin.qq.com/debug
輸入你的appid和secret,因爲配置自定義菜單以後,菜單就會一直存在,不須要代碼去維持,因此我選擇了在這兒獲取access_token,固然你也能夠在你的程序中去獲取,而後再寫個配置菜單的頁面,那就更方便了。
而後選擇接口類型爲自定義菜單:
access_token填你剛纔獲取的就好,注意這個是有時效的,通常爲7200秒,過時的話再從新獲取就行了。
body是你配置菜單的json,簡單講一下:
{ "button":[ { "type":"click", "name":"今日歌曲", "key":"V1001_TODAY_MUSIC" }, { "name":"菜單", "sub_button":[ { "type":"view", "name":"搜索", "url":"http://www.soso.com/" }, { "type":"miniprogram", "name":"wxa", "url":"http://mp.weixin.qq.com", "appid":"wx286b93c14bbf93aa", "pagepath":"pages/lunar/index" }, { "type":"click", "name":"贊一下咱們", "key":"V1001_GOOD" }] }] }
button是一級菜單數組,每一個元素表明一個一級菜單,注意一級菜單最多三個,每一個菜單最多4個字,超出顯示...,每一個一級菜單下的二級菜單最多5個,每一個二級菜單最多7個字,超出顯示...。
type是按鈕類型,根據須要選擇就好:
一、click:點擊推事件用戶點擊click類型按鈕後,微信服務器會經過消息接口推送消息類型爲event的結構給開發者(參考消息接口指南),而且帶上按鈕中開發者填寫的key值,開發者能夠經過自定義的key值與用戶進行交互; 二、view:跳轉URL用戶點擊view類型按鈕後,微信客戶端將會打開開發者在按鈕中填寫的網頁URL,可與網頁受權獲取用戶基本信息接口結合,得到用戶基本信息。 三、scancode_push:掃碼推事件用戶點擊按鈕後,微信客戶端將調起掃一掃工具,完成掃碼操做後顯示掃描結果(若是是URL,將進入URL),且會將掃碼的結果傳給開發者,開發者能夠下發消息。 四、scancode_waitmsg:掃碼推事件且彈出「消息接收中」提示框用戶點擊按鈕後,微信客戶端將調起掃一掃工具,完成掃碼操做後,將掃碼的結果傳給開發者,同時收起掃一掃工具,而後彈出「消息接收中」提示框,隨後可能會收到開發者下發的消息。 五、pic_sysphoto:彈出系統拍照發圖用戶點擊按鈕後,微信客戶端將調起系統相機,完成拍照操做後,會將拍攝的相片發送給開發者,並推送事件給開發者,同時收起系統相機,隨後可能會收到開發者下發的消息。 六、pic_photo_or_album:彈出拍照或者相冊發圖用戶點擊按鈕後,微信客戶端將彈出選擇器供用戶選擇「拍照」或者「從手機相冊選擇」。用戶選擇後即走其餘兩種流程。 七、pic_weixin:彈出微信相冊發圖器用戶點擊按鈕後,微信客戶端將調起微信相冊,完成選擇操做後,將選擇的相片發送給開發者的服務器,並推送事件給開發者,同時收起相冊,隨後可能會收到開發者下發的消息。 八、location_select:彈出地理位置選擇器用戶點擊按鈕後,微信客戶端將調起地理位置選擇工具,完成選擇操做後,將選擇的地理位置發送給開發者的服務器,同時收起位置選擇工具,隨後可能會收到開發者下發的消息。 九、media_id:下發消息(除文本消息)用戶點擊media_id類型按鈕後,微信服務器會將開發者填寫的永久素材id對應的素材下發給用戶,永久素材類型能夠是圖片、音頻、視頻、圖文消息。請注意:永久素材id必須是在「素材管理/新增永久素材」接口上傳後得到的合法id。 十、view_limited:跳轉圖文消息URL用戶點擊view_limited類型按鈕後,微信客戶端將打開開發者在按鈕中填寫的永久素材id對應的圖文消息URL,永久素材類型只支持圖文消息。請注意:永久素材id必須是在「素材管理/新增永久素材」接口上傳後得到的合法id。
根據須要,組織好你的json,填入body輸入框就好了,點擊檢查問題,若是檢查經過,菜單就建立成功了,檢查失敗的話,再具體看一下報錯信息。首次設置會當即生效,修改的話須要5分鐘才刷新,能夠選擇先取消關注公衆號,而後再關注,就能當即看到效果了。
這裏講的都是經過微信接口調試頁面作的,流程都是同樣的,固然也能夠寫在你的程序裏,按步驟調用相關接口就好了。