引言及内容概要

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

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

音乐消息说明

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

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

  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均可)。例如,要搜索歌曲零点乐队的“相信自己”,可以像下面这样:

  1. // GB2312编码的音乐搜索链接
  2. http://box.zhangmen.baidu.com/x?op=12&count=1&title=%CF%E0%D0%C5%D7%D4%BC%BA$$
  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$$

通过浏览器访问上面的地址,返回的是如下格式的XML数据:

  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,那么如何得到歌词呢?本例并不关心歌词,只是附带提一下。歌词的地址如下:

  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拼接起来是高品质音乐的链接。也就是说,普通品质和高品质的音乐链接如下:

  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类中,该类的代码如下:

  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. import org.dom4j.Document;
  7. import org.dom4j.Element;
  8. import org.dom4j.io.SAXReader;
  9. import org.liufeng.course.message.resp.Music;
  10. /**
  11. * 百度音乐搜索API操作类
  12. *
  13. * @author liufeng
  14. * @date 2013-12-09
  15. */
  16. public class BaiduMusicService {
  17. /**
  18. * 根据名称和作者搜索音乐
  19. *
  20. * @param musicTitle 音乐名称
  21. * @param musicAuthor 音乐作者
  22. * @return Music
  23. */
  24. public static Music searchMusic(String musicTitle, String musicAuthor) {
  25. // 百度音乐搜索地址
  26. String requestUrl = "http://box.zhangmen.baidu.com/x?op=12&count=1&title={TITLE}
    AUTHOR

    $$";

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

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

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

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

上述代码的逻辑比较简单,用户发送“歌曲+名称”或者“歌曲+名称@演唱者”就能搜索歌曲,搜索不到时会提示用户,如果发送其他内容回复歌曲点播功能的用法。

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

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

    引言及内容概要 微信公众平台支持向用户回复音乐消息,用户收到音乐消息后,点击即可播放音乐.通过音乐消息,公众账号可以实现音乐搜索(歌曲点播)功能,即用户输入想听的音乐名称,公众账号返回对应的音乐(歌曲 ...

  2. [041] 微信公众帐号开发教程第17篇-应用实例之智能翻译

    内容概要 本篇文章为大家演示怎样在微信公众帐号上实现"智能翻译",本例中翻译功能是通过调用"百度翻译API"实现的.智能翻译是指用户随意输入想要翻译的内容(单词 ...

  3. 微信公众帐号开发教程第16篇-应用实例之历史上的今天

    内容概要 本篇文章主要讲解如何在微信公众帐号上实现"历史上的今天"功能.这个例子本身并不复杂,但希望通过对它的学习,读者能够对正则表达式有一个新的认识,能够学会运用现有的网络资源丰 ...

  4. 微信公众帐号开发教程第17篇-应用实例之智能翻译

    内容概要 本篇文章为大家演示如何在微信公众帐号上实现"智能翻译",本例中翻译功能是通过调用"百度翻译API"实现的.智能翻译是指用户任意输入想要翻译的内容(单词 ...

  5. 微信公众帐号开发教程第6篇-消息及消息处理工具的封装

    本篇内容主要讲解如何将微信公众平台定义的消息及消息相关的操作封装成工具类,方面后期的使用  官方文档 接下来要做的就是将消息请求.回复中定义的消息进行封装,建立与之对应的Java类(Java是一门面向 ...

  6. 微信公众帐号开发教程第1篇-引言(转)

    接触微信公众帐号已经有两个多月的时间了,在这期间,除了陆续完好个人公众帐号xiaoqrobot以外,还带领团队为公司开发了两个企业应用:一个是普通类型的公众帐号,还有一个是会议类型的公众帐号.经过这3 ...

  7. 微信公众帐号开发教程第13篇-图文消息全攻略

    引言及内容概要 已经有几位读者抱怨"柳峰只用到文本消息作为示例,从来不提图文消息,都不知道图文消息该如何使用",好吧,我错了,原本以为把基础API封装完.框架搭建好,再给出一个文本 ...

  8. [025] 微信公众帐号开发教程第1篇-引言

    接触微信公众帐号已经有两个多月的时间了,在这期间,除了陆续完善个人公众帐号xiaoqrobot以外,还带领团队为公司开发了两个企业应用:一个是普通类型的公众帐号,另一个是会议类型的公众帐号.经过这3个 ...

  9. 微信公众帐号开发教程第13篇-图文消息全攻略 -- 转载

    引言及内容概要 已经有几位读者抱怨"柳峰只用到文本消息作为示例,从来不提图文消息,都不知道图文消息该如何使用",好吧,我错了,原本以为把基础API封装完.框架搭建好,再给出一个文本 ...

最新文章

  1. leangoo领歌scrum工具任务到期提醒支持通知到飞书
  2. 冠榕智能灯光控制协议分析(controller init)
  3. 如何使用CppUnit进行单元测试
  4. 基于JAVA+SpringMVC+Mybatis+MYSQL的音乐播放系统
  5. innobackupex和Xtrabackup备份和恢复MySQL数据
  6. android调用系统相机进行拍照,android调用系统相机拍照
  7. 简单好用的ImageAI编程库!选择最适合你的!
  8. 明星证券化之殇|一点财经
  9. if函数的语法c语言并列,逻辑函数IF的各种使用方法
  10. 怎么从身份证号码批量提取出生年月日?
  11. 乘飞机选座位的胡思乱想
  12. 《林超:给年轻人的跨学科通识课》导图 04:函数模型
  13. h5微信f分享链接给对方获取对方手机号_企业微信可以搜索微信号添加好友/客户吗?怎样主动添加通过率高?...
  14. 基于MQTT协议的Mosquitto的使用及libmosquitto客户端编程
  15. Content type 'multipart/form-data;boundary=--------------------------258075776767858126421870;chars
  16. Latex之安装GBK字体
  17. postgresql 11.2 下载
  18. 先电OpenStack搭建
  19. mysql视图的特点有哪些_MySQL之视图
  20. 百年前的小学课本令人汗颜!

热门文章

  1. android数字滚动动画,数字滚动效果 RollingText
  2. 若泽大数据-剑指数仓培训笔记1
  3. 面对音乐行业解决音乐侵权严重痛点:启动数字化转型的设计思维工作坊
  4. 怎么选择WiFi短信认证硬件供应商?
  5. 监控dns流量有哪些方法?
  6. JavaScript高级第01天笔记
  7. 校园贷受阻,汽车分期能帮趣店挽救困局吗?
  8. 游戏感虚拟感觉的游戏设计师_从零到游戏设计师:即使您没有任何经验,如何开始制作视频游戏...
  9. Analytic hierarchy process
  10. 春招:我居然三天就拿到了offer?