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

文章目录

  • 1、项目简介
  • 2、创建SSM项目
    • 专业版 idea 创建方式
    • 社区版 idea 创建方式
  • 3.创建数据库和配置文件
    • 数据库设计
    • 配置数据库和xml
    • 最终的配置文件信息,我就不给了。自己写。
  • 4.实现登录的准备工作(登录模块设计)
    • 1 创建User类
    • 2 创建对应的Mapper和Controller
      • 2.1 创建接口UserMapper
      • 2.2 创建UserMapper.xml
    • 3 实现登录
      • 3.1 登录的请求和响应设计
      • 3.2 创建UserController类
      • 3.3 使用postman验证登录功能
      • 3.4 优化代码
        • 设计统一的响应体类工具类
        • 优化 UserController 代码。
    • 总代码
      • UserMapper
      • UuserMapper.xml
      • UserService
      • UserController
      • 工具类 - ResponseBodyMessage
      • 工具类 - Constant
  • 4 BCrypt加密的原理
    • 4.1 MD5加密
    • 4.2 BCrypt加密设计
      • 解析:
    • 总结
    • 两种加密方式的区别
    • 代码 - 工具类 - MD5Util
    • 代码 - 工具类 - BCryptTest
  • 5 加密登录实现
    • 5.1 UserMapper类新增方法 && UserMapper.xml配置
    • 5.2 修改UserController类
    • 5.3 创建包config,新建AppConfig类
    • 5.4 spring-boot启动类注解
    • 5.5 测试效果
    • 代码 - config包 - AppConfig
  • 上传音乐模块设计
    • 1 上传音乐的接口设计
      • 新建Music类:
    • 2 创建MusicController类 && MusicService 类
      • 测试:
    • 3 如何判断上传的文件是mp3
      • 文件格式:
      • ID3V1部分 && ID3V2
    • 4 实现数据库上传
      • 定义接口MusicMapper && 定义MusicMapper.xml
      • 在MusicController类中 间接 引入MusicMapper
    • 5 验证整体文件上传
    • 6 总结
      • 6.1 MultipartFile类
      • 6.2 如何判断是不是音乐文件?
      • 6.3 相同的音乐是否可以上传成功?如何处理?
    • 7、涉及到的一些辅助类
      • 工具类 - TestTime
      • config - AppConfig - 实现了 AOP 思想 - 拦截器
      • 自定义连接器 MusicInterceptor - 登录验证
  • 播放音乐模块设计
    • 1 请求响应设计
    • 在 MusicController 新增一个 路由为get 的 方法
    • 测试
  • 删除音乐模块设计
    • 删除单个音乐
      • 1 请求响应设计
      • 2 代码实现
      • 3 验证结果
    • 批量删除选中的音乐
      • 1 请求响应设计
      • 2 代码实现
      • 3 验证结果
  • 查询音乐模块设计
    • 1 请求响应模块设计
    • 2 代码实现
      • MusicMapper.java接口新增方法
      • MusicMapper.xml新增配置
      • MusicController类 && MusicService 类,新增方法
    • 3 验证结果
  • 喜欢/收藏 音乐模块设计
    • 添加音乐至喜欢的列表模块设计
      • 1 请求响应模块设计
      • 2、代码实现
        • 实现LoveMusicMapper接口,收藏/喜欢音乐功能
        • 实现LoveMusicMapper.xml
        • 实现LoveMusicService类 && LoveMusicController类
      • 3 验证结果
  • 查询喜欢的音乐模块设计
    • 1 请求响应设计
    • 2 代码实现
      • 实现LoveMusicMapper新增方法:
      • 实现LoveMusicMapper.xml
      • 实现 LoveMusicService 新增方法
      • 实现LoveMusicController,新增方法
    • 3 验证结果
  • 移除喜欢的音乐模块设计
    • 1 请求响应设计
    • 2 代码实现
      • 实现 LoveMusicMapper接口新增方法
      • 实现 LoveMusicMapper.xml
      • 实现 LoveMusicService 新增方法
      • 实现 LoveMusicController 新增方法
    • 3 验证结果
  • 总代码 - LoveMusic/Controller/service/Mapper/Mapper.xml
    • LoveMusicMapper
    • LoveMusicMapper.xml
    • LoveMusicService
    • LoveMusicController
  • 删除音乐功能完善 - MusicController - deleteSelMusic - delete
    • LoveMusicMapper接口新增方法:
    • 实现 LoveMusicMapper.xml
    • 实现 LoveMusicService 新增方法
    • 修改前的准备工作
    • MusicController - delete - 修改
    • MusicController - deleteSelMusic - 修改
    • 验证效果
  • 总代码 Music/Controller/service/Mapper/Mapper.xml
      • MusicMapper
      • MusicMapper.xml
      • MusicService
      • MusicController

