微信公众帐号开发教程第18篇-应用实例之音乐搜索

来源:互联网 发布:netstat命令查看端口 编辑:程序博客网 时间:2024/05/16 10:12

引言及内容概要

微信公众平台支持向用户回复音乐消息,用户收到音乐消息后,点击即可播放音乐。通过音乐消息,公众账号可以实现音乐搜索(歌曲点播)功能,即用户输入想听的音乐名称,公众账号返回对应的音乐(歌曲)。读者可以关注xiaoqrobot体验该功能,操作指南及使用如下所示。


考虑到歌曲名称有重复的情况,用户还可以同时指定歌曲名称、演唱者搜索歌曲。下面就为读者详细介绍歌曲点播功能的实现过程。


音乐消息说明

在微信公众平台开发者文档中提到,向用户回复音乐消息需要构造如下格式的XML数据。

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <xml>  
  2.     <ToUserName><![CDATA[toUser]]></ToUserName>  
  3.     <FromUserName><![CDATA[fromUser]]></FromUserName>  
  4.     <CreateTime>12345678</CreateTime>  
  5.     <MsgType><![CDATA[music]]></MsgType>  
  6.     <Music>  
  7.         <Title><![CDATA[TITLE]]></Title>  
  8.         <Description><![CDATA[DESCRIPTION]]></Description>  
  9.         <MusicUrl><![CDATA[MUSIC_Url]]></MusicUrl>  
  10.         <HQMusicUrl><![CDATA[HQ_MUSIC_Url]]></HQMusicUrl>  
  11.         <ThumbMediaId><![CDATA[media_id]]></ThumbMediaId>  
  12.     </Music>  
  13. </xml>  
上面XML中,需要注意的是<Music>节点中的参数,说明如下:

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的请求地址如下:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. 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均可)。例如,要搜索歌曲零点乐队的“相信自己”,可以像下面这样:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // GB2312编码的音乐搜索链接  
  2. 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$$$$  
  3. // UTF-8编码的音乐搜索链接  
  4. 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$$$$  
通过浏览器访问上面的地址,返回的是如下格式的XML数据:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <result>  
  2.     <count>1</count>  
  3.     <url>  
  4.         <encode>  
  5.             <![CDATA[http://zhangmenshiting.baidu.com/data2/music/44277542/ZWZla2xra2pfn6NndK6ap5WXcJVob5puZ2trbWprmnBjZ2xolpeZa2drZmWZmZmdl2hjZWhvnWlpYmRtZmltcGplZFqin5t1YWBobW5qcGxia2NmZ2twbzE$]]>  
  6.         </encode>  
  7.         <decode>  
  8.             <![CDATA[44277542.mp3?xcode=a39c6698955c82594aab36931dcbef60139f180191368931&mid=0.59949419022597]]>  
  9.         </decode>  
  10.         <type>8</type>  
  11.         <lrcid>64644</lrcid>  
  12.         <flag>1</flag>  
  13.     </url>  
  14.     <durl>  
  15.         <encode>  
  16.             <![CDATA[http://zhangmenshiting2.baidu.com/data2/music/44277530/ZWZla2xramhfn6NndK6ap5WXcJVob5puZ2trbWprmnBjZ2xolpeZa2drZmWZmZmdl2hjaGhvnZ5qlGRpbpedamJla1qin5t1YWBobW5qcGxia2NmZ2twbzE$]]>  
  17.         </encode>  
  18.         <decode>  
  19.             <![CDATA[44277530.mp3?xcode=a39c6698955c82594aab36931dcbef60439ff9b159af2138&mid=0.59949419022597]]>  
  20.         </decode>  
  21.         <type>8</type>  
  22.         <lrcid>64644</lrcid>  
  23.         <flag>1</flag>  
  24.     </durl>  
  25.     <p2p>  
  26.         <hash>022bc0fbf66cd19bea96db49634419dc2600393f</hash>  
  27.         <url>  
  28.             <![CDATA[ ]]>  
  29.         </url>  
  30.         <type>mp3</type>  
  31.         <size>5236902</size>  
  32.         <bitrate>192</bitrate>  
  33.     </p2p>  
  34. </result>  

返回结果中的主要参数说明如下:

1)<count> 表示搜索到的音乐数;

2)<url>中包含了普通品质的音乐链接,<durl>中包含了高品质音乐的链接;

3)<encode>中包含了加密后的音乐链接,实际上只是对音乐名称进行了加密,<decode>中包含了解密后的音乐名称。因此,要获取音乐的链接就需要重点分析<encode>和<decode>中的内容,下面会专门为读者进行介绍。

4)<type>表示音乐文件的类型,如rm、wma、mp3等;

