基于 Spring boot + MyBatis 的在线音乐播放系统

目录

    一、项目截图

    二、创建 SpringBoot 项目

1、在 IDEA 中创建一个 SpringBoot 项目

2、设置项目名称

3、选择项目依赖

4、选择项目存放路径,就可以创建出一个 SpringBoot 项目

三、配置数据库和xml

1、打开application.properties

2、配置如下信息

四、数据库设计

五、工具包

1、ResponseBodyMessage 类

2、Constant 类

3、数据加密

1. MD5 加密

MD5 的使用

2. BCrypt 加密

Bcrypt 的使用

3. BCrypt加密与MD5加密的区别:

六、配置拦截器

1、创建 config 包,在 config 包中创建 LoginInterceptor 类

2、在 config 包中创建 AppConfig 类

七、实现登录模块

1、登录功能的请求和响应设计

2、创建 User 类

3、创建对应的 Mapper 和 Controller

1. 创建接口 UserMapper

2. 创建 UserMapper.xml

4、在 UserMapper 接口中新增 selectByName 方法

5、创建 UserController 类

6、登录成功测试

7、前端代码

八、实现注册模块

1、注册功能的请求和响应设计

2、在 UserMapper 接口新增方法

3、UserMapper.xml 文件中添加代码

4、在 UserController 类中添加 register 方法

5、注册功能测试

6、前端代码

九、实现上传音乐模块

1、上传音乐功能的请求和响应设计

2、创建 Music 类

3、创建接口 MusicMapper

4、创建 MusicMapper.xml

5、创建 MusicController 类

6、上传音乐功能测试

7、前端代码

十、实现播放音乐模块

1、播放音乐功能的请求和响应设计

2、在 MusicController 类中添加 playMusic 方法

3、播放音乐功能测试

4、前端代码

十一、实现删除音乐模块

1、删除单个音乐

1. 删除单个音乐的请求和响应设计

2. 在 MusicMapper 接口中添加代码

3. MusicMapper.xml 文件中添加代码

4. 在 MusicController 类中添加 deleteByMusicId 方法

5. 删除单个音乐功能测试

6. 前端代码

2、批量删除选中的音乐

1. 批量删除选中的音乐的请求和响应设计

2. 在 MusicController 类中添加 deleteSelMusic 方法

3. 批量删除选中的音乐功能测试

4. 前端代码

十二、实现查询音乐模块

1、查询音乐的请求和响应设计

2、在 MusicMapper 接口中添加代码

3、MusicMapper.xml 文件中添加代码

4、在 MusicController 类中添加 findMusic 方法

5、查询音乐功能测试

1. 查询所有的音乐

2. 模糊匹配,查询指定的音乐

6、前端代码

十三、实现收藏音乐模块

1、收藏音乐的请求和响应设计

2、创建 LoveMusic 类

3、创建接口 LoveMusicMapper

4、创建 LoveMusicMapper.xml

5、创建 LoveMusicController 类

6、收藏音乐功能测试

7、前端代码

十四、实现查询收藏的音乐模块

1、查询收藏音乐的请求和响应设计

2、在 LoveMusicMapper 接口中添加代码

3、LoveMusicMapper.xml 文件中添加代码

4、在 LoveMusicController 类中添加 findLoveMusic 方法

5、查询收藏音乐功能测试

1. 查询所有的收藏音乐

2. 模糊匹配,查询指定的收藏音乐

6、前端代码

十五、实现取消收藏音乐模块

1、取消(移除)收藏音乐的请求和响应设计

2、在 LoveMusicMapper 接口中添加代码

3、LoveMusicMapper.xml 文件中添加代码

4、在 LoveMusicController 类中添加 removeLoveMusic 方法

5、取消收藏音乐功能测试

6、前端代码

十六、完善删除音乐功能

1、在 LoveMusicMapper 接口中添加代码

2、LoveMusicMapper.xml 文件中添加代码

3、调整 MusicController 类中的 deleteMusicByMusicId 和 deleteSelMusic 方法

4、功能测试

1. 查询已上传的音乐

 2. 查询已收藏的音乐

3. 删除已上传的音乐(music 表中的数据)

4. 上传的音乐删除后,收藏的音乐也会被删除


一、项目截图

二、创建 SpringBoot 项目

1、在 IDEA 中创建一个 SpringBoot 项目

2、设置项目名称

3、选择项目依赖

4、选择项目存放路径,就可以创建出一个 SpringBoot 项目

三、配置数据库和xml

1、打开application.properties

2、配置如下信息

#配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=你的用户名
spring.datasource.password=你的密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#配置xml
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml

# 音乐上传后的路径
music.local.path=E:/SaveMusic/(填写存放歌曲的路径)

#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB

# 配置springboot日志调试模式是否开启
debug=true

# 设置打印日志的级别,及打印sql语句
#日志级别:trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
logging.level.com.example.onlinemusic.mapper=debug
#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG

四、数据库设计

-- 创建数据库
drop database if exists `onlinemusic`;
create database if not exists `onlinemusic` character set utf8;
-- 使用数据库
use `onlinemusic`;

-- 用户表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`userid` INT PRIMARY KEY AUTO_INCREMENT comment '用户id',
`username` varchar(20) NOT NULL comment '用户名',
`password` varchar(255) NOT NULL comment '密码'
);

-- 歌曲表
DROP TABLE IF EXISTS `music`;
CREATE TABLE `music` (
`musicid` int PRIMARY KEY AUTO_INCREMENT comment '歌曲id',
`title` varchar(50) NOT NULL comment '歌曲名称',
`singer` varchar(30) NOT NULL comment '歌手',
`time` varchar(13) NOT NULL comment '上传歌曲时间',
`url` varchar(1000) NOT NULL comment '存放歌曲的路径',
`userid` int(11) NOT NULL comment '上传歌曲的用户'
);

-- 歌曲收藏表
DROP TABLE IF EXISTS `lovemusic`;
CREATE TABLE `lovemusic` (
`loveid` int PRIMARY KEY AUTO_INCREMENT comment '收藏歌曲的id',
`user_id` int(11) NOT NULL comment '收藏歌曲的用户id',
`music_id` int(11) NOT NULL comment '歌曲id'
);

五、工具包

在 package com.example.musicserver 目录下创建一个 tools 包(工具包),在这个包中存放整个项目要使用的工具类。

1、ResponseBodyMessage 类

  • 设计统一的响应体工具类,因为做任何操作时都需要响应,所以封装一个通用的响应工具类,这个工具类设计成一个泛型类。
package com.example.onlinemusic.tools;

import lombok.Data;

@Data
public class ResponseBodyMessage <T>{
    private int status; //状态码

    private String message; // 返回的信息(出错的原因等)

    private T data; // 返回给前端的数据(因为返回的数据类型不确定,可能是 String,boolea,int ...,因此使用泛型)

    public ResponseBodyMessage(int status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }
}

2、Constant 类

  • 这个类用来存储不变的常量。 例如:设置 session 对象中的 key 值,key 是一个不变的字符串。
  • 如果在其他地方获取对应的 session 就可以通过这个类中的字符串进行获取。
package com.example.onlinemusic.tools;

public class Constant {
    public static final String USER_SESSION_KEY= "USERINFO_SESSION_KEY"; // 设置 session 中的 key 值
}

3、数据加密

1. MD5 加密

MD5是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程不可逆; 但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。

  • 彩虹表:彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有,有了它可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。

更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的。

  • 加盐的做法:盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中。

MD5 的使用

(1)在 pom.xml 文件中添加依赖(添加到 <dependencies> </dependencies> 标签内)

