SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现

作者@ Autumn60

欢迎关注:点赞收藏✍️留言


目录

​编辑

一、项目介绍

二、项目的创建

2.1、社区版本的IDEA项目的创建

三、数据库的设计

3.1、数据库表的设计

3.1.1、User表的设计 

3.1.2、Music表的设计

1.3-LoveMusic表的设计

3.2、数据库的配置信息

四、实现登录的准备工作

4.1、创建User类

4.2、创建对应的Mapper接口和Controller

五、实现登录 

5.1.实现登录请求和响应的设计

5.2.创建UserController 类

5.2.1、根据逻辑实现代码

5.2.2、UserMapper中的操作

5.2.3、数据库当中的操作;

5.2.4、验证功能是否正常

5.3、优化代码

5.3.1、将登录的用户写入到Session中

​编辑5.3.2、验证优化后的代码是否成功

六、Bcrypt加密

6.1、注册功能的实现

​编辑​编辑 6.2、MD5加密

6.3、Bcrypt加密

6.3.1、Bcrypt加密第一种

6.3.2、第二种直接注入的方式调用;

6.3.3、验证注册加密功能

七、上传音乐模块

7.1、上传音乐的接口设计

7.2、创建MusicController类与对应的MusicMapper ​编辑

7.3、在Mybatis下面创建对应的XML文件

7.4实现功能

7.5上传验证:

7.6如何判断上传的文件是mp3(功能扩展未写)

7.7实现数据库上传

1.实现Mapper的 与 对应 Mybatis 的设计

 调用 启动!!​编辑

测试 启动!!

Mapper类的实现 :

 数据库的实现 :

Controller类的实现:

 验证 :

八、播放音乐模块的设计

8.1请求与响应的设计

8.2、ResponseEntity类

 8.3、实现对应的功能类

九、删音乐模块设计

9.1删除单个音乐

9.1.2、设计对应的Mapper和Mybatis;

9.2批量删除音乐

十、查询音乐模块的设计

10.1、请求与响应的设计

10.2、Mapper与XML的设计

10.3、实现具体类的功能

10.4、对实现的功能进行验证

十一、收藏音乐模块的实现

11.1、设计请求与响应

​编辑11.2、设计对应的Mapper与XML

11.3、实现对应的功能;

11.4、验证 启动!

十二、移除喜欢的音乐模块设计 

12.1、请求与响应的设计

12.2、对应Mapper与XML的设计

12.3、Controller具体功能的实现

一、Controller包的源码

1.LoveMusicController的实现; 

2.MusicController类的实现

3.UserController类的实现

二、Mapper包的源码

1.LoveMusic的源码 

2.MusicMapper

3.UserMapper

三、model包的源码

1.Music的实现

2.User实现

四、tools包的源码(工具包)有的不用

1.BcryptTest(加密类)

2.Constant(用来存储登录信息)

3.MD5Util加密

4.ResponseBodyMessage (返回响应体的设计)

五、启动类的源码 

 六、Mybatis的三个XML文件

1.LoveMusicMapper.xml 

2.MusicMapper.xml

3.UserMapper.xml

七、配置项

 至此结束


一、项目介绍

在实现整个项目中用的技术栈SpringBoot+Mybatis+Jquery+前端部分等

项目整体主要是实现的功能:

  1. 登录
  2. 查询
  3. 播放歌曲
  4. 删除
  5. 选中歌曲进行删除
  6. 喜欢
  7. 喜欢列表
  8. 添加歌曲
  9. 目前先实现这些功能,后面会根据项目人易用性进行功能的扩展

项目实现技术栈 :

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

二、项目的创建

2.1、社区版本的IDEA项目的创建

我自己在用的IDEA版本是

因为在3.2版本后面下载SpringBoot插件需要收费,所以明显白嫖更香一点;

创建项目 :

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第1张图片


  项目创建好之后的设置

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第2张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第3张图片

在做这个项目的过程中,需要对数据库来进行设计,在整个项目的过程中,分为两个部分,

一个是客户端,对于Web项目来说这里也就是浏览器,浏览器会发起请求给后端(服务器),假设要登录,服务器传给数据库一个用户名 和 密码,然后数据库来进行查询的操作,把查询得到的结果返回给服务器来进行一个校验,账号和密码是否正确 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第4张图片

三、数据库的设计

3.1、数据库表的设计

根据需求来分析设计数据库的表和字段

既然要登录,就要有User表(用户表)

要听音乐就要有Music表

两者间的关系 :

一个用户可以收藏多个音乐,一个音乐也可以被多个用户听,或者收藏,多对多关系

想要实现对音乐的收藏就要有一张中间表,所以又创建一张表 LoveMusic表 (收藏表)

先创建一个用来存储的数据库

create database onlinemusic character set utf8;

3.1.1、User表的设计 

用户例如:张三,李四,用户不一样就用ID字段来进行区分

-------------------------------------------------------------------------------------------------------------------------

use onlinemusic;

drop talbe if exists `user`;
create table user (
    `id` int primary key auto_increment,
    `username` varchar(20) not null,
    `password`  varchar(255) not null
);

3.1.2、Music表的设计

        id用来区分每首音乐

        title则为歌名

        singer为歌手

        time上传歌曲的时间

        url在哪里加载音乐并且听

        userid用来连接用户表的外键

-------------------------------------------------------------------------------------------------------------------------

drop talbe if exists `music`;
create Table `music` (
    `id` int primary key auto_increment,
    `title` varchar(50) not null,
    `singer`  varchar(30) not null,
    `time`  varchar(13) not null,
    `url`  varchar(1000) not null,
    `userid`  varchar(11) not null
);

1.3-LoveMusic表的设计

drop talbe if exists `lovemusic`;
create Table `lovemusic` (
    `id` int primary key auto_increment,
    `user_id` varchar(11) not null,
    `music_id`  varchar(11) not null
);

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第5张图片

3.2、数据库的配置信息

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第6张图片

把配置信息添加到里面去 

#数据库的配置,这里的数据库配置只是在本地的localhost上面配置的本地mysql数据库;
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/online_music? characterEncoding=utf8&serverTimezone=UTC   #这里的online_music 是自己的数据库名称
spring.datasource.username=root      #此处填写的是自己的数据库账户(默认是root)
spring.datasource.password=          #此处填写的是自己的数据库密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

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

#配置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.online_music.mapper=debug
#扫描的包 : druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG

四、实现登录的准备工作

4.1、创建User类