1、项目简介

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

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


2、创建SSM项目

专业版 idea 创建方式

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


社区版 idea 创建方式

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


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

首先,我们要明白 一个项目 可以分为两个部分。
1、客户端(前端)
2、服务器(后端)
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第3张图片
所以,我们的第一步就是要去设计

数据库设计

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

在整个项目中,我们目前发现有三个数据表是需要创建的。
下面,我们就来设计这些数据表中的字段,都有哪些。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第4张图片
我们数据库就设计好了,接下来就是将 笔记本(我用的是subline)上的 SQL 拷贝 MySQL 上。

此时,我们在 MySQL就创建好了 一个 musiclibrary 数据库,并在里面创建三张表 user,music,lovemusic。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第5张图片


配置数据库和xml

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


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


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

1 创建User类

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


2 创建对应的Mapper和Controller

2.1 创建接口UserMapper

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


2.2 创建UserMapper.xml

MyBatis 的 xml 配置


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">
mapper>

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


3 实现登录

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


3.1 登录的请求和响应设计

在这里插入图片描述


3.2 创建UserController类

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


3.3 使用postman验证登录功能

在测试之前,我们需要做一件事:
给 数据库的 user 表插入一条用户信息。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第12张图片
再把我们的项目启动起来。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第13张图片
然后,我们在使用 postman去构造一个post请求,来看一下 idea 打印的效果。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第14张图片
还没有完!我的后端是要查询的结果 返回给前端的。
所以说:我们 代码还需要进行优化。


3.4 优化代码

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

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


优化 UserController 代码。

在这里插入图片描述

我们再来使用 postman 来测试一下
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第16张图片
还需要咋改进型一下,密码不用返回。为了安全。
如果你直接返回,别人只要进行抓包,密码 就被得到了。
在这里插入图片描述
再来测一下
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第17张图片
另外,登录成功后,我们还需要创建一个会话。
就是下次用户自来访问的时候,就不用在登陆的。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第18张图片
但是!还存在着一个问题:会话属性名称太长了,万一写错,就找不到这个属性值了。
通常情况下,我们是这样去做的。如下所示
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第19张图片
到这里,代码差不多优化完了。


总代码

UserMapper

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

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

UuserMapper.xml


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加密的原理

我们的密码是不能就这样传递的,别人一抓包就能获取到我们用户名和密码
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第20张图片
那么,我们能不能对密码 进行 加密操作,让对方即使获取到我们的请求,也无法获取到重要信息。

我们先来了解 MD5 加密。


4.1 MD5加密

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

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

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

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

不安全的原因:

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

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

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


<dependency>
<groupId>commons-codecgroupId>
<artifactId>commons-codecartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-lang3artifactId>
<version>3.9version>
dependency>

将上述的 MD5 依赖,拷贝 我们项目中 pom.xml 文件中。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第21张图片
在tools包,新建MD5Util类。
用于模拟实现 前后端加密的过程。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第22张图片
下面我们来启动 main 方法,看看效果。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第23张图片
不管运行多少次,这个密码是规定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。

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

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

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


4.2 BCrypt加密设计

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

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

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

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

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


<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.securitygroupId>
<artifactId>spring-security-configartifactId>
dependency>

将上面的依赖,拷贝到 pom.xml 文件中。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第24张图片
在springboot启动类添加:

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

SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第25张图片
如果不加这个就会报错!至于原因稍后再说。

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

创建BCryptTest测试类:
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第26张图片


解析:

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配置

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


5.2 修改UserController类

在修改 UserController 类之前,我们需要在 UserService 添加一个对应的方法。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第28张图片
此时,我们再来修改 UserController 的 login 方法。
算了,还创建一个新的login 方法,将原来的 login 改个名 和 改个路由。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第29张图片
下面,我们就来创建一个 新的 login 方法。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第30张图片
写到这里代码,就修改完了。
但是!我们可以 对 引入 BCrypt 的方式,进行优化。
可以通过 属性注入的方法来实现。


5.3 创建包config,新建AppConfig类

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


5.4 spring-boot启动类注解

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

还记得,我们在 启动类注解上加的 “ 料 ”。
下面,我们来说一下为什么要加上这个。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第33张图片


5.5 测试效果