<!-- md5 依赖 -->
<dependency>
	<groupId>commons-codec</groupId>
	<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-lang3</artifactId>
	<version>3.9</version>
</dependency>

(2)在 tools 包中创建 MD5Util 类

package com.example.onlinemusic.tools;

import org.apache.commons.codec.digest.DigestUtils;

public class MD5Util {
    // 定义一个固定的盐值
    private static final String salt = "1j2a3v4a5"; // 盐值可以自定义

    public static String md5(String src) {
        return DigestUtils.md5Hex(src);
    }

    /**
     * 第一次加密 :模拟前端自己加密,然后传到后端
     *
     * @param inputPass
     * @return
     */
    public static String inputPassToFormPass(String inputPass) {
        String str = "" + salt.charAt(1) + salt.charAt(3) + inputPass
                + salt.charAt(5) + salt.charAt(6);
        return md5(str);
    }

    /**
     * 第2次MD5加密
     *
     * @param formPass 前端加密过的密码,传给后端进行第2次加密
     * @param salt     用户数据库当中的盐值
     * @return
     */
    public static String formPassToDBPass(String formPass, String salt) {
        String str = "" + salt.charAt(0) + salt.charAt(2) + formPass + salt.charAt(5)
                + salt.charAt(4);
        return md5(str);
    }

    /**
     * 上面两个函数合到一起进行调用
     *
     * @param saltDB
     * @return
     * @paraminputPass
     */
    public static String inputPassToDbPass(String inputPass, String saltDB) {
        String formPass = inputPassToFormPass(inputPass);
        String dbPass = formPassToDBPass(formPass, saltDB);
        return dbPass;
    }

    public static void main(String[] args) {
        System.out.println("对用户输入密码进行第1次加密:" + inputPassToFormPass("123456"));
        System.out.println("对用户输入密码进行第2次加密:" + formPassToDBPass(inputPassToFormPass("123456"), salt));
        System.out.println("对用户输入密码进行第2次加密:" + inputPassToDbPass("123456", salt));
    }
}

运行结果

不管运行多少次,这个密码是规定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。

2. BCrypt 加密

  • Bcrypt 就是一款加密工具,可以比较方便地实现数据的加密工作。也可以简单理解为它内部自己实现了随机加盐处理 。
  • 使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。
  • Bcrypt生成的密文是60位的,而MD5的是32位的,因此 Bcrypt 破解难度更大。

Bcrypt 的使用

(1)在 pom.xml 文件中添加依赖(添加到 <dependencies> </dependencies> 标签内)

<!-- security依赖包 (加密)-->
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-config</artifactId>
</dependency>

(2)在springboot启动类添加下面的内容

@SpringBootApplication(exclude =
		{org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})

(3)在 tools 包中创建 BCryptTest 测试类

package com.example.onlinemusic.tools;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptTest {
    public static void main(String[] args) {
        //模拟从前端获得的密码
        String password = "123456";
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String newPassword = bCryptPasswordEncoder.encode(password);
        System.out.println("加密的密码为: "+newPassword);

         //使用matches方法进行密码的校验
        boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword);

        //返回true
        System.out.println("加密的密码和正确密码对比结果: "+same_password_result);
        boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword);

        //返回false
        System.out.println("加密的密码和错误的密码对比结果: " + other_password_result);
    }
}

运行结果(每次运行的生成的密码都不一样)

  • encode方法:对用户密码进行加密。
  • matches方法:参数一,待检验的未加密的密码 。参数二:从数据库中查询出的加密后密码 。

3. BCrypt加密与MD5加密的区别:

  • BCrypt加密: 一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
  • MD5加密: 是不加盐的单向Hash,不可逆的加密算法,同一个密码经过hash的时候生成的是同一个hash值,在大多数的情况下,有些经过md5加密的方法将会被破解。
  • Bcrypt生成的密文是60位的。而MD5的是32位的。
  • 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全,但加密更慢。
  • 虽然BCrpyt也是输入的字符串+盐,但是与MD5+盐的主要区别是:每次加的盐不同(BCrpyt 中的盐值是随即生成的),导致每次生成的结果也不相同。无法比对!

六、配置拦截器

  • 未登录的情况下拦截其他页面,登录成功后才可以访问其他界面

1、创建 config 包,在 config 包中创建 LoginInterceptor 类

package com.example.onlinemusic.config;

import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
            return false;
        }
        return true;
    }
}

2、在 config 包中创建 AppConfig 类

