微信開發學習總結(三)——開發微信公眾號的最基本功能——普通消息的接收和回復
來自: http://www.cnblogs.com/xdp-gacl/p/5161206.html
在上篇《微信開發學習總結(二)——微信開發入門》我們介紹了微信公眾平臺的基本原理,如何接入微信公眾號,如何保持access_token的長期有效性以及進行了簡單的文本消息測試,本篇再來具體細說一如何實現微信公眾號的最基本功能:普通消息的接收和回復。
一、微信公眾平臺消息管理接口介紹
要實現微信公眾號的普通消息的接收和回復,我們需要先熟悉微信公眾平臺API中消息接口部分,點此進入,點擊后將進入到【消息管理】部分,如下圖所示:
對于普通消息的接收和回復我們只需要關注上圖中的"接收消息——接收普通消息"和"發送消息——被動回復消息"
1.1、消息接收
先來說說接收消息, 當普通微信用戶向公眾賬號發消息時,微信服務器會先接收到用戶發送的消息,然后將用戶消息按照指定的XML格式組裝好數據,最后POST消息的XML數據包到開發者填寫的URL上。
接收到的普通消息的消息類型目前有以下幾種:
1 文本消息
2 圖片消息
3 語音消息
4 視頻消息
5 小視頻消息
6 地理位置消息
7 鏈接消息
每一種消息類型都有其指定的XML數據格式,這7種消息的xml格式請到官方文檔查看,有具體的格式定義和屬性說明。格式很簡單,基本共有屬性包括ToUserName、FromUserName、CreateTime、MsgType、MsgId,并且每種類型有自己特殊的屬性。
接收消息的過程其實就是獲取post請求的這個xml,然后對這個xml進行分析的過程。post請求的入口還是之前提到的微信公眾號接入的那個地址,整個公眾號的所有請求都會走這個入口,只是接入時是get請求,其它情況下是post請求。
1.2、消息回復
微信服務器在將用戶的消息發給公眾號的開發者服務器地址后,會等待開發者服務器回復響應消息。微信服務器在五秒內收不到響應會斷掉連接,并且重新發起請求,總共重試三次。
假如服務器無法保證在五秒內處理并回復,必須做出下述回復,這樣微信服務器才不會對此作任何處理,并且不會發起重試(這種情況下,可以使用客服消息接口進行異步回復),否則,將出現嚴重的錯誤提示。詳見下面說明:
1、(推薦方式)直接回復success 2、直接回復空串(指字節長度為0的空字符串,而不是XML結構體中content字段的內容為空)
一旦遇到以下情況,微信都會在公眾號會話中,向用戶下發系統提示“該公眾號暫時無法提供服務,請稍后再試”:
1、開發者在5秒內未回復任何內容 2、開發者回復了異常數據,比如JSON數據等
另外,請注意,回復圖片等多媒體消息時需要預先通過素材管理接口上傳臨時素材到微信服務器,可以使用素材管理中的臨時素材,也可以使用永久素材。
消息回復目前支持回復文本、圖片、圖文、語音、視頻、音樂,每一種類型的消息都有特定的XML數據格式。這幾種回復消息的xml數據格式請參考官方文檔,有具體的格式定義和屬性說明。格式很簡單,基本共有屬性包括ToUserName、FromUserName、CreateTime、MsgType,并且每種類型有自己特殊的屬性。
二、微信公眾號的普通消息的接收和回復
2.1、接收消息
接收消息和被動回復消息這兩個動作是不分家的,這本來就是一個交互場景,一般情況就是公眾號通過分析接收到的消息,會給出對應的回復。
之前說過了,接收消息的過程其實就是獲取微信服務器通過post請求的發送給我們公眾號服務器的xml數據,然后我們的公眾號服務器再對這個xml進行解析處理的過程。為了方便解析XML數據,我們借助于dom4j,dom4j是一個十分優秀的JavaXML API,具有性能優異、功能強大和極其易使用的特點,是用來讀寫XML文件的。針對微信服務器發來的xml請求數據,我們寫一個parseXml方法來處理,parseXml方法的代碼如下:
1 /** 2 * 解析微信發來的請求(XML) 3 * 4 * @param request 封裝了請求信息的HttpServletRequest對象 5 * @return map 解析結果 6 * @throws Exception 7 */ 8 public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { 9 // 將解析結果存儲在HashMap中 10 Map<String, String> map = new HashMap<String, String>(); 11 // 從request中取得輸入流 12 InputStream inputStream = request.getInputStream(); 13 // 讀取輸入流 14 SAXReader reader = new SAXReader(); 15 Document document = reader.read(inputStream); 16 // 得到xml根元素 17 Element root = document.getRootElement(); 18 // 得到根元素的所有子節點 19 List<Element> elementList = root.elements(); 20 21 // 遍歷所有子節點 22 for (Element e : elementList) { 23 System.out.println(e.getName() + "|" + e.getText()); 24 map.put(e.getName(), e.getText()); 25 } 26 27 // 釋放資源 28 inputStream.close(); 29 inputStream = null; 30 return map; 31 }
然后在處理微信請求的入口servlet的doPost方法中調用parseXml方法即可,調用代碼如下:
1 /** 2 * 處理微信服務器發來的消息 3 */ 4 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 5 // TODO 接收、處理、響應由微信服務器轉發的用戶發送給公眾帳號的消息 6 // 將請求、響應的編碼均設置為UTF-8(防止中文亂碼) 7 request.setCharacterEncoding("UTF-8"); 8 response.setCharacterEncoding("UTF-8"); 9 System.out.println("請求進入"); 10 String responseMessage; 11 try { 12 //解析微信發來的請求,將解析后的結果封裝成Map返回 13 Map<String,String> map = MessageHandlerUtil.parseXml(request); 14 System.out.println("開始構造響應消息"); 15 responseMessage = MessageHandlerUtil.buildResponseMessage(map); 16 System.out.println(responseMessage); 17 if(responseMessage.equals("")){ 18 responseMessage ="未正確響應"; 19 } 20 } catch (Exception e) { 21 e.printStackTrace(); 22 System.out.println("發生異常:"+ e.getMessage()); 23 responseMessage ="未正確響應"; 24 } 25 //發送響應消息 26 response.getWriter().println(responseMessage); 27 }
這樣我們就完成了消息的接收,消息接收之后,我們就要根據消息類型進行響應了,寫一個根據消息類型構造返回消息的方法,代碼如下:
1 /** 2 * 根據消息類型構造返回消息 3 * @param map 封裝了解析結果的Map 4 * @return responseMessage(響應消息) 5 */ 6 public static String buildResponseMessage(Map map) { 7 //響應消息 8 String responseMessage = ""; 9 //得到消息類型 10 String msgType = map.get("MsgType").toString(); 11 System.out.println("MsgType:" + msgType); 12 //消息類型 13 MessageType messageEnumType = MessageType.valueOf(MessageType.class, msgType.toUpperCase()); 14 switch (messageEnumType) { 15 case TEXT: 16 //處理文本消息 17 responseMessage = handleTextMessage(map); 18 break; 19 case IMAGE: 20 //處理圖片消息 21 responseMessage = handleImageMessage(map); 22 break; 23 case VOICE: 24 //處理語音消息 25 responseMessage = handleVoiceMessage(map); 26 break; 27 case VIDEO: 28 //處理視頻消息 29 responseMessage = handleVideoMessage(map); 30 break; 31 case SHORTVIDEO: 32 //處理小視頻消息 33 responseMessage = handleSmallVideoMessage(map); 34 break; 35 case LOCATION: 36 //處理位置消息 37 responseMessage = handleLocationMessage(map); 38 break; 39 case LINK: 40 //處理鏈接消息 41 responseMessage = handleLinkMessage(map); 42 break; 43 case EVENT: 44 //處理事件消息,用戶在關注與取消關注公眾號時,微信會向我們的公眾號服務器發送事件消息,開發者接收到事件消息后就可以給用戶下發歡迎消息 45 responseMessage = handleEventMessage(map); 46 default: 47 break; 48 } 49 //返回響應消息 50 return responseMessage; 51 }
這樣我們就完成了根據消息類型進行響應了,在處理微信請求的入口servlet的doPost方法中調用buildResponseMessage方法即可,doPost方法的完整代碼在上面已經貼出來了.buildResponseMessage方法中使用到了一個MessageType類,這是一個消息類型枚舉類,MessageType類的代碼如下:
1 /** 2 * 接收到的消息類型 3 */ 4 public enum MessageType { 5 TEXT,//文本消息 6 IMAGE,//圖片消息 7 VOICE,//語音消息 8 VIDEO,//視頻消息 9 SHORTVIDEO,//小視頻消息 10 LOCATION,//地理位置消息 11 LINK,//鏈接消息 12 EVENT//事件消息 13 }
2.2、回復消息
下面我基于這樣一個業務場景來演示構造回復的消息,接收到文本消息"文本",回復文本消息;接收到“圖片”,回復圖片消息;接收到“語音”,回復語音消息;接收到“視頻”,回復視頻消息;接收到“音樂”,回復音樂消息;接收到“圖文”,回復圖文消息。下面具體說明各種消息的構建,只貼出核心代碼,一些輔助代碼類請自行下載項目代碼參考.
2.2.1、回復文本消息
接收的文本消息的XML數據格式如下:
1 <xml> 2 <ToUserName><![CDATA[toUser]]></ToUserName> 3 <FromUserName><![CDATA[fromUser]]></FromUserName> 4 <CreateTime>1348831860</CreateTime> 5 <MsgType><![CDATA[text]]></MsgType> 6 <Content><![CDATA[this is a test]]></Content> 7 <MsgId>1234567890123456</MsgId> 8 </xml>
回復的文本消息的XML數據格式如下:
1 <xml> 2 <ToUserName><![CDATA[發消息的人,即訂閱者]]></ToUserName> 3 <FromUserName><![CDATA[微信公眾號本身]]></FromUserName> 4 <CreateTime>消息創建時間(整形)</CreateTime> 5 <MsgType><![CDATA[text]]></MsgType> 6 <Content><![CDATA[消息內容]]></Content> 7 </xml>
其中接收消息格式中的ToUserName便是回復消息的FromUserName,接收消息格式中的FromUserName便是回復消息的ToUserName。CreateTime為消息發送的時間戳。MsgType為消息類型,文本為text。Content為消息內容。具體每一種類型消息的回復,就是構造此種類型的xml格式內容,格式大同小異,只是音樂、視頻、語音、圖文格式相對于文本消息構造的xml內容稍微復雜一點。
寫一個構建文本消息的方法,代碼如下:
1 /** 2 * 構造文本消息 3 * @param map 封裝了解析結果的Map 4 * @param content 文本消息內容 5 * @return 文本消息XML字符串 6 */ 7 private static String buildTextMessage(Map<String, String> map, String content) { 8 //發送方帳號 9 String fromUserName = map.get("FromUserName"); 10 // 開發者微信號 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 文本消息XML數據格式 14 * <xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>1348831860</CreateTime> 18 <MsgType><![CDATA[text]]></MsgType> 19 <Content><![CDATA[this is a test]]></Content> 20 <MsgId>1234567890123456</MsgId> 21 </xml> 22 */ 23 return String.format( 24 "<xml>" + 25 "<ToUserName><![CDATA[%s]]></ToUserName>" + 26 "<FromUserName><![CDATA[%s]]></FromUserName>" + 27 "<CreateTime>%s</CreateTime>" + 28 "<MsgType><![CDATA[text]]></MsgType>" + 29 "<Content><![CDATA[%s]]></Content>" + 30 "</xml>", 31 fromUserName, toUserName, getMessageCreateTime(), content); 32 }
2.2.2、回復圖片消息
寫一個構建圖片消息的方法,代碼如下:
1 /** 2 * 構造圖片消息 3 * @param map 封裝了解析結果的Map 4 * @param mediaId 通過素材管理接口上傳多媒體文件得到的id 5 * @return 圖片消息XML字符串 6 */ 7 private static String buildImageMessage(Map<String, String> map, String mediaId) { 8 //發送方帳號 9 String fromUserName = map.get("FromUserName"); 10 // 開發者微信號 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 圖片消息XML數據格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[image]]></MsgType> 19 <Image> 20 <MediaId><![CDATA[media_id]]></MediaId> 21 </Image> 22 </xml> 23 */ 24 return String.format( 25 "<xml>" + 26 "<ToUserName><![CDATA[%s]]></ToUserName>" + 27 "<FromUserName><![CDATA[%s]]></FromUserName>" + 28 "<CreateTime>%s</CreateTime>" + 29 "<MsgType><![CDATA[image]]></MsgType>" + 30 "<Image>" + 31 " <MediaId><![CDATA[%s]]></MediaId>" + 32 "</Image>" + 33 "</xml>", 34 fromUserName, toUserName, getMessageCreateTime(), mediaId); 35 }
2.2.3、回復音樂消息
寫一個構建音樂消息的方法,代碼如下:
1 /** 2 * 構造音樂消息 3 * @param map 封裝了解析結果的Map 4 * @param music 封裝好的音樂消息內容 5 * @return 音樂消息XML字符串 6 */ 7 private static String buildMusicMessage(Map<String, String> map, Music music) { 8 //發送方帳號 9 String fromUserName = map.get("FromUserName"); 10 // 開發者微信號 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 音樂消息XML數據格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[music]]></MsgType> 19 <Music> 20 <Title><![CDATA[TITLE]]></Title> 21 <Description><![CDATA[DESCRIPTION]]></Description> 22 <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl> 23 <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl> 24 <ThumbMediaId><![CDATA[media_id]]></ThumbMediaId> 25 </Music> 26 </xml> 27 */ 28 return String.format( 29 "<xml>" + 30 "<ToUserName><![CDATA[%s]]></ToUserName>" + 31 "<FromUserName><![CDATA[%s]]></FromUserName>" + 32 "<CreateTime>%s</CreateTime>" + 33 "<MsgType><![CDATA[music]]></MsgType>" + 34 "<Music>" + 35 " <Title><![CDATA[%s]]></Title>" + 36 " <Description><![CDATA[%s]]></Description>" + 37 " <MusicUrl><![CDATA[%s]]></MusicUrl>" + 38 " <HQMusicUrl><![CDATA[%s]]></HQMusicUrl>" + 39 "</Music>" + 40 "</xml>", 41 fromUserName, toUserName, getMessageCreateTime(), music.title, music.description, music.musicUrl, music.hqMusicUrl); 42 }
2.2.4、回復視頻消息
寫一個構建視頻消息的方法,代碼如下:
1 /** 2 * 構造視頻消息 3 * @param map 封裝了解析結果的Map 4 * @param video 封裝好的視頻消息內容 5 * @return 視頻消息XML字符串 6 */ 7 private static String buildVideoMessage(Map<String, String> map, Video video) { 8 //發送方帳號 9 String fromUserName = map.get("FromUserName"); 10 // 開發者微信號 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 音樂消息XML數據格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[video]]></MsgType> 19 <Video> 20 <MediaId><![CDATA[media_id]]></MediaId> 21 <Title><![CDATA[title]]></Title> 22 <Description><![CDATA[description]]></Description> 23 </Video> 24 </xml> 25 */ 26 return String.format( 27 "<xml>" + 28 "<ToUserName><![CDATA[%s]]></ToUserName>" + 29 "<FromUserName><![CDATA[%s]]></FromUserName>" + 30 "<CreateTime>%s</CreateTime>" + 31 "<MsgType><![CDATA[video]]></MsgType>" + 32 "<Video>" + 33 " <MediaId><![CDATA[%s]]></MediaId>" + 34 " <Title><![CDATA[%s]]></Title>" + 35 " <Description><![CDATA[%s]]></Description>" + 36 "</Video>" + 37 "</xml>", 38 fromUserName, toUserName, getMessageCreateTime(), video.mediaId, video.title, video.description); 39 }
2.2.5、回復語音消息
寫一個構建語音消息的方法,代碼如下:
1 /** 2 * 構造語音消息 3 * @param map 封裝了解析結果的Map 4 * @param mediaId 通過素材管理接口上傳多媒體文件得到的id 5 * @return 語音消息XML字符串 6 */ 7 private static String buildVoiceMessage(Map<String, String> map, String mediaId) { 8 //發送方帳號 9 String fromUserName = map.get("FromUserName"); 10 // 開發者微信號 11 String toUserName = map.get("ToUserName"); 12 /** 13 * 語音消息XML數據格式 14 *<xml> 15 <ToUserName><![CDATA[toUser]]></ToUserName> 16 <FromUserName><![CDATA[fromUser]]></FromUserName> 17 <CreateTime>12345678</CreateTime> 18 <MsgType><![CDATA[voice]]></MsgType> 19 <Voice> 20 <MediaId><![CDATA[media_id]]></MediaId> 21 </Voice> 22 </xml> 23 */ 24 return String.format( 25 "<xml>" + 26 "<ToUserName><![CDATA[%s]]></ToUserName>" + 27 "<FromUserName><![CDATA[%s]]></FromUserName>" + 28 "<CreateTime>%s</CreateTime>" + 29 "<MsgType><![CDATA[voice]]></MsgType>" + 30 "<Voice>" + 31 " <MediaId><![CDATA[%s]]></MediaId>" + 32 "</Voice>" + 33 "</xml>", 34 fromUserName, toUserName, getMessageCreateTime(), mediaId); 35 }
2.2.6、回復圖文消息
寫一個構建圖文消息的方法,代碼如下:
1 /** 2 * 構造圖文消息 3 * @param map 封裝了解析結果的Map 4 * @return 圖文消息XML字符串 5 */ 6 private static String buildNewsMessage(Map<String, String> map) { 7 String fromUserName = map.get("FromUserName"); 8 // 開發者微信號 9 String toUserName = map.get("ToUserName"); 10 NewsItem item = new NewsItem(); 11 item.Title = "微信開發學習總結(一)——微信開發環境搭建"; 12 item.Description = "工欲善其事,必先利其器。要做微信公眾號開發,那么要先準備好兩樣必不可少的東西:\n" + 13 "\n" + 14 " 1、要有一個用來測試的公眾號。\n" + 15 "\n" + 16 " 2、用來調式代碼的開發環境"; 17 item.PicUrl = "http://images2015.cnblogs.com/blog/289233/201601/289233-20160121164317343-2145023644.png"; 18 item.Url = "http://www.cnblogs.com/xdp-gacl/p/5149171.html"; 19 String itemContent1 = buildSingleItem(item); 20 21 NewsItem item2 = new NewsItem(); 22 item2.Title = "微信開發學習總結(二)——微信開發入門"; 23 item2.Description = "微信服務器就相當于一個轉發服務器,終端(手機、Pad等)發起請求至微信服務器,微信服務器然后將請求轉發給我們的應用服務器。應用服務器處理完畢后,將響應數據回發給微信服務器,微信服務器再將具體響應信息回復到微信App終端。"; 24 item2.PicUrl = ""; 25 item2.Url = "http://www.cnblogs.com/xdp-gacl/p/5151857.html"; 26 String itemContent2 = buildSingleItem(item2); 27 28 29 String content = String.format("<xml>\n" + 30 "<ToUserName><![CDATA[%s]]></ToUserName>\n" + 31 "<FromUserName><![CDATA[%s]]></FromUserName>\n" + 32 "<CreateTime>%s</CreateTime>\n" + 33 "<MsgType><![CDATA[news]]></MsgType>\n" + 34 "<ArticleCount>%s</ArticleCount>\n" + 35 "<Articles>\n" + "%s" + 36 "</Articles>\n" + 37 "</xml> ", fromUserName, toUserName, getMessageCreateTime(), 2, itemContent1 + itemContent2); 38 return content; 39 40 } 41 42 /** 43 * 生成圖文消息的一條記錄 44 * 45 * @param item 46 * @return 47 */ 48 private static String buildSingleItem(NewsItem item) { 49 String itemContent = String.format("<item>\n" + 50 "<Title><![CDATA[%s]]></Title> \n" + 51 "<Description><![CDATA[%s]]></Description>\n" + 52 "<PicUrl><![CDATA[%s]]></PicUrl>\n" + 53 "<Url><![CDATA[%s]]></Url>\n" + 54 "</item>", item.Title, item.Description, item.PicUrl, item.Url); 55 return itemContent; 56 }
根據上述提到的消息回復業務場景,我們可以寫一個handleTextMessage方法來作為構造各種回復消息的處理入口,代碼如下:
1 /** 2 * 接收到文本消息后處理 3 * @param map 封裝了解析結果的Map 4 * @return 5 */ 6 private static String handleTextMessage(Map<String, String> map) { 7 //響應消息 8 String responseMessage; 9 // 消息內容 10 String content = map.get("Content"); 11 switch (content) { 12 case "文本": 13 String msgText = "孤傲蒼狼又要開始寫博客總結了,歡迎朋友們訪問我在博客園上面寫的博客\n" + 14 "<a href=\"http://www.cnblogs.com/xdp-gacl\">孤傲蒼狼的博客</a>"; 15 responseMessage = buildTextMessage(map, msgText); 16 break; 17 case "圖片": 18 //通過素材管理接口上傳圖片時得到的media_id 19 String imgMediaId = "dSQCiEHYB-pgi7ib5KpeoFlqpg09J31H28rex6xKgwWrln3HY0BTsoxnRV-xC_SQ"; 20 responseMessage = buildImageMessage(map, imgMediaId); 21 break; 22 case "語音": 23 //通過素材管理接口上傳語音文件時得到的media_id 24 String voiceMediaId = "h3ul0TnwaRPut6Tl1Xlf0kk_9aUqtQvfM5Oq21unoWqJrwks505pkMGMbHnCHBBZ"; 25 responseMessage = buildVoiceMessage(map,voiceMediaId); 26 break; 27 case "圖文": 28 responseMessage = buildNewsMessage(map); 29 break; 30 case "音樂": 31 Music music = new Music(); 32 music.title = "趙麗穎、許志安 - 亂世俱滅"; 33 music.description = "電視劇《蜀山戰紀》插曲"; 34 music.musicUrl = "http://gacl.ngrok.natapp.cn/music/music.mp3"; 35 music.hqMusicUrl = "http://gacl.ngrok.natapp.cn/music/music.mp3"; 36 responseMessage = buildMusicMessage(map, music); 37 break; 38 case "視頻": 39 Video video = new Video(); 40 video.mediaId = "GqmIGpLu41rtwaY7WCVtJAL3ZbslzKiuLEXfWIKYDnHXGObH1CBH71xtgrGwyCa3"; 41 video.title = "小蘋果"; 42 video.description = "小蘋果搞笑視頻"; 43 responseMessage = buildVideoMessage(map, video); 44 break; 45 default: 46 responseMessage = buildWelcomeTextMessage(map); 47 break; 48 49 } 50 //返回響應消息 51 return responseMessage; 52 }
到此,回復想消息的相關處理代碼就編寫完成了,將項目部署到Tomcat服務器進行測試,記得使用Ngrok將內網的服務器映射到外網,否則無法使用微信測試,如下:
關于Ngrok的使用方式之前的《微信開發學習總結(一)——微信開發環境搭建》博客中已經介紹過了,這里就不再重復講解了,沒了解過Ngrok的朋友可以去看看《微信開發學習總結(一)——微信開發環境搭建》這篇博客.
使用微信進行消息回復測試,測試效果如下:
</div> </div> </div>