SSM项目 – Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔

字典

文章目录

智能车大赛

1、项目简介

项目的名称: Online Music Player(在线音乐播放器)
需要实现的功能

zookeeper

1、登录
2、新增(上传)歌曲
3、删除指定歌曲
4、批量删除选中的歌曲
5、查询你要想要的歌曲
6、添加歌曲至你的喜欢列表
7、移除喜欢的歌曲。

玫瑰

项目技术栈:

下载

1、Spring Boot
2、MyBatis
3、前端(HTML,CSS,JS,jQuery)

默认浏览器

如果你基础不扎实,上来就做项目。
我建议你:把 JavaEE 进阶专栏的知识 ,看完再说。

还有,如果你的 前端知识,也没有学习过,可以参考这个 JavaEE初阶专栏的文章。它有讲关于前端三剑客的文章,都是一些非常基础,非常原生的 API,只是扫盲级别的知识,不会太难。

另外,MySQL的话,你可以看这个专栏的文章https://blog.csdn.net/darkandgrey/category_11662384.html

vue


Icon组件

2、创建SSM项目

专业版 idea 创建方式

在这里插入图片描述

java stream


关键点检测

社区版 idea 创建方式

在这里插入图片描述

多个catch的使用注意


城市功能区划分布数据

3.创建数据库和配置文件

首先,我们要明白 一个项目 可以分为两个部分。
1、客户端(前端)
2、服务器(后端)
在这里插入图片描述
所以,我们的第一步就是要去设计

Prim

数据库设计

首先,我们知道 一个 音乐播放器中有几个实体。
1、(user)用户
2、(music)音乐
这两个实体类就可以看成两张数据表(user 和 music 表)。
这两张表的对应关系:
一个用户可以收藏多首歌曲,一首歌曲也可以被多个用户收藏。
即:用户 和 音乐 是 多对多的关系。
因为 每个用户都有这自己的收藏歌单,所以,我们还需要创建一个 收藏表(lovemusic)。

在整个项目中,我们目前发现有三个数据表是需要创建的。
下面,我们就来设计这些数据表中的字段,都有哪些。
在这里插入图片描述
我们数据库就设计好了,接下来就是将 笔记本(我用的是subline)上的 SQL 拷贝 MySQL 上。
在这里插入图片描述
此时,我们在 MySQL就创建好了 一个 musiclibrary 数据库,并在里面创建三张表 user,music,lovemusic。
在这里插入图片描述

ldr


ue5

配置数据库和xml

在这里插入图片描述

斗地主


docker gitlab

最终的配置文件信息,我就不给了。自己写。


andorid原生插件

4.实现登录的准备工作(登录模块设计)

1 创建User类

在这里插入图片描述

opengl


redis 复制集群搭建手册

2 创建对应的Mapper和Controller

2.1 创建接口UserMapper

在这里插入图片描述

qiankun


vuex

2.2 创建UserMapper.xml

MyBatis 的 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="">
</mapper>

在这里插入图片描述

空域


设计规范

3 实现登录

在这里插入图片描述


3.1 登录的请求和响应设计

在这里插入图片描述


3.2 创建UserController类

在这里插入图片描述


3.3 使用postman验证登录功能

在测试之前,我们需要做一件事:
给 数据库的 user 表插入一条用户信息。
在这里插入图片描述
再把我们的项目启动起来。
在这里插入图片描述
然后,我们在使用 postman去构造一个post请求,来看一下 idea 打印的效果。
在这里插入图片描述
还没有完!我的后端是要查询的结果 返回给前端的。
所以说:我们 代码还需要进行优化。


3.4 优化代码

设计统一的响应体类工具类

在这里插入图片描述


优化 UserController 代码。

在这里插入图片描述

我们再来使用 postman 来测试一下
在这里插入图片描述
还需要咋改进型一下,密码不用返回。为了安全。
如果你直接返回,别人只要进行抓包,密码 就被得到了。
在这里插入图片描述
再来测一下
在这里插入图片描述
另外,登录成功后,我们还需要创建一个会话。
就是下次用户自来访问的时候,就不用在登陆的。
在这里插入图片描述
但是!还存在着一个问题:会话属性名称太长了,万一写错,就找不到这个属性值了。
通常情况下,我们是这样去做的。如下所示
在这里插入图片描述
到这里,代码差不多优化完了。


总代码

UserMapper

@Mapper
public interface UserMapper {
    // 登录方法
    User login(User loginUser);

    //  根据用户名称查询用户信息
    User selectByName(@Param("username") String username);
}

UuserMapper.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.onlinemusicplayer.mapper.UserMapper">
<!--  登录方法  -->
    <select id="login" resultType="com.example.onlinemusicplayer.model.User">
        select * from user where username=#{username} and password=#{password};
    </select>
<!--  根据用户名称查询用户信息  -->
    <select id="selectByName" resultType="com.example.onlinemusicplayer.model.User">
        select * from user where username=#{username};
    </select>
</mapper>

UserService

@Service
public class UserService {
    @Autowired
    UserMapper userMapper;
    // 根据 用户名 和 密码 登录的方法
    public User login(User userLogin){
        return userMapper.login(userLogin);
    }
    // 根据用户名查询对应的用户信息
    public User selectByName(String username){
        return  userMapper.selectByName(username);
    }

}

UserController

// 这个controller,就负责关于 用户的登录
@RestController
@RequestMapping(value = "/user")
public class UserController {
    @Autowired
    UserService userService;