package com.example.onlinemusic.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder(){
        return  new BCryptPasswordEncoder();
    }

    /**
     * 添加拦截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 登录之后才可以访问其他页面
        LoginInterceptor loginInterceptor = new LoginInterceptor();
        registry.addInterceptor(loginInterceptor).
                // 拦截所有的
                addPathPatterns("/**")
                //排除所有的JS
                .excludePathPatterns("/js/**.js")
                //排除images下所有的元素
                .excludePathPatterns("/images/**")
                .excludePathPatterns("/css/**.css")
                .excludePathPatterns("/fronts/**")
                .excludePathPatterns("/player/**")
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/register.html")
                //排除登录和注册接口
                .excludePathPatterns("/user/login")
                .excludePathPatterns("/user/register");
    }
}

七、实现登录模块

1、登录功能的请求和响应设计

请求:
{
  post, // 使用 post 请求
  /user/login // 请求路径
  data:{ username, password } // 传入的数据
}

响应:
{
  "status": 200,
   "message": "登录成功",
   "data": {
             "id": xxxxx,
             "username": xxxxxx,
             "password": xxxxxxxx
   }

}

响应设计字段解释:
{
  状态码为 200 表示成功,-200表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息(返回用户id,用户名)
} 

2、创建 User 类

  • 在 package com.example.musicserver.model 包中创建User类
package com.example.onlinemusic.model;

import lombok.Data;

@Data
public class User {
    private int userId; // 用户id
    private String username; // 用户名
    private String password; // 密码
}

3、创建对应的 Mapper 和 Controller

1. 创建接口 UserMapper

  • 在 package com.example.musicserver.mapper 包中创建 UserMapper 接口

package com.example.onlinemusic.mapper;

import com.example.onlinemusic.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User login(User loginUser);
}

2. 创建 UserMapper.xml

  • 在resource目录下,新建 mybatis 文件夹,新建 UserMapper.xml,在 UserMapper.xml 文件中添加配置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.UserMapper">
    <select id="login" resultType="com.example.onlinemusic.model.User">
        select * from user where username=#{username} and password=#{password}
    </select>

    <select id="selectByName" resultType="com.example.onlinemusic.model.User">
        select * from user where username=#{username};
    </select>

</mapper>

4、在 UserMapper 接口中新增 selectByName 方法

// 通过用户名查询用户是否存在(用户名是唯一的)
User selectByName(String username); 

5、创建 UserController 类

  • 在 package com.example.musicserver.controller 包下,创建 UserController 类
package com.example.onlinemusic.controller;

import com.example.onlinemusic.mapper.UserMapper;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserMapper userMapper;

    // 使用 BCrypt 对密码进行加密
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @RequestMapping("/login")
    // 传入用户名和密码
    public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){

        // 调用 UserMapper 接口
        User user = userMapper.selectByName(username);

        // 判断是否登录成功
        if(user != null){
            System.out.println("登录成功"); // 可以在控制台中打印登录信息

            // 判断当前用户输入的密码(password) 与 数据库中查询到的密码(加密的密码,getPassword())是否匹配
            boolean flag = bCryptPasswordEncoder.matches(password,user.getPassword());

            if(!flag){
                // 密码不匹配,登录失败
                return new ResponseBodyMessage<>(-200,"用户名或密码错误",user);
            }

            // 如果登录成功就将信息写入到 session 中(在 session 中存储了一个用户信息对象,此后可以随时从 session 中将这个对象取出来进行一些操作)
            request.getSession().setAttribute(Constant.USER_SESSION_KEY,user);
            // 状态码为200,表示登录成功,并返回用户信息
            return   new ResponseBodyMessage<>(200,"登录成功",user);
        }else{
            System.out.println("登录失败");
            // 状态码为500,表示登录失败,并返回用户信息
            return   new ResponseBodyMessage<>(-200,"用户名或密码错误",user);

        }
    }
}

6、登录成功测试

  • 在数据库中插入一条数据,启动项目,使用 postman 进行测试。

7、前端代码

    <script>
        $(function(){
            $("#submit").click(function(){
                // 点击登录按钮,获取用户名和密码
                var username = $("#username").val();
                var password = $("#password").val();

                // 判断用户名和密码是否为空(使用 trim 方法,防止输入空格)
                if(username.trim() == "" || password.trim() == ""){
                    alert("账号或密码不能为空");
                    return;
                }
                // 如果用户名和密码不为空,使用 Ajax 传入请求
                $.ajax({
                    type:"POST",
                    url:"/user/login",
                    data:{
                            "username":username,
                            "password":password
                    },
                    // 服务器返回的数据类型
                    dataType:"json",
                    // 请求成功,服务器返回数据
                    success:function(data){
                        console.log(data);
                        // 如果状态码为 200,表示登录成功
                        if(data.status == 200){
                            alert("登录成功");
                            // 跳转到指定页面
                            window.location.href="list.html";
                        }else{
                            alert("登录失败,账号或密码错误");
                            // 登录失败,将用户名或密码置空
                            $("#username").val("");
                            $("#password").val("");
                        }
                    }
                });
            });
        });

        $(function () {
			  $("#register").click(function () {
				  window.location.href="register.html";
              });
          });

    </script>

八、实现注册模块

1、注册功能的请求和响应设计

请求:
{
  post, // 使用 post 请求
  /user/register // 请求路径
  data:{ username, password } // 传入的数据
}

响应:
{
  "status": 200,
   "message": "注册成功",
   "data": {
             "id": xxxxx,
             "username": xxxxxx,
             "password": xxxxxxxx
   }

}

响应设计字段解释:
{
  状态码为 200 表示成功,-200表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息(返回用户id,用户名)
} 

2、在 UserMapper 接口新增方法

    // 输入用户名和密码,注册账号
    boolean insertInToValues(String username,String password);

3、UserMapper.xml 文件中添加代码

    <insert id="insertInToValues" >
        insert into user(username,password)
        values(#{username},#{password});
    </insert>

4、在 UserController 类中添加 register 方法

    /**
     * 用户注册
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/register")
    public ResponseBodyMessage<Boolean> register(@RequestParam String username,@RequestParam String password) {
        User user1 = userMapper.selectByName(username);
        if(user1 != null) {
            return new ResponseBodyMessage<>(-1,"当前用户已经存在",false);
        }else {
            String newPassword = bCryptPasswordEncoder.encode(password);
            boolean flag = userMapper.insertInToValues(username,newPassword);
            if(flag == true){
                return new ResponseBodyMessage<>(200,"注册成功",true);
            }else{
                return new ResponseBodyMessage<>(-200,"注册失败",false);
            }
        }
    }

5、注册功能测试

6、前端代码

    <script>
        $(function(){
            $("#register").click(function(){
                var username = $("#username").val();
                var password = $("#password").val();
                $.ajax({
                 url: "/user/register",
                 type: "POST",
                 data:{
                        "username":username,
                        "password":password
                    },
                dataType:"json",
                 success: function(data){
                     console.log(data);
                     if(data.status == 200) {
                        alert("注册成功");
                        window.location.href="login.html";
                     }else{
                        alert("注册失败");
                        $("#username").val("");
                        $("#password").val("");
                        $("#repassword").val("");
                     }
                 }
             })
            })
        });
    </script>

九、实现上传音乐模块

1、上传音乐功能的请求和响应设计

请求:
{
    post, // 使用 post 请求
    /music/upload // 请求路径
    {singer,MultipartFile file},//上传歌手,歌曲文件
} 

响应:
{
    "status": 200, 
    "message": "上传成功!",
    "data": true
}

响应设计字段解释:
{
  状态码为 200 表示成功,-200 表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息,true 表示上传成功,false 表示上传失败
} 

2、创建 Music 类

  • 在 package com.example.musicserver.model 包中创建 Music 类
package com.example.onlinemusic.model;

import lombok.Data;

@Data
public class Music {
    private int musicId; // 歌曲id
    private String title; // 歌曲名称
    private String singer; //歌手
    private String time; // 上传歌曲的时间
    private String url; // 上传歌曲的路径
    private int userId; // 上传歌曲的用户
}

3、创建接口 MusicMapper

  • 在 package com.example.musicserver.mapper 包中创建 MusicMapper接口
package com.example.onlinemusic.mapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface MusicMapper {
    /**
     *  插入音乐
     * @param title
     * @param singer
     * @param time
     * @param url
     * @param userId
     * @return
     */
    int insert(String title,String singer,String time,String url,int userId);

    /**
     *  查询歌曲名
     * @param title
     * @return
     */
    List<Music> selectBytitle(String title);
}