在根目录下面创建一个model 包,然后根据数据库User表来创建对应的类,并添加@data注解

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第7张图片

4.2、创建对应的Mapper接口和Controller

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第8张图片

下面配置信息中,com.example.online_music.mapper.UserMapper 这段代码为自己的路径





五、实现登录 

5.1.实现登录请求和响应的设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第9张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第10张图片

前段输入账号和密码后,传递给服务器,服务器再从数据库中进行查找,然后校验,最后再返回给前段,判断登录是否正常 

5.2.创建UserController 类

先建包再建类

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第11张图片

5.2.1、根据逻辑实现代码
package com.example.online_music.controller;

import com.example.online_music.mapper.UserMapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

    @RequestMapping("/login")
    //当前端请求这个路径时传递过来两个参数一个是Username,一个是Password
    public void login (@RequestParam  String username,@RequestParam String password) {
        //当创建好这个类后,拿到username 和 password 之后就能去数据库中进行查找了
    }
}

 当创建好类后,实现登录逻辑

先创建一个UserMapper 对象,通过写@Autowired注解来进行注入,然后创建User对象,对前端传递过来的 username 和 password 写入到 User类 当中对应属性中,通过创建好的UserMapper对象进行调用登录方法,进行对数据库的执行,执行的结果是User类型,所以在创建一个User对象来进行接收,接收之后进行if判断,如果 user 不为空 则 登录成功,否则登录失败(这里可以进行优化,可以把返回的结果写入到Session中,方便后续的调用);

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper userMapper;
    //用来标识 http 请求和地址与 RestController 类的方法之间的映射
    @RequestMapping("/login")
    public void login (@RequestParam  String username,@RequestParam String password) {
        User userLogin = new User();
        //当请求过来的参数,就要传参给username 和 password然后传递给数据库来进行操作;
        userLogin.setUsername(username);
        userLogin.setPassword(password);

        User user = userMapper.login(userLogin);
        //如果查到用户了,返回的user中存储的是账户和密码,如果错误,则是null
        if(user != null) {
            System.out.println("登录成功");
            //这里优化,如果登录成功之后可以把用户写入到session当中方便后面的调用
        } else {
            System.out.println("账户或密码错误");
        }
    }
}
5.2.2、UserMapper中的操作

对应的UserMapper,这里接受到对象loginUser 就是上面设置好账户密码的User对象然后执行数据库,进行响应操作;

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第12张图片

5.2.3、数据库当中的操作;

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第13张图片






    
5.2.4、验证功能是否正常

因为数据库当中是没有任何一条账户信息的,所以可以手动插入一条,然后通过使用Postman来构造一个响应验证登录的操作

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第14张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第15张图片

        设计出完整的登录初步模块没问题 

5.3、优化代码

当请求之后,最好给前段返回一个响应,最好用josn来返回数据;,所以新建一个响应的类,有可能做任何操作都要有想应所以创建一个tools包,然后再创建一个响应的工具类

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第16张图片

既然写好了响应的类,再返回到登录的实现类当中优化代码

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第17张图片

优化点 : 另外,登录成功后,我们还需要创建一个会话。
就是下次用户自来访问的时候,就不用在登陆的。所以再创建一个变量写入到Session当中即可,创建好的对象,在任何地方都可以使用;

5.3.1、将登录的用户写入到Session中

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第18张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第19张图片5.3.2、验证优化后的代码是否成功

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第20张图片

但是这种写法有个问题,那就是登录应该是和前端传递过来的密码来进行和账户进行对比,所以写一个根据账户在数据中查找密码的功能,查到了再决定是否登录 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第21张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第22张图片

六、Bcrypt加密

6.1、注册功能的实现

设计请求与响应部分

如果要写一个注册功能的话,也就是说: 用户输入username 和password 自动调用加密,然后把加密后的密码写入到数据库当中去,此处需要写一个判断,判断你的账户和密码中是否包含空格,如果包含空格,就不能注册,目前先写一个返回false即可,后面再进行优化;

总结:

1.根据用户输入过来的账户和密码,判断合法性

2.调用相对应的Mapper;

3.插入到数据库当中

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第23张图片

完事具备,只欠写代码,直接在UserController再写一个注册的类;最后再写加密

 /**
     *
     * TODO[未写完]在前端页面上面再补充一个跳转功能,注册成功之后跳转到登录页面
     * 注册功能
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/enroll")
    public ResponseBodyMessage enroll (@RequestParam  String username,
                                                @RequestParam String password) {
       、//实现对应的注册功能,然后对密码进行加密
    }

 2.设计对应的Mapper与Mybatis;

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第24张图片SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第25张图片 6.2、MD5加密

我们传递过去的密码不能是这种形式的,这种一抓包就能知道账户和密码,所以就需要进行加密

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第26张图片

那么,我们能不能对密码 进行 加密操作,让对方即使获取到我们的请求,也无法获取到重要信息。 
我们先来了解 MD5 加密。

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

彩虹表:
彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有。
有了它可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。
而 MD5加密出来的密码是固定,比如: 123 在加密之后,为 dhadhadhad。
只要有一个“密码库”记录了这一个组合,就可以根据 加密之后的密码,获取到 原本密码。
所以说:彩虹表 天克 MD5 加密。

不安全的原因:

1、 暴力攻击速度很快
2、 字典表很大
3、 碰撞
参考链接:https://md5.cc/news1.aspx
更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。

密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
比如 :

        现在有一个条数据支持1万块,但是破解起来需要3年的时间,等到破解的时候,这个数据恐怕已经不值钱了!这种费力不讨好的事情,人家不会去做。

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

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第27张图片

需要添加依赖: 



commons-codec
commons-codec


org.apache.commons
commons-lang3
3.9

2.Bcrypt加密:

在MD5加密的过的密码上面,再此进行添加盐值,进行加密

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第28张图片

 首先先定义一个盐值,然后随机抽取盐值和传递过来的密码来进行拼接加密,前端加密了一次,传递给后端后,后端拿到加密后的值,再根据自定义的盐值来进行二次加密,就会得到加密后的密码

package com.example.online_music.tools;

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

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

    public static String md5(String src) {
        //传入一个 src 来进行_MD5加密
        return DigestUtils.md5Hex(src);
    }

    /**
     * 第一次加密, 模拟前段自己加密,然后传递到后端
     * 此处为模拟前端加密
     * 把自己定义的盐值,和用户在前端输入的密码,任意抽取盐值的下标,和盐值拼接起来进行加密
     * @param inputpass   前端输入的密码
     * @return
     */
    public static String inputPassToFormPass(String inputpass) {
        //此处的salt是自定义的盐值,来取出自定义盐值的下标,任意抽取盐值的下标,和盐值拼接起来进行加密
        String str ="" + salt.charAt(1) + salt.charAt(3) + inputpass
                +salt.charAt(5) + salt.charAt(6);
        return md5(str);
    }

    /**
     * 第二次 md5加密
     * 此处为模拟后端加密
     * 也就是模拟在传递到后端后,把此刻传递过来的str再此随机抽取盐值,和str进拼接完成第二次加密
     * @param formPass  前端加密过的密码(str),传给后端进行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 inputPass
     * @param saltDB
     * @return
     */
    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"),
                "4e5r6v7f8n"));
        System.out.println("一次性两次加密: " + inputPassToDbPass("123456","4e5r6v7f8n"));
    }
}