    @RequestMapping(value = "/login2")
    public ResponseBodyMessage<User> login2(@RequestParam String username, @RequestParam String password,
                                           HttpServletRequest request){
        // 由于我们在mapper中定义的方法的参数是一个 User对象,所以,我们需要将其包装一下
        User userLogin = new User();
        userLogin.setUsername(username);
        userLogin.setPassword(password);
        User user = userService.login(userLogin);
        // 对查询到的结果,进行判断
        if(user == null){
            // 登录失败
            // 状态码为负数表示 登录失败。message 是错误信息,userLogin 是 按个用户登录失败了。
            return new ResponseBodyMessage<>(-1,"登录失败",userLogin);
        }else{
            // 登录成功
            HttpSession session = request.getSession(true);
            user.setPassword("");
            session.setAttribute(Constant.USERINFO_SESSION_KEY,user);
            userLogin.setPassword("");
            return new ResponseBodyMessage<>(0,"登录成功",userLogin);
        }
    }
    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @RequestMapping(value = "/login")
    public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password,
                                           HttpServletRequest request){
        // 根据 前端传递的 用户名 username,来查询对应的用户信息
        User user = userService.selectByName(username);

        // 对查询到的结果,进行判断
        if(user == null){
            // 登录失败【用户名不存在】
            // 状态码为负数表示 登录失败。message 是错误信息,userLogin 是 按个用户登录失败了。
            return new ResponseBodyMessage<>(-1,"登录失败",user);
        }else{
            // 用户名存在
            // 判断 密码 是否和前端传递的数据是一致的。
            boolean flag = bCryptPasswordEncoder.matches(password,user.getPassword());
            if(flag){
                HttpSession session = request.getSession(true);
                user.setPassword("");
                session.setAttribute(Constant.USERINFO_SESSION_KEY,user);
                return new ResponseBodyMessage<>(0,"登录成功",user);
            }
            user.setPassword(""); // 我个人认为:哪怕加密,密码也不能返回的。安全嘛。
            return new ResponseBodyMessage<>(-1,"登录失败",user);
        }
    }
}

工具类 – ResponseBodyMessage

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

    private String message;// 返回的信息是【出错的原因】

    private T date;// 返回给前端的数据

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

工具类 – Constant

public class Constant {
    public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}


4 BCrypt加密的原理

我们的密码是不能就这样传递的,别人一抓包就能获取到我们用户名和密码
在这里插入图片描述
那么,我们能不能对密码 进行 加密操作,让对方即使获取到我们的请求,也无法获取到重要信息。

我们先来了解 MD5 加密。


4.1 MD5加密

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

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

而 MD5加密出来的密码是固定,比如: 123 在加密之后,为 dhadhadhad。
只要有一个“密码库”记录了这一个组合,就可以根据 加密之后的密码,获取到 原本密码。

所以说:彩虹表 天克 MD5 加密。

不安全的原因:

1、 暴力攻击速度很快
2、 字典表很大
3、 碰撞

参考链接https://md5.cc/news1.aspx
更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
比如现在有一个条数据支持1万块,但是破解起来需要3年的时间。
等到破解的时候,这个数据恐怕已经不值钱了!
这种费力不讨好的事情,人家不会去做。

这里我们介绍加盐的做法:
盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中。
需要添加依赖:

<!-- 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>

将上述的 MD5 依赖,拷贝 我们项目中 pom.xml 文件中。
在这里插入图片描述
在tools包,新建MD5Util类。
用于模拟实现 前后端加密的过程。
在这里插入图片描述
下面我们来启动 main 方法,看看效果。
在这里插入图片描述
不管运行多少次,这个密码是规定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。

所以,最好的加密方式,就是将我们的 固定盐,修改成 随机盐。

这也是为什么我们在某个平台创建账户,官方要求我们的密码是 数字和字母混合的,而且长度不能低于某个界限。

就是为了提高我们密码的安全性。


4.2 BCrypt加密设计

Bcrypt 就是一款加密工具,可以比较方便地实现数据的加密工作。

你也可以简单理解为它内部自己实现了随机加盐处理 。

我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。

Bcrypt生成的密文是60位的。而MD5的是32位的。Bcrypt破解难度更大。

实现 BCrypt 加密,需要添加一个框架

<!-- 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>

将上面的依赖,拷贝到 pom.xml 文件中。
在这里插入图片描述
在springboot启动类添加:

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

在这里插入图片描述
如果不加这个就会报错!至于原因稍后再说。

BCrypt加密的操作是非常简单的!至少比 MD5 简单不少!

创建BCryptTest测试类:
在这里插入图片描述


解析:

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


总结

1、 密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
2、 使用BCrypt相比于MD5加密更好的一点在于,破解的难度上加大
3、 BCrypt的破解成本增加了,导致系统的运行成本也会大大的增加 。
4、 回到本质的问题,你的数据库中的数据价值如何?如果你是银行类型的,那么使用BCrypt是不错的,一般情况使用MD5加盐,已经够用了。
参考链接:https://blog.csdn.net/muyimo/article/details/118811514


两种加密方式的区别

BCrypt加密:

一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。

MD5加密:

是不加盐的单向Hash,不可逆的加密算法,同一个密码经过hash的时候生成的是同一个hash值,在大多数的情况下,有些经过md5加密的方法将会被破解。

注意!我们刚才在模拟实现 MD5 的时候,是加了盐的!
只不过是固定盐。
当然,也可以是随机盐,只不过要我们自己去实现宇哥随机盐。
也就是说 MD5 是可以 加 盐 混合使用的。

Bcrypt生成的密文是60位的。而MD5的是32位的。

目前,MD5和BCrypt比较流行。
相对来说,BCrypt比MD5更安全,但加密更慢。
虽然BCrpyt也是输入的字符串+盐,但是与MD5+盐的主要区别是:
每次加的盐不同,导致每次生成的结果也不相同。无法比对!


代码 – 工具类 – MD5Util

public class MD5Util {

    // 定义一个固定的盐值
    private static final String salt = "1b2i3t4e";

   // 定义一个 调用md5加密 的方法
    public static String md5(String src){
        return DigestUtils.md5Hex(src);// 进行 md5 加密
    }

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