4、创建 MusicMapper.xml

  • 在 package resources.mybatis 下创建 MusicMapper.xml 文件,在 MusicMapper.xml 文件中添加配置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.UserMapper">

    <insert id="insert">
        insert into music(title,singer,time,url,userId)
        values(#(title),#(singer),#(time),#(url),#(userId));
    </insert>

    <select id="selectBytitle" resultType="com.example.onlinemusic.model.Music">
        select * from music where title = #{title};
    </select>

</mapper>

5、创建 MusicController 类

  • 在 package com.example.musicserver.controller 包下,创建 MusicController 类
package com.example.onlinemusic.controller;

import com.example.onlinemusic.mapper.LoveMusicMapper;
import com.example.onlinemusic.mapper.MusicMapper;
import com.example.onlinemusic.model.Music;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.apache.ibatis.binding.BindingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;


@RestController
@RequestMapping("/music")
public class MusicController {

    // 在配置文件中添加歌曲路径
    @Value("${music.local.path}")
    private String SAVE_PATH;

    @Autowired
    private MusicMapper musicMapper;

    @Autowired
    private LoveMusicMapper loveMusicMapper;

    /**
     *  上传音乐
     *  请求路径:/music/upload
     * @param singer 上传歌手
     * @param file 上传歌曲
     * @param request 请求,验证是否登录
     * @return 返回true表示上传成功,返回false表示上传失败
     */
    @RequestMapping("/upload")
    public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer, @RequestParam ("filename")MultipartFile file, HttpServletRequest request, HttpServletResponse response){

        // 1. 检查是否登录
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
            System.out.println("没有登录");
            return  new ResponseBodyMessage<>(-200,"请登录后再进行上传",false);
        }

        // 2. 获取的是文件的完整名称,包括文件名称+文件拓展名
        String fileNameAndType = file.getOriginalFilename();

        // 3. 查询数据库中是否存在当前要上传的音乐(歌曲名+歌手)
        /**
         *  获取标题(标题不包含后缀.mp3)
         *  使用 lastIndexOf 从后向前找第一个 .
         */
        int index = fileNameAndType.lastIndexOf(".");
        String title = fileNameAndType.substring(0,index);

        // 使用 list 存放歌曲,获取歌曲名
        List<Music> list = musicMapper.selectBytitle(title);
        if(list != null){
            for(Music music : list){
                // 判断当前上传的歌曲+歌手在数据库中是否存在,如果存在则上传失败(歌曲名+歌手 不能重复)
                if(music.getSinger().equals(singer)){
                    return new ResponseBodyMessage<>(-200,"上传失败,数据库中存在此歌曲,不能重复上传",false);
                }
            }
        }

        // 2. 数据上传到服务器

        // 上传文件路径
        String path = SAVE_PATH+fileNameAndType;

        // 上传文件
        File dest = new File(path);
        if(!dest.exists()){
            //如果路径不存在就创建目录
            dest.mkdir();
        }
        try {
            // 将接收到的文件传输到给定目标路径
            file.transferTo(dest);
        } catch (IOException e) {
            e.printStackTrace();
            return new ResponseBodyMessage<>(-200,"上传失败,服务器出现问题",false);
        }

        // 3. 判断上传的文件是否为mp3文件(判断是否存在 TAG 字符)
        File file1 = new File(path);
        byte[] bytes = null;
        try {
            bytes = Files.readAllBytes(file1.toPath());
            if(bytes == null){
                return new ResponseBodyMessage<>(-200,"上传失败,文件不存在",false);
            }
            String str = new String(bytes);
            if(!str.contains("TAG")){
                file1.delete();
                return new ResponseBodyMessage<>(-200,"上传的文件不是mp3文件",false);
            }
        } catch (IOException e) {
            e.printStackTrace();
            return new ResponseBodyMessage<>(-200,"上传失败,服务器出现问题",false);
        }

        // 4. 将数据上传到数据库中(1. 准备数据      2. 调用 insert)


        /**
         *  获取 userId
         * 登录成功后将用户信息写到 session 中,通过 session 中key值(Constant.USERINFO) 就可以获取到对应的 value 值(用户信息)
         */
        User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
        // 获取用户Id
        int userId = user.getUserId();

        /**
         *  url 的作用: 播放音乐->发送 http 请求
         */
        String url = "/music/get?path="+title; // 将 url 存入数据库时不用加后缀 .mp3,在取数据的时候加一个后缀就可以了

        /**
         *  获取上传的时间
         *  将获取的时间格式化为:年-月-日 的形式
         */
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String time = simpleDateFormat.format(new Date());

        // 插入数据
        try {
            int ret = musicMapper.insert(title,singer,time,url,userId);
            if(ret == 1){
                // 数据插入成功
                // 这里应该跳转到音乐列表页面
                response.sendRedirect("/list.html");
                return new ResponseBodyMessage<>(200,"数据库上传成功",true);
            }else{
                // 数据插入失败
                return new ResponseBodyMessage<>(-200,"数据库上传失败",false);
            }
        }catch (BindingException | IOException e){
            // 数据库上传失败,将上传到文件夹中的数据删除
            dest.delete();
            e.printStackTrace();
            return new ResponseBodyMessage<>(-200,"数据库上传失败",false);
        }
    }

    /**
     *  播放音乐
     *  请求路径:/music/get?get=xxx.mp3
     * @param path
     * @return
     */
        @RequestMapping("/get")
        public ResponseEntity<byte[]> playMusic(@RequestParam String path) {
        File file = new File(SAVE_PATH+path);
        byte[] bytes = null;
            try {
                bytes  = Files.readAllBytes(file.toPath()); // 将文件路径中的文件以字节的形式读取,放到 bytes 数组中
                if(bytes == null){
                    // 如果没有读取的文件,则返回状态码 400
                    return ResponseEntity.badRequest().build();
                }
                // 成功读取到文件
                return ResponseEntity.ok(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 如果没有读取的文件,则返回状态码 400
            return ResponseEntity.badRequest().build();
    }

    /**
     * 删除单个音乐
     * 请求路径:/music/delete?musicId=x
     * @param musicId
     * @return
     */
    @RequestMapping("/delete")
    public ResponseBodyMessage<Boolean> deleteMusicByMusicId(@RequestParam String musicId){
        /**
         * 1. 检查待删除的音乐是否存在
         * 2. 如果存在要删除的音乐
         *      1. 删除数据库中的数据
         *      2. 删除服务器上的数据
         */
        // 检查待删除的音乐是否存在
        Music music = musicMapper.findMusicById(Integer.parseInt(musicId));
        if(music == null){
            System.out.println("在控制台打印日志:没有要删除的音乐id");
            return new ResponseBodyMessage<>(-200,"要删除的音乐不存在",false);
        }else{
            // 调用 musicMapper 接口中的 deleteMusicById 方法删除数据库中的数据
           int ret = musicMapper.deleteMusicById(Integer.parseInt(musicId));

           if(ret == 1){
               // 成功删除数据库中的数据
               /*int index = music.getUrl().lastIndexOf("=");
               String fileName = music.getUrl().substring(index+1);*/

               String fileName = music.getTitle();
               // 根据存放音乐的路径删除服务器中的数据
               File file = new File(SAVE_PATH+fileName+".mp3");
               System.out.println("在控制台打印日志:当前音乐的路径:"+file.getPath());
                //对删除服务器中的数据进行判断
               if(file.delete()){
                   // 删除成功
                   return new ResponseBodyMessage<>(200,"音乐删除成功",true);
               }else{
                   return new ResponseBodyMessage<>(-200,"服务器中的音乐删除失败",false);
               }
           }else{
               return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
           }
        }
    }

    /**
     *  批量删除选中的音乐
     *  请求路径:/music/deleteSel
     * @param musicId
     * @return
     */
    @RequestMapping("/deleteSel")
    public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("musicId[]") List<Integer> musicId) {
        System.out.println("在控制台打印日志:所有音乐的 Id:"+musicId);
        int sum = 0; // 统计删除的音乐
        for (int i = 0; i < musicId.size(); i++) {
            Music music = musicMapper.findMusicById(musicId.get(i));
            if (music == null) {
                System.out.println("没有要删除的音乐id");
                return new ResponseBodyMessage<>(-200, "要删除的音乐不存在", false);
            }
            int ret = musicMapper.deleteMusicById(musicId.get(i));
            if (ret == 1) {
                // 成功删除数据库中的数据
                String fileName = music.getTitle();
                // 根据存放音乐的路径删除服务器中的数据
                File file = new File(SAVE_PATH + fileName + ".mp3");
                System.out.println("当前音乐的路径:" + file.getPath());
                //对删除服务器中的数据进行判断
                if (file.delete()) {
                    // 成功删除一条数据,sum 就加上 ret(数据库中成功删除)
                    sum += ret;
                } else {
                    return new ResponseBodyMessage<>(-200, "服务器中的音乐删除失败", false);
                }
            }else{
                return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
            }
        }
        if(sum == musicId.size()){
            // 选中的数据全部删除成功
            System.out.println("在控制台打印日志:选择中的歌曲删除成功");
            return new ResponseBodyMessage<>(200,"音乐删除成功",true);
        }else{
            System.out.println("在控制台打印日志:选择中的歌曲删除失败");
            return new ResponseBodyMessage<>(-200,"音乐删除失败",false);
        }
    }

    /**
     *  查询音乐
     * @param musicName
     * @return
     */
    @RequestMapping("/findmusic")
    public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName){
        List<Music> musicList = null;
        if(musicName != null){
            // 模糊查询,根据歌曲名查询指定的歌曲
            musicList = musicMapper.findMusicByName(musicName);
        }else{
            // 查询所有的音乐
            musicList = musicMapper.findMusic();
        }
        // 查询成功,返回查询到的音乐信息
        return new ResponseBodyMessage<>(200,"查询成功",musicList);
    }
}

