http://blog.csdn.net/lyq8479/article/details/17232631
引言及內容概要
微信公衆平臺支持向用戶回覆音樂消息,用戶收到音樂消息後,點擊即可播放音樂。通過音樂消息,公衆賬號可以實現音樂搜索(歌曲點播)功能,即用戶輸入想聽的音樂名稱,公衆賬號返回對應的音樂(歌曲)。讀者可以關注xiaoqrobot體驗該功能,操作指南及使用如下所示。
考慮到歌曲名稱有重複的情況,用戶還可以同時指定歌曲名稱、演唱者搜索歌曲。下面就爲讀者詳細介紹歌曲點播功能的實現過程。
音樂消息說明
在微信公衆平臺開發者文檔中提到,向用戶回覆音樂消息需要構造如下格式的XML數據。
- <xml>
- <ToUserName><![CDATA[toUser]]></ToUserName>
- <FromUserName><![CDATA[fromUser]]></FromUserName>
- <CreateTime>12345678</CreateTime>
- <MsgType><![CDATA[music]]></MsgType>
- <Music>
- <Title><![CDATA[TITLE]]></Title>
- <Description><![CDATA[DESCRIPTION]]></Description>
- <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl>
- <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl>
- <ThumbMediaId><![CDATA[media_id]]></ThumbMediaId>
- </Music>
- </xml>
1)參數Title:標題,本例中可以設置爲歌曲名稱;
2)參數Description:描述,本例中可以設置爲歌曲的演唱者;
3)參數MusicUrl:普通品質的音樂鏈接;
4)參數HQMusicUrl:高品質的音樂鏈接,在WIFI環境下會優先使用該鏈接播放音樂;
5)參數ThumbMediaId:縮略圖的媒體ID,通過上傳多媒體文件獲得;它指向的是一張圖片,最終會作爲音樂消息左側綠色方形區域的背景圖片。
上述5個參數中,最爲重要的是MusicUrl和HQMusicUrl,這也是本文的重點,如何根據歌曲名稱獲得歌曲的鏈接。如果讀者只能得到歌曲的一個鏈接,可以將MusicUrl和HQMusicUrl設置成一樣的。至於ThumbMediaId參數,必須是通過微信認證的服務號才能得到,普通的服務號與訂閱號可以忽略該參數,也就是說,在回覆給微信服務器的XML中可以不包含ThumbMediaId參數。
百度音樂搜索API介紹
上面提到,給用戶回覆音樂消息最關鍵在於如何根據歌曲名稱獲得歌曲的鏈接,我們必須找一個現成的音樂搜索API,除非讀者自己有音樂服務器,或者只向用戶回覆固定的幾首音樂。百度有一個非公開的音樂搜索API,之所以說非公開,是因爲筆者沒有在百度官網的任何地方看到有關該API的介紹,但這並不影響讀者對本例的學習,我們仍然可以調用它。百度音樂搜索API的請求地址如下:
- http://box.zhangmen.baidu.com/x?op=12&count=1&title=TITLE$$AUTHOR$$$$
http://box.zhangmen.baidu.com爲百度音樂盒的首頁地址,上面的鏈接中不用管參數op和count,重點關注TITLE和AUTHOR,TITLE表示歌曲名稱,AUTHOR表示演唱者,AUTHOR可以爲空,參數TITLE和AUTHOR需要進行URL編碼(UTF-8或GB2312均可)。例如,要搜索歌曲零點樂隊的“相信自己”,可以像下面這樣:
- // GB2312編碼的音樂搜索鏈接
- http://box.zhangmen.baidu.com/x?op=12&count=1&title=%CF%E0%D0%C5%D7%D4%BC%BA$$%C1%E3%B5%E3%C0%D6%B6%D3$$$$
- // UTF-8編碼的音樂搜索鏈接
- http://box.zhangmen.baidu.com/x?op=12&count=1&title=%E7%9B%B8%E4%BF%A1%E8%87%AA%E5%B7%B1$$%E9%9B%B6%E7%82%B9%E4%B9%90%E9%98%9F$$$$
- <result>
- <count>1</count>
- <url>
- <encode>
- <![CDATA[http://zhangmenshiting.baidu.com/data2/music/44277542/ZWZla2xra2pfn6NndK6ap5WXcJVob5puZ2trbWprmnBjZ2xolpeZa2drZmWZmZmdl2hjZWhvnWlpYmRtZmltcGplZFqin5t1YWBobW5qcGxia2NmZ2twbzE$]]>
- </encode>
- <decode>
- <![CDATA[44277542.mp3?xcode=a39c6698955c82594aab36931dcbef60139f180191368931&mid=0.59949419022597]]>
- </decode>
- <type>8</type>
- <lrcid>64644</lrcid>
- <flag>1</flag>
- </url>
- <durl>
- <encode>
- <![CDATA[http://zhangmenshiting2.baidu.com/data2/music/44277530/ZWZla2xramhfn6NndK6ap5WXcJVob5puZ2trbWprmnBjZ2xolpeZa2drZmWZmZmdl2hjaGhvnZ5qlGRpbpedamJla1qin5t1YWBobW5qcGxia2NmZ2twbzE$]]>
- </encode>
- <decode>
- <![CDATA[44277530.mp3?xcode=a39c6698955c82594aab36931dcbef60439ff9b159af2138&mid=0.59949419022597]]>
- </decode>
- <type>8</type>
- <lrcid>64644</lrcid>
- <flag>1</flag>
- </durl>
- <p2p>
- <hash>022bc0fbf66cd19bea96db49634419dc2600393f</hash>
- <url>
- <![CDATA[ ]]>
- </url>
- <type>mp3</type>
- <size>5236902</size>
- <bitrate>192</bitrate>
- </p2p>
- </result>
返回結果中的主要參數說明如下:
1)<count> 表示搜索到的音樂數;
2)<url>中包含了普通品質的音樂鏈接,<durl>中包含了高品質音樂的鏈接;
3)<encode>中包含了加密後的音樂鏈接,實際上只是對音樂名稱進行了加密,<decode>中包含了解密後的音樂名稱。因此,要獲取音樂的鏈接就需要重點分析<encode>和<decode>中的內容,下面會專門爲讀者進行介紹。
4)<type>表示音樂文件的類型,如rm、wma、mp3等;
5)<lrcid>是歌詞的ID,<url>中的歌詞ID爲64644,那麼如何得到歌詞呢?本例並不關心歌詞,只是附帶提一下。歌詞的地址如下:
- http://box.zhangmen.baidu.com/bdlrc/646/64644.lrc
下面來看如何從<encode>和<decode>中得到音樂鏈接。爲了便於說明,筆者將上面搜索結果中的<url>和<durl>部分抽取出來,並進行了標註,如下圖所示。
上圖中,1和2拼接起來是普通品質音樂的鏈接,3和4拼接起來是高品質音樂的鏈接。也就是說,普通品質和高品質的音樂鏈接如下:
- // 普通品質音樂鏈接
- http://zhangmenshiting.baidu.com/data2/music/44277542/44277542.mp3?xcode=a39c6698955c82594aab36931dcbef60139f180191368931
- // 高品質音樂鏈接
- http://zhangmenshiting2.baidu.com/data2/music/44277530/44277530.mp3?xcode=a39c6698955c82594aab36931dcbef60439ff9b159af2138
編程調用百度音樂搜索API
知道如何從API返回結果中得到音樂鏈接後,就可以編寫程序來實現了。筆者將發送HTTP請求、URL編碼、解析XML等操作全部封裝在BaiduMusicService類中,該類的代碼如下:
- import java.io.InputStream;
- import java.io.UnsupportedEncodingException;
- import java.net.HttpURLConnection;
- import java.net.URL;
- import java.util.List;
- import org.dom4j.Document;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- import org.liufeng.course.message.resp.Music;
- /**
- * 百度音樂搜索API操作類
- *
- * @author liufeng
- * @date 2013-12-09
- */
- public class BaiduMusicService {
- /**
- * 根據名稱和作者搜索音樂
- *
- * @param musicTitle 音樂名稱
- * @param musicAuthor 音樂作者
- * @return Music
- */
- public static Music searchMusic(String musicTitle, String musicAuthor) {
- // 百度音樂搜索地址
- String requestUrl = "http://box.zhangmen.baidu.com/x?op=12&count=1&title={TITLE}$${AUTHOR}$$$$";
- // 對音樂名稱、作者進URL編碼
- requestUrl = requestUrl.replace("{TITLE}", urlEncodeUTF8(musicTitle));
- requestUrl = requestUrl.replace("{AUTHOR}", urlEncodeUTF8(musicAuthor));
- // 處理名稱、作者中間的空格
- requestUrl = requestUrl.replaceAll("\\+", "%20");
- // 查詢並獲取返回結果
- InputStream inputStream = httpRequest(requestUrl);
- // 從返回結果中解析出Music
- Music music = parseMusic(inputStream);
- // 如果music不爲null,設置標題和描述
- if (null != music) {
- music.setTitle(musicTitle);
- // 如果作者不爲"",將描述設置爲作者
- if (!"".equals(musicAuthor))
- music.setDescription(musicAuthor);
- else
- music.setDescription("來自百度音樂");
- }
- return music;
- }
- /**
- * UTF-8編碼
- *
- * @param source
- * @return
- */
- private static String urlEncodeUTF8(String source) {
- String result = source;
- try {
- result = java.net.URLEncoder.encode(source, "UTF-8");
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- return result;
- }
- /**
- * 發送http請求取得返回的輸入流
- *
- * @param requestUrl 請求地址
- * @return InputStream
- */
- private static InputStream httpRequest(String requestUrl) {
- InputStream inputStream = null;
- try {
- URL url = new URL(requestUrl);
- HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();
- httpUrlConn.setDoInput(true);
- httpUrlConn.setRequestMethod("GET");
- httpUrlConn.connect();
- // 獲得返回的輸入流
- inputStream = httpUrlConn.getInputStream();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return inputStream;
- }
- /**
- * 解析音樂參數
- *
- * @param inputStream 百度音樂搜索API返回的輸入流
- * @return Music
- */
- @SuppressWarnings("unchecked")
- private static Music parseMusic(InputStream inputStream) {
- Music music = null;
- try {
- // 使用dom4j解析xml字符串
- SAXReader reader = new SAXReader();
- Document document = reader.read(inputStream);
- // 得到xml根元素
- Element root = document.getRootElement();
- // count表示搜索到的歌曲數
- String count = root.element("count").getText();
- // 當搜索到的歌曲數大於0時
- if (!"0".equals(count)) {
- // 普通品質
- List<Element> urlList = root.elements("url");
- // 高品質
- List<Element> durlList = root.elements("durl");
- // 普通品質的encode、decode
- String urlEncode = urlList.get(0).element("encode").getText();
- String urlDecode = urlList.get(0).element("decode").getText();
- // 普通品質音樂的URL
- String url = urlEncode.substring(0, urlEncode.lastIndexOf("/") + 1) + urlDecode;
- if (-1 != urlDecode.lastIndexOf("&"))
- url = urlEncode.substring(0, urlEncode.lastIndexOf("/") + 1) + urlDecode.substring(0, urlDecode.lastIndexOf("&"));
- // 默認情況下,高音質音樂的URL 等於 普通品質音樂的URL
- String durl = url;
- // 判斷高品質節點是否存在
- Element durlElement = durlList.get(0).element("encode");
- if (null != durlElement) {
- // 高品質的encode、decode
- String durlEncode = durlList.get(0).element("encode").getText();
- String durlDecode = durlList.get(0).element("decode").getText();
- // 高品質音樂的URL
- durl = durlEncode.substring(0, durlEncode.lastIndexOf("/") + 1) + durlDecode;
- if (-1 != durlDecode.lastIndexOf("&"))
- durl = durlEncode.substring(0, durlEncode.lastIndexOf("/") + 1) + durlDecode.substring(0, durlDecode.lastIndexOf("&"));
- }
- music = new Music();
- // 設置普通品質音樂鏈接
- music.setMusicUrl(url);
- // 設置高品質音樂鏈接
- music.setHQMusicUrl(durl);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return music;
- }
- // 測試方法
- public static void main(String[] args) {
- Music music = searchMusic("相信自己", "零點樂隊");
- System.out.println("音樂名稱:" + music.getTitle());
- System.out.println("音樂描述:" + music.getDescription());
- System.out.println("普通品質鏈接:" + music.getMusicUrl());
- System.out.println("高品質鏈接:" + music.getHQMusicUrl());
- }
- }
下面對代碼進行簡單的說明:
1)代碼中的Music類是對音樂消息的封裝(不包括ThumbMediaId參數),讀者可以在本系列教程的第4篇中找到該類的定義;2)運行上述代碼需要引入dom4j的JAR包,筆者使用的是dom4j-1.6.1.jar;
3)searchMusic()方法是提供給外部調用的,在CoreService類中會調用該方法獲得音樂消息需要的Music相關的4個參數(Title、Description、MusicUrl和HQMusicUrl);
4)parseMusic()方法用於解析XML,讀者可以結合代碼中的註釋和之前對XML的分析進行理解,這裏就不再贅述了。
5)116行、127行中的get(0)表示返回多條音樂結果時默認取第一條。
公衆賬號後臺的實現
在公衆賬號後臺的CoreService類中,需要對用戶發送的文本消息進行判斷,如果是以“歌曲”兩個字開頭,就認爲用戶是在使用“歌曲點播”功能,此時需要對“歌曲”兩個字之後的內容進行判斷,如果包含“@”符號,就表示需要按演唱者搜索,否則不指定演唱者。CoreService類的完整代碼如下:
- package org.liufeng.course.service;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.liufeng.course.message.resp.Music;
- import org.liufeng.course.message.resp.MusicMessage;
- import org.liufeng.course.message.resp.TextMessage;
- import org.liufeng.course.util.MessageUtil;
- /**
- * 核心服務類
- *
- * @author liufeng
- * @date 2013-12-10
- */
- public class CoreService {
- /**
- * 處理微信發來的請求
- *
- * @param request
- * @return
- */
- public static String processRequest(HttpServletRequest request) {
- // 返回給微信服務器的xml消息
- String respXml = null;
- // 文本消息內容
- String respContent = null;
- try {
- // xml請求解析
- Map<String, String> requestMap = MessageUtil.parseXml(request);
- // 發送方帳號(open_id)
- String fromUserName = requestMap.get("FromUserName");
- // 公衆帳號
- String toUserName = requestMap.get("ToUserName");
- // 消息類型
- String msgType = requestMap.get("MsgType");
- // 回覆文本消息
- TextMessage textMessage = new TextMessage();
- textMessage.setToUserName(fromUserName);
- textMessage.setFromUserName(toUserName);
- textMessage.setCreateTime(new Date().getTime());
- textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
- // 文本消息
- if (MessageUtil.REQ_MESSAGE_TYPE_TEXT.equals(msgType)) {
- // 文本消息內容
- String content = requestMap.get("Content").trim();
- // 如果以“歌曲”2個字開頭
- if (content.startsWith("歌曲")) {
- // 將歌曲2個字及歌曲後面的+、空格、-等特殊符號去掉
- String keyWord = content.replaceAll("^歌曲[\\+ ~!@#%^-_=]?", "");
- // 如果歌曲名稱爲空
- if ("".equals(keyWord)) {
- respContent = getUsage();
- } else {
- String[] kwArr = keyWord.split("@");
- // 歌曲名稱
- String musicTitle = kwArr[0];
- // 演唱者默認爲空
- String musicAuthor = "";
- if (2 == kwArr.length)
- musicAuthor = kwArr[1];
- // 搜索音樂
- Music music = BaiduMusicService.searchMusic(musicTitle, musicAuthor);
- // 未搜索到音樂
- if (null == music) {
- respContent = "對不起,沒有找到你想聽的歌曲<" + musicTitle + ">。";
- } else {
- // 音樂消息
- MusicMessage musicMessage = new MusicMessage();
- musicMessage.setToUserName(fromUserName);
- musicMessage.setFromUserName(toUserName);
- musicMessage.setCreateTime(new Date().getTime());
- musicMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_MUSIC);
- musicMessage.setMusic(music);
- respXml = MessageUtil.musicMessageToXml(musicMessage);
- }
- }
- }
- }
- // 未搜索到音樂時返回使用指南
- if (null == respXml) {
- if (null == respContent)
- respContent = getUsage();
- textMessage.setContent(respContent);
- respXml = MessageUtil.textMessageToXml(textMessage);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return respXml;
- }
- /**
- * 歌曲點播使用指南
- *
- * @return
- */
- public static String getUsage() {
- StringBuffer buffer = new StringBuffer();
- buffer.append("歌曲點播操作指南").append("\n\n");
- buffer.append("回覆:歌曲+歌名").append("\n");
- buffer.append("例如:歌曲存在").append("\n");
- buffer.append("或者:歌曲存在@汪峯").append("\n\n");
- buffer.append("回覆“?”顯示主菜單");
- return buffer.toString();
- }
- }