    /*
    * 第二次 MD5 加密(服务器加密)
    * 前端加密过的密码,传给后端,由后端进行第二次加密
    * */
    public static String formPassToDBPass(String fromPass,String salt){
        // 先对密码加盐
        String str = "" + salt.charAt(0) + salt.charAt(2) + fromPass
                + salt.charAt(5) + salt.charAt(4);
        // 然后,再进行一次 md5 加密
        return md5(str);
    }

    /*
    * 上面两个函数喝到一起进行调用
    * */
    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("对用户输入密码进行第一次加密:" + inputPassToFormPass("123456"));

        System.out.println("对用户输入密码进行第二次加密:" + formPassToDBPass(inputPassToFormPass("123456")
                ,"1b2i3t4e"));

        System.out.println("对用户输入密码进行第2次加密:" + inputPassToDbPass("123456","1b2i3t4e"));
    }
}


代码 – 工具类 – BCryptTest

public class BCryptTest {
    public static void main(String[] args) {
        //模拟从前端获得的密码
        String password = "123456";
        // 星舰一个 BCryptPasswordEncoder
        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);
    }
}


5 加密登录实现

1、根据用户名称 查询 当前是否存在这样的用户
2、取出当前用户的密码,进行匹配,查看密码是否一样,一样则登录成功,反之,则登录失败。

5.1 UserMapper类新增方法 && UserMapper.xml配置

在这里插入图片描述


5.2 修改UserController类

在修改 UserController 类之前,我们需要在 UserService 添加一个对应的方法。
在这里插入图片描述
此时,我们再来修改 UserController 的 login 方法。
算了,还创建一个新的login 方法,将原来的 login 改个名 和 改个路由。
在这里插入图片描述
下面,我们就来创建一个 新的 login 方法。
在这里插入图片描述
写到这里代码,就修改完了。
但是!我们可以 对 引入 BCrypt 的方式,进行优化。
可以通过 属性注入的方法来实现。


5.3 创建包config,新建AppConfig类

在这里插入图片描述


5.4 spring-boot启动类注解

在这里插入图片描述

还记得,我们在 启动类注解上加的 “ 料 ”。
下面,我们来说一下为什么要加上这个。
在这里插入图片描述


5.5 测试效果

在测试效果之前,我们先给数据库注入一条加密数据。
在这里插入图片描述

我们先来演示 启动类注解有配置信息的情况下,是什么效果。
在这里插入图片描述
效果很显著,登录成功了!

我再来把 启动类注解 的配置信息删除掉。看看是什么效果
在这里插入图片描述
这其实也就是登录失败了!
这是因为在SpringBoot中,默认的Spring Security生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。所以登录失败了。

所以,我们需要理解 Security。
但是!其实我们可以不用那么麻烦,我们只是用到了它下面的 BCryptPasswordEncoder 这个类而已,并没有用到这个框架,
故:我们只要知道启动类注解需要加上一个 配置信息 过滤 安全验证的庎,就可以使用到它下面的类了。

PS:测完了,Ctrl + z 还原一下,后面还会用到这个类。
后面我们在实现注册用户功能的时候,就会使用 这个类 对 密码进行加密。


代码 – config包 – AppConfig

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

上传音乐模块设计

1 上传音乐的接口设计

就是约定好 请求 和 响应 的 格式 和 信息。
在这里插入图片描述
约定好 请求 和 响应 之后,我们就可以完成部分代码了。

因为我们要上传音乐,所以,要有一个类来与数据库进行交互。
这也是我们在设计数据库的时候,就考虑好了的。
在这里插入图片描述


新建Music类:

在这里插入图片描述


2 创建MusicController类 && MusicService 类

在这里插入图片描述

下面我们就来实现一个自定义拦截器(AOP),让它统一处里验证用户登录的功能
在这里插入图片描述
下面我们来创建一个 类(自定义的拦截器)
在这里插入图片描述
将⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则
在这里插入图片描述
回过头来,继续我们 MusicController 代码。

PS:本来我还想要进行 统一数据返回的格式,但是,不是很熟练。
等我验证一下想法,到时候,可能会修改这篇博客,或者写一个小续集。

在这里插入图片描述
到这里,一个上传文件 的代码逻辑就出不多了。
注意!是差不多!就是还差一点才能算是完成。
因为我们还没有和数据库进行交互。
没有将数据存入到数据库中的 music 表里面!!!


测试:

在这里插入图片描述

效果很理想!
下面我们就可以是岸 将 歌曲信息存入 数据库中了。
但是!
在此之前,我们需要做一个优化。
来看下面
在这里插入图片描述
这里存在着一个问题:我们怎么知道它上传的是一个 MP3 文件,万一,它上传错了呢?
上传了一个图片文件,那能当作 音乐文件 来播放嘛?
很显然是不行的!
那么,接下来,我们就来看看 如何去判断上传的文件是 MP3 格式的。


3 如何判断上传的文件是mp3

每个文件都有自己的构成方式(格式)
【不能通过后缀名判断,因为后缀是篡改的!】
我们判断的是文件本身的结构 和 组成。

我不能说:你上传了一个图片文件,我把它当音频文件来播放吧?
这显然是不科学,也是不可能的。


文件格式:

在这里插入图片描述
由上图结构可知,每个Frame都由帧头和数据部分组成。我们来看每个帧头的数据格式。
在这里插入图片描述


ID3V1部分 && ID3V2

在这里插入图片描述

看完这张表,我们可不可以这样认为:

我们只需要查看 音频文件尾部的 128 个字节 的 前三个字节里面,有没有 TAG 字样 就行了?
如果前三个字节中有 “TAG” 字样(标记)。就说明它是一个 音频文件。
反之,如果不包含,则说明它不是一个 音频文件。
答案可以告诉你,确实就是这么做的!
存放“ TAG ”字符,表示 ID3 V1.0 标准 ,根据是否包含这个字符,判断是不是音频文件。

而 ID3V2的长度是不固定的,所以通过它是很难判断文件是否是音频文件。
在这里插入图片描述
参考链接:
https://blog.csdn.net/ffjffjffjffjffj/article/details/99691239
https://www.cnblogs.com/ranson7zop/p/7655474.html
https://blog.csdn.net/sunshine1314/article/details/2514322