5)<lrcid>是歌词的ID,<url>中的歌词ID为64644,那么如何得到歌词呢?本例并不关心歌词,只是附带提一下。歌词的地址如下:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. http://box.zhangmen.baidu.com/bdlrc/646/64644.lrc  
其中,http://box.zhangmen.baidu.com/bdlrc/是固定值;646为歌词所在目录名,计算方法为歌词ID(64644)除以100,取整数部分;64644.lrc是歌词文件名。

下面来看如何从<encode>和<decode>中得到音乐链接。为了便于说明,笔者将上面搜索结果中的<url>和<durl>部分抽取出来,并进行了标注,如下图所示。


上图中,1和2拼接起来是普通品质音乐的链接,3和4拼接起来是高品质音乐的链接。也就是说,普通品质和高品质的音乐链接如下:

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. // 普通品质音乐链接  
  2. http://zhangmenshiting.baidu.com/data2/music/44277542/44277542.mp3?xcode=a39c6698955c82594aab36931dcbef60139f180191368931  
  3. // 高品质音乐链接  
  4. http://zhangmenshiting2.baidu.com/data2/music/44277530/44277530.mp3?xcode=a39c6698955c82594aab36931dcbef60439ff9b159af2138  
参数xcode可以理解为随机验证码,每次搜索得到的值都不一样,如果不带该参数会报未授权异常“401 Authorization Required”。需要注意的是,xcode是有时间限制的,超过限制再访问链接会报异常:{"Error":{"code":"2","Message":"object not exists","LogId":"3456414897"}}。在xcode有效的前提下,通过浏览器访问上面的音乐链接,会提示下载音乐。


编程调用百度音乐搜索API