不管运行多少次,这个密码是固定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码 的强度也加大了。破解成本也增加了(下方会优化)SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第29张图片

6.3、Bcrypt加密

重点 :参数第一个一定要是明文,第二个一定是密文SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第30张图片

6.3.1、Bcrypt加密第一种

直接调用写好的Bcrypt类来进行加密

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

第一步添加依赖


		
			org.springframework.security
			spring-security-web
		
		
			org.springframework.security
			spring-security-config
		

在启动类注解中添加参数 

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

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第31张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第32张图片

两次都是对 123465 来进行加密,可以明显的看出来两次加密后得到的密码是不一样;

测试的代码类

package com.example.online_music.tools;

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

public class BcryptTest {
    public static void main(String[] args) {
        //模拟从前端拿到密码
        String password = "123456";
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //通过调用 BCryptPasswordEncoder 类对象的 encode 方法来对密码进行加密;
        String newpassword = bCryptPasswordEncoder.encode(password);

        System.out.println(newpassword);
    }
}

可以看出来每次加密后的值都是不一样的,那么他是如何实现数据库对比的呢

//此处可以通过 bCryptPasswordEncoder 类下的matches 方法来对原密码和加密后的密码进行对比;
boolean same_password_result = bCryptPasswordEncoder.matches(password,newpassword);

System.out.println("加密后的密码和原密码进行对比 :" + same_password_result);

boolean other_password_result = bCryptPasswordEncoder.matches("987654",newpassword);

System.out.println("错误的密码和原密码进行对比" + other_password_result);

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第33张图片

实现注册功能的代码写入

 /**
     *
     * TODO[未写完]在前端页面上面再补充一个跳转功能,注册成功之后跳转到登录页面
     * 注册功能
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/enroll")
    public ResponseBodyMessage enroll (@RequestParam  String username,
                                                @RequestParam String password) {
        User userLogin = new User();
        //拿到前端传递过来的数据之后,判断账户名和密码是否包含空格;
        System.out.println("xxxxx");
        if (username.contains(" ") || password.contains(" ")) {
            System.out.println("注册失败,账户名或密码不能包含空格");
            return new ResponseBodyMessage(-1,"注册失败,账户名或密码不能包含空格",false);
        } else {

            //对密码进行Bcrypt加密操作
            //String newPassword = bcryptTest.encrypt(password);//调用工具类进行加密
            String newPassword = bCryptPasswordEncoder.encode(password);//写入Bean对象,直接调用类,来进行加密
            //这里如果不在配置类中添加Bean对象注入的话,会造成空指针异常的问题

            //设置账户名和密码
            userLogin.setUsername(username);
            userLogin.setPassword(newPassword);

            //2.调用对应的Mapper来执行对数据库的操作;因为insert 操作返回的是整形(也就是插入成功,共修改1行数据)所以判断即可;
            int ret = userMapper.enroll(userLogin);

            //调用完后注册成功与否的判断
            if(ret == 1 ) {
                System.out.println("注册成功");
                return new ResponseBodyMessage(0,"注册成功",true);
            }
            System.out.println("注册失败!");
            return new ResponseBodyMessage(-1,"注册失败",true);
        }
    }
6.3.2、第二种直接注入的方式调用;
第二种写法如果不在配置类中添加Bean对象注入的话,会造成空指针异常的问题

这时候再往回推,我们在启动类当中添加了一个这样的参数

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第34张图片

 所以需要使用这个参数过滤掉其他不用的东西的,如果不过滤掉,默认的 Spring-Security 是生效了的,此时的接口都是被保护起来的,我们需要通过验证才能正常的访问,通过上述的配置,就可以禁用默认的登录验证

所以在创建一个包

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第35张图片

6.3.3、验证注册加密功能

前端

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第36张图片

后端的信息:SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第37张图片

再看数据库当中 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第38张图片

可以看到注册成功了 

总结:

        1. 密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。

        2. 使用BCrypt相比于MD5加密更好的一点在于,破解的难度上加大。

        3. BCrypt的破解成本增加了,导致系统的运行成本也会大大的增加 。

        4. 回到本质的问题,你的数据库中的数据价值如何?如果你是银行类型的,那么使用BCrypt是不错的,一般情 况使用MD5加盐,已经够用了。 

两者的区别 :

        1.BCrypt加密:

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

        2.MD5加密:

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

         3.Bcrypt生成的密文是60位的。而MD5的是32位的。 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全,但加密更慢。

        4.虽然BCrpyt也是输入的字符串 +盐,但是与MD5+盐的主要区别是:每次加的盐不同,导致每次生成的结果也不相同。无法比对!

七、上传音乐模块

7.1、上传音乐的接口设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第39张图片

根据数据库music表的设计创建出对应的实体SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第40张图片

7.2、创建MusicController类与对应的MusicMapper SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第41张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第42张图片

7.3、在Mybatis下面创建对应的XML文件

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第43张图片

7.4实现功能

上传音乐需要用到MultipartFile 类 ,在org.springframework.web.multipart包当中,是Spring框架中处理文件上传的主要类。

主要方法介绍:

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第44张图片

实现服务器上传的功能

 /**
     * 上传文件
     * 1.上传到服务器
     * 2.上传到数据库
     * @param file
     * @param singer
     * @param request
     * @return
     */
    @RequestMapping("/upload")
    public ResponseBodyMessage upload(@RequestParam("filename") MultipartFile file,
                                               @RequestParam String singer,
                                               HttpServletRequest request) {
        // TODO[未优化]上传必须登录,不登录可以听音乐,把登录注册和音乐列表页写在一个页面上面
        //1.先判断是否登录
        if(request.getSession() != null) {
            System.out.println("已登录");

            //2.通过_MultipartFile_类来获取前端上传的文件的信息;
            String fileNameAndType = file.getOriginalFilename();//获取到上传文件的完整名;

            //3.写一个自己想要上传的路径,和文件名进行拼接,判断路径的合法性
            String path = SAVE_PATH + fileNameAndType; //将文件与路径拼接起来,就构成了上传文件的路径
            File dest = new File(path);
            //exists()判断路径是否存在
            if(!dest.exists()) {
                //mkdir()创建路径;
                dest.mkdir();
            }

            //走到这说明此路径必然存在,所以现在只需要上传即可
            //4.上传
            try {
                file.transferTo(dest);
                return new ResponseBodyMessage(0,"服务器上传成功",false);
            } catch (IOException e) {
                e.printStackTrace();
                return new ResponseBodyMessage(-1,"服务器上传失败",false);
            }
        } else {
            System.out.println("请登录后上传");
            return new ResponseBodyMessage(-1,"请登录后上传",false);
        }

    }