4 实现数据库上传

上述实现只是实现了简单的本地上传文件,还未将数据插入到数据库当中,接下来我们实现数据库中数据的写入。


定义接口MusicMapper && 定义MusicMapper.xml

在这里插入图片描述


在MusicController类中 间接 引入MusicMapper

注意!为了符合规范。
我们不能直接引入 MusicMapper,而是创建同 MusicService 作用 MusicMapper 接口 的调用。
MusicController 只是负责校验(处理)前端数据,具体调读那个接口由MusicService 负责。
所以,我们先去创建 MusicService 类。
在这里插入图片描述
下面我们在 MusicController 中 去上传文件信息到数据库中。
需要做两步:
1、准备好存储的数据
2、调用 MusicService 中 insert 方法。

1、准备好存储的数据

1.1、歌名(title)
在这里插入图片描述
1,2、歌手(singer)
在这里插入图片描述
1.3、用户id(userid)
在这里插入图片描述
1.4 url -> 播放音乐 -> http 请求
在这里插入图片描述
1.5、时间(time)
在这里插入图片描述
现在我们就需要存储信息,都准备好了。

现在我们就可以准备 调用 MusicService 的 insert 方法了。
在这里插入图片描述
这个时候,我们才真正完整了上传文件 的 业务代码。


5 验证整体文件上传

在这里插入图片描述

在这里我犯了一个错误,导致上面的异常。
就是我在使用 postman 构造请求的时候,不小心 歌手(singer)的参数多传递了一次。导致 歌手名称的长度超过了限制。
在这里插入图片描述
写到这里,虽然上传文件的代码就写完了。
但是!还存在着一个致命的问题!!!
如果我们重复上传一首歌,数据库的music 会无限添加歌曲信息。
也就是说:不具备 “去重”的功能。
下面我们来对代码进行 调整。
在这里插入图片描述


6 总结

6.1 MultipartFile类

在 org.springframework.web.multipart 包当中,是Spring框架中处理文件上传的主要类。
所以必须引入Spring框架。一般来讲使用MultipartFile这个类主要是来实现以表单的形式进行文件上传功能 。
参考链接:https://www.jianshu.com/p/e3d798c906cd

方法名 作用
String getOriginalFileName () 获取的是文件的完整名称,包括文件名称+文件拓展名。
String getContentType () 获取的是文件的类型,注意是文件的类型,不是文件的拓展名
boolean isEmpty () 用来判断传入的文件是否为空,如果为空则表示没有传入任何文件
long getSize() 获取文件的大小,单位是字节
void transferTo(File dest) 将接收到的文件传输到给定目标路径

6.2 如何判断是不是音乐文件?

我们只需要查看 音频文件尾部的 128 个字节 的 前三个字节里面,有没有 TAG 字样 就行了.
“ TAG ”字符,表示 ID3 V1.0 标准 ,根据是否包含这个字符,判断是不是音频文件。

记住!每一个种类型的文件,它的结构是不一样的!!!
所以,我们才能根据它们的数据结构进行判断,判断它们的文件类型是哪一种。


6.3 相同的音乐是否可以上传成功?如何处理?

不可以,因为不合理!
在进行 上传文件 之前,需要先进行数据库的查询(根据 title 和 singer 字段信息),如果查询结果不为null,说明上传歌曲已在数据库中存在,直接返回。
反之,插叙结果为 null,说明 该歌曲是第一次上传。
然后才能继续后面的上传文件 和 插入数据库 的操作。


7、涉及到的一些辅助类

工具类 – TestTime

public class TestTime {
    public static void main(String[] args) {
        // 通过 SimpleDateFormat 来规定时间的格式,不了解的,可以百度一下。
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        // 通过 Date 类 来获取当前系统时间,再按照 SimpleDateFormat 规定日期形式进行转换。
        String time = sf.format(new Date());
        System.out.println("当前的时间:" + time);

        SimpleDateFormat sf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // 通过 Date 类 来获取当前系统时间,再按照 SimpleDateFormat 规定日期形式进行转换。
        String time2 = sf2.format(new Date());
        System.out.println("当前的时间:" + time2);
    }
}

config – AppConfig – 实现了 AOP 思想 – 拦截器

@Configuration
public class AppConfig implements WebMvcConfigurer {
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder(){
        return  new BCryptPasswordEncoder();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MusicInterceptor())
                .addPathPatterns("/**")// 拦截 所有的 url
                .excludePathPatterns("/user/login")// 登录功能不拦截。不登录,哪来的信息给我们验证?
                .excludePathPatterns("/user/login2");// 虽然login2 只是演示,但也属于登录功能,也就不拦截了

    }
}


自定义连接器 MusicInterceptor – 登录验证

public class MusicInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response, Object handler) throws Exception {
        // 我们前面在登录的时候,登录成功的话会创建 session,并且在在里面设置 用户属性
        HttpSession session = request.getSession(false);
        if(session != null && session.getAttribute(Constant.USERINFO_SESSION_KEY)!=null){
            return true; // 表示通过验证,用户处于已登录状态
        }
        //这里可以加一个 重定向,先暂时不管它!
        System.out.println("用户未登录!");
        return false;// 表示 用户未登录。
    }
}

播放音乐模块设计

1 请求响应设计

在这里插入图片描述

想要实现这个功能,需要创建一个 类(ResponseEntity)

ResponseEntity对象,是Spring对请求响应的封装。
它继承了HttpEntity对象,包含了Http的响应码(httpstatus)、响应头(header)、响应体(body)三个部分。
ResponseEntity类继承自HttpEntity类,被用于Controller层方法 。ResponseEntity.ok 方法有2个方法,分别是有参数和没有参数。