在测试效果之前,我们先给数据库注入一条加密数据。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第34张图片

我们先来演示 启动类注解有配置信息的情况下,是什么效果。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第35张图片
效果很显著,登录成功了!

我再来把 启动类注解 的配置信息删除掉。看看是什么效果
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第36张图片
这其实也就是登录失败了!
这是因为在SpringBoot中,默认的Spring Security生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。所以登录失败了。

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

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


代码 - config包 - AppConfig

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

上传音乐模块设计

1 上传音乐的接口设计

就是约定好 请求 和 响应 的 格式 和 信息。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第37张图片
约定好 请求 和 响应 之后,我们就可以完成部分代码了。

因为我们要上传音乐,所以,要有一个类来与数据库进行交互。
这也是我们在设计数据库的时候,就考虑好了的。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第38张图片


新建Music类:

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


2 创建MusicController类 && MusicService 类

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

下面我们就来实现一个自定义拦截器(AOP),让它统一处里验证用户登录的功能
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第41张图片
下面我们来创建一个 类(自定义的拦截器)
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第42张图片
将⾃定义拦截器加⼊到框架的配置中,并且设置拦截规则
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第43张图片
回过头来,继续我们 MusicController 代码。

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

SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第44张图片
到这里,一个上传文件 的代码逻辑就出不多了。
注意!是差不多!就是还差一点才能算是完成。
因为我们还没有和数据库进行交互。
没有将数据存入到数据库中的 music 表里面!!!


测试:

效果很理想!
下面我们就可以是岸 将 歌曲信息存入 数据库中了。
但是!
在此之前,我们需要做一个优化。
来看下面
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第45张图片
这里存在着一个问题:我们怎么知道它上传的是一个 MP3 文件,万一,它上传错了呢?
上传了一个图片文件,那能当作 音乐文件 来播放嘛?
很显然是不行的!
那么,接下来,我们就来看看 如何去判断上传的文件是 MP3 格式的。


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

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

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


文件格式:

SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第46张图片
由上图结构可知,每个Frame都由帧头和数据部分组成。我们来看每个帧头的数据格式。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第47张图片


ID3V1部分 && ID3V2

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

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

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

而 ID3V2的长度是不固定的,所以通过它是很难判断文件是否是音频文件。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第49张图片
参考链接:
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

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


在MusicController类中 间接 引入MusicMapper

注意!为了符合规范。
我们不能直接引入 MusicMapper,而是创建同 MusicService 作用 MusicMapper 接口 的调用。
MusicController 只是负责校验(处理)前端数据,具体调读那个接口由MusicService 负责。
所以,我们先去创建 MusicService 类。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第51张图片
下面我们在 MusicController 中 去上传文件信息到数据库中。
需要做两步:
1、准备好存储的数据
2、调用 MusicService 中 insert 方法。

1、准备好存储的数据

1.1、歌名(title)
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第52张图片
1,2、歌手(singer)
在这里插入图片描述
1.3、用户id(userid)
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第53张图片
1.4 url -> 播放音乐 -> http 请求
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第54张图片
1.5、时间(time)
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第55张图片
现在我们就需要存储信息,都准备好了。

现在我们就可以准备 调用 MusicService 的 insert 方法了。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第56张图片
这个时候,我们才真正完整了上传文件 的 业务代码。


5 验证整体文件上传

在这里我犯了一个错误,导致上面的异常。
就是我在使用 postman 构造请求的时候,不小心 歌手(singer)的参数多传递了一次。导致 歌手名称的长度超过了限制。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第57张图片
写到这里,虽然上传文件的代码就写完了。
但是!还存在着一个致命的问题!!!
如果我们重复上传一首歌,数据库的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 请求响应设计

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

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

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


你要返回 404,也是一样的步骤。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第59张图片
好了,现在呢,我们想要 那个 字节数组 a 返回给其前端,我们该怎么做呢?
来看下面!
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第60张图片
下面,我们就来看看:怎么把它应用到我们的项目中。

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


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

就拿上面的方法给个名字就行了。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第61张图片
接下来就是修改方法的内容。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第62张图片


测试

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

另外,你可以搜索一下 “TAG”
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第64张图片
这获取到的字节信息,就是一个 MP3 的文件信息。

但是!有人就发现问题了,这个 获取资源的路径 和 我们 在数据库中存储的不一样啊。
而且,你前面还把它给拎出来了!
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第65张图片
为了满足某些朋友的好奇心,我们来尝试一下,在获取音乐文件信息的时候,不加上 后缀,会是什么样子的效果?
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第66张图片