6、上传音乐功能测试

7、前端代码

    <div class="upload-container">
        <form method="post" enctype="multipart/form-data" action="/music/upload">
            <div class="upload-dialog">
                <strong>上传本地音乐</strong>
                <em>XiaoXiangYeYu's music upload_music</em>
                <div class="row">
                    <span>音乐</span>
                    <input type="file" id="file" name="filename" placeholder="上传歌曲" class="upload_txtbx"/>
                </div>
                <div class="row">
                    <span>歌手</span>
                    <input type="text" id="singer" name="singer" placeholder="请输入歌手名" class="upload_txtbx"/>
                </div>
                <div class="row">
                    <input type="submit" id="submit" value="上传歌曲" class="submit_btn"/>
                </div>
            </div>
        </form>
    </div>

十、实现播放音乐模块

1、播放音乐功能的请求和响应设计

请求:
{
    get, // 使用 get 请求
    /music/get?path=xxx.mp3 // 请求路径(数据库中存储的 url)
} 

响应:
{
    音乐数据本身的字节信息 // 服务器将数据以字节的形式返回给客户端,客户端获取到信息后就可以进行解析,然后播放音乐
}

2、在 MusicController 类中添加 playMusic 方法

   /**
     * 播放音乐
     * 请求路径:/music/get?paht=xxx.mp3
     * @param path
     * @return
     */
        @RequestMapping("/get")
        public ResponseEntity<byte[]> playMusic(@RequestParam String path) {
        File file = new File(SAVE_PATH+path);
        byte[] bytes = null;
            try {
                bytes  = Files.readAllBytes(file.toPath()); // 将文件路径中的文件以字节的形式读取,放到 bytes 数组中
                if(bytes == null){
                    // 如果没有读取的文件,则返回状态码 400
                    return ResponseEntity.badRequest().build();
                }
                // 成功读取到文件
                return ResponseEntity.ok(bytes);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 如果没有读取的文件,则返回状态码 400
            return ResponseEntity.badRequest().build();
    }

3、播放音乐功能测试

4、前端代码

<script>
    var audios = document.getElementsByTagName("audio");
    // 暂停函数
    function pauseAll() {
            var self = this;
            [].forEach.call(audios, function (i) {
            // 将audios中其他的audio全部暂停
            i !== self && i.pause();
        })
    }
        // 给play事件绑定暂停函数
        [].forEach.call(audios, function (i) {
        i.addEventListener("play", pauseAll.bind(i));
    });
</script>

十一、实现删除音乐模块

1、删除单个音乐

1. 删除单个音乐的请求和响应设计

请求:
{
    post, // 使用 post 请求
    /music/delete, // 请求路径
    musicId // 要删除歌曲的 id
} 

响应:
{
    "status": 200,
    "message": "删除成功!",
    "data": true
}

响应设计字段解释:
{
  状态码为 200 表示成功,-200 表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息,true 表示删除成功,false 表示删除失败
} 

2. 在 MusicMapper 接口中添加代码

    /**
     * 通过音乐 Id 查询当前音乐是否存在
     * @param musicId
     * @return
     */
    Music findMusicById(int musicId);

    /**
     * 通过当前音乐 Id 删除音乐
     * @param musicId
     * @return
     */
    int deleteMusicById(int musicId);

3. MusicMapper.xml 文件中添加代码

    <select id="findMusicById" resultType="com.example.onlinemusic.model.Music">
        select * from music where musicid = #{musicid};
    </select>

    <delete id="deleteMusicById" parameterType="java.lang.Integer">
        delete from music where musicid = #{musicid};
    </delete>

4. 在 MusicController 类中添加 deleteByMusicId 方法

    /**
     * 删除单个音乐
     * 请求路径:/music/delete?musicId=x
     * @param musicId
     * @return
     */
    @RequestMapping("/delete")
    public ResponseBodyMessage<Boolean> deleteByMusicId(@RequestParam String musicId){
        /**
         * 1. 检查待删除的音乐是否存在
         * 2. 如果存在要删除的音乐
         *      1. 删除数据库中的数据
         *      2. 删除服务器上的数据
         */
        // 检查待删除的音乐是否存在
        Music music = musicMapper.findMusicById(Integer.parseInt(musicId));
        if(music == null){
            System.out.println("在控制台打印日志:没有要删除的音乐id");
            return new ResponseBodyMessage<>(-200,"要删除的音乐不存在",false);
        }else{
            // 调用 musicMapper 接口中的 deleteMusicById 方法删除数据库中的数据
           int ret = musicMapper.deleteMusicById(Integer.parseInt(musicId));

           if(ret == 1){
               // 成功删除数据库中的数据
               String fileName = music.getTitle();

               // 根据存放音乐的路径删除服务器中的数据
               File file = new File(SAVE_PATH+fileName+".mp3");
               System.out.println("在控制台打印日志:当前音乐的路径:"+file.getPath());

               //对删除服务器中的数据进行判断
               if(file.delete()){
                   // 删除成功
                   return new ResponseBodyMessage<>(200,"音乐删除成功",true);
               }else{
                   return new ResponseBodyMessage<>(-200,"服务器中的音乐删除失败",false);
               }
           }else{
               return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
           }
        }
    }

5. 删除单个音乐功能测试

6. 前端代码

        function deleteInfo(obj){
            console.log(obj);
            $.ajax({
                url:"/music/delete",
                type:"POST",
                data:{
                    "musicId":obj
                },
                dataType:"json",

                success:function(data){
                    console.log(data);
                    if(data.data == true){
                        alert("删除成功,重新加载当前页面");
                        window.location.href = "list.html";
                    }else{
                        alert("删除失败");
                    }
                }

            });
        }

2、批量删除选中的音乐

1. 批量删除选中的音乐的请求和响应设计

请求:
{
    post, // 使用 post 请求
    /music/deleteSel, // 请求路径
    data:{
       "id":musicId // 要删除的歌曲 id 的数组
    } 
} 

响应:
{
    "status": 200,
    "message": "批量删除成功!",
    "data": true
}

响应设计字段解释:
{
  状态码为 200 表示成功,-200 表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息,true 表示删除成功,false 表示删除失败
} 

2. 在 MusicController 类中添加 deleteSelMusic 方法

    /**
     *  批量删除选中的音乐
     *  请求路径:/music/deleteSel
     * @param musicId
     * @return
     */
    @RequestMapping("/deleteSel")
    public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("musicId[]") List<Integer> musicId) {
        System.out.println("在控制台打印日志:所有音乐的 Id:"+musicId);
        int sum = 0; // 统计删除的音乐
        for (int i = 0; i < musicId.size(); i++) {
            Music music = musicMapper.findMusicById(musicId.get(i));
            if (music == null) {
                System.out.println("没有要删除的音乐id");
                return new ResponseBodyMessage<>(-200, "要删除的音乐不存在", false);
            }
            int ret = musicMapper.deleteMusicById(musicId.get(i));
            if (ret == 1) {
                // 成功删除数据库中的数据
                String fileName = music.getTitle();
                // 根据存放音乐的路径删除服务器中的数据
                File file = new File(SAVE_PATH + fileName + ".mp3");
                System.out.println("当前音乐的路径:" + file.getPath());
                //对删除服务器中的数据进行判断
                if (file.delete()) {
                    // 成功删除一条数据,sum 就加上 ret(数据库中成功删除)
                    sum += ret;
                } else {
                    return new ResponseBodyMessage<>(-200, "服务器中的音乐删除失败", false);
                }
            }else{
                return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
            }
        }
        if(sum == musicId.size()){
            // 选中的数据全部删除成功
            System.out.println("在控制台打印日志:整体删除成功");
            return new ResponseBodyMessage<>(200,"音乐删除成功",true);
        }else{
            System.out.println("在控制台打印日志:整体删除失败");
            return new ResponseBodyMessage<>(-200,"音乐删除失败",false);
        }
    }
}