在这里插入图片描述
你要返回 404,也是一样的步骤。
在这里插入图片描述
好了,现在呢,我们想要 那个 字节数组 a 返回给其前端,我们该怎么做呢?
来看下面!
在这里插入图片描述
下面,我们就来看看:怎么把它应用到我们的项目中。

PS:如果你想了解更多,可参考下面这些文章
参考链接:
https://www.jianshu.com/p/1238bfb29ee1
https://blog.csdn.net/qq_43317193/article/details/100109136


在 MusicController 新增一个 路由为get 的 方法

就拿上面的方法给个名字就行了。
在这里插入图片描述
接下来就是修改方法的内容。
在这里插入图片描述


测试

在这里插入图片描述

另外,你可以搜索一下 “TAG”
在这里插入图片描述
这获取到的字节信息,就是一个 MP3 的文件信息。

但是!有人就发现问题了,这个 获取资源的路径 和 我们 在数据库中存储的不一样啊。
而且,你前面还把它给拎出来了!
在这里插入图片描述
为了满足某些朋友的好奇心,我们来尝试一下,在获取音乐文件信息的时候,不加上 后缀,会是什么样子的效果?
在这里插入图片描述

下面,我们Laura验证非音乐文件是否可以 “ 播放 ” ?
在这里插入图片描述
对于我们当前的项目来说:
如果我们去播放一首 不是音乐的文件,是不会成功的!
但是!这个情况是可以避免的!
在上传文件之前,进行校验就行了。
但是,我搜了一下,没有什么实际意义的信息,搞得我还有点蒙。
总之,就是没能成功实现这个功能。
有兴趣的,可以自己去实现,我还需要在琢磨琢磨。。。。


删除音乐模块设计

删除单个音乐

1 请求响应设计

在这里插入图片描述


2 代码实现

在 MusicMapper 接口中定义两个方法。
1、deleteMusicById(根据歌曲id 删除 对应的音乐)
2、selectMusicById(根据歌曲id,查询对应的音乐信息)
在这里插入图片描述

之后便是,在 xml 文件中,写SQL。
在这里插入图片描述
在MusicService 中,添加对应 调用 mapper接口的方法。
在这里插入图片描述
在 MusicController 中 定义对应 的 delete 映射方法。
在这里插入图片描述


3 验证结果

在这里插入图片描述


批量删除选中的音乐

1 请求响应设计

在这里插入图片描述


2 代码实现

在 MusicMapper 类中,新增一个 deletSelMusic 方法
在这里插入图片描述
代码实现起来,并没有多难,
就是加一个for循环 和 sum,方法参数需要绑定传递过来的数组,就没了。

我的批量删除想法:
不管前端传递过来的数组元素id,在数据库中是否有对应的数据。
不存在的id,默认删除成功。
存在的id,进行删除。
如果 数据库 和 服务器有任意一个删除失败,则返回当前 批量删除失败的信息,并且返回当前成功删除多少首歌曲。
全部删除成功,视为批量删除完成。


3 验证结果

在这里插入图片描述


查询音乐模块设计

1 请求响应模块设计

此处查询需要满足几个功能:
1、 支持模糊查询
2、 支持传入参数为空
在这里插入图片描述


2 代码实现

MusicMapper.java接口新增方法

这个接口方法,将会使用到重载!
在这里插入图片描述


MusicMapper.xml新增配置

在这里插入图片描述


MusicController类 && MusicService 类,新增方法

先来看 MusicService类 嫌憎的方法。
在这里插入图片描述

接着我们来看 MusicController 类新增方法
在这里插入图片描述


3 验证结果

在这里插入图片描述


喜欢/收藏 音乐模块设计

添加音乐至喜欢的列表模块设计

1 请求响应模块设计

在这里插入图片描述

我们需要注意一个个问题:
服务器在收到请求之后:
在添加音乐到喜欢列表中,需要先检测这首歌是否已经被收藏。

收藏过,就是不能再收藏了
反之,没收藏过,就可以进行收藏。


2、代码实现

实现LoveMusicMapper接口,收藏/喜欢音乐功能

实现LoveMusicMapper接口,收藏/喜欢音乐功能:
1、需要查询此次收藏音乐是否之前收藏过,收藏过则不能添加
2、没有收藏过,插入数据库中一条记录
在这里插入图片描述


实现LoveMusicMapper.xml

在这里插入图片描述

写到这里,我才发现:我前面一时手快创建的 LoveMusic 没用到!
别慌!万一后面需要呢? 先留着。
在这里插入图片描述


实现LoveMusicService类 && LoveMusicController类

先来实现 LoveMusicService类
在这里插入图片描述
再来看 LoveMusicController类
在这里插入图片描述


3 验证结果

在这里插入图片描述


查询喜欢的音乐模块设计

1 请求响应设计

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

1、 支持模糊查询
2、 支持传入参数为空

和前面收藏音乐,几乎一摸一样。
在这里插入图片描述


2 代码实现

实现LoveMusicMapper新增方法:

在这里插入图片描述


实现LoveMusicMapper.xml

首先,我们来理通一下SQL的编写逻辑:
在这里插入图片描述
此时,你再来看我在 xml 文件中写的SQL。
在这里插入图片描述
如果你想使用 resultMap,可以参考MyBatis查询数据库 && Spring Boot 单元测试 – 细节狂魔
在这里插入图片描述
反正我个人觉得没必要,太麻烦了。
因为我们的实体类中,并没有存在像 集合(List) 这样的属性(这个属性还是数据表中没有的属性)。


实现 LoveMusicService 新增方法

在这里插入图片描述


实现LoveMusicController,新增方法

这个我不细说,和前面的 MusicController中的 findMusic 方法一毛一样。
在这里插入图片描述


3 验证结果

在这里插入图片描述


移除喜欢的音乐模块设计

1 请求响应设计

在这里插入图片描述

这里唯一需要注意的是:这移除的 lovemusic 里面的信息。
与 music 表 无关。
也就是说:删除 lovemusic 表中的信息,不会影响到 music表中的数据。