7.5上传验证:

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第45张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第46张图片

看一看到服务器端已经上传成功了

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

这里存在着一个问题:我们怎么知道它上传的是一个 MP3 文件,万一,它上传错了呢?
上传了一个图片文件,那能当作音乐文件来播放嘛?
很显然是不行的!
那么,接下来,我们就来看看 如何去判断上传的文件是 MP3 格式的。

7.6如何判断上传的文件是mp3(功能扩展未写)

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

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

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第47张图片

由上图结构可知,每个Frame都由帧头和数据部分组成。我们来看每个帧头的数据格式

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第48张图片

ID3V1部分 && ID3V2SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第49张图片

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

我们只需要查看 音频文件尾部的 128 个字节 的 前三个字节里面,有没有 TAG 字样 就行了?
如果前三个字节中有 “TAG” 字样(标记)。就说明它是一个 音频文件。
反之,如果不包含,则说明它不是一个 音频文件。
存放“ TAG ”字符,表示 ID3 V1.0 标准 ,根据是否包含这个字符,判断是不是音频文件,而 ID3V2的长度是不固定的,所以通过它是很难判断文件是否是音频文件。

而 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

7.7实现数据库上传

如果在上传数据库的时候,上传失败了,但是此时服务器当中已经上传成功了,这时候就要对服务器中的文件进行一个删除,保证上传失败,服务器和数据库中是空的情况,所以在正式实现之前,对保存文件的操作进行一个优化,

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第50张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第51张图片


1.实现Mapper的 与 对应 Mybatis 的设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第52张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第53张图片

再上传音乐类中实现相应的功能;

要上传就要获取对应的信息;这里的信息分别是 :

1. title(歌名) :前面上传服务器时

因为一个文件名包括两部分,一个是后缀名,一个前面的名称,只需要截取就可以获取当歌名了;


2. singer : 前端会传递过来一个歌手

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第54张图片

3. time : 通过使用这个类(SimpleDateFormat)来设置 获取的时间格式;

这个类通过网上查即可,此处的 new Date 底层也是常规的 System.current这种来实现的;

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第55张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第56张图片

4. url : 播放音乐的路径  ->

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第57张图片

5. userid : 因为之前已经登录过了,所以直接通过创建好的变量来获取当前的登录信息并以User类型存储,通过 user.getId(),来直接获取用户id; 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第58张图片

此时信息都有了万事俱备只欠东风

 调用 启动!!SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第59张图片
测试 启动!!

查看日志修改BUG,从日志中可以看出来SQL语句写错了,但是有个问题就是,咱们写SQL语句的初心是吧音乐信息添加到数据库当中去,所以SQL语句出错了,和添加到服务器当中去有什么关系

优化上传操作为一个事务;

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第60张图片

在Mybatis的MusicMapper.XML中查找SQL语句中查找BUG 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第61张图片

修改之后再次运行

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第62张图片

但是这里有个逻辑问题,如果是同一首歌不同的人唱,是否可以上传呢,如果在上传的时候,数据库当中已经存在这首歌了,是否还要上传呢 ?既然想到了就实现对应的合法性判断;

Mapper类的实现 :

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第63张图片

 数据库的实现 :

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第64张图片

Controller类的实现:

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第65张图片

 验证 :

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第66张图片

八、播放音乐模块的设计

8.1请求与响应的设计