3. 批量删除选中的音乐功能测试

4. 前端代码

 $(function(){
            $("#submit1").click(function(){
                var name = $("#exampleInputName2").val();
                load(name);
                // window.location.href = "findMusic?musicName="+name;
            });

            $.when(load).done(function(){
                $("#delete").click(function(){
                    var id = new Array(); // 音乐Id
                    var i = 0; // 数组下标
                    // 遍历checkbox
                    $("input:checkbox").each(function(){
                        // 如果被选中,this代表发生事件的dom元素,<input>
                        if($(this).is(":checked")){
                            id[i] = $(this).attr("id");
                            i++;
                        }
                    });
                    console.log(id);

                    $.ajax({
                        url:"/music/deleteSel",
                        data:{
                            "musicId":id
                        },
                        dataType:"json",
                        type:"POST",

                        success:function(obj){
                            if(obj.data == true){
                                alert("删除成功");
                                window.location.href = "list.html";
                            }else{
                                alert("删除失败");
                            }
                        }
                    });
                });
            });

        });

十二、实现查询音乐模块

1、查询音乐的请求和响应设计

此处查询需要满足两个功能:

  1. 支持模糊查询
  2. 支持传入参数为空,当参数为空时默认查询到所有的音乐
请求:
{
    get, // 使用 get 请求
    /music/findmusic, // 请求路径
    data:{musicName:musicName}, // 根据歌曲名进行查询
} 

响应:【不给musicName传参】// 如果不传参时默认查询到所有的音乐
{
    "status": 200,
    "message": "查询到了歌曲的信息",
    "data": [
    {
        "id": 19,
        "title": "张靓颖 - 我的梦",
        "singer": "张靓颖",
        "url": "/music/get?path=张靓颖 - 我的梦",
        "time": "2022-08-20",
        "userid": 1
    },
    {
        "id": 20,
        "title": "纯音乐 - Victory",
        "singer": "张三",
        "url": "/music/get?path=纯音乐 - Victory",
        "time": "2022-03-20",
        "userid": 1
    }]
} 

响应:【给musicName传参】// 如果传入参数返回指定查询的歌曲
{
    "status": 200,
    "message": "查询到了歌曲的信息",
    "data": [
    {
        "id": 19,
        "title": "张靓颖 - 我的梦",
        "singer": "张靓颖",
        "url": "/music/get?path=张靓颖 - 我的梦",
        "time": "2022-08-20",
        "userid": 1
    }]
}

响应设计字段解释:
{
  状态码为 200 表示成功
  状态描述信息,描述此次请求成功
  返回的数据,请求成功后给前端的数据信息,返回查询到的音乐信息(歌曲id、歌曲名、歌手、存放歌曲的路径、上传时间、上传用户的id)
} 

2、在 MusicMapper 接口中添加代码

    /**
     * 查询所有的音乐
     * @return
     */
    List<Music> findMusic();

    /**
     * 模糊查询,根据歌曲名查询指定的歌曲
     * @param musicName
     * @return
     */
    List<Music> findMusicByName(String musicName);

3、MusicMapper.xml 文件中添加代码

    <select id="findMusic" resultType="com.example.onlinemusic.model.Music">
        select * from music ;
    </select>

    <select id="findMusicByName" resultType="com.example.onlinemusic.model.Music">
        select * from music where title like concat('%', #{musicName}, '%');
    </select>

4、在 MusicController 类中添加 findMusic 方法

    /**
     *  查询音乐
     * @param musicName
     * @return
     */
    @RequestMapping("/findmusic")
    public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName){
        List<Music> musicList = null;
        if(musicName != null){
            // 模糊查询,根据歌曲名查询指定的歌曲
            musicList = musicMapper.findMusicByName(musicName);
        }else{
            // 查询所有的音乐
            musicList = musicMapper.findMusic();
        }
        // 查询成功,返回查询到的音乐信息
        return new ResponseBodyMessage<>(200,"查询成功",musicList);
    }

5、查询音乐功能测试

1. 查询所有的音乐

2. 模糊匹配,查询指定的音乐

6、前端代码

    <script type="text/javascript">
        // 查询
        $(function(){
            load();
        });

        // musicName 可以传参(模糊匹配),也可以不传参(不传参默认传入的是所有的音乐)
        function load(musicName){

        $.ajax({
            type:"GET",
            url:"/music/findmusic",
            data:{
                    "musicName":musicName
            },
            // 服务器返回的数据类型
            dataType:"json",

            // obj 查找存储的所有信息
            success:function(obj){
                console.log(obj);
                // data数组,存放歌曲信息
                var data = obj.data;

                var s = '';
                for(var i=0;i<data.length;i++){
                    
                    var musicUrl = data[i].url+".mp3";
                    console.log(musicUrl);
                    s += '<tr>';
                    s += '<th> <input id="'+data[i].musicId+'"type="checkbox"> </th>';
                    s += '<td>'+ data[i].title +'</td>';
                    s += '<td>'+ data[i].singer +'</td>';
                    s += "<td <a href=\"\">  <audio src= \""+ musicUrl+"\"  + controls=\"controls\" preload=\"auto\" loop=\"loop\" class=\"audio_btn\">  >"  + "</audio> </a> </td>";
                    s += '<td> <button class = "btn btn-primary" onclick="loveInfo('+data[i].musicId+')"> 收藏歌曲 </button>' + '</td>';
                    s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+ data[i].musicId +')" >删除歌曲</button>'+'</td>';
                    s += '</tr>';    
                    }

                    $("#info").html(s);
                }
            });
        }

十三、实现收藏音乐模块

1、收藏音乐的请求和响应设计

请求:
{
    post, // 使用 post 请求
    /lovemusic/likeMusic // 请求路径
    data: music_id //音乐id
} 

响应:
{
    "status": 0,
    "message": "点赞音乐成功",
    "data": true
}

响应设计字段解释:
{
  状态码为 200 表示成功,-200 表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息,true 表示收藏成功,false 表示收藏失败
} 

2、创建 LoveMusic 类

  • 在 package com.example.musicserver.model 包中创建 LoveMusic 类
package com.example.onlinemusic.model;

import lombok.Data;

@Data
public class LoveMusic {
    private int loveId;
    private int userId;
    private int musicId;
}

3、创建接口 LoveMusicMapper

  • 在 package com.example.musicserver.mapper 包中创建 LoveMusicMapper 接口
package com.example.onlinemusic.mapper;

import com.example.onlinemusic.model.LoveMusic;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LoveMusicMapper {

    /**
     * 查询是否已经收藏过该音乐
     * @param userId
     * @param musicId
     * @return
     */
    LoveMusic findLoveMusic(int userId, int musicId);

    /**
     * 收藏音乐
     * @param userId
     * @param musicId
     * @return
     */
    boolean insertLoveMusic(int userId,int musicId);

}