2 代码实现

实现 LoveMusicMapper接口新增方法

在这里插入图片描述


实现 LoveMusicMapper.xml

在这里插入图片描述


实现 LoveMusicService 新增方法

在这里插入图片描述


实现 LoveMusicController 新增方法

在这里插入图片描述

注意!我们不用去删除服务器上的音乐文件。
服务器上的音乐文件,就好比是 源文件,而我们的收藏表,只是一个记录表,记录每首歌的id,后面就可以根据 歌曲id 和 用户id 去发送一个 Http 播放请求了。

再形象一点,我们一首 可以移除之后,再收藏。这个大家应该都知道吧?
其实我们移除的是一条记录,而不是源文件。
如果你的移除,还有 删除源文件的功能。
那么,请问 别人想听这首歌,怎么听?
你源文件都给别人删了,别人还听什么????

我们在这个方法中,是不可以实现删除源文件的。


3 验证结果

在这里插入图片描述


总代码 – LoveMusic/Controller/service/Mapper/Mapper.xml

LoveMusicMapper

@Mapper
public interface LoveMusicMapper {
//  需要查询此次收藏音乐是否之前收藏过
    Music findLoveMusicByMusicIdAndUserId(@Param("userid") Integer userid,@Param("musicid") Integer musicid);

//  向 数据表 lovemusic 插入一条信息
    boolean insertLoveMusic(@Param("userid") Integer userid,@Param("musicid") Integer musicid);

//     如果没有传入 歌曲名/字符,显示当前用户收藏的所有音乐
    List<Music> findLoveMusicByKeyAndUserId(@Param("user_id") Integer user_id);

//    反之,传入了 歌曲名/字符,进行模糊查询。
    List<Music> findLoveMusicByKeyAndUserId(@Param("title") String title,@Param("user_id") Integer user_id);

    // 根据 userId 和 musicId 来删除(移除)收藏表中的信息
    Integer deleteLoveMusic(@Param("userid") Integer userid,@Param("musicid") Integer musicid);

