要實現微信公衆號的普通消息的接收和回覆,咱們須要先熟悉微信公衆平臺API中消息接口部分,點此進入,點擊後將進入到【消息管理】部分,以下圖所示:php
對於普通消息的接收和回覆咱們只須要關注上圖中的"接收消息——接收普通消息"和"發送消息——被動回覆消息"html
先來講說接收消息, 當普通微信用戶向公衆帳號發消息時,微信服務器會先接收到用戶發送的消息,而後將用戶消息按照指定的XML格式組裝好數據,最後POST消息的XML數據包到開發者填寫的URL上。java
接收到的普通消息的消息類型目前有如下幾種:web
1 文本消息
2 圖片消息
3 語音消息
4 視頻消息
5 小視頻消息
6 地理位置消息
7 連接消息apache
每一種消息類型都有其指定的XML數據格式,這7種消息的xml格式請到官方文檔查看,有具體的格式定義和屬性說明。格式很簡單,基本共有屬性包括ToUserName、FromUserName、CreateTime、MsgType、MsgId,而且每種類型有本身特殊的屬性。json
接收消息的過程其實就是獲取post請求的這個xml,而後對這個xml進行分析的過程。post請求的入口仍是以前提到的微信公衆號接入的那個地址,整個公衆號的全部請求都會走這個入口,只是接入時是get請求,其它狀況下是post請求。api
微信服務器在將用戶的消息發給公衆號的開發者服務器地址後,會等待開發者服務器回覆響應消息。微信服務器在五秒內收不到響應會斷掉鏈接,而且從新發起請求,總共重試三次。服務器
假如服務器沒法保證在五秒內處理並回復,必須作出下述回覆,這樣微信服務器纔不會對此做任何處理,而且不會發起重試(這種狀況下,可使用客服消息接口進行異步回覆),不然,將出現嚴重的錯誤提示。詳見下面說明:微信
一、(推薦方式)直接回復success 二、直接回復空串(指字節長度爲0的空字符串,而不是XML結構體中content字段的內容爲空)
一旦遇到如下狀況,微信都會在公衆號會話中,向用戶下發系統提示「該公衆號暫時沒法提供服務,請稍後再試」:微信開發
一、開發者在5秒內未回覆任何內容 二、開發者回覆了異常數據,好比JSON數據等
另外,請注意,回覆圖片等多媒體消息時須要預先經過素材管理接口上傳臨時素材到微信服務器,可使用素材管理中的臨時素材,也可使用永久素材。
消息回覆目前支持回覆文本、圖片、圖文、語音、視頻、音樂,每一種類型的消息都有特定的XML數據格式。這幾種回覆消息的xml數據格式請參考官方文檔,有具體的格式定義和屬性說明。格式很簡單,基本共有屬性包括ToUserName、FromUserName、CreateTime、MsgType,而且每種類型有本身特殊的屬性。
接收消息和被動回覆消息這兩個動做是不分家的,這原本就是一個交互場景,通常狀況就是公衆號經過分析接收到的消息,會給出對應的回覆。
以前說過了,接收消息的過程其實就是獲取微信服務器經過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 }
下面我基於這樣一個業務場景來演示構造回覆的消息,接收到文本消息"文本",回覆文本消息;接收到「圖片」,回覆圖片消息;接收到「語音」,回覆語音消息;接收到「視頻」,回覆視頻消息;接收到「音樂」,回覆音樂消息;接收到「圖文」,回覆圖文消息。下面具體說明各類消息的構建,只貼出核心代碼,一些輔助代碼類請自行下載項目代碼參考.
接收的文本消息的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 }
寫一個構建圖片消息的方法,代碼以下:
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 }
寫一個構建音樂消息的方法,代碼以下:
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 }
寫一個構建視頻消息的方法,代碼以下:
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 }
寫一個構建語音消息的方法,代碼以下:
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 }
寫一個構建圖文消息的方法,代碼以下:
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 " 一、要有一個用來測試的公衆號。\n" + 15 "\n" + 16 " 二、用來調式代碼的開發環境"; 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的朋友能夠去看看《微信開發學習總結(一)——微信開發環境搭建》這篇博客.
使用微信進行消息回覆測試,測試效果以下:
能夠看到,每一種消息都正常響應了.
這裏須要說一下圖片,語音,視頻的回覆消息構造,這三種消息構造時的都須要一個mediaId,而這個mediaId是經過素材管理接口上傳多媒體文件獲得的,爲了構造圖片,語音,視頻的這幾種回覆消息,我事先準備好了測試素材,以下圖所示:
而後經過微信公衆號平臺提供的素材管理接口將圖片,語音,視頻上傳到微信服務器上,上傳成功後,微信服務器會給咱們返回一個mediaId,用於標識上傳成功的多媒體素材,上傳素材的工具類代碼以下:
1 package me.gacl.wx.util; 2 3 import com.alibaba.fastjson.JSON; 4 import com.alibaba.fastjson.JSONException; 5 import com.alibaba.fastjson.JSONObject; 6 import org.apache.commons.httpclient.HttpClient; 7 import org.apache.commons.httpclient.HttpException; 8 import org.apache.commons.httpclient.methods.PostMethod; 9 import org.apache.commons.httpclient.methods.multipart.FilePart; 10 import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; 11 import org.apache.commons.httpclient.methods.multipart.Part; 12 import org.apache.commons.httpclient.methods.multipart.StringPart; 13 import org.apache.commons.httpclient.protocol.Protocol; 14 import org.apache.commons.httpclient.protocol.SSLProtocolSocketFactory; 15 import org.apache.http.HttpStatus; 16 17 import javax.net.ssl.*; 18 import java.io.*; 19 import java.net.HttpURLConnection; 20 import java.net.URL; 21 import java.security.cert.CertificateException; 22 import java.security.cert.X509Certificate; 23 24 /** 25 * Created by allen on 2016/1/29. 26 */ 27 public class WeChatApiUtil { 28 // token 接口(GET) 29 private static final String ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; 30 // 素材上傳(POST)https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE 31 private static final String UPLOAD_MEDIA = "https://api.weixin.qq.com/cgi-bin/media/upload"; 32 // 素材下載:不支持視頻文件的下載(GET) 33 private static final String DOWNLOAD_MEDIA = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s"; 34 35 public static String getTokenUrl(String appId, String appSecret) { 36 return String.format(ACCESS_TOKEN, appId, appSecret); 37 } 38 39 public static String getDownloadUrl(String token, String mediaId) { 40 return String.format(DOWNLOAD_MEDIA, token, mediaId); 41 } 42 43 /** 44 * 通用接口獲取Token憑證 45 * 46 * @param appId 47 * @param appSecret 48 * @return 49 */ 50 public static String getToken(String appId, String appSecret) { 51 if (appId == null || appSecret == null) { 52 return null; 53 } 54 55 String token = null; 56 String tockenUrl = WeChatApiUtil.getTokenUrl(appId, appSecret); 57 String response = httpsRequestToString(tockenUrl, "GET", null); 58 JSONObject jsonObject = JSON.parseObject(response); 59 if (null != jsonObject) { 60 try { 61 token = jsonObject.getString("access_token"); 62 } catch (JSONException e) { 63 token = null;// 獲取token失敗 64 } 65 } 66 return token; 67 } 68 69 /** 70 * 微信服務器素材上傳 71 * 72 * @param file 表單名稱media 73 * @param token access_token 74 * @param type type只支持四種類型素材(video/image/voice/thumb) 75 */ 76 public static JSONObject uploadMedia(File file, String token, String type) { 77 if (file == null || token == null || type == null) { 78 return null; 79 } 80 81 if (!file.exists()) { 82 System.out.println("上傳文件不存在,請檢查!"); 83 return null; 84 } 85 86 String url = UPLOAD_MEDIA; 87 JSONObject jsonObject = null; 88 PostMethod post = new PostMethod(url); 89 post.setRequestHeader("Connection", "Keep-Alive"); 90 post.setRequestHeader("Cache-Control", "no-cache"); 91 FilePart media; 92 HttpClient httpClient = new HttpClient(); 93 //信任任何類型的證書 94 Protocol myhttps = new Protocol("https", new SSLProtocolSocketFactory(), 443); 95 Protocol.registerProtocol("https", myhttps); 96 97 try { 98 media = new FilePart("media", file); 99 Part[] parts = new Part[]{new StringPart("access_token", token), 100 new StringPart("type", type), media}; 101 MultipartRequestEntity entity = new MultipartRequestEntity(parts, 102 post.getParams()); 103 post.setRequestEntity(entity); 104 int status = httpClient.executeMethod(post); 105 if (status == HttpStatus.SC_OK) { 106 String text = post.getResponseBodyAsString(); 107 jsonObject = JSONObject.parseObject(text); 108 } else { 109 System.out.println("upload Media failure status is:" + status); 110 } 111 } catch (FileNotFoundException e) { 112 e.printStackTrace(); 113 } catch (HttpException e) { 114 e.printStackTrace(); 115 } catch (IOException e) { 116 e.printStackTrace(); 117 } 118 return jsonObject; 119 } 120 121 /** 122 * 多媒體下載接口 123 * 124 * @param fileName 素材存儲文件路徑 125 * @param token 認證token 126 * @param mediaId 素材ID(對應上傳後獲取到的ID) 127 * @return 素材文件 128 * @comment 不支持視頻文件的下載 129 */ 130 public static File downloadMedia(String fileName, String token, 131 String mediaId) { 132 String url = getDownloadUrl(token, mediaId); 133 return httpRequestToFile(fileName, url, "GET", null); 134 } 135 136 /** 137 * 多媒體下載接口 138 * 139 * @param fileName 素材存儲文件路徑 140 * @param mediaId 素材ID(對應上傳後獲取到的ID) 141 * @return 素材文件 142 * @comment 不支持視頻文件的下載 143 */ 144 public static File downloadMedia(String fileName, String mediaId) { 145 String appId = "wxbe4d433e857e8bb1"; 146 String appSecret = "ccbc82d560876711027b3d43a6f2ebda"; 147 String token = WeChatApiUtil.getToken(appId, appSecret); 148 return downloadMedia(fileName,token,mediaId); 149 } 150 151 /** 152 * 以http方式發送請求,並將請求響應內容輸出到文件 153 * 154 * @param path 請求路徑 155 * @param method 請求方法 156 * @param body 請求數據 157 * @return 返回響應的存儲到文件 158 */ 159 public static File httpRequestToFile(String fileName, String path, String method, String body) { 160 if (fileName == null || path == null || method == null) { 161 return null; 162 } 163 164 File file = null; 165 HttpURLConnection conn = null; 166 InputStream inputStream = null; 167 FileOutputStream fileOut = null; 168 try { 169 URL url = new URL(path); 170 conn = (HttpURLConnection) url.openConnection(); 171 conn.setDoOutput(true); 172 conn.setDoInput(true); 173 conn.setUseCaches(false); 174 conn.setRequestMethod(method); 175 if (null != body) { 176 OutputStream outputStream = conn.getOutputStream(); 177 outputStream.write(body.getBytes("UTF-8")); 178 outputStream.close(); 179 } 180 181 inputStream = conn.getInputStream(); 182 if (inputStream != null) { 183 file = new File(fileName); 184 } else { 185 return file; 186 } 187 188 //寫入到文件 189 fileOut = new FileOutputStream(file); 190 if (fileOut != null) { 191 int c = inputStream.read(); 192 while (c != -1) { 193 fileOut.write(c); 194 c = inputStream.read(); 195 } 196 } 197 } catch (Exception e) { 198 } finally { 199 if (conn != null) { 200 conn.disconnect(); 201 } 202 203 /* 204 * 必須關閉文件流 205 * 不然JDK運行時,文件被佔用其餘進程沒法訪問 206 */ 207 try { 208 inputStream.close(); 209 fileOut.close(); 210 } catch (IOException execption) { 211 } 212 } 213 return file; 214 } 215 216 /** 217 * 上傳素材 218 * @param filePath 媒體文件路徑(絕對路徑) 219 * @param type 媒體文件類型,分別有圖片(image)、語音(voice)、視頻(video)和縮略圖(thumb) 220 * @return 221 */ 222 public static JSONObject uploadMedia(String filePath,String type){ 223 File f = new File(filePath); // 獲取本地文件 224 String appId = "wxbe4d433e857e8bb1"; 225 String appSecret = "ccbc82d560876711027b3d43a6f2ebda"; 226 String token = WeChatApiUtil.getToken(appId, appSecret); 227 JSONObject jsonObject = uploadMedia(f, token, type); 228 return jsonObject; 229 } 230 231 /** 232 * 發送請求以https方式發送請求並將請求響應內容以String方式返回 233 * 234 * @param path 請求路徑 235 * @param method 請求方法 236 * @param body 請求數據體 237 * @return 請求響應內容轉換成字符串信息 238 */ 239 public static String httpsRequestToString(String path, String method, String body) { 240 if (path == null || method == null) { 241 return null; 242 } 243 244 String response = null; 245 InputStream inputStream = null; 246 InputStreamReader inputStreamReader = null; 247 BufferedReader bufferedReader = null; 248 HttpsURLConnection conn = null; 249 try { 250 TrustManager[] tm = {new JEEWeiXinX509TrustManager()}; 251 SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); 252 sslContext.init(null, tm, new java.security.SecureRandom()); 253 SSLSocketFactory ssf = sslContext.getSocketFactory(); 254 System.out.println(path); 255 URL url = new URL(path); 256 conn = (HttpsURLConnection) url.openConnection(); 257 conn.setSSLSocketFactory(ssf); 258 259 conn.setDoOutput(true); 260 conn.setDoInput(true); 261 conn.setUseCaches(false); 262 conn.setRequestMethod(method); 263 if (null != body) { 264 OutputStream outputStream = conn.getOutputStream(); 265 outputStream.write(body.getBytes("UTF-8")); 266 outputStream.close(); 267 } 268 269 inputStream = conn.getInputStream(); 270 inputStreamReader = new InputStreamReader(inputStream, "UTF-8"); 271 bufferedReader = new BufferedReader(inputStreamReader); 272 String str = null; 273 StringBuffer buffer = new StringBuffer(); 274 while ((str = bufferedReader.readLine()) != null) { 275 buffer.append(str); 276 } 277 278 response = buffer.toString(); 279 } catch (Exception e) { 280 281 } finally { 282 if (conn != null) { 283 conn.disconnect(); 284 } 285 try { 286 bufferedReader.close(); 287 inputStreamReader.close(); 288 inputStream.close(); 289 } catch (IOException execption) { 290 291 } 292 } 293 return response; 294 } 295 296 public static void main(String[] args) throws Exception{ 297 //媒體文件路徑 298 String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/image/我.jpg"; 299 //String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/voice/voice.mp3"; 300 //String filePath = "D:\\JavaSoftwareDevelopeFolder\\IntelliJ IDEA_Workspace\\WxStudy\\web\\media\\video\\小蘋果.mp4"; 301 //媒體文件類型 302 String type = "image"; 303 //String type = "voice"; 304 //String type = "video"; 305 JSONObject uploadResult = uploadMedia(filePath, type); 306 //{"media_id":"dSQCiEHYB-pgi7ib5KpeoFlqpg09J31H28rex6xKgwWrln3HY0BTsoxnRV-xC_SQ","created_at":1455520569,"type":"image"} 307 System.out.println(uploadResult.toString()); 308 309 //下載剛剛上傳的圖片以id命名 310 String media_id = uploadResult.getString("media_id"); 311 File file = downloadMedia("D:/" + media_id + ".png", media_id); 312 System.out.println(file.getName()); 313 314 } 315 } 316 317 class JEEWeiXinX509TrustManager implements X509TrustManager { 318 public void checkClientTrusted(X509Certificate[] chain, String authType) 319 throws CertificateException { 320 } 321 322 public void checkServerTrusted(X509Certificate[] chain, String authType) 323 throws CertificateException { 324 } 325 326 public X509Certificate[] getAcceptedIssuers() { 327 return null; 328 } 329 }
在工具類寫一個main方法測試素材上傳和下載,代碼以下:
1 public static void main(String[] args) throws Exception{ 2 //媒體文件路徑 3 String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/image/我.jpg"; 4 //String filePath = "D:/JavaSoftwareDevelopeFolder/IntelliJ IDEA_Workspace/WxStudy/web/media/voice/voice.mp3"; 5 //String filePath = "D:\\JavaSoftwareDevelopeFolder\\IntelliJ IDEA_Workspace\\WxStudy\\web\\media\\video\\小蘋果.mp4"; 6 //媒體文件類型 7 String type = "image"; 8 //String type = "voice"; 9 //String type = "video"; 10 JSONObject uploadResult = uploadMedia(filePath, type); 11 //{"media_id":"dSQCiEHYB-pgi7ib5KpeoFlqpg09J31H28rex6xKgwWrln3HY0BTsoxnRV-xC_SQ","created_at":1455520569,"type":"image"} 12 System.out.println(uploadResult.toString()); 13 14 //下載剛剛上傳的圖片以id命名 15 String media_id = uploadResult.getString("media_id"); 16 File file = downloadMedia("D:/" + media_id + ".png", media_id); 17 System.out.println(file.getName()); 18 19 }
運行結果以下:
能夠看到,素材上傳成功後,微信服務器就會返回一個media_id,用於標識上傳後的文件.有了這個media_id後,咱們就能夠構建咱們想要的圖片,語音,視頻回覆消息了.
下面再說一下音樂的回覆消息的構造,音樂素材也是我事先在服務器上準備了一個music.mp3音樂文件,而且保證能夠正常使用外網訪問,以下所示:
而後將MusicUrl和HQMusicUrl的地址指向了服務器上的music.mp3文件的訪問地址,以下:
1 Music music = new Music(); 2 music.title = "趙麗穎、許志安 - 亂世俱滅"; 3 music.description = "電視劇《蜀山戰紀》插曲"; 4 music.musicUrl = "http://gacl.ngrok.natapp.cn/media/music/music.mp3"; 5 music.hqMusicUrl = "http://gacl.ngrok.natapp.cn/media/music/music.mp3"; 6 responseMessage = buildMusicMessage(map, music);
這樣就能夠正常構造音樂回覆消息了.