首先需要確認一點,一旦接入第三方服務器,微信就認為你已經具備了開發能力,像自動回復、關鍵詞回復、自定義菜單這些功能,微信公眾平台就不再提供了(需要開發者調用相關接口),停用服務器之后,這些功能也就恢復了,二者是互斥的。
說明一下,本文的例子是node+express搭建服務,消息加解密方式為明文模式,請酌情參考。
一、搭建本地調試環境,需要將本地服務穿透出去,便於外網訪問,可以用花生殼或者ngrok等,能穿透內網就可以,這里就不多說了。
二、服務器配置及校驗
現在我們已經有了一個可供外網訪問的本地服務,接下來說一下微信公眾平台的相關配置:
登錄微信公眾平台,找到基本配置

可以看到服務器配置一項(我這里是已經啟用過的),點擊右側的修改配置

先說一下大致流程,信息編輯完畢之后點擊提交,微信服務器會向你所填寫的URL發送一條get請求,你的服務器必須要能接到這條請求,然后拿微信服務器帶來的參數進行驗證,驗證完畢之后,返回驗證結果給微信,微信拿到想要的結果之后(至於具體返回什么,后面會說),你的服務器就算是在微信服務器“備案”成功了,接下來點擊啟用就可以了,啟用之后,微信服務器一旦收到消息,就會向你所填寫的URL發送一條post請求(確保你的服務器在5秒內做出響應,不然會發生一些錯誤,具體錯誤可查看微信文檔),請求攜帶的參數是xml格式的,注意配置一下,不要以json的形式去接收,解析xml,能拿到信息發送者、接收者、信息內容、事件類型等數據,然后就可以根據事件類型、信息等做出相應的處理。
填寫URL:支持https和http,格式為http://xxxxxx.com+接口路徑,例如https://www.baidu.com/authorize,https://www.baidu.com是你的服務地址,authotize是你的接口路徑。
填寫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是按鈕類型,根據需要選擇就好:
1、click:點擊推事件用戶點擊click類型按鈕后,微信服務器會通過消息接口推送消息類型為event的結構給開發者(參考消息接口指南),並且帶上按鈕中開發者填寫的key值,開發者可以通過自定義的key值與用戶進行交互; 2、view:跳轉URL用戶點擊view類型按鈕后,微信客戶端將會打開開發者在按鈕中填寫的網頁URL,可與網頁授權獲取用戶基本信息接口結合,獲得用戶基本信息。 3、scancode_push:掃碼推事件用戶點擊按鈕后,微信客戶端將調起掃一掃工具,完成掃碼操作后顯示掃描結果(如果是URL,將進入URL),且會將掃碼的結果傳給開發者,開發者可以下發消息。 4、scancode_waitmsg:掃碼推事件且彈出“消息接收中”提示框用戶點擊按鈕后,微信客戶端將調起掃一掃工具,完成掃碼操作后,將掃碼的結果傳給開發者,同時收起掃一掃工具,然后彈出“消息接收中”提示框,隨后可能會收到開發者下發的消息。 5、pic_sysphoto:彈出系統拍照發圖用戶點擊按鈕后,微信客戶端將調起系統相機,完成拍照操作后,會將拍攝的相片發送給開發者,並推送事件給開發者,同時收起系統相機,隨后可能會收到開發者下發的消息。 6、pic_photo_or_album:彈出拍照或者相冊發圖用戶點擊按鈕后,微信客戶端將彈出選擇器供用戶選擇“拍照”或者“從手機相冊選擇”。用戶選擇后即走其他兩種流程。 7、pic_weixin:彈出微信相冊發圖器用戶點擊按鈕后,微信客戶端將調起微信相冊,完成選擇操作后,將選擇的相片發送給開發者的服務器,並推送事件給開發者,同時收起相冊,隨后可能會收到開發者下發的消息。 8、location_select:彈出地理位置選擇器用戶點擊按鈕后,微信客戶端將調起地理位置選擇工具,完成選擇操作后,將選擇的地理位置發送給開發者的服務器,同時收起位置選擇工具,隨后可能會收到開發者下發的消息。 9、media_id:下發消息(除文本消息)用戶點擊media_id類型按鈕后,微信服務器會將開發者填寫的永久素材id對應的素材下發給用戶,永久素材類型可以是圖片、音頻、視頻、圖文消息。請注意:永久素材id必須是在“素材管理/新增永久素材”接口上傳后獲得的合法id。 10、view_limited:跳轉圖文消息URL用戶點擊view_limited類型按鈕后,微信客戶端將打開開發者在按鈕中填寫的永久素材id對應的圖文消息URL,永久素材類型只支持圖文消息。請注意:永久素材id必須是在“素材管理/新增永久素材”接口上傳后獲得的合法id。

根據需要,組織好你的json,填入body輸入框就行了,點擊檢查問題,如果檢查通過,菜單就創建成功了,檢查失敗的話,再具體看一下報錯信息。首次設置會立即生效,修改的話需要5分鍾才刷新,可以選擇先取消關注公眾號,然后再關注,就能立即看到效果了。
這里講的都是通過微信接口調試頁面做的,流程都是一樣的,當然也可以寫在你的程序里,按步驟調用相關接口就行了。