知道如何从API返回结果中得到音乐链接后,就可以编写程序来实现了。笔者将发送HTTP请求、URL编码、解析XML等操作全部封装在BaiduMusicService类中,该类的代码如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. import java.io.InputStream;  
  2. import java.io.UnsupportedEncodingException;  
  3. import java.net.HttpURLConnection;  
  4. import java.net.URL;  
  5. import java.util.List;  
  6.   
  7. import org.dom4j.Document;  
  8. import org.dom4j.Element;  
  9. import org.dom4j.io.SAXReader;  
  10.   
  11. import org.liufeng.course.message.resp.Music;  
  12.   
  13. /** 
  14.  * 百度音乐搜索API操作类 
  15.  *  
  16.  * @author liufeng 
  17.  * @date 2013-12-09 
  18.  */  
  19. public class BaiduMusicService {  
  20.     /** 
  21.      * 根据名称和作者搜索音乐 
  22.      *  
  23.      * @param musicTitle 音乐名称 
  24.      * @param musicAuthor 音乐作者 
  25.      * @return Music 
  26.      */  
  27.     public static Music searchMusic(String musicTitle, String musicAuthor) {  
  28.         // 百度音乐搜索地址  
  29.         String requestUrl = "http://box.zhangmen.baidu.com/x?op=12&count=1&title={TITLE}$${AUTHOR}$$$$";  
  30.         // 对音乐名称、作者进URL编码  
  31.         requestUrl = requestUrl.replace("{TITLE}", urlEncodeUTF8(musicTitle));  
  32.         requestUrl = requestUrl.replace("{AUTHOR}", urlEncodeUTF8(musicAuthor));  
  33.         // 处理名称、作者中间的空格  
  34.         requestUrl = requestUrl.replaceAll("\\+""%20");  
  35.   
  36.         // 查询并获取返回结果  
  37.         InputStream inputStream = httpRequest(requestUrl);  
  38.         // 从返回结果中解析出Music  
  39.         Music music = parseMusic(inputStream);  
  40.   
  41.         // 如果music不为null,设置标题和描述  
  42.         if (null != music) {  
  43.             music.setTitle(musicTitle);  
  44.             // 如果作者不为"",将描述设置为作者  
  45.             if (!"".equals(musicAuthor))  
  46.                 music.setDescription(musicAuthor);  
  47.             else  
  48.                 music.setDescription("来自百度音乐");  
  49.         }  
  50.         return music;  
  51.     }  
  52.   
  53.     /** 
  54.      * UTF-8编码 
  55.      *  
  56.      * @param source 
  57.      * @return 
  58.      */  
  59.     private static String urlEncodeUTF8(String source) {  
  60.         String result = source;  
  61.         try {  
  62.             result = java.net.URLEncoder.encode(source, "UTF-8");  
  63.         } catch (UnsupportedEncodingException e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.         return result;  
  67.     }  
  68.   
  69.     /** 
  70.      * 发送http请求取得返回的输入流 
  71.      *  
  72.      * @param requestUrl 请求地址 
  73.      * @return InputStream 
  74.      */  
  75.     private static InputStream httpRequest(String requestUrl) {  
  76.         InputStream inputStream = null;  
  77.         try {  
  78.             URL url = new URL(requestUrl);  
  79.             HttpURLConnection httpUrlConn = (HttpURLConnection) url.openConnection();  
  80.             httpUrlConn.setDoInput(true);  
  81.             httpUrlConn.setRequestMethod("GET");  
  82.             httpUrlConn.connect();  
  83.             // 获得返回的输入流  
  84.             inputStream = httpUrlConn.getInputStream();  
  85.         } catch (Exception e) {  
  86.             e.printStackTrace();  
  87.         }  
  88.         return inputStream;  
  89.     }  
  90.   
  91.     /** 
  92.      * 解析音乐参数 
  93.      *  
  94.      * @param inputStream 百度音乐搜索API返回的输入流 
  95.      * @return Music 
  96.      */  
  97.     @SuppressWarnings("unchecked")  
  98.     private static Music parseMusic(InputStream inputStream) {  
  99.         Music music = null;  
  100.         try {  
  101.             // 使用dom4j解析xml字符串  
  102.             SAXReader reader = new SAXReader();  
  103.             Document document = reader.read(inputStream);  
  104.             // 得到xml根元素  
  105.             Element root = document.getRootElement();  
  106.             // count表示搜索到的歌曲数  
  107.             String count = root.element("count").getText();  
  108.             // 当搜索到的歌曲数大于0时  
  109.             if (!"0".equals(count)) {  
  110.                 // 普通品质  
  111.                 List<Element> urlList = root.elements("url");  
  112.                 // 高品质  
  113.                 List<Element> durlList = root.elements("durl");  
  114.   
  115.                 // 普通品质的encode、decode  
  116.                 String urlEncode = urlList.get(0).element("encode").getText();  
  117.                 String urlDecode = urlList.get(0).element("decode").getText();  
  118.                 // 普通品质音乐的URL  
  119.                 String url = urlEncode.substring(0, urlEncode.lastIndexOf("/") + 1) + urlDecode;  
  120.                 if (-1 != urlDecode.lastIndexOf("&"))  
  121.                     url = urlEncode.substring(0, urlEncode.lastIndexOf("/") + 1) + urlDecode.substring(0, urlDecode.lastIndexOf("&"));  
  122.   
  123.                 // 默认情况下,高音质音乐的URL 等于 普通品质音乐的URL  
  124.                 String durl = url;  
  125.   
  126.                 // 判断高品质节点是否存在  
  127.                 Element durlElement = durlList.get(0).element("encode");  
  128.                 if (null != durlElement) {  
  129.                     // 高品质的encode、decode  
  130.                     String durlEncode = durlList.get(0).element("encode").getText();  
  131.                     String durlDecode = durlList.get(0).element("decode").getText();  
  132.                     // 高品质音乐的URL  
  133.                     durl = durlEncode.substring(0, durlEncode.lastIndexOf("/") + 1) + durlDecode;  
  134.                     if (-1 != durlDecode.lastIndexOf("&"))  
  135.                         durl = durlEncode.substring(0, durlEncode.lastIndexOf("/") + 1) + durlDecode.substring(0, durlDecode.lastIndexOf("&"));  
  136.                 }  
  137.                 music = new Music();  
  138.                 // 设置普通品质音乐链接  
  139.                 music.setMusicUrl(url);  
  140.                 // 设置高品质音乐链接  
  141.                 music.setHQMusicUrl(durl);  
  142.             }  
  143.         } catch (Exception e) {  
  144.             e.printStackTrace();  
  145.         }  
  146.         return music;  
  147.     }  
  148.   
  149.     // 测试方法  
  150.     public static void main(String[] args) {  
  151.         Music music = searchMusic("相信自己""零点乐队");  
  152.         System.out.println("音乐名称:" + music.getTitle());  
  153.         System.out.println("音乐描述:" + music.getDescription());  
  154.         System.out.println("普通品质链接:" + music.getMusicUrl());  
  155.         System.out.println("高品质链接:" + music.getHQMusicUrl());  
  156.     }  
  157. }  

下面对代码进行简单的说明:

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类的完整代码如下:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. package org.liufeng.course.service;  
  2.   
  3. import java.util.Date;  
  4. import java.util.Map;  
  5. import javax.servlet.http.HttpServletRequest;  
  6. import org.liufeng.course.message.resp.Music;  
  7. import org.liufeng.course.message.resp.MusicMessage;  
  8. import org.liufeng.course.message.resp.TextMessage;  
  9. import org.liufeng.course.util.MessageUtil;  
  10.   
  11. /** 
  12.  * 核心服务类 
  13.  *  
  14.  * @author liufeng 
  15.  * @date 2013-12-10 
  16.  */  
  17. public class CoreService {  
  18.     /** 
  19.      * 处理微信发来的请求 
  20.      *  
  21.      * @param request 
  22.      * @return 
  23.      */  
  24.     public static String processRequest(HttpServletRequest request) {  
  25.         // 返回给微信服务器的xml消息  
  26.         String respXml = null;  
  27.         // 文本消息内容  
  28.         String respContent = null;  
  29.         try {  
  30.             // xml请求解析  
  31.             Map<String, String> requestMap = MessageUtil.parseXml(request);  
  32.             // 发送方帐号(open_id)  
  33.             String fromUserName = requestMap.get("FromUserName");  
  34.             // 公众帐号  
  35.             String toUserName = requestMap.get("ToUserName");  
  36.             // 消息类型  
  37.             String msgType = requestMap.get("MsgType");  
  38.   
  39.             // 回复文本消息  
  40.             TextMessage textMessage = new TextMessage();  
  41.             textMessage.setToUserName(fromUserName);  
  42.             textMessage.setFromUserName(toUserName);  
  43.             textMessage.setCreateTime(new Date().getTime());  
  44.             textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);  
  45.   
  46.             // 文本消息  
  47.             if (MessageUtil.REQ_MESSAGE_TYPE_TEXT.equals(msgType)) {  
  48.                 // 文本消息内容  
  49.                 String content = requestMap.get("Content").trim();  
  50.                 // 如果以“歌曲”2个字开头  
  51.                 if (content.startsWith("歌曲")) {  
  52.                     // 将歌曲2个字及歌曲后面的+、空格、-等特殊符号去掉  
  53.                     String keyWord = content.replaceAll("^歌曲[\\+ ~!@#%^-_=]?""");  
  54.                     // 如果歌曲名称为空  
  55.                     if ("".equals(keyWord)) {  
  56.                         respContent = getUsage();  
  57.                     } else {  
  58.                         String[] kwArr = keyWord.split("@");  
  59.                         // 歌曲名称  
  60.                         String musicTitle = kwArr[0];  
  61.                         // 演唱者默认为空  
  62.                         String musicAuthor = "";  
  63.                         if (2 == kwArr.length)  
  64.                             musicAuthor = kwArr[1];  
  65.   
  66.                         // 搜索音乐  
  67.                         Music music = BaiduMusicService.searchMusic(musicTitle, musicAuthor);  
  68.                         // 未搜索到音乐  
  69.                         if (null == music) {  
  70.                             respContent = "对不起,没有找到你想听的歌曲<" + musicTitle + ">。";  
  71.                         } else {  
  72.                             // 音乐消息  
  73.                             MusicMessage musicMessage = new MusicMessage();  
  74.                             musicMessage.setToUserName(fromUserName);  
  75.                             musicMessage.setFromUserName(toUserName);  
  76.                             musicMessage.setCreateTime(new Date().getTime());  
  77.                             musicMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_MUSIC);  
  78.                             musicMessage.setMusic(music);  
  79.                             respXml = MessageUtil.musicMessageToXml(musicMessage);  
  80.                         }  
  81.                     }  
  82.                 }  
  83.             }  
  84.             // 未搜索到音乐时返回使用指南  
  85.             if (null == respXml) {  
  86.                 if (null == respContent)  
  87.                     respContent = getUsage();  
  88.                 textMessage.setContent(respContent);  
  89.                 respXml = MessageUtil.textMessageToXml(textMessage);  
  90.             }  
  91.         } catch (Exception e) {  
  92.             e.printStackTrace();  
  93.         }  
  94.         return respXml;  
  95.     }  
  96.   
  97.     /** 
  98.      * 歌曲点播使用指南 
  99.      *  
  100.      * @return 
  101.      */  
  102.     public static String getUsage() {  
  103.         StringBuffer buffer = new StringBuffer();  
  104.         buffer.append("歌曲点播操作指南").append("\n\n");  
  105.         buffer.append("回复:歌曲+歌名").append("\n");  
  106.         buffer.append("例如:歌曲存在").append("\n");  
  107.         buffer.append("或者:歌曲存在@汪峰").append("\n\n");  
  108.         buffer.append("回复“?”显示主菜单");  
  109.         return buffer.toString();  
  110.     }  
  111. }  