下面,我们Laura验证非音乐文件是否可以 “ 播放 ” ?
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第67张图片
对于我们当前的项目来说:
如果我们去播放一首 不是音乐的文件,是不会成功的!
但是!这个情况是可以避免的!
在上传文件之前,进行校验就行了。
但是,我搜了一下,没有什么实际意义的信息,搞得我还有点蒙。
总之,就是没能成功实现这个功能。
有兴趣的,可以自己去实现,我还需要在琢磨琢磨。。。。


删除音乐模块设计

删除单个音乐

1 请求响应设计

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


2 代码实现

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

之后便是,在 xml 文件中,写SQL。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第69张图片
在MusicService 中,添加对应 调用 mapper接口的方法。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第70张图片
在 MusicController 中 定义对应 的 delete 映射方法。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第71张图片


3 验证结果

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


批量删除选中的音乐

1 请求响应设计

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


2 代码实现

在 MusicMapper 类中,新增一个 deletSelMusic 方法
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第74张图片
代码实现起来,并没有多难,
就是加一个for循环 和 sum,方法参数需要绑定传递过来的数组,就没了。

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


3 验证结果


查询音乐模块设计

1 请求响应模块设计

此处查询需要满足几个功能:
1、 支持模糊查询
2、 支持传入参数为空
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第75张图片


2 代码实现

MusicMapper.java接口新增方法

这个接口方法,将会使用到重载!
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第76张图片


MusicMapper.xml新增配置

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


MusicController类 && MusicService 类,新增方法

先来看 MusicService类 嫌憎的方法。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第78张图片

接着我们来看 MusicController 类新增方法
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第79张图片


3 验证结果

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


喜欢/收藏 音乐模块设计

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

1 请求响应模块设计

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

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

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


2、代码实现

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

实现LoveMusicMapper接口,收藏/喜欢音乐功能:
1、需要查询此次收藏音乐是否之前收藏过,收藏过则不能添加
2、没有收藏过,插入数据库中一条记录
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第82张图片


实现LoveMusicMapper.xml

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

写到这里,我才发现:我前面一时手快创建的 LoveMusic 没用到!
别慌!万一后面需要呢? 先留着。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第84张图片


实现LoveMusicService类 && LoveMusicController类

先来实现 LoveMusicService类
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第85张图片
再来看 LoveMusicController类
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第86张图片


3 验证结果

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


查询喜欢的音乐模块设计

1 请求响应设计

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

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

和前面收藏音乐,几乎一摸一样。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第88张图片


2 代码实现

实现LoveMusicMapper新增方法:

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


实现LoveMusicMapper.xml

首先,我们来理通一下SQL的编写逻辑:
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第90张图片
此时,你再来看我在 xml 文件中写的SQL。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第91张图片
如果你想使用 resultMap,可以参考MyBatis查询数据库 && Spring Boot 单元测试 - 细节狂魔
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第92张图片
反正我个人觉得没必要,太麻烦了。
因为我们的实体类中,并没有存在像 集合(List) 这样的属性(这个属性还是数据表中没有的属性)。


实现 LoveMusicService 新增方法

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


实现LoveMusicController,新增方法

这个我不细说,和前面的 MusicController中的 findMusic 方法一毛一样。
SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第94张图片


3 验证结果

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


移除喜欢的音乐模块设计

1 请求响应设计

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

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


2 代码实现

实现 LoveMusicMapper接口新增方法

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


实现 LoveMusicMapper.xml

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


实现 LoveMusicService 新增方法

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


实现 LoveMusicController 新增方法

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

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

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

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


3 验证结果

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


总代码 - 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


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>

    <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

SSM项目 - Online Music Player(在线音乐播放器)- Java后端框架程序部分 -细节狂魔_第102张图片
这两个方法 删除的都是原文件,并没有涉及到 收藏音乐 的移除操作。
你想想看,我源文件都给你删掉了,你收藏表的数据不就成摆设了吗?
那还留着它干什么?
在这里,我们就是要去添加这个操作 来完善删除功能。


LoveMusicMapper接口新增方法:

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


实现 LoveMusicMapper.xml

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


实现 LoveMusicService 新增方法

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


修改前的准备工作

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


MusicController - delete - 修改

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


MusicController - deleteSelMusic - 修改

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


验证效果

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


总代码 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


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>

    <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);
    }
}

你可能感兴趣的:(-,在线音乐播放器,java,mybatis,mysql,SpringMVC,SpringAOP)