4、创建 LoveMusicMapper.xml

  • 在 package resources.mybatis 下创建 LoveMusicMapper.xml 文件,在 LoveMusicMapper.xml 文件中添加配置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.LoveMusicMapper">

    <select id="findLoveMusic" resultType="com.example.onlinemusic.model.LoveMusic">
        select * from lovemusic where user_id = #{userId} and music_id = #{musicId};
    </select>

    <insert id="insertLoveMusic">
        insert into lovemusic(user_id,music_id) values(#{userId}, #{musicId});
    </insert>

</mapper>

5、创建 LoveMusicController 类

  • 在 package com.example.musicserver.controller 包下,创建 LoveMusicController 类
package com.example.onlinemusic.controller;

import com.example.onlinemusic.mapper.LoveMusicMapper;
import com.example.onlinemusic.model.LoveMusic;
import com.example.onlinemusic.model.Music;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;

@RestController
@RequestMapping("/lovemusic")
public class LoveMusicController {

    @Autowired
    private LoveMusicMapper loveMusicMapper;

    /**
     * 收藏音乐
     * 请求路径:/lovemusic/likeMusic?music_id=x
     * @param music_id 传入的音乐 id
     * @param request
     * @return
     */
    @RequestMapping("/likeMusic")
    public ResponseBodyMessage<Boolean> likeMusic(@RequestParam String music_id, HttpServletRequest request){

        // 1. 检查是否登录
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
            System.out.println("登录失败");
            return  new ResponseBodyMessage<>(-200,"请登录后再进行收藏",false);
        }

        // 2. 获取 usrId
        User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
        int userId = user.getUserId();
        System.out.println("在控制台打印日志:user_id:"+userId);

        // 3. 将传入的 music_id 转换成整型(获取 musicId)
        int musicId = Integer.parseInt(music_id);
        System.out.println("在控制台打印日志:music_id:"+musicId);

        // 4. 判断当前歌曲是否被收藏过
        LoveMusic loveMusic = loveMusicMapper.findLoveMusic(userId,musicId);
        if(loveMusic != null){
            // 之前收藏过这首歌曲
            return new ResponseBodyMessage<>(-200,"该歌曲已被收藏,请勿重复收藏",false);
        }else{
           boolean ret =  loveMusicMapper.insertLoveMusic(userId,musicId);
           if(ret){
               return new ResponseBodyMessage<>(200,"收藏成功",true);
           }else{
               return new ResponseBodyMessage<>(-200,"收藏失败",false);
           }
        }
    }
}

6、收藏音乐功能测试

7、前端代码

        function loveInfo(obj){
            $.ajax({
                type:"POST",
                url:"/lovemusic/likeMusic",
                data:{
                    "music_id":obj
                },
                dataType:"json",

                success:function(data){
                    if(data.data == true){
                        alert("收藏成功");
                        window.location.href = "loveMusic.html";
                    }else{
                        alert("收藏失败");
                    }
                }
            });
        }

十四、实现查询收藏的音乐模块

1、查询收藏音乐的请求和响应设计

此处查询需要满足两个功能:

  1. 支持模糊查询
  2. 支持传入参数为空,当参数为空时默认查询到所有的音乐
请求:
{
    get, // 使用 get 请求
    /lovemusic/findlovemusic, // 请求路径
    data:{musicName:musicName}, // 根据歌曲名进行查询
} 

响应:【不传入musicName参数】// 如果不传参时默认查询到所有的音乐
{
    "status": 200,
    "message": "查询到了所有的收藏的音乐",
    "data": [
    {
        "id": 19,
        "title": "张靓颖 - 我的梦",
        "singer": "张靓颖",
        "url": "/music/get?path=张靓颖 - 我的梦",
        "time": "2022-08-20",
        "userid": 1
    },
    {
        "id": 20,
        "title": "纯音乐 - Victory",
        "singer": "张三",
        "url": "/music/get?path=纯音乐 - Victory",
        "time": "2022-03-20",
        "userid": 1
    }]
} 

响应:【传入musicName参数】// 如果传入参数返回指定查询的歌曲
{
    "status": 200,
    "message": "查询到了收藏歌曲的信息",
    "data": [
    {
        "id": 19,
        "title": "张靓颖 - 我的梦",
        "singer": "张靓颖",
        "url": "/music/get?path=张靓颖 - 我的梦",
        "time": "2022-08-20",
        "userid": 1
    }]
}

响应设计字段解释:
{
  状态码为 200 表示成功
  状态描述信息,描述此次请求成功
  返回的数据,请求成功后给前端的数据信息,返回查询到的音乐信息(歌曲id、歌曲名、歌手、存放歌曲的路径、上传时间、上传用户的id)
} 

2、在 LoveMusicMapper 接口中添加代码

    /**
     * 查询当前用户收藏的所有音乐
     * @param userId
     * @return
     */
    List<Music> findLoveMusicByUserId(int userId);

    /**
     * 模糊查询,根据歌曲名查询当前用户指定的收藏歌曲
     * @param musicName
     * @param userId
     * @return
     */
    List<Music> findLoveMusicByMusicNameAndUserId(String musicName,int userId);

3、LoveMusicMapper.xml 文件中添加代码

    <select id="findLoveMusicByUserId" resultType="com.example.onlinemusic.model.Music">
        select music.*
        from music,lovemusic
        where music.musicid = lovemusic.music_id and user_id = #{userId};
    </select>

    <select id="findLoveMusicByMusicNameAndUserId" resultType="com.example.onlinemusic.model.Music">
        select music.*
        from music,lovemusic
        where music.musicid = lovemusic.music_id and user_id = #{userId} and title like concat('%', #{musicName}, '%');
    </select>

4、在 LoveMusicController 类中添加 findLoveMusic 方法

    @RequestMapping("findlovemusic")
    public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String musicName,HttpServletRequest request){
        // 1. 检查是否登录
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
            System.out.println("登录失败");
            return  new ResponseBodyMessage<>(-200,"请登录后再查找",null);
        }

        // 2. 获取 usrId
        User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
        int user_id = user.getUserId();
        System.out.println("在控制台打印日志:user_id:"+user_id);

        List<Music> musicList = null;
        if(musicName == null){
            //查询当前用户收藏的所有音乐
            musicList = loveMusicMapper.findLoveMusicByUserId(user_id);
        }else{
            //模糊查询,根据歌曲名查询当前用户指定的收藏歌曲
            musicList = loveMusicMapper.findLoveMusicByMusicNameAndUserId(musicName,user_id);
        }
        return new ResponseBodyMessage<>(200,"查询成功",musicList);
    }

5、查询收藏音乐功能测试

1. 查询所有的收藏音乐

2. 模糊匹配,查询指定的收藏音乐

6、前端代码

         $(function(){
            load();
        });

        // musicName 可以传参(模糊匹配),也可以不传参(不传参默认传入的是所有的音乐)
        function load(musicName){

        $.ajax({
            type:"GET",
            url:"/lovemusic/findlovemusic",
            data:{
                    "musicName":musicName
            },
            // 服务器返回的数据类型
            dataType:"json",

            // obj 查找存储的所有信息
            success:function(obj){
                console.log(obj);
                // data数组,存放歌曲信息
                var data = obj.data;

                var s = '';
                for(var i=0;i<data.length;i++){
                    var musicUrl = data[i].url+".mp3";
                    s += '<tr>';
                    s += '<td>'+ data[i].title +'</td>';
                    s += '<td>'+ data[i].singer +'</td>';
                    s += "<td <a href=\"\">  <audio src= \""+ musicUrl+"\"  + controls=\"controls\" preload=\"auto\" loop=\"loop\" class=\"audio_btn\">  >"  + "</audio> </a> </td>";
                    s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+ data[i].musicId +')">取消收藏</button>'+'</td>';
                    s += '</tr>';    
                    }

                    $("#info").html(s);
                }
            });
        }

十五、实现取消收藏音乐模块

1、取消(移除)收藏音乐的请求和响应设计

请求:
{
    post, // 使用 post 请求
    /lovemusic/removelovemusic, // 请求路径
    data:{id:music_id} // 根据收藏列表中音乐的 id 进行移除
} 

响应:
{
    "status": 200,
    "message": "取消收藏成功!",
    "data": true
}


响应设计字段解释:
{
  状态码为 200 表示成功,-200 表示失败
  状态描述信息,描述此次请求成功或者失败的原因
  返回的数据,请求成功后,需要给前端的数据信息,true 表示移除收藏成功,false 表示移除收藏失败
}

2、在 LoveMusicMapper 接口中添加代码

    /**
     *  移除某个用户收藏的引用
     * @param userId 用户的 ID
     * @param musicId 待移除音乐的 ID
     * @return 受影响的行数
     */
    int removeLoveMusic(int userId,int musicId);

3、LoveMusicMapper.xml 文件中添加代码

    <delete id="removeLoveMusic" parameterType="java.lang.Integer">
        delete from lovemusic where user_id = #{userId} and music_id = #{musicId};
    </delete>

4、在 LoveMusicController 类中添加 removeLoveMusic 方法

    /**
     * 移除收藏的音乐
     * @param music_id
     * @param request
     * @return
     */
    @RequestMapping("/removelovemusic")
    public ResponseBodyMessage<Boolean> removeLoveMusic(@RequestParam String music_id,HttpServletRequest request){

        // 1. 检查是否登录
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
            System.out.println("登录失败");
            return  new ResponseBodyMessage<>(-200,"请登录后再移除",false);
        }

        // 2. 获取 usrId
        User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
        int userId = user.getUserId();
        System.out.println("在控制台打印日志:user_id:"+userId);

        //  3. 将传入的 music_id 转换成整型(获取 musicId)
        int musicId = Integer.parseInt(music_id);
        System.out.println("在控制台打印日志:music_id:"+musicId);

        int ret = loveMusicMapper.removeLoveMusic(userId,musicId);
        if(ret == 1){
            return new ResponseBodyMessage<>(200,"取消收藏成功",true);
        }else{
            return new ResponseBodyMessage<>(-200,"取消收藏失败",false);
        }
    }

5、取消收藏音乐功能测试

6、前端代码

       function deleteInfo(obj){
            console.log(obj);
            $.ajax({
                url:"/lovemusic/removelovemusic",
                type:"POST",
                data:{
                    "music_id":obj
                },
                dataType:"json",

                success:function(data){
                    console.log(data);
                    if(data.data == true){
                        alert("取消收藏成功,重新加载当前页面");
                        window.location.href = "loveMusic.html";
                    }else{
                        alert("取消收藏失败");
                    }
                }

            });
        }

十六、完善删除音乐功能

前面所完成的功能中存在一个BUG,当成功删除音乐表(music)中的音乐后,收藏表(lovemusic)中的音乐不会被删除。

1、在 LoveMusicMapper 接口中添加代码

    /**
     * 根据音乐的ID进行删除
     * @param musicId
     * @return
     */
    int deleteLoveMusicByMusicId(int musicId);

2、LoveMusicMapper.xml 文件中添加代码

    <delete id="deleteLoveMusicByMusicId" parameterType="java.lang.Integer">
        delete from lovemusic where music_id = #{musicId};
    </delete>

3、调整 MusicController 类中的 deleteMusicByMusicId 和 deleteSelMusic 方法

// 在 MusicController 注入 LoveMusicMapper 
    @Autowired
    private LoveMusicMapper loveMusicMapper;

// 调整后的 deleteMusicByMusicId 方法 和 deleteSelMusic 方法
   /**
     * 删除单个音乐
     * 请求路径:/music/delete?musicId=x
     * @param musicId
     * @return
     */
    @RequestMapping("/delete")
    public ResponseBodyMessage<Boolean> deleteMusicByMusicId(@RequestParam String musicId){
        /**
         * 1. 检查待删除的音乐是否存在
         * 2. 如果存在要删除的音乐
         *      1. 删除数据库中的数据
         *      2. 删除服务器上的数据
         */
        // 检查待删除的音乐是否存在
        Music music = musicMapper.findMusicById(Integer.parseInt(musicId));
        if(music == null){
            System.out.println("在控制台打印日志:没有要删除的音乐id");
            return new ResponseBodyMessage<>(-200,"要删除的音乐不存在",false);
        }else{
            // 调用 musicMapper 接口中的 deleteMusicById 方法删除数据库中的数据
           int ret = musicMapper.deleteMusicById(Integer.parseInt(musicId));

           if(ret == 1){
               // 成功删除数据库中的数据
               String fileName = music.getTitle();

               // 根据存放音乐的路径删除服务器中的数据
               File file = new File(SAVE_PATH+fileName+".mp3");
               System.out.println("在控制台打印日志:当前音乐的路径:"+file.getPath());
                //对删除服务器中的数据进行判断
               if(file.delete()){
                   // 同步删除 lovemusic 表中的音乐
                   loveMusicMapper.deleteLoveMusicByMusicId(Integer.parseInt(musicId));
                   // 删除成功
                   return new ResponseBodyMessage<>(200,"音乐删除成功",true);
               }else{
                   return new ResponseBodyMessage<>(-200,"服务器中的音乐删除失败",false);
               }
           }else{
               return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
           }
        }
    }

    /**
     *  批量删除选中的音乐
     *  请求路径:/music/deleteSel
     * @param musicId
     * @return
     */
    @RequestMapping("/deleteSel")
    public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("musicId[]") List<Integer> musicId) {
        System.out.println("在控制台打印日志:所有音乐的 Id:"+musicId);
        int sum = 0; // 统计删除的音乐
        for (int i = 0; i < musicId.size(); i++) {
            Music music = musicMapper.findMusicById(musicId.get(i));
            if (music == null) {
                System.out.println("没有要删除的音乐id");
                return new ResponseBodyMessage<>(-200, "要删除的音乐不存在", false);
            }
            int ret = musicMapper.deleteMusicById(musicId.get(i));
            if (ret == 1) {
                // 成功删除数据库中的数据
                String fileName = music.getTitle();
                // 根据存放音乐的路径删除服务器中的数据
                File file = new File(SAVE_PATH + fileName + ".mp3");
                System.out.println("当前音乐的路径:" + file.getPath());
                //对删除服务器中的数据进行判断
                if (file.delete()) {
                    // 同步删除 lovemusic 表中的音乐
                    loveMusicMapper.deleteLoveMusicByMusicId(musicId.get(i));
                    // 成功删除一条数据,sum 就加上 ret(数据库中成功删除)
                    sum += ret;
                } else {
                    return new ResponseBodyMessage<>(-200, "服务器中的音乐删除失败", false);
                }
            }else{
                return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
            }
        }
        if(sum == musicId.size()){
            // 选中的数据全部删除成功
            System.out.println("在控制台打印日志:选择中的歌曲删除成功");
            return new ResponseBodyMessage<>(200,"音乐删除成功",true);
        }else{
            System.out.println("在控制台打印日志:选择中的歌曲删除失败");
            return new ResponseBodyMessage<>(-200,"音乐删除失败",false);
        }
    }

4、功能测试

1. 查询已上传的音乐

 2. 查询已收藏的音乐

3. 删除已上传的音乐(music 表中的数据)

4. 上传的音乐删除后,收藏的音乐也会被删除

  • 7
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潇湘夜雨.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值