上述代码的逻辑比较简单,用户发送“歌曲+名称”或者“歌曲+名称@演唱者”就能搜索歌曲,搜索不到时会提示用户,如果发送其他内容回复歌曲点播功能的用法。



PS:CSDN2013年度博客之星评选活动已经拉开序幕,2013年12月14日开始投票,请为柳峰(用户名:lyq8479)投上您的宝贵一票,谢谢!

投票地址:http://vote.blog.csdn.net/blogstaritem/blogstar2013/lyq8479

转帖请注明本文出自柳峰的博客(http://blog.csdn.net/lyq8479),请尊重他人的辛勤劳动成果,谢谢!

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 驾照过期3个月怎么办 驾照过期6个月怎么办 b本扣分了6分怎么办 b2驾照提前换证有扣分怎么办 酒驾驾驶证被扣怎么办 驾驶证被扣了分怎么办 c1驾照扣了12分怎么办 结婚证丢了怎么办离婚手续 结婚证不见了怎么办离婚手续 科目一身份丢了怎么办 考驾照身份证过期了怎么办 酒驾发交通事故致人死亡怎么办 车保险快到期了怎么办 车保险贴掉了怎么办 小车撞凹进去了怎么办 被代位追偿了怎么办 车被别人抵押了怎么办 朋友没驾照借车怎么办 车子被朋友撞了怎么办 电车被交警扣了怎么办 e照扣了12分怎么办 驾驶证被扣33分怎么办 a2驾驶员扣33分怎么办 驾驶证被扣48分怎么办 驾照扣了33分怎么办 车辆被扣36分怎么办 车被朋友借走了怎么办 在中国终身禁驾怎么办 c驾驶证忘年审了怎么办 驾驶证脱审1年多怎么办 驾驶证脱审四个月怎么办 驾照b2过期没审怎么办 a照驾照过期没审怎么办 驾照扣60多分怎么办 无证违章被扣分怎么办 变味的牛奶喝了怎么办 孩子喝了坏牛奶怎么办 孕妇喝了坏牛奶怎么办 驾照过期五年了怎么办 科目一没过之后怎么办? 重庆科目一没过怎么办