    // 根据 musicid 删除收藏表上的文件
    Integer deleteLoveMusicById(@Param("musicid") Integer musicid);
}

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.onlinemusicplayer.mapper.LoveMusicMapper">
<!--  收藏一首音乐  -->
    <insert id="insertLoveMusic">
        insert into lovemusic (user_id,music_id) values (#{userid},#{musicid});
    </insert>

<!--  移除收藏表中的信息  -->
    <delete id="deleteLoveMusic">
        delete from lovemusic where user_id=#{userid} and music_id=#{musicid}
    </delete>
<!--  根据 musicid 删除信息   -->
    <delete id="deleteLoveMusicById">
        delete from lovemusic where music_id=#{musicid}
    </delete>

    <!-- 查询需要收藏的音乐,是否已经被收藏过。 -->
    <select id="findLoveMusicByMusicIdAndUserId" resultType="com.example.onlinemusicplayer.model.Music">
        select * from lovemusic where user_id=#{userid} and music_id=#{musicid};
    </select>

<!--  如果没有传入 歌曲名/字符,显示当前用户收藏的所有音乐。反之,传入了 歌曲名/字符,进行模糊查询。  -->
    <select id="findLoveMusicByKeyAndUserId" resultType="com.example.onlinemusicplayer.model.Music">
        select music.* from music,lovemusic where #{user_id}=lovemusic.user_id and music.id=lovemusic.music_id
        <if test="title!=null">
            and music.title like concat('%',#{title},'%')
        </if>
    </select>
</mapper>

LoveMusicService

@Service
public class LoveMusicService {
    @Autowired
    private LoveMusicMapper loveMusicMapper;

    // 查询此次收藏音乐是否之前收藏过
    public Music findLoveMusicByMusicIdAndUserId(Integer userid,Integer musicid){
        return loveMusicMapper.findLoveMusicByMusicIdAndUserId(userid,musicid);
    }
    // 向 数据表 lovemusic 插入一条信息
    public boolean insertLoveMusic(Integer userid,Integer musicid){
        return loveMusicMapper.insertLoveMusic(userid,musicid);
    }

    //     如果没有传入 歌曲名/字符,显示当前用户收藏的所有音乐
    public List<Music> findLoveMusicByKeyAndUserId(Integer user_id){
        return loveMusicMapper.findLoveMusicByKeyAndUserId(user_id);
    }

    //    反之,传入了 歌曲名/字符,进行模糊查询。
    public List<Music> findLoveMusicByKeyAndUserId(String title,Integer user_id){
        return loveMusicMapper.findLoveMusicByKeyAndUserId(title,user_id);
    }

    // 根据 userId 和 musicId 来删除(移除)收藏表中的信息
    public Integer deleteLoveMusic(Integer userid,Integer musicid){
        return loveMusicMapper.deleteLoveMusic(userid,musicid);
    }

    // 根据 musicid 删除收藏表上的信息
    public Integer deleteLoveMusicById(Integer musicid){
        return loveMusicMapper.deleteLoveMusicById(musicid);
    }

}

LoveMusicController

@RestController
@RequestMapping(value = "/lovemusic")
public class LoveMusicController {
    @Autowired
    LoveMusicService loveMusicService;

    @Transactional
    @RequestMapping(value = "/likemusic")
    public ResponseBodyMessage<Boolean> likeMusic(@RequestParam Integer musicid, HttpServletRequest request){
        // 获取 用户id
        HttpSession session = request.getSession(false);
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        Integer userid = user.getId();

        // 验证 上传歌曲是否 已经被上传
        Music music = loveMusicService.findLoveMusicByMusicIdAndUserId(userid,musicid);
        if(music != null){
            return new ResponseBodyMessage<>(-1,"歌曲已收藏,请重新选择!",false);
        }
        if(loveMusicService.insertLoveMusic(userid,musicid)){
            return new ResponseBodyMessage<>(0,"添加成功!",true);
        }
        return new ResponseBodyMessage<>(-1,"添加失败,请稍后再试!",false);
    }

    @RequestMapping(value = "/findlovemusic")
    public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String title,
                                                          HttpServletRequest request){
        // 获取 用户id
        HttpSession session = request.getSession(false);
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        Integer user_id = user.getId();

        List<Music> list = null;
        if(title == null){
            list = loveMusicService.findLoveMusicByKeyAndUserId(user_id);
        }else{
            list = loveMusicService.findLoveMusicByKeyAndUserId(title,user_id);
        }
        return new ResponseBodyMessage<>(0,"查询到了相关收藏的音乐",list);
    }

    @RequestMapping(value = "deletelovemusic")
    @Transactional
    public ResponseBodyMessage<Boolean> deleteLoveMusic(@RequestParam Integer musicid,
                                                        HttpServletRequest request){
        // 获取 用户id
        HttpSession session = request.getSession(false);
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        Integer userid = user.getId();

        // 移除收藏音乐
        Integer ret = loveMusicService.deleteLoveMusic(userid,musicid);
        if(ret != 1){
            return new ResponseBodyMessage<>(-1,"取消收藏失败!",false);
        }
        return new ResponseBodyMessage<>(0,"取消收藏成功!",true);
    }
}

删除音乐功能完善 – MusicController – deleteSelMusic – delete

在这里插入图片描述
这两个方法 删除的都是原文件,并没有涉及到 收藏音乐 的移除操作。
你想想看,我源文件都给你删掉了,你收藏表的数据不就成摆设了吗?
那还留着它干什么?
在这里,我们就是要去添加这个操作 来完善删除功能。


LoveMusicMapper接口新增方法:

在这里插入图片描述


实现 LoveMusicMapper.xml

在这里插入图片描述


实现 LoveMusicService 新增方法

在这里插入图片描述


修改前的准备工作

在这里插入图片描述


MusicController – delete – 修改

>


MusicController – deleteSelMusic – 修改

在这里插入图片描述


验证效果

在这里插入图片描述


总代码 Music/Controller/service/Mapper/Mapper.xml


MusicMapper

@Mapper
public interface MusicMapper {
//   上传歌曲(向 music 表中 插入一条数据)
    Integer insert(@Param("title") String title,@Param("singer") String singer,
                   @Param("time") String time,@Param("url") String url,
                   @Param("userid") Integer userid);

    // 检查上传歌曲是否已经在数据库存在
    Music select(@Param("title") String title,@Param("singer") String singer);

    // 查询当前要删除的音乐是否在数据中存在。
    Music selectMusicById(@Param("id") Integer id);

    // 删除 指定 id 的 音乐
    Integer deleteMusicById(@Param("id") Integer id);

    // 查询音乐: 模糊查询:查询结果 1 ~ n 首歌曲
    List<Music> findByMusicByName(@Param("name") String name);
    // 查询所有音乐。
    List<Music> findByMusicByName();
}

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.onlinemusicplayer.mapper.MusicMapper">
<!-- 存储上传的歌曲信息 -->
    <insert id="insert">
        insert into music (`title`,`singer`,`time`,`url`,`userid`)
        values (#{title},#{singer},#{time},#{url},#{userid});
    </insert>
<!--  删除 指定 id 的 音乐  -->
    <delete id="deleteMusicById">
        delete from music where id=#{id};
    </delete>
    <!--  查询上传歌曲是否已经在数据库中存储了  -->
    <select id="select" resultType="com.example.onlinemusicplayer.model.Music">
        select * from music where title=#{title} and singer=#{singer};
    </select>
<!--  查询当前要删除的音乐是否在数据中存在。  -->
    <select id="selectMusicById" resultType="com.example.onlinemusicplayer.model.Music">
        select * from music where id=#{id};
    </select>
<!--  查询收藏音乐方法(重载)  -->
    <select id="findByMusicByName" resultType="com.example.onlinemusicplayer.model.Music">
        select * from music <where>
        <if test="name!=null">
            title like concat('%',#{name},'%');
        </if>
    </where>
    </select>


</mapper>

MusicService

@Service
public class MusicService {
    @Autowired
    private MusicMapper musicMapper;

    // 添加 上传歌曲的相关信息
    public Integer insert(String title,String singer,
                          String time,String url,Integer userid){
       return musicMapper.insert(title,singer,time,url,userid);
    }

    // 查询 上传歌曲 是否已经在数据库 存在
    public Music select(String title,String singer){
        return musicMapper.select(title,singer);
    }

    // 查询当前要删除的音乐是否在数据中存在。
    public Music selectMusicById(Integer id){
        return musicMapper.selectMusicById(id);
    }

    // 删除 指定 id 的 音乐
    public Integer deleteMusicById(Integer id){
        return musicMapper.deleteMusicById(id);
    }

    // 模糊查询
    public List<Music> findByMusicByName(String name){
        return musicMapper.findByMusicByName(name);
    }
    // 查询全部
    public List<Music> findByMusicByName(){
        return musicMapper.findByMusicByName();
    }
}


MusicController

@RestController
@RequestMapping(value = "/music")
public class MusicController {
    // 存储路径最好是 正斜杠,反斜杠还需要穿衣,有的服务器不识别 两个 \\ 的。
    @Value("${music.local.path}")
    private String SAVE_PATH;
//    private String SAVE_PATH = "G:/online_music_audio/";

    @Autowired
    MusicService musicService;

    @Autowired
    LoveMusicService loveMusicService;

    @RequestMapping(value = "/upload")
    @Transactional// 默认的事务传播级别 REQUIRED
    public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer,
                                                    @RequestPart("filename") MultipartFile file,
                                                    HttpServletRequest request){
        // 获取到上传文件的名称
        String fileNameAndType = file.getOriginalFilename();// 文件名.文件类型

        // 歌名
        String title = fileNameAndType.substring(0,fileNameAndType.lastIndexOf("."));
        // 查询上传歌曲,是否已经存储过了。
        Music music = musicService.select(title,singer);
        if(music != null){
            return new ResponseBodyMessage<>(-1,"此歌曲已上传,请重新选择!",false);
        }

        // 存储路径
        String path = SAVE_PATH  +fileNameAndType;
        // “封装”
        File dest = new File(path);
        // 如果存储路径不存在,则进行创建。
        if(!dest.exists()){
            dest.mkdir();
        }

        // 指定 上传音频文件,上传之后存储的位置
        try {
            file.transferTo(dest);
//            return new ResponseBodyMessage<>(0,"上传成功",true);
        } catch (IOException e) {
            //  当异常被 tryCatch 捕获时,要么 直接抛出异常 throw e;要么使用下面的 保存点 方法来触发事务的回滚
            // 否则是不会触发事务回滚的!!!
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return new ResponseBodyMessage<>(-1,"服务器上传失败",false);
        }
        // 进行上传数据库
        //1、准备要 上传的数据

        // 用户id
        User user = (User)request.getSession().getAttribute(Constant.USERINFO_SESSION_KEY);
        Integer userid = user.getId();
        // url -> 播放音乐 -> http 请求
        String url = "/music/get?path=" +title;
        // time
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        // 通过 Date 类 来获取当前系统时间,再按照 SimpleDateFormat 规定日期形式进行转换。
        String time = sf.format(new Date());

        //2、调用 MusicService 中的 insert 方法
        Integer ret = musicService.insert(title,singer,time,url,userid);
        if(ret == 1){
            // 插入成功(上传成功)
            // 其实这里,按照我们的感觉来说,上传成功后,应该跳转 歌曲列表页,方便直接看到效果。
            // 但是!我们前端还没有做,所以,先就这样。
            return new ResponseBodyMessage<>(0,"数据库上传成功!",true);
        }
        return  new ResponseBodyMessage<>(-1,"数据库上传失败!",false);
    }

    /*
    * 读取音乐文件,构造一个类似请求:/music/get?path=xxx.mp3
    * */
    @RequestMapping(value = "/get")
    public ResponseEntity<byte[]> get(String path) {
        // 将读取文件的路径准备好
        File file = new File(SAVE_PATH + path);
        // 通过 readAllBytes 将 file 路径的文件 读成字节的形式
        byte[] a = null;
        try {
            a = Files.readAllBytes(file.toPath());
            if(a != null){
                // 读取成功,将其返回给前端
                return ResponseEntity.ok(a);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 读取失败
        return  ResponseEntity.badRequest().build();
    }

    @RequestMapping(value = "/delete")
    public ResponseBodyMessage<Boolean> delete(@RequestParam(required = false) Integer id,
                                               HttpServletRequest request){
        if(id == null){
            return new ResponseBodyMessage<>(-1,"无效id!",false);
        }
        // 检查 删除歌曲是否在数据库中存在
        Music music = musicService.selectMusicById(id);
        if(music == null){
            return new ResponseBodyMessage<>(-1,"你要删除的歌曲不存在,请重新选择!",false);
        }
        // 根据 id 删除指定的歌曲信息【数据库】
        Integer ret = musicService.deleteMusicById(id);
        if(0 == ret){
            return new ResponseBodyMessage<>(-1,"删除失败!",false);
        }

        // 这里还需要删除 服务器上存储的歌曲数据。
        // 获取文件名,例如:/music/get?path=Bedroom Eyes
        int index = music.getUrl().lastIndexOf("=");
        String fileName = music.getUrl().substring(index+1);//截取 歌名 Bedroom Eyes
        File file = new File(SAVE_PATH + fileName + ".mp3");
        if(file.delete()){
            loveMusicService.deleteLoveMusicById(id);
            return new ResponseBodyMessage<>(0,"删除成功!",true);
        }
        return new ResponseBodyMessage<>(-1,"删除失败!",false);
    }

    /*
    * 批量删除操作
    * */
    @RequestMapping(value = "deletesel")// 全部小写,稍微注意一下。前面写顺手了。
    @Transactional
    public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("ids[]") List<Integer> ids){
        int sum = 0;
        for (int i = 0; i < ids.size(); i++) {
            // 查询歌曲是否存在
            Music music = musicService.selectMusicById(ids.get(i));
            if(music == null){
                // 歌曲不存在,默认删除成功。
                sum+=1;
                continue;
            }
            int ret = musicService.deleteMusicById(ids.get(i));
            if(1 == ret){
                // 获取文件名,例如:/music/get?path=Bedroom Eyes
                int index = music.getUrl().lastIndexOf("=");
                String fileName = music.getUrl().substring(index+1);//截取 歌名 Bedroom Eyes
                File file = new File(SAVE_PATH + fileName + ".mp3");
                if(file.delete()){
                    sum++;
                    loveMusicService.deleteLoveMusicById(ids.get(i));
                    continue;
                }
                // 走到这一步,说明服务器删除文件失败,那么相应的事务操作,需要进行回滚。
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
                // 返回 删除失败的信息,以及当前成功删除了多少首歌。
                return new ResponseBodyMessage<>(-1,"批量删除失败!当前成功删除了" + sum + "首歌。",false);
            }
            // 返回 删除失败的信息,以及当前成功删除了多少首歌。
            return new ResponseBodyMessage<>(-1,"批量删除失败!当前成功删除了" + sum + "首歌。",false);
        }
        // 走到这一步说明 前端要求删除的 歌曲,全部删除完成。
        return new ResponseBodyMessage<>(0,"批量删除成功!一共删除了" + sum + "首歌!",true);
    }

    @RequestMapping(value = "/findmusic")
    public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String name){
        List<Music> list = null;
        if(name == null){
            list = musicService.findByMusicByName();
        }else{
            list = musicService.findByMusicByName(name);
        }
        return new ResponseBodyMessage<>(0,"查询到了歌曲信息!",list);
    }
}

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注