A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 wordge 于 2017-8-31 22:07 编辑
前言:
    鉴于现在网上没有一个成熟的音乐第三方接口(其原因:音乐版权问题),但是又想在自己的web项目中添加在线音乐怎么办?那下面我们就一起来剖析QQ音乐(y.qq.com)播放音乐时候的请求响应来打造一个自己的在线音乐(搜索、显示、播放)的小应用。本例子仅做学习使用。其中代码部分没有做严谨判断,先实现功能。


一、搜索音乐
1.输入实时匹配推荐歌曲名称
打开chrome浏览器(web前端开发利器之一)、打开QQ音乐网站、打开F12开发者工具

紧接着,我们在搜索框中输入几个字符试试(先别点击查询),看看F12面板中Network有什么变化

我们每输入一个字母就会向QQ音乐后台发送异步请求,下面我们随便点击一个请求来看看具体的请求和响应的内容


我们来分析下请求部分
请求:
url:https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg
参数名               含义
is_xml               0代表获取json数据   1代表获取xml数据                  可空:默认为0
format              jsonp方式获取(一般跨域会用到)   json               可空:默认就是json
key                   查询的关键字                                                          非空
g_tk                  QQ官方的一个加密值,具体没有去研究、先别管     可空
jsonpCallback   jsonp的回调函数名称                                             可空:非jsonp方式调用   经测试中自己的应用中以jsonp调用会报错,其原因是jsonp的回调方法名,其有具体的算法
loginUin           登录身份验证码,如果qq登录后就是你的qq号码      可空
hostUin            主机身份验证码,具体没有去研究、先别管                可空
inCharset         请求字符编码                                                            可空
outCharset       响应字符编码                                                            可空
notice               通知,具体没有去研究、先别管                                 可空
platform          平台                                                                           可空
needNewCode  是否需要新编码 ,具体没有去研究、先别管               可空
响应:
参数                  含义
code                 状态码
subcode            子编码
data                  数据对象
data-->album    搜索匹配推荐专辑数据
data-->mv         搜索匹配推荐MV数据
data-->singer    搜索匹配推荐歌手数据
data-->song      搜索匹配推荐单曲数据,这是我们要的数据

下面我们根据这个url来测试下:

实现思路:
1.创建一个页面,一个输入框
2.输入框监听键盘事件,获取输入框输入内容,做ajax请求到Servlet
3.Servlet中根据页面ajax请求过来的key参数值来调用之前分析好的QQ音乐的推荐匹配地址(这里用的是Jodd.jar来做的http模拟请求,当然也可以使用httpclient.jar等)
4.根据调用QQ音乐推荐匹配地址返回的数据获取itemList的内容,然后返回到info.jsp做一个下拉显示



好,分析得到上面的数据后,下面我们就来做这件的搜索实时匹配推荐歌曲
先来张效果图


2.项目结构(简单的servlet工程)

3.页面代码
[HTML] 纯文本查看 复制代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>嘿马音乐</title>
<script type="text/javascript" src="${ pageContext.request.contextPath }/jquery-1.8.3.js"></script>
<script type="text/javascript" src="${ pageContext.request.contextPath }/search.js"></script>
<style type="text/css">
        #d1 tr:hover{
                background-color: #ddd;
        }
</style>
</head>
<body style="min-height: 600px;">
        <div style="text-align: center;">
                <h1>嘿马♪♫</h1>
                <input type="text" id="key" name="key" style="width:400px;height:30px;" placeholder="刚刚好">
                <input id="querybtn" type="button" value="嘿一下" style="height: 30px;margin-left: 2px;">
        </div>
        <div id="d1" style="display:none;position:absolute;top:103px;left:400px;border:1px solid gray;width:400px;height:200px;"></div>
</body>
</html>

4.js部分代码
[JavaScript] 纯文本查看 复制代码
var index = 0;
var oldIndex = 0;
$(function(){
        // 为文本框绑定事件:
        $("#key").keyup(function(event){
                var currentCode = event.keyCode;
                if(currentCode==13 && $("#d1 td").size()!=0){
                        //回车键的时候选中当前 并关闭
                        $("#d1").hide();
                        return;
                }
                // 获得文本框的值:
                var word = $(this).val();
                // 异步发送请求:
                if(currentCode!=38 && currentCode!=40){
                        if(word != ""){
                                $.post("/online_music/RecommendMusicServlet",{"key":word},function(data){
                                        if(""!=$.trim(data)){
                                                $("#d1").show().html(data);
                                                $("#d1 td").each(function(){
                                                        $(this).click(function(){
                                                                $("#key").val($(this).text());
                                                                $("#d1").hide();
                                                        });
                                                });
                                        }else{
                                                $("#d1").hide();
                                        }
                                });
                        }else{
                                $("#d1").hide();
                        }
                }else{
                        var size = $("#d1 td").size();
                        if(size==0){
                                return;
                        }

                        $("#d1 td").eq(index).css("background-color","#ddd");
                        $("#d1 td").eq(oldIndex).css("background-color","");
                        $("#key").val($("#d1 td").eq(index).text());
                        
                        if(currentCode==38){//向上
                                if(index!=0){
                                        oldIndex = index;
                                        index--;
                                }else{
                                        oldIndex = index;
                                        index = size-1;
                                }
                        }else if(currentCode==40){//向下
                                if(index>=(size-1)){
                                        oldIndex = index;
                                        index = 0;
                                }else{
                                        oldIndex = index;
                                        index++;
                                }
                        }
                        
                }
        });
        
        //点击body的时候关闭推荐面板
        $("body").click(function(){
                $("#d1").hide();
        });
        
        $("#key").focus(function(){
                var word = $(this).val();
                if(word != ""){
                        $.post("/online_music/RecommendMusicServlet",{"key":word},function(data){
                                if(""!=$.trim(data)){
                                        $("#d1").show().html(data);
                                        $("#d1 td").each(function(){
                                                $(this).click(function(){
                                                        $("#key").val($(this).text());
                                                        $("#d1").hide();
                                                });
                                        });
                                }else{
                                        $("#d1").hide();
                                }
                        });
                }else{
                        $("#d1").hide();
                }
        });
        
        //点击查询按钮“嘿一下”进行模糊查询
        $("#querybtn").click(function(){
                var word = $("#key").val();
                world = $.trim(word);
                if(word != ""){
                        window.open("/online_music/PageSong?w="+word);
                }
        });
        
});

5.输入实时推荐匹配servlet
[Java] 纯文本查看 复制代码
package com.net023.servlet;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import jodd.json.JsonParser;

@WebServlet("/RecommendMusicServlet")
public class RecommendMusicServlet extends HttpServlet {
        private static final long serialVersionUID = 1L;
       
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                try {
                        // 接收参数:
                        request.setCharacterEncoding("UTF-8");
                        String word = request.getParameter("key").trim();
                        
                        //https://c.y.qq.com/soso/fcgi-bin/client_search_cp?p=3&n=30&w=七里香&format=json
                        
                        List<Map<String, Object>> itemlist = null;
                        if(null!=word&&!"".equals(word)){
                                HttpResponse httpResponse = HttpRequest.get("https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg?is_xml=0&format=json").query("key", word).send();
                                String bodyText = httpResponse.bodyText();
                                Map<String, Object> dd = new JsonParser().parse(bodyText);
                                Map<String, Object> data = (Map<String, Object>) dd.get("data");
                                Map<String, Object> song = (Map<String, Object>) data.get("song");
                                if(null!=song){
                                        itemlist = (List<Map<String, Object>>) song.get("itemlist");
                                }
                        }
                        /**
                         * {
                    "docid": "3236607256197134876",
                    "id": "107762070",
                    "mid": "001hs89m3C5p3g",
                    "name": "小半",
                    "singer": "陈粒"
                },
                         */
                        request.setAttribute("list", itemlist);
                        request.getRequestDispatcher("/info.jsp").forward(request, response);
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException();
                }
        }

        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                doGet(request, response);
        }
}


6.info.jsp
[HTML] 纯文本查看 复制代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:if test="${not empty list }">
        <table border="0" width="100%">
                <c:forEach var="song" items="${ list }">
                <tr>
                        <td id="${song.mid }">${ song.name }</td>
                </tr>
                </c:forEach>
        </table>
</c:if>




二、模糊查询
1.同样我们打开chrome浏览器、F12开发者工具、点击输入框后面的查询按钮、查看Network的请求响应详情


发现会有一个请求,并返回和我们输入查询相关的返回数据(json)、下面我们就来分析下这个请求和响应


歌曲图片根据查看QQ音乐网站的音乐图片查看图片地址为
https://y.gtimg.cn/music/photo_new/T002R300x300M000 + albummid + .jpg
或者
https://y.gtimg.cn/music/photo_new/T002R150x150M000 + albummid + .jpg
我们来分析下请求部分
请求:
url:https://c.y.qq.com/soso/fcgi-bin/client_search_cp
参数名               含义
p                       当前请求数据页码                 
format              jsonp方式获取(一般跨域会用到) 、 json
new_json          0 旧json数据结构    1新json数据结构
n                      分页每页显示记录条数      
w                     查询的关键字                                                          非空
...其他参数我们用不到,不再介绍
响应:只介绍我们能用到的
参数                  含义
data              返回数据
data-->song   返回当前页的数据
data-->curnum  分页记录大小
data-->curpage   当前页码
data-->totalnum   总共记录数
data-->list   


下面我们根据这个url来测试下:


实现思路:
1.输入框输入要查询的歌曲名称、点击查询“嘿一下”
2.根据输入的内容跳转到新开的浏览器访问Servlet
3.Servlet中根据请求过来的w参数值来调用之前分析好的QQ音乐的模糊查询地址(这里用的是Jodd.jar来做的http模拟请求,当然也可以使用httpclient.jar等)
4.根据调用QQ音乐模糊查询返回的数据获取list的内容,然后返回到songs.jsp进行循环遍历展示
当然我们这里没有做实际的分页,下次再继续优化


来张效果图



2.分页模糊查询servlet(PageSong)

3.songs.jsp
[Groovy] 纯文本查看 复制代码
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
 <%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>搜索结果</title>
</head>
<body>
        <ul>
                <c:forEach items="${songlist }" var="song">
                <c:if test="${not empty  song.songmid }">
                        <li>
                                <img alt="" src="https://y.gtimg.cn/music/photo_new/T002R150x150M000${song.albummid }.jpg">
                                <a href="${ pageContext.request.contextPath }/ListenToMusicServlet?songMid=${song.songmid }" target="_blank">#歌曲名称:${song.songname }     #歌手:${song.singer[0].name }</a>
                        </li>
                        <br/>                        
                        <br/>                        
                </c:if>
                </c:forEach>
        </ul>
</body>
</html>


三、播放音乐
1.点击一个歌曲,点击播放

2.在播放页面打开F12,强制刷新、播放、查看请求响应
经过分析查看,我们可以看到有2条重要的请求

我们先来查看分析第一个勾选的请求


其规则就是之前我们获取list中的songmid作为参数、filename(‘C400’+songmid+‘.m4a’)、guid(任意的几个数字)、cid(固定值205361747)然后作为参数访问https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg,然后返回vkey
完整的url为https://c.y.qq.com/base/fcgi-bin/fcg_music_express_mobile3.fcg?cid=205361747&guid=55&songmid={0}&filename={1}
然后我们来分析第二个勾选的请求url

其规则就是我们之前获取的vkey和之前使用的guid和fromtag固定值66作为参数访问http://dl.stream.qqmusic.qq.com/C400+songmid+.m4a
其完整url为http://dl.stream.qqmusic.qq.com/C400+songmid+.m4a?vkey={0}&fromtag={1}&guid={2}
下面我们就来访问下这两个url


实现思路:
1.根据我们之前模糊查询后,循环遍历的jsp页面上点击链接,带上songmid参数、访问servlet
2.在servlet中根据songmid来访问第一个url来得到vkey,然后再根据vkey来拼接生成最终播放音乐的url,然后重定向


2.ListenToMusicServlet

源码附件...

7.png (19.01 KB, 下载次数: 3)

7.png

online_music.rar

1.83 MB, 下载次数: 32

8 个回复

倒序浏览
军哥,屌屌的
回复 使用道具 举报
听过米粒姑娘的电台吗,挺好的
回复 使用道具 举报
哇哇哇,良心分享
回复 使用道具 举报
期待第二部吧、分页、歌词、点歌播放{:8_515:}
回复 使用道具 举报
Oliverwqcwrw 发表于 2017-9-2 10:42
听过米粒姑娘的电台吗,挺好的

刚才特意搜索了下  挺好听的
回复 使用道具 举报
现在http://dl.stream.qqmusic.qq.com/C400+songmid+.m4a?vkey={0}&fromtag={1}&guid={2}这种方式是不是不能用了?播放不了,提示403
回复 使用道具 举报 1 0
Port 金牌黑马 2017-9-12 08:45:37
8#
回复 使用道具 举报
蔡老师,屌屌的
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马