播放音乐这里会用到几个类,File,Files(用来读取文件的信息

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第67张图片

8.2、ResponseEntity

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

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第68张图片

 8.3、实现对应的功能类
/**
     * 前段传递过来的是一个路径
     * 整体逻辑
     * 1.通过前端传递path的值
     * @param path
     * @return
     */
    @RequestMapping("/get")
    public ResponseEntity func(String path) throws IOException {
        File file = new File(SAVE_PATH + path);
        //通过给定的路径来读取指定的文件
        byte[] a = Files.readAllBytes(file.toPath());
        if(a == null) {
            System.out.println("播放音乐失败");
            //badRequest这个方法返回的状态码是400
            return ResponseEntity.badRequest().build();
        }
        //如果走到这说明读取成功
        System.out.println("播放音乐成功");
        //此OK方法的状态码是200,而a在这里读取的mp3文件,以二进制输出;
        return ResponseEntity.ok(a);
    }

8.4、对播放功能进行验证SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第69张图片

九、删音乐模块设计

9.1删除单个音乐

请求与响应的设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第70张图片

9.1.2、设计对应的Mapper和Mybatis;

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

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第71张图片

Mybatis:

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第72张图片

实现具体的功能

逻辑 : 既然是删除已经上传了的音乐,那么删除其实是分为两步的,第一步就是删除数据库当中的音乐,第二步就是删除服务器当中的音乐

第一步的实现逻辑 : 想要删除数据库中的音乐,这时候是根据音乐ID来进行删除的,而ID是用户给的,所以先创建一个类传递过来的参数是ID

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第73张图片

 SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第74张图片

第二步的逻辑实现 : 删除服务器中的音乐,怎么删除 ?

1.找到音乐名称        (因为上面进行数据库删除的时候,查询的返回结果Music对象接受,直接.title获取即可)

2.把已有上传到服务器的路径拿过来,进行拼接(也就是上传的时候的SAVE_PATH)

3.调用对应的Mapper删除

实现完整的删除类

/**
     * 删除音乐的总逻辑
     * 1.删除数据库
     * 2.删除服务器
     * @param id
     * @return
     */
    @RequestMapping("/delete")
    public ResponseBodyMessage delete(@RequestParam String id) {
        //1.删除数据库
        int iid = Integer.parseInt(id);
        Music music = musicMapper.selectMusicById(iid);


        if(music == null) {
            System.out.println("没找到你想要删除的音乐");
            return new ResponseBodyMessage<>(-1,"没找到你想要删除的音乐",false);
        } else {
            //不为空直接把id传递过去进行删除即可
            int ret = musicMapper.deleteMusicById(iid);
            if(ret == 1) {
                System.out.println("数据库删除成功");

                //2.服务器删除
                File file = new File(SAVE_PATH + music.getTitle() + ".mp3");
                System.out.println("要删除音乐的文件绝对路径是 : " + file);
                file.delete();
                System.out.println("服务器删除成功");
                return new ResponseBodyMessage<>(0,"删除成功",true);
            } else {
                System.out.println("数据库删除失败");
                return new ResponseBodyMessage<>(-1,"删除失败",false);
            }
        }
    }

测试启动! 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第75张图片

9.2批量删除音乐

批量删除,本质上是和单个删除没什么区别,只不过是把单个删除放入到循环当中了,id也是通过数组的方式来传递了

/**
     * 批量进行删除
     * 和单个删除没什么区别,不过是用数组存起来了,本质上还是一个一个删,用for循环即可解决;
     * @param id
     * @return
     */
    @RequestMapping("/deleteSel")
    public ResponseBodyMessage deleteSelMusic(@RequestParam("id[]") List id) {
        System.out.println("所有的要删除的ID :" + id);
        int sum = 0;//用来计数,删除1个加1,直到删完
        for (int i = 0; i < id.size(); i++) {
            int musicId = id.get(i);
            Music music = musicMapper.selectMusicById(musicId);
            if (music == null) {
                System.out.println("没有个id的音乐");
                return new ResponseBodyMessage<>(-1, "没有你想删除的歌", false);
            }
            int ret = musicMapper.deleteMusicById(musicId);
            if (ret == 1) {
                sum += ret;
                //2.2删除服务器本身文件'

                //获取文件名
                System.out.println("数据库删除成功");
                //第二种直接通过获取title获取id的文件名;
                String filename = music.getTitle();
                File file = new File(SAVE_PATH + filename + ".mp3");//路径删除需要手动写一个.MP3
                System.out.println("当前的路径" + file.getPath());
            } else {
                System.out.println("数据库删除失败");
                return new ResponseBodyMessage<>(-1,"数据库删除失败",false);
            }
        }
        if(sum == id.size()) {
            System.out.println("整体删除成功");
            return new ResponseBodyMessage<>(0,"音乐删除成功",true);
        } else {
            System.out.println("整体删除失败");
            return new ResponseBodyMessage<>(-1,"音乐删除失败",false);
        }
    }

十、查询音乐模块的设计

此处查询需要满足几个功能:
1、 支持模糊查询
2、 支持传入参数为空

10.1、请求与响应的设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第76张图片

10.2、Mapper与XML的设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第77张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第78张图片

关于XML中sql语句有一个concat函数,可以参考以下连接;

https://blog.csdn.net/qq_34292479/article/details/86500185

10.3、实现具体类的功能

/**
     * 查询音乐
     * @param musicName
     * @return
     */
    @RequestMapping("/findmusic")
    //这里RequestParam 默认为 true,改为false后,传递参数可以为空;也就是不传递参数;
    public ResponseBodyMessage> findMusic(@RequestParam(required = false) String musicName) {
        List musicList = null;
        if(musicName != null) {
            System.out.println("根据歌名查询");
            musicList = musicMapper.findMusicByName(musicName);
        } else {
            System.out.println("查询所有");
            musicList = musicMapper.findMusic();
        }
        System.out.println("查询到的歌曲 :" + musicList);
        System.out.println("Xxxxxxxxxx");
        return new ResponseBodyMessage<>(0,"查询到所有的音乐",musicList);
    }

到此为止,这个功能也就写结束了

10.4、对实现的功能进行验证

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第79张图片

 和预知结果一致SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第80张图片SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第81张图片

十一、收藏音乐模块的实现

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

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

11.1、设计请求与响应
SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第82张图片11.2、设计对应的Mapper与XML

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第83张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第84张图片

11.3、实现对应的功能;

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第85张图片

因为根据逻辑写,已经收藏过了,那么就进行取消收藏; 设计对应的Mapper与XML

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第86张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第87张图片

这个类写好之后,回过头来继续来根据逻辑来写

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第88张图片

11.4、验证 启动!

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第89张图片

至此收藏音乐模块已实现完毕


十二、移除喜欢的音乐模块设计 

12.1、请求与响应的设计

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第90张图片

这里唯一需要注意的是:这移除的 lovemusic 里面的信息。
与 music 表 无关。
也就是说:删除 lovemusic 表中的信息,不会影响到 music表中的数据。但是删除music中,这里的lovemusic中会不会删除呢 ?所以要回到之前普通的musicController中设计删除模块;

所以退回到Music删除模块进行补充

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第91张图片

在到对应的LoveMusic中写一个只根据音乐Id删除接口 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第92张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第93张图片

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第94张图片

12.2、对应Mapper与XML的设计

这里的Mapper与XML用的其实上面上传功能时插入到里面一遍设计了,这里直接调用即可使用,也就是11.3目录的取消收藏功能;

12.3、Controller具体功能的实现

和之前一样,根据用户id和音乐id来进行移除即可,这里就不讲了,直接删除即可

源码启动!

/**
     * 移出收藏
     * @param id
     * @param request
     * @return
     */
    @RequestMapping("/deletelovemusic")
    public ResponseBodyMessage deleteLoveMusic(@RequestParam("id") String id,
                                                        HttpServletRequest request) {
        int musicId = Integer.parseInt(id);

        //检查是否登录
        HttpSession httpSession = request.getSession(false);
        if(httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null ) {
            System.out.println("未登录");
            return new ResponseBodyMessage<>(-1,"请登录后操作",false);
        }

        User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();

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

    }

验证启动

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第95张图片

没什么问题,此模块也就实现了

一、Controller包的源码

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第96张图片

1.LoveMusicController的实现; 

package com.example.online_music.controller;


import com.example.online_music.mapper.LoveMusicMapper;
import com.example.online_music.model.Music;
import com.example.online_music.model.User;
import com.example.online_music.tools.Constant;
import com.example.online_music.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;

@RestController
@RequestMapping("/loveMusic")

public class LoveMusicController {

    @Autowired
    private LoveMusicMapper loveMusicMapper;
    @RequestMapping("/likeMusic")
    public ResponseBodyMessage likeMusic(@RequestParam("id") String id,
                                                  HttpServletRequest request) {
        int musicId = Integer.parseInt(id);
        System.out.println("musicId : " + musicId);

        //检查是否登录    TODO[不强制跳转]后面会做拦截
        HttpSession httpSession = request.getSession(false);
        if(httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null ) {
            System.out.println("未登录");
            return new ResponseBodyMessage<>(-1,"请登录后上传",false);
        }


        User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();
        System.out.println("userId : " + userId);

        Music music = loveMusicMapper.findLoveMusicByMusicIdAndUserId(userId,musicId);
        if(music != null) {
            //之前收藏过了,那么点击后就取消收藏
            System.out.println("因为之前收藏过,取消收藏了");
            int ret = loveMusicMapper.deleteLoveMusic(userId,musicId);
            if(ret == 0) {
                System.out.println("取消失败");
            } else {
                return new ResponseBodyMessage<>(-1, "因为之前收藏过,取消收藏了", false);
            }
        }

        //添加收藏
        boolean effect = loveMusicMapper.insertLoveMusic(userId,musicId);
        if(effect) {
            return new ResponseBodyMessage<>(0,"收藏成功",true);
        } else {
            return new ResponseBodyMessage<>(-1,"收藏失败",false);
        }
    }
    /**
     * 移出收藏
     * @param id
     * @param request
     * @return
     */
    @RequestMapping("/deletelovemusic")
    public ResponseBodyMessage deleteLoveMusic(@RequestParam("id") String id,
                                                        HttpServletRequest request) {
        int musicId = Integer.parseInt(id);

        //检查是否登录
        HttpSession httpSession = request.getSession(false);
        if(httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null ) {
            System.out.println("未登录");
            return new ResponseBodyMessage<>(-1,"请登录后操作",false);
        }

        User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();

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

2.MusicController类的实现

package com.example.online_music.controller;

import com.example.online_music.mapper.LoveMusicMapper;
import com.example.online_music.mapper.MusicMapper;
import com.example.online_music.model.Music;
import com.example.online_music.model.User;
import com.example.online_music.tools.Constant;
import com.example.online_music.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.transaction.annotation.Transactional;
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.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;
    //    private String SAVE_PATH = "C:/work/local/music1/";
    @Autowired
    private MusicMapper musicMapper;

    @Autowired
    private LoveMusicMapper loveMusicMapper;

    /**
     * 上传文件
     * 1.上传到服务器
     * 2.上传到数据库
     * 如果能上传,说明已经登录过了,所以只需要获取当前的用户信息,存储到user当中,通过user.getId来获取当前userid;
     *
     * @param file
     * @param singer
     * @param request
     * @return
     */
    @RequestMapping("/upload")
    public ResponseBodyMessage upload(@RequestParam("filename") MultipartFile file,
                                               @RequestParam String singer,
                                               HttpServletRequest request) {
        // TODO[未优化]上传必须登录,不登录可以听音乐,把登录注册和音乐列表页写在一个页面上面
        //1.先判断是否登录
        //前面在登录的时候,登录成功的话会创建 Session, 并在里面设置用户属性
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(Constant.USERINFO_SESSION_KEY) != null) {
            System.out.println("已登录");


            //2.通过_MultipartFile_类来获取前端上传的文件的信息;
            String fileNameAndType = file.getOriginalFilename();//获取到上传文件的完整名;


            String title = fileNameAndType.substring(0, fileNameAndType.lastIndexOf("."));
            Music music = musicMapper.select(title, singer);
            if (music == null) {
                //3.写一个自己想要上传的路径,和文件名进行拼接,判断路径的合法性
                String path = SAVE_PATH + fileNameAndType; //将文件与路径拼接起来,就构成了上传文件的路径
                File dest = new File(path);
                //exists()判断路径是否存在
                if (!dest.exists()) {
                    //mkdir()创建路径;
                    dest.mkdir();
                }

                //走到这说明此路径必然存在,所以现在只需要上传即可
                //4.上传
                try {
                    file.transferTo(dest);
                    //设置存储时间的格式
                    SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
                    String time = sf.format(new Date());
                    String url = "/music/get?path=" + title;
                    User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
                    int userid = user.getId();


                    //调用insert 插入
                    try {
                        int ret = 0;
                        ret = musicMapper.upLoad(title, singer, time, url, userid);
                        if (ret == 1) {
                            return new ResponseBodyMessage(0, "数据库上传成功", true);
                        }
                    } catch (BindingException e) {
                        //数据库上传失败之后会进行一个删除
                        dest.delete();
                        return new ResponseBodyMessage<>(-1, "数据库上传失败", false);
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                    return new ResponseBodyMessage(-1, "服务器上传失败", false);
                }
            }
            return new ResponseBodyMessage(-1, "歌曲已存在", false);
        } else {
            System.out.println("请登录后上传");
            return new ResponseBodyMessage(-1, "请登录后上传", false);
        }
    }

    /**
     * 前段传递过来的是一个路径
     * 整体逻辑
     * 1.通过前端传递path的值
     * @param path
     * @return
     */
    @RequestMapping("/get")
    public ResponseEntity func(String path) throws IOException {
        File file = new File(SAVE_PATH + path);
        //通过给定的路径来读取指定的文件
        byte[] a = Files.readAllBytes(file.toPath());
        if(a == null) {
            System.out.println("播放音乐失败");
            //badRequest这个方法返回的状态码是400
            return ResponseEntity.badRequest().build();
        }
        //如果走到这说明读取成功
        System.out.println("播放音乐成功");
        //此OK方法的状态码是200,而a在这里读取的mp3文件,以二进制输出;
        return ResponseEntity.ok(a);
    }

    /**
     * 删除音乐的总逻辑
     * 1.删除数据库
     * 2.删除服务器
     * @param id
     * @return
     */
    @RequestMapping("/delete")
    public ResponseBodyMessage delete(@RequestParam String id) {
        //1.删除数据库
        int iid = Integer.parseInt(id);
        Music music = musicMapper.selectMusicById(iid);


        if(music == null) {
            System.out.println("没找到你想要删除的音乐");
            return new ResponseBodyMessage<>(-1,"没找到你想要删除的音乐",false);
        } else {
            //不为空直接把id传递过去进行删除即可
            int ret = musicMapper.deleteMusicById(iid);
            if(ret == 1) {
                System.out.println("数据库删除成功");

                //2.服务器删除
                File file = new File(SAVE_PATH + music.getTitle() + ".mp3");
                System.out.println("要删除音乐的文件绝对路径是 : " + file);
                if(file.delete()) {
                    //同步删除loveMusic中的音乐

                    loveMusicMapper.deleteLoveMusicByMusicId(iid);
                    return new ResponseBodyMessage<>(0,"服务器文件删除成功",true);
                } else {
                    return new ResponseBodyMessage<>(-1,"服务器文件删除失败",false);
                }
            } else {
                System.out.println("数据库删除失败");
                return new ResponseBodyMessage<>(-1,"删除失败",false);
            }
        }
    }
    /**
     * 批量进行删除
     * 和单个删除没什么区别,不过是用数组存起来了,本质上还是一个一个删,用for循环即可解决;
     * @param id
     * @return
     */
    @RequestMapping("/deleteSel")
    public ResponseBodyMessage deleteSelMusic(@RequestParam("id[]") List id) {
        System.out.println("所有的要删除的ID :" + id);
        int sum = 0;//用来计数,删除1个加1,直到删完
        for (int i = 0; i < id.size(); i++) {
            int musicId = id.get(i);
            Music music = musicMapper.selectMusicById(musicId);
            if (music == null) {
                System.out.println("没有个id的音乐");
                return new ResponseBodyMessage<>(-1, "没有你想删除的歌", false);
            }
            int ret = musicMapper.deleteMusicById(musicId);
            if (ret == 1) {
                sum += ret;
                //2.2删除服务器本身文件'

                //获取文件名
                System.out.println("数据库删除成功");
                //第二种直接通过获取title获取id的文件名;
                String filename = music.getTitle();
                File file = new File(SAVE_PATH + filename + ".mp3");//路径删除需要手动写一个.MP3
                System.out.println("当前的路径" + file.getPath());
            } else {
                System.out.println("数据库删除失败");
                return new ResponseBodyMessage<>(-1,"数据库删除失败",false);
            }
        }
        if(sum == id.size()) {
            System.out.println("整体删除成功");
            return new ResponseBodyMessage<>(0,"音乐删除成功",true);
        } else {
            System.out.println("整体删除失败");
            return new ResponseBodyMessage<>(-1,"音乐删除失败",false);
        }
    }
    /**
     * 查询音乐
     * @param musicName
     * @return
     */
    @RequestMapping("/findmusic")
    //这里RequestParam 默认为 true,改为false后,传递参数可以为空;也就是不传递参数;
    public ResponseBodyMessage> findMusic(@RequestParam(required = false) String musicName) {
        List musicList = null;
        if(musicName != null) {
            System.out.println("根据歌名查询");
            musicList = musicMapper.findMusicByName(musicName);
        } else {
            System.out.println("查询所有");
            musicList = musicMapper.findMusic();
        }
        System.out.println("查询到的歌曲 :" + musicList);
        System.out.println("Xxxxxxxxxx");
        return new ResponseBodyMessage<>(0,"查询到所有的音乐",musicList);
    }

}

3.UserController类的实现

package com.example.online_music.controller;

import com.example.online_music.mapper.UserMapper;
import com.example.online_music.model.User;
import com.example.online_music.tools.BcryptTest;
import com.example.online_music.tools.Constant;
import com.example.online_music.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;

    //用来对密码进行加密操作;
    private BcryptTest bcryptTest;

    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;
    //用来标识 http 请求和地址与 RestController 类的方法之间的映射
    @RequestMapping("/login")
    public ResponseBodyMessage login (@RequestParam  String username, @RequestParam String password,
                                               HttpServletRequest request) {
        //直接根据前端传递过来的用户名查询对应的密码,判断即可
        User user = userMapper.selectByName(username);

        //如果查到用户了,返回的user中存储的是账户和密码,如果错误,则是null
        if(user == null ) {
            System.out.println("账户或密码错误");
            return new ResponseBodyMessage(-1,"账号或密码错误",false);
        } else {
            boolean fig = bCryptPasswordEncoder.matches(password,user.getPassword());
            if(!fig) {
                System.out.println("密码错误,登录失败");
                return new ResponseBodyMessage<>(-1,"用户名或密码错误",false);
            }
            System.out.println("登录成功");
            request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);
            return new ResponseBodyMessage(0,"登录成功",true);
        }
    }

    /**
     *
     * TODO[未写完]在前端页面上面再补充一个跳转功能,注册成功之后跳转到登录页面
     * 注册功能
     * @param username
     * @param password
     * @return
     */
    @RequestMapping("/enroll")
    public ResponseBodyMessage enroll (@RequestParam  String username,
                                                @RequestParam String password) {
        User userLogin = new User();
        //拿到前端传递过来的数据之后,判断账户名和密码是否包含空格;
        System.out.println("xxxxx");
        if (username.contains(" ") || password.contains(" ")) {
            System.out.println("注册失败,账户名或密码不能包含空格");
            return new ResponseBodyMessage(-1,"注册失败,账户名或密码不能包含空格",false);
        } else {

            //对密码进行Bcrypt加密操作
            //String newPassword = bcryptTest.encrypt(password);//调用工具类进行加密
            String newPassword = bCryptPasswordEncoder.encode(password);//写入Bean对象,直接调用类,来进行加密
            //这里如果不在配置类中添加Bean对象注入的话,会造成空指针异常的问题

            //设置账户名和密码
            userLogin.setUsername(username);
            userLogin.setPassword(newPassword);

            //2.调用对应的Mapper来执行对数据库的操作;因为insert 操作返回的是整形(也就是插入成功,共修改1行数据)所以判断即可;
            int ret = userMapper.enroll(userLogin);

            //调用完后注册成功与否的判断
            if(ret == 1 ) {
                System.out.println("注册成功");
                return new ResponseBodyMessage(0,"注册成功",true);
            }
            System.out.println("注册失败!");
            return new ResponseBodyMessage(-1,"注册失败",true);
        }
    }
}

二、Mapper包的源码

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第97张图片

1.LoveMusic的源码 

package com.example.online_music.mapper;

import com.example.online_music.model.Music;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LoveMusicMapper {
    /**
     * 查询喜欢的音乐
     * @param userId
     * @param musicId
     * @return
     */
    Music findLoveMusicByMusicIdAndUserId(int userId, int musicId);

    /**
     * 收藏音乐
     * @param userId
     * @param musicId
     * @return
     */
    boolean insertLoveMusic(int userId,int musicId);
    /**
     * 取消收藏
     * @param userId    用户ID
     * @param musicId   音乐Id
     * @return 受影响的行数
     */
    int deleteLoveMusic(int userId,int musicId);

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

2.MusicMapper

package com.example.online_music.mapper;

import com.example.online_music.model.Music;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface MusicMapper {
    int upLoad(String title, String singer, String time, String url,int userid);
    Music select(String title,String singer);
    //根据id来进行删除音乐
    int deleteMusicById(@Param("id") Integer id);
    //根据id来查询音乐
    Music selectMusicById(@Param("id") Integer id);
    //默认查询所有,所以用List存储
    List findMusic();
    //指定歌名来进行查询
    List findMusicByName(String musicName);
}

3.UserMapper

package com.example.online_music.mapper;


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

@Mapper
public interface UserMapper {
    User login(User loginUser);
    int enroll(User userLogin);
    User selectByName(String username);
}

三、model包的源码

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第98张图片

1.Music的实现

package com.example.online_music.model;

import lombok.Data;

@Data
public class Music {
    private int id;
    private String title;
    private String singer;
    private String time;
    private String url;
    private String userid;
}

2.User实现

package com.example.online_music.model;

import lombok.Data;

@Data
public class User {
    private int id;
    private String username;
    private String password;
}

四、tools包的源码(工具包)有的不用

1.BcryptTest(加密类)

package com.example.online_music.tools;

import com.example.online_music.controller.UserController;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BcryptTest {
    //返回参数写成 String 类型,用来接收密码;
    public static String encrypt(String password) {
        //模拟从前端拿到密码
        //此处的这个password 是默认的123456
        //String password = "123456";
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //通过调用 BCryptPasswordEncoder 类对象的 encode 方法来对密码进行加密;
        String newpassword = bCryptPasswordEncoder.encode(password);
        System.out.println("加密后的密码 : " + newpassword);

//        //此处可以通过 bCryptPasswordEncoder 类下的matches 方法来对原密码和加密后的密码进行对比;
//        boolean same_password_result = bCryptPasswordEncoder.matches(password,newpassword);
//
//        System.out.println("加密后的密码和原密码进行对比 :" + same_password_result);
//
//        boolean other_password_result = bCryptPasswordEncoder.matches("987654",newpassword);
//
//        System.out.println("错误的密码和原密码进行对比" + other_password_result);
//        //把加密后的密码进行返回;
        return newpassword;
    }
}

2.Constant(用来存储登录信息)

package com.example.online_music.tools;

public class Constant {
    //用来存储登录信息用
    public static final String USERINFO_SESSION_KEY="";
}

3.MD5Util加密

package com.example.online_music.tools;

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

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

    public static String md5(String src) {
        //传入一个 src 来进行_MD5加密
        return DigestUtils.md5Hex(src);
    }

    /**
     * 第一次加密, 模拟前段自己加密,然后传递到后端
     * 此处为模拟前端加密
     * 把自己定义的盐值,和用户在前端输入的密码,任意抽取盐值的下标,和盐值拼接起来进行加密
     * @param inputpass   前端输入的密码
     * @return
     */
    public static String inputPassToFormPass(String inputpass) {
        //此处的salt是自定义的盐值,来取出自定义盐值的下标,任意抽取盐值的下标,和盐值拼接起来进行加密
        String str ="" + salt.charAt(1) + salt.charAt(3) + inputpass
                +salt.charAt(5) + salt.charAt(6);
        return md5(str);
    }

    /**
     * 第二次 md5加密
     * 此处为模拟后端加密
     * 也就是模拟在传递到后端后,把此刻传递过来的str再此随机抽取盐值,和str进拼接完成第二次加密
     * @param formPass  前端加密过的密码(str),传给后端进行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 inputPass
     * @param saltDB
     * @return
     */
    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"),
                "4e5r6v7f8n"));
        System.out.println("一次性两次加密: " + inputPassToDbPass("123456","4e5r6v7f8n"));
    }
}

4.ResponseBodyMessage (返回响应体的设计)

package com.example.online_music.tools;


import lombok.Data;

@Data
//设置为泛型类,因为响应返回的可能是不同的数据类型
public class ResponseBodyMessage {
    private int status;  //返回的状态码
    private String message;//返回给前端的信息(比如出错的信息,等等);
    private T data;         //返回给前端的数据;数据可能不同,所以设置泛型;

    //通过右键构建出构造类
    public ResponseBodyMessage(int status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }
}

五、启动类的源码 

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第99张图片

package com.example.online_music;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

	public static void main(String[] args) {
		SpringApplication.run(online_MusicApplication.class, args);
	}

}

 六、Mybatis的三个XML文件

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第100张图片

1.LoveMusicMapper.xml 






    
    
    
    
        insert into lovemusic(user_id,music_id) values(#{userId},#{musicId});
    

    
    
        delete from lovemusic where user_id=#{userId} and music_id=#{musicId};
    

    
        delete from lovemusic where music_id=#{musicId};
    

2.MusicMapper.xml





    
        insert into music(title,singer,time,url,userid)
        values(#{title},#{singer},#{time},#{url},#{userid});
    

    

    
        delete from music where id=#{id};
    

    

    

    

3.UserMapper.xml






    


    
        insert into user(username,password) values(#{username},#{password});
    


    

七、配置项

SSM项目 - OnlineMusicPlayer(在线音乐播放器) Java后端部分实现_第101张图片

#数据库的配置,这里的数据库配置只是在本地的localhost上面配置的本地mysql数据库;
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/online_music?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

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

#配置SpringBoot上传文件的大小,每个文件默认是15MB,单次最大不超过100MB
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size = 100MB

#存储音乐的路径
music.local.path= C:/work/local/music1/

#配置SpringBoot 日志调试模式是否开启
debug=true
#设置日志的级别,以及打印sql语句
#日志级别 : trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
logging.level.com.example.online_music.mapper=debug
#扫描的包 : druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG

 至此结束

SSM项目后端部分已经结束,后续会继续更新部署部分与前端部分的补齐;

你可能感兴趣的:(java,jvm,spring,boot,spring,mybatis)