# Online music player **Repository Path**: gwpCoder/online-music-player ## Basic Information - **Project Name**: Online music player - **Description**: 基于SpringBoot的在线音乐播放器web项目 支持用户登录(密码加密传输),音乐上传,音乐播放,查询,删除功能,还具有收藏喜欢列表! - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-07-26 - **Last Updated**: 2022-10-16 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[TOC](目录) # 项目演示 - 项目部署链接地址: > [OnlineMusicPlayer](http://43.142.82.4:8080/login.html) - 项目演示: ![image-20220813153108143](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220813153108143.png) ![image-20220811144401094](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220811144401094.png) # 创建项目 因为我们的`online-music-player`项目是基于`SpringBoot`框架开发的,所以我们需要创建一个`SpringBoot`项目! image-20220726115032421 image-20220726115505183 选择`SpringBoot`版本并初步导入依赖! ![image-20220726120252271](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726120252271.png) ![image-20220726121723445](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726121723445.png) # 数据库的设计 ![image-20220726122546219](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726122546219.png) 根据我们的演示我们可以得知我们需要创建`onlinemusic`数据库,其下有3张结果! ![image-20220726123715300](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726123715300.png) - 创建`onlinemusic` 数据库 ```mysql -- 创建onlinemusic数据库 drop database if exists onlinemusic; create database if not exists onlinemusic character set utf8; -- 使用onlinemusic use onlinemusic; ``` - 创建`user`用户表 ```mysql -- 创建user表 drop table if exists user; create table user ( id int primary key auto_increment, -- 设置自增主键 id username varchar(20) not null, -- 用户名不能为空! password varchar(255) not null -- 这里密码不为空,长度255留有足够长度加密操作 ); ``` - 创建`music`音乐列表 ```mysql -- 创建music表 drop table 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, -- 歌曲路径 user_id int(11) not null ); ``` - 创建`lovemusic`收藏音乐列表 ```mysql -- 创建lovemusic表 drop table if exists lovemusic; create table lovemusic( id int primary key auto_increment, user_id int(11) not null, -- 用户id music_id int(11) not null -- 音乐id ); ``` `onlinemusic`数据库下的3张表创建成功后! ![image-20220726132147784](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726132147784.png) 3张表结构如下! ![image-20220726132259078](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726132259078.png) 整个`db.sql`文件 ```mysql -- 创建onlinemusic数据库 drop database if exists onlinemusic; create database if not exists onlinemusic character set utf8; -- 使用onlinemusic use onlinemusic; -- 创建user表 drop table if exists user; create table user ( id int primary key auto_increment, -- 设置自增主键 id username varchar(20) not null, -- 用户名不能为空! password varchar(255) not null -- 这里密码不为空,长度255留有足够长度加密操作 ); -- 创建music表 drop table 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, -- 歌曲路径 user_id int(11) not null ); -- 创建lovemusic表 drop table if exists lovemusic; create table lovemusic( id int primary key auto_increment, user_id int(11) not null, -- 用户id music_id int(11) not null -- 音乐id ); ``` # 配置数据库和xml 打开`Application.properties`文件进行配置 ```properties #配置数据库 spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?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 # 配置springboot日志调试模式是否开启 debug=true # 设置打印日志的级别,及打印sql语句 #日志级别:trace,debug,info,warn,error #基本日志 logging.level.root=INFO logging.level.com.example.onlinemusic.mapper=debug #扫描的包:druid.sql.Statement类和frank包 logging.level.druid.sql.Statement=DEBUG logging.level.com.example=DEBUG ``` # 登入注册模块设计 ## 创建User类 我们先创建一个`model`包用来保存实体类 在其下创建`User`类! ```java package com.example.onlinemusic.model; import lombok.Data; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 13:37 */ @Data //Data注解生成了setter/getter/tostring方法 public class User { private int id; private String username; private String password; } ``` ## 创建对应的Mapper和Controller ### 创建UserMapper接口 创建`Mapper`包保存Mapper接口! ```java //UserMapper package com.example.onlinemusic.mapper; import com.example.onlinemusic.model.User; import org.apache.ibatis.annotations.Mapper; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 13:38 */ @Mapper //实现xml映射,无需通过其他的mapper映射文件! public interface UserMapper { //登入功能! User login(User loginUser); } ``` ![image-20220726141845600](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726141845600.png) ### 创建UserMapper.xml 在`resource`包下创建`mybatis`包用于保存mapper.xml文件,再创建`UserMapper.xml` ```xml ``` ![image-20220726141907881](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726141907881.png) ## 实现登入 ### 设置登入的请求和响应 - 请求 ```java 请求: { post, //请求方法 /user/login //请求的url data:{username,password} //传输的数据! } ``` - 响应 ```java 响应: { "status":0, //status 为0表示登入成功,为负数表示登入失败! "message":"登入成功", // 放回登入信息! "data":{ // 登入成功后获取到相应的用户信息! "id":xxxx, "username":xxxx, "password":xxxx } } ``` ### 创建UserController类 创建一个`controller`包,在其包下创建一个UserController类 ```java package com.example.onlinemusic.controller; import com.example.onlinemusic.mapper.UserMapper; import com.example.onlinemusic.model.User; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 15:12 */ @RestController // @ResponseBody + @Controller @RequestMapping("/user") //设置路由 用来映射请求! public class UserController { //将UserMapper注入! @Resource private UserMapper userMapper; @RequestMapping("/login") public void login(@RequestParam String username,@RequestParam String password){ User userLogin = new User(); userLogin.setUsername(username); userLogin.setPassword(password); User user = userMapper.login(userLogin); //先初步测试一下,后面再完善 if(user!=null){//登入成功! System.out.println("登入成功!"); }else{ System.out.println("登入失败!"); } } } ``` ![image-20220726203942253](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726203942253.png) 这里只是粗略的写一下登入逻辑,然后验证是否可行,我们再进行后续代码的完善! ### pastman验证登入功能 ![image-20220726204051182](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726204051182.png) ![image-20220726204136503](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726204136503.png) ![image-20220726204151778](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726204151778.png) 我们对照数据库中的`user`表中的数据!所以该登入请求成功! ### 封装响应 刚刚我们的登入逻辑是没有了问题,但是我们的服务器并没有给客户端返回响应,所以我们需要根据约定的响应,登入请求后分装并返回! 创建一个`tools`包统一保存一些通用的代码,创建响应类`ResponseBodyMessage`这里响应信息我们可以通过泛型,就可以变成通用的响应! ```java package com.example.onlinemusic.tools; import lombok.Data; /** * Created with IntelliJ IDEA. * Description:统一(泛型)的响应体 * User: hold on * Date: 2022-07-26 * Time: 21:32 */ @Data public class ResponseBodyMessage { private int status;//状态码 0 表示成功,-1表示失败! private String message;//响应信息描述 private T data; //返回的数据,这里采用泛型因为响应的数据的种类很多 public ResponseBodyMessage(int status, String message, T data) { this.status = status; this.message = message; this.data = data; } } ``` 验证 ![image-20220726215005432](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726215005432.png) ### Session创建 我们再对刚刚的登入功能创建`Session` 我们通过`HttpServlet`下的getSession方法获取到Session,然后再通过`SetAttribute`方法设置会话,保存在服务器中! ```java //优化后的UserController类! package com.example.onlinemusic.controller; import com.example.onlinemusic.mapper.UserMapper; import com.example.onlinemusic.model.Contant; import com.example.onlinemusic.model.User; import com.example.onlinemusic.tools.ResponseBodyMessage; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 15:12 */ @RestController // @ResponseBody + @Controller @RequestMapping("/user") //设置路由 用来映射请求! public class UserController { //将UserMapper注入! @Resource private UserMapper userMapper; @RequestMapping("/login") //@RequestParam SpringMVC下的注解,表示该参数必须传入给服务器! //value = "前端参数名",required = true/false,defaultValue ="默认值" //这里required设置为ture表示该参数必传,默认为true,如果设置了defaultValue那默认required为false public ResponseBodyMessage login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){ User userLogin = new User(); userLogin.setUsername(username); userLogin.setPassword(password); //调用mapper下的 login查询user! User user = userMapper.login(userLogin); //返回响应 if(user!=null){//登入成功! //登入成功就在服务器保存该Session会话! //这里我们的key值可以通过常量值设置,避免后面出错! //request.getSession().setAttribute("USERINFO_SESSION_KEY",user); // request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user); return new ResponseBodyMessage (0,"登入成功",userLogin); }else{ return new ResponseBodyMessage (-1,"登入失败",userLogin); } } } ``` 我们通过Fiddler抓包获取响应, 设置了`Session`会话响应 ![image-20220726221715118](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726221715118.png) 未设置`Session`会话响应! ![image-20220726221843242](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726221843242.png) 我们可以看到设置了的返回的响应头中有Session信息,否则没有! ## Bcrypet加密原理 我们知道如果我们登入时传输的密码通过明文传输的话,就不安全,会被其他人盗取,所以我们要对密码进行加密! 目前主流的加密方式有2种 - MD5加密 - Bcrypet加密 我们先对这两种加密方式进行了解,便于后续我们对登入功能进行加密操作! ### MD5加密 MD5加密是一个安全的散列算法(哈希算法),就是通过对某一密码进行哈希操作,然后得到哈希后的加密字符串密码,一般这里加密后密码的长度比原来密码长度长,我们这里的加密操作是不可逆的!所以当我们对一个密码进行MD5加密后得到的字符串,我们无法通过逆操作解密,所以相对安全. MD5的不足之处,在于我们每次对同一个密码加密后得到的结果都是固定值,这就是使得,我们可以通过彩虹表(密码本,里面记录了密码加密算法后得到结果的映射关系),我们通过彩虹表查询进行暴力破解,就可以拿到密码! ![image-20220726232223419](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726232223419.png) 我们也可以对MD5加密进行优化,就是对密码进行加盐操作,再进行MD5散列算法,这里的加盐指的是对密码添加一些单词,也就是字符,通过加盐操作,使得密码长度更长也就更安全,MD5加密后得到的结果也就更难破解! 如果我们要使用MD5加密,我们要在项目中导入MD5依赖 ```xml commons-codec commons-codec org.apache.commons commons-lang3 3.9 ``` 模拟MD5加密操作: ```java package com.example.onlinemusic.tools; import org.apache.commons.codec.digest.DigestUtils; /** * Created with IntelliJ IDEA. * Description:对密码加盐后再md5 * User: hold on * Date: 2022-07-26 * Time: 23:28 */ public class MD5Util { //定义一个固定的盐值 private static final String salt = "1b2i3t4e"; public static String md5(String src) { return DigestUtils.md5Hex(src); } /** * 第一次加密 :模拟前端自己加密,然后传到后端 * @param inputPass * @return */ public static String inputPassToFormPass(String inputPass) { String str = ""+salt.charAt(1)+salt.charAt(3) + inputPass +salt.charAt(5) + salt.charAt(6); return md5(str); } /** * 第2次MD5加密 * @param formPass 前端加密过的密码,传给后端进行第2次加密 * @param salt 用户数据库当中的盐值 * @return */ public static String formPassToDBPass(String formPass, String salt) { String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) + salt.charAt(4); return md5(str); } /** * 上面两个函数合到一起进行调用 * @param * @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("对用户输入密码进行第1次加密:"+inputPassToFormPass("123456")); System.out.println("对用户输入密码进行第2次加密:"+formPassToDBPass(inputPassToFormPass("123456"), "1b2i3t4e")); System.out.println("对用户输入密码进行第2次加密:"+inputPassToDbPass("123456", "1b2i3t4e")); } } ``` ![image-20220726233230278](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726233230278.png) 虽然这里的加盐操作使得加密后的密码长度更长了,但是还是解决不了md5对一个密码加密得到的结果相同,除非我们这里采用随机盐! ### BCrypet加密 这里的`BCrypet`加密方式也是一种安全的不可逆的散列算法加密操作,`BCrypet`加密和`MD5`不同之处在于每次对同一个密码加密得到的结果都不相同,也就是在其内部实现了随机加盐处理.这就很好解决了`MD5`加密的缺点.所以`BCrypet`加密方式更加安全!并且`BCrypet`加密可以使加密得到的密文长度最大为60位,而MD5是32位,所以相对于MD5加密,`BCrypet`破解难度更大! **使用`BCrypet`加密:** 引入依赖: ```xml org.springframework.security spring-security-web org.springframework.security spring-security-config ``` ```java //BCrypet加密使用演示 package com.example.onlinemusic.tools; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 23:42 */ public class BCrypetTest { public static void main(String[] args) { //模拟从前端获得的密码 String password = "123456"; BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String newPassword = bCryptPasswordEncoder.encode(password); System.out.println("加密的密码为: "+newPassword); //使用matches方法进行密码的校验 boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword); //返回true System.out.println("加密的密码和正确密码对比结果: "+same_password_result); boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword); //返回false System.out.println("加密的密码和错误的密码对比结果: " + other_password_result); } } ``` > 这里`BCrypet`加密工具主要通过`BCrypetPassWordEncoder`对象下的`encode`加密方法对密码进行加密和`matches`匹配算法通过密码和加密后的密文进行匹配! ![image-20220726234759160](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726234759160.png) ![image-20220726234815961](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220726234815961.png) 可以看到这里每次加密的结果都不一样,但是都能和正确密码匹配成功! **MD5和BCrypet的异同** - MD5:一种不加盐的单向hash,不可逆的加密算法,对同一个密码每次hash加密得到的hash值结果都是一样的!所以大多数情况下可以破解! - BCrypet:一种加盐的单向Hash,不可逆的加密算法,每次对同一个密码进行加密的结果不同,破解难度更高! 这2个都是目前主流的加密算法,BCrypet更加安全,但是效率低! BCrypet的加盐操作是加入的随机盐,所以每次的加密结果都不一样! 指的注意的是并没有什么密码是绝对安全的,无论那种加密方式都可以被破解的,只是破解的成本和时间问题!如果你的数据并没有价值,那么破解你的密码就毫无意义,也就很安全! ## 加密登入实现 > 因为我们matches通过前端传输过来的密码对比数据库中密码即可判断密码是否正确! > 我们知道每次encode后的密码都不一样,所以我们不能通过查询数据库中username+password验证! > 我们先通过username查询到数据库中的密码,然后通过mathes匹配判断是否登入成功! - UserMapper添加查询用户方法! ![image-20220727121658099](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727121658099.png) ```java //加密后的登入方法! public ResponseBodyMessage login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){ User user = userMapper.selectUserByUserName(username); //返回响应 if(user!=null){//查询到username用户! BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); //匹配验证密码是否正确! boolean flg = bCryptPasswordEncoder.matches(password,user.getPassword()); if(flg){//登入成功! request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user); return new ResponseBodyMessage (0,"登入成功",user); }else{//密码错误! return new ResponseBodyMessage (0,"用户名或密码错误",user); } }else{//用户不存在! return new ResponseBodyMessage (-1,"用户名或密码错误",user); } } ``` **pastman验证代码:** ![image-20220727122016630](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727122016630.png) 这里我们发现失败了,因为我们引入`BCrypet`加密依赖时,导入的`security`框架,我们只是用了其下的一个类用于加密,并没有用到该框架的功能!而导入`security`框架后,该项目中的接口都需要身份验证和授权!所以这个我们就登入失败了! 我们在启动类上加上一行注解即可解决该问题! ![image-20220727122834874](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727122834874.png) ```java @SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}) ``` ![image-20220727122756040](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727122756040.png) ### 创建config类,添加AppConfig类 刚刚`BCrypetPasswordEncoder`对象创建使用方式并不符合`SpringIoC`思想,所以我们通过`Bean`注解先将该对象注册到`Spring`中,然后通过`Spring`获取对象! ```java @Configuration public class AppConfig { //将BCrypetPasswordEncoder对象交给spring管理! @Bean public BCryptPasswordEncoder getBCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } } ``` ![image-20220727123829855](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727123829855.png) 这样我们的登入功能就完善好了! 实现注册功能 **约定注册请求和响应** ```java 请求: { post url:user/register data:{username,password} } ``` ```java 响应: { status:"状态码" message:"响应信息" data:{username,password} } ``` **UserMapper类中添加一个注册接口!** ```java //注册功能! int register(String username,String password); ``` **Mapper.xml下实现该接口** ```xml insert into user (username,password) values(#{username},#{password}); ``` **Controller的User类下实现注册功能** ```java //注册功能 @RequestMapping("/register") public ResponseBodyMessageregister(@RequestParam String username,@RequestParam String password,HttpServletRequest request){ //1.首先查询该用户是否存在! User user = userMapper.selectUserByUserName(username); if(user!=null){//查询到该用户,说明用户存在! return new ResponseBodyMessage(-1,"该用户已注册,请修改用户名重新注册",null); } //2.用户不存在,就注册该用户! //对密码进行加密后保存在数据库中! password = appConfig.getBCryptPasswordEncoder().encode(password); //将用户注册到数据库中! userMapper.register(username,password); //将注册好的用户信息放回给客户端! user = userMapper.selectUserByUserName(username); request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user); return new ResponseBodyMessage(0,"注册成功!",user); } ``` **postman验证** - 用户存在 ![image-20220727132235399](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727132235399.png) ![image-20220727131627680](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727131627680.png) - 用户不存在注册成功! ![image-20220727133836156](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727133836156.png) ![image-20220727133851917](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727133851917.png) **通过注册的用户验证一下登入功能** ![image-20220727133954061](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220727133954061.png) # 上传音乐模块 # 上传音乐模块设计 ## 上传音乐请求和响应 ```java 请求: { post, url:music/upload data:{singer,MultipartFile file} //上传音乐的歌手名和音乐文件 } ``` ```java 响应: { status:0,//0表示成功,-1失败! message:"响应信息", data:true //true表示成功 } ``` ## Music类 ```java package com.example.onlinemusic.model; import lombok.Data; /** * Created with IntelliJ IDEA. * Description:Music实体类 * User: hold on * Date: 2022-07-27 * Time: 15:37 */ @Data public class Music { private int id; private String title; private String singer; private String time; private String url; private int user_id; } ``` ## MusicController类 这里`MusicController`类中的上传方法,需要处理2部分内容 - 将音乐文件上传到服务器下 - 将上传的音乐信息上传到数据库中 ## 上传到服务器 ```java package com.example.onlinemusic.controller; import com.example.onlinemusic.model.Contant; import com.example.onlinemusic.tools.ResponseBodyMessage; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import sun.util.logging.resources.logging; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.File; import java.io.IOException; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-27 * Time: 15:40 */ @RestController @RequestMapping("/music") public class MusicController { // //文件上传服务器后的路径地址! // public static final String SAVE_PATH = "D:/uploadmusicfile/"; //我们可以将该路径信息设置到配置文件中! @Value("${music.path.save}") private String SAVE_PATH; //这里的上传需要将音乐上传到服务器,还有就是需要将音乐信息上传到数据库! @RequestMapping("/upload") public ResponseBodyMessage UploadMusic(String singer, MultipartFile file, HttpServletRequest request){ //1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器! //获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname); //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File desc = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!desc.exists()){ desc.mkdir(); } //将音乐文件上传到该目录下! try { file.transferTo(desc); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"上传失败",false); } //上传成功 return new ResponseBodyMessage<>(0,"上传成功",true); } } ``` **postman验证** - 未登录上传文件 ![image-20220728220551808](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220728220551808.png) - 登入后上传 ![image-20220728222451783](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220728222451783.png) ![image-20220728221805447](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220728221805447.png) 可以看到我们设置的服务器目录下就含有了该上传的音乐文件! ## 如何保证上传的文件是音乐文件 ***可以通过后缀名`.MP3`嘛?*** > 虽然这是一种最简单的解决方案,但是显然不可以的,如果有人将不是`.MP3`文件改成后缀为`.MP3`的音乐文件,不过这种情况比较罕见! ***那么我们如何检测用户上传的是音乐文件呢?*** > 其实每一种文件都有自己特点的文件结构! > > 我们拿`.MP3`文件举例: > > **文件结构如下:** > > ![image-20220729133426738](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729133426738.png) > > 一个MP3文件结构分成3部分! > > 而确定是否是MP3文件可以通过`MPEG`音频标签 > > ![image-20220729134018771](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729134018771.png) > > 例如我们通过`ID3V1`128字节中的前3个字节中的标签标志包含了字符`TAG`就可以判断该文件是`MP3`文件了! ## 增加音乐文件校验功能 我们先创建一个文件校验的接口,测试一下: ```java //音乐文件验证 @RequestMapping("/ismp3") public ResponseBodyMessage ismp3(String path){ String str = null; File file = null; byte [] fileByte = null; try { file= new File(SAVE_PATH+File.separator+path); //获取到这个文件的所有字节信息 fileByte = Files.readAllBytes(file.toPath()); } catch (IOException e) { e.printStackTrace(); } //将字节数组转成字符串 str = new String(fileByte); //获取到最后128个字节的字符串信息! str = str.substring(str.length()-128); if(str.contains("TAG")){//最后128字节,含有音乐文件标志 return new ResponseBodyMessage(0,"mp3文件",str); } //没有音乐文件标识 return new ResponseBodyMessage(-1,"非mp3文件",str); } ``` **验证:** 音乐文件: ![](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801134440491.png) 非音乐文件: ![image-20220801135011056](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801135011056.png) 测试该方法无误,我们就将其封装到上传音乐的模块中! ```java //1.先验证是否为音乐文件! boolean flg = false; try { //获取到后128位含有标志字节的字符串 String str = new String(file.getBytes()); String flgTAG = str.substring(str.length()-128); if(flgTAG.contains("TAG")){ //含有标志位,为音乐文件! flg = true; } } catch (IOException e) { e.printStackTrace(); } if(!flg){//不是音乐文件 return new ResponseBodyMessage<>(-1,"文件有误,并非mp3音乐文件",false); } ``` **验证:** 音乐文件上传成功: ![](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801140311459.png) 非音乐文件: ![image-20220801141902651](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801141902651.png) ## 上传到数据库 我们上传音乐信息到数据库,就是向数据库中的`music`表中插入数据! 我们首先要明确我们需要到数据库那些信息! 我们看一下我们`music`表结构! ![image-20220728232037725](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220728232037725.png) > id:自增主键不需要上传! > title:歌曲名我们可以通过文件名去掉`.MP3`后缀获取! > singer:歌手 我们请求信息中有! > time:我们可以通过`java`中的`SimpleDateFormat`类获取到上传时间 > url:音乐的`url`,因为我们上传的音乐就是用来后面播放的嘛,而我们数据的传输是通过`http`协议的, > > 我们后面通过这个`url`就可以找到该音乐的位置! > > ```java > //先用这样的方式保存url > /music/get?title(歌曲名称) > ``` > > user_id:我们可以通过`session`中获取上传用户`id` ### MusicMapper - 接口 ```java package com.example.onlinemusic.mapper; import org.apache.ibatis.annotations.Mapper; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-28 * Time: 23:27 */ @Mapper public interface MusicMapper { //上传音乐 /** * * @param title 文件名去后缀得到音乐名 * @param singer * @param time 通过SimpleDateFormat类获取到上传时间! * @param url 便于后面播放! * @param user_id 通过session获取 * @return */ int upload(String title,String singer,String time,String url,String user_id); } ``` - xml实现 ```xml insert into music (title,singer,time,url,user_id) values(#{title},#{singer},#{url},#{user_id}) ``` ### SimpleDateFormat类和Date类获取系统时间并格式化 ```java package com.example.onlinemusic.tools; import java.text.SimpleDateFormat; import java.util.Date; /** * Created with IntelliJ IDEA. * Description:SimpleDateFormat格式化时间类学习! * User: hold on * Date: 2022-07-28 * Time: 23:43 */ public class GetTimeTest { public static void main(String[] args) { //我们可以通过 java.utilev包下的Date类获取到当前系统时间! Date currentTime = new Date(); System.out.println(currentTime); //获取时间格式化类 //年月日 y M d //时分秒 H m s //通过构造方法传入需要设置的时间格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //将当前时间设置成你需要的格式! String time = dateFormat.format(new Date()); System.out.println(time); } } ``` - > new Date() 获取到当前系统时间 > > SimpleDateFormat 类 对时间进行格式化处理 > > ```java > yyyy-MM-dd HH:mm:ss //年-月-日 时:分:秒 > ``` > > ### MusicController完善数据库上传 ```java package com.example.onlinemusic.controller; import com.example.onlinemusic.mapper.MusicMapper; import com.example.onlinemusic.model.Contant; import com.example.onlinemusic.model.Music; import com.example.onlinemusic.model.User; import com.example.onlinemusic.tools.ResponseBodyMessage; import org.apache.ibatis.annotations.Mapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.datasource.SimpleDriverDataSource; import org.springframework.stereotype.Controller; 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 sun.util.logging.resources.logging; import javax.annotation.Resource; 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.nio.file.Path; import java.text.SimpleDateFormat; import java.util.Date; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-27 * Time: 15:40 */ @RestController @RequestMapping("/music") public class MusicController { // //文件上传服务器后的路径地址! // private String SAVE_PATH = "D:/uploadmusicfile/"; //我们可以将该路径信息设置到配置文件中! @Value("${music.path.save}") private String SAVE_PATH; //上传音乐到数据库的url前缀 @Value("${music.url}") private String URL_PRE; @Resource //属性注入 private MusicMapper musicMapper; //这里的上传需要将音乐上传到服务器,还有就是需要将音乐信息上传到数据库! @RequestMapping("/upload") //当我们没有传歌手信息时,就默认为未知歌手 public ResponseBodyMessage UploadMusic(@RequestParam(defaultValue = "未知歌手") String singer, @RequestParam(value = "filename") MultipartFile file, HttpServletRequest request){ //1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器! //获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname); //上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println(title+"已存在"); return new ResponseBodyMessage<>(-1,"重复上传,歌曲已存在",false); } //歌曲未上传 //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File dest = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!dest.exists()){ dest.mkdir(); System.out.println("mkdir"+dest); } //将音乐文件上传到该目录下! try { file.transferTo(dest); System.out.println(dest); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"服务器上传失败",false); } //服务器上传成功,我们就需要对数据库进行上传信息! //1.数据准备 //1).title 通过文件名截取到歌名,验证上传重复上传时已获取title //String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //2).singer 直接获取用户上传的 singer //3).time SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String time = simpleDateFormat.format(new Date()); //4).url 可以通过拼接! eg: music/get?path=隆里电丝 String url = URL_PRE+title; //5).user_id 通过当前session获取,验证重复上传时已获取 // User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); // int user_id = user.getId(); int ret = musicMapper.upload(title,singer,time,url,user_id); if(ret!=1){//上传失败! //我们数据库上传失败,那么就需要将服务器下的该音乐文件删除 System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件"); dest.delete(); return new ResponseBodyMessage<>(-1,"数据库上传失败",false); } return new ResponseBodyMessage<>(0,"上传成功",true); } //实现支持多个文件上传就将MultipartFile改成数组即可! @RequestMapping("/uploads") public ResponseBodyMessage UploadMusics(@RequestParam(defaultValue = "未知歌手") String singer, @RequestParam(value = "filename") MultipartFile[] files, HttpServletRequest request){ //1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器! //保存上传失败的歌曲信息 StringBuilder uploadfailinfo = new StringBuilder(); for (MultipartFile file:files) { //获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname); //上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println(title+"已存在"); //保存歌曲信息,用于返回前端用户! uploadfailinfo.append(title+","); //进行下一首歌曲上传 continue; } //歌曲未上传 //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File dest = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!dest.exists()){ dest.mkdir(); System.out.println("mkdir"+dest); } //将音乐文件上传到该目录下! try { file.transferTo(dest); System.out.println(dest); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"服务器上传失败",false); } //服务器上传成功,我们就需要对数据库进行上传信息! //1.数据准备 //1).title 通过文件名截取到歌名,验证上传重复上传时已获取title //String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //2).singer 直接获取用户上传的 singer //3).time SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String time = simpleDateFormat.format(new Date()); //4).url 可以通过拼接! eg: music/get?path=隆里电丝 String url = URL_PRE+title; //5).user_id 通过当前session获取,验证重复上传时已获取 // User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); // int user_id = user.getId(); int ret = musicMapper.upload(title,singer,time,url,user_id); if(ret!=1){//上传失败! //我们数据库上传失败,那么就需要将服务器下的该音乐文件删除 System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件"); dest.delete(); return new ResponseBodyMessage<>(-1,"数据库上传失败",false); } } if(uploadfailinfo.length()==0) { //说明全部歌曲上传成功! return new ResponseBodyMessage<>(0, "上传成功", true); } //部分歌曲上传失败 return new ResponseBodyMessage<>(0,"歌曲:"+uploadfailinfo+"已存在,上传失败,"+"其他歌曲上传成功",true); } } ``` **验证:** ![image-20220729143336681](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729143336681.png) ![image-20220729143353645](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729143353645.png) 查看数据库`music`表 ![image-20220729143244779](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729143244779.png) ### 一个用户重复上传一首歌曲解决 > 显然我们的上传功能还有待优化,我们需要解决一个用户多次上传一首歌曲的行为! > > **在验证用户登入的行为后,再进行该用户上传的音乐文件是否已经上传过的验证** - 我们通过查询数据库信息,从而验证 **MusicMapper接口** ```java /** * 通过用户id和音乐信息验证是否重复上传 * @param title * @param singer * @param user_id * @return */ Music getMusicByUidAndMusicInfo(String title, String singer, int user_id); } ``` **xml实现** ```java ``` **Controller重复上传验证** ```java //上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println("重复上传"); return new ResponseBodyMessage<>(-1,"上传失败,歌曲已存在",false); } ``` **postman验证** ![image-20220729150224406](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729150224406.png) ![image-20220729150340098](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729150340098.png) ## 批量上传歌曲 我们可以将参数`file`改成`MulitspartFile`的数组,就可以一次性批量上传多首歌曲 但是我们这里的歌手信息就无法全部上传咯! ```java //实现支持多个文件上传就将MultipartFile改成数组即可! @RequestMapping("/uploads") public ResponseBodyMessage UploadMusics(@RequestParam(defaultValue = "未知歌手") String singer, @RequestParam(value = "filename") MultipartFile[] files, HttpServletRequest request){ //1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器! //保存上传失败的歌曲信息 StringBuilder uploadfailinfo = new StringBuilder(); for (MultipartFile file:files) { //获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname); //上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println(title+"已存在"); //保存歌曲信息,用于返回前端用户! uploadfailinfo.append(title+","); //进行下一首歌曲上传 continue; } //歌曲未上传 //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File dest = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!dest.exists()){ dest.mkdir(); System.out.println("mkdir"+dest); } //将音乐文件上传到该目录下! try { file.transferTo(dest); System.out.println(dest); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"服务器上传失败",false); } //服务器上传成功,我们就需要对数据库进行上传信息! //1.数据准备 //1).title 通过文件名截取到歌名,验证上传重复上传时已获取title //String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //2).singer 直接获取用户上传的 singer //3).time SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String time = simpleDateFormat.format(new Date()); //4).url 可以通过拼接! eg: music/get?path=隆里电丝 String url = URL_PRE+title; //5).user_id 通过当前session获取,验证重复上传时已获取 // User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); // int user_id = user.getId(); int ret = musicMapper.upload(title,singer,time,url,user_id); if(ret!=1){//上传失败! //我们数据库上传失败,那么就需要将服务器下的该音乐文件删除 System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件"); dest.delete(); return new ResponseBodyMessage<>(-1,"数据库上传失败",false); } } if(uploadfailinfo.length()==0) { //说明全部歌曲上传成功! return new ResponseBodyMessage<>(0, "上传成功", true); } //部分歌曲上传失败 return new ResponseBodyMessage<>(0,"歌曲:"+uploadfailinfo+"已存在,上传失败,"+"其他歌曲上传成功",true); } ``` **验证:** ![image-20220801124134954](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801124134954.png) ## 上传音乐模块总结 - 上传包括服务器和数据库上传 - 这里上传文件用到了`Spring`框架中处理文件上传的主要类`MulitspartFile`类,这个类主要实现的是前端用表单的方式进行提交! ![image-20220729230213100](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220729230213100.png) - 上传服务器时要验证音乐是否重复上传,这里通过查询数据库中的信息进行验证 - 在上传音乐时验证是否为`MP3`文件,我们通过`MP3`文件结构中的最后128个字节下有一个`TAG`标签即可验证 # 播放音乐模块设计 ## 请求响应设计 ```java 请求: { get /music/get?path=xxx.mp3 } ``` ```java 响应: { data:音乐数据本身的字节信息 } ``` > 可以看到我们这里请求的设计采用的是`get`方法,通过`/music/get?path=xxx.mp3`获取到响应的音乐信息,这也就和我们之前设计的保存音乐的`url`匹配上了! > > 而我们响应只要将对应的音乐字节信息返回给浏览器即可! ## 代码实现 ```java //播放音乐 @RequestMapping("/get") public ResponseEntity get(String path){ //我们要先获取到该音乐保存在服务器下的路径信息! try { byte[] fileByte = null; //获取到文件对象 File file = new File(SAVE_PATH +File.separator+ path); //获取到该文件对象的路径信息 Path filepath = file.toPath(); //读取文件中的所有字节 fileByte = Files.readAllBytes(filepath); return ResponseEntity.ok(fileByte); }catch (IOException e){ e.printStackTrace(); } return ResponseEntity.badRequest().build(); } ``` **方法讲解** - `Files.readAllBytes(Path path);` > 读取文件中的所有字节,参数是`Path`路径值! - `File.separator` > 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串! > > 就是在`Windows`系统下是`\`,而在`Linux`下是`/` - `ReponseEntity` > 这是`Spring`对请求响应的分装,继承了`HttpEntity`对象,包含`Http`响应码(`HttpStatus`),响应头(`header`),响应体(`body`)3部分! ![image-20220801125519306](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801125519306.png) `ResponseEntity`类继承自`HttpEntity`类,被用于Controller层方法 ! 我们可以通过这个类下面提供的静态方法,封装一下响应返回给浏览器! ![image-20220801130437746](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801130437746.png) **`ok`静态方法:** ```java //这个方法若被调用的话,返回OK状态 public static ResponseEntity.BodyBuilder ok(){ return status(HttpStatus.OK); } //这个方法若被调用的话,返回body内容和OK状态 public static ResponseEntity ok(T body) { ResponseEntity.BodyBuilder builder = ok(); //ResponseEntity可以通过这个builder返回任意类型的body内容 return builder.body(body); } ``` 我们代码里的`ResponseEntity.ok(fileByte);`就是将`ok`状态和`fileByte`音乐文件信息以`body`的形式返回给前端! ## 验证结果 音乐文件有`TAG`标志 ![image-20220801131113688](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801131113688.png) 假如我们拿到的并不是`mp3`文件! ![image-20220801131314451](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801131314451.png) 这里的`隆里电丝.mp3`我是通过`png`文件改成了这个,显然找不到这个音乐标志! # 删除音乐模块 ## 删除单个音乐 ### 请求响应设计 ```java 请求: { post /music/delete id } 响应: { status:0, message:"删除成功" data:true } ``` ### 代码实现 这里的删除操作需要分成2步 - 将服务器下的文件进行删除 - 将数据库中的文件信息删除 所以我们需要先查询到该id的音乐信息,再进行删除! - Mapper接口 ```java /** * 通过音乐id删除音乐! * @param id * @return */ int deleteById(int id); /** * 通过id获取到音乐信息 * @param id * @return */ Music getMusicById(int id); ``` - Mapper实现 ![image-20220801145400887](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801145400887.png) - Controller层代码实现 ```java //删除单个音乐 @RequestMapping("/delete") public ResponseBodyMessage deleteMusic(@RequestParam Integer id){ //1.首先找到该音乐信息 Music music = musicMapper.getMusicById(id); if(music==null){//音乐不存在 //未找到该音乐 return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false); } //2.进行音乐删除 //2.1 删除服务器下的音乐文件 //找到服务器下该音乐文件路径 File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3"); if(file==null){//服务器下不存在该文件 return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false); } //删除 file.delete(); //2.2 删除数据库下的音乐信息 int ret = musicMapper.deleteById(id); if(ret!=1){//数据库删除失败 return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false); } return new ResponseBodyMessage<>(0,"删除成功!",true); } ``` ### 验证结果 - 先查看数据库下的音乐信息 ![image-20220801145648988](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801145648988.png) - 删除的音乐不存在 ![image-20220801145605376](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801145605376.png) - 删除音乐存在 ![image-20220801145741138](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801145741138.png) ![image-20220801145811018](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801145811018.png) 删除成功! ## 批量删除音乐 ### 请求响应设计 ```java 请求: { post /music/deleteAll id[] } 响应: { status:0 message:"删除成功" data:true } ``` ### 代码实现 我们只需要在删除单个音乐的基础上进行代码的修改即可! 直接增加`Controller`层代码即可! ```java //批量删除音乐 @RequestMapping("/deleteAll") public ResponseBodyMessage deleteMusicAll(@RequestParam(value = "id[]") List ids){ String message = null; for (Integer id:ids) { //1.首先找到该音乐信息 Music music = musicMapper.getMusicById(id); if(music==null){//音乐不存在 //未找到该音乐 //保存这个音乐id信息 message += id+" "; continue; //return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false); } //2.进行音乐删除 //2.1 删除服务器下的音乐文件 //找到服务器下该音乐文件路径 File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3"); System.out.println("musicPath:"+file.getPath()); if(file==null){//服务器下不存在该文件 //保存这个id信息 message += id + ""; continue; //return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false); } //删除 if(!file.delete()){ //删除失败 message += id + ""; continue; //return new ResponseBodyMessage<>(-1,"服务器删除失败",false); } //2.2 删除数据库下的音乐信息 int ret = musicMapper.deleteById(id); if(ret!=1){//数据库删除失败 message += id+" "; continue; //return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false); } } if(message==null){ return new ResponseBodyMessage<>(0,"删除成功!",true); } //部分删除失败 return new ResponseBodyMessage<>(0,"id:" + message+" 删除失败!",true); } ``` ### 验证结果 - 删除成功 ![image-20220801160935061](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801160935061.png) ![image-20220801161039645](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801161039645.png) - 删除失败 ![image-20220801161253586](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801161253586.png) # 查询音乐模块 我们的查询需要支持一下功能 - 查询给定名称的歌曲 - 给定歌曲名称全 查询到单个歌曲 - 给定名称字段(支持模糊匹配查询) 查询到多个歌曲 - 未给定歌曲名 就查询所有歌曲 ## 请求响应设计 ```java 请求: { get /music/findMusic musicName } ``` ```java 响应: { status:0 message:"查询成功" data: { { id:2 title:"隆里电丝" singer:"大傻" time:"2022年8月1日" url:/music/get?path="隆里电丝" }, .... } } ``` ## 代码实现 - `MusicMapper`接口新增方法 - 查询所有音乐 - 模糊匹配查询某些音乐 ```java /** *查询所有歌曲 * @return */ List findMusic(); /** * 通过名称查询到歌曲信息,支持模糊查询 * @return */ List findMusicByName(String musicName); ``` - `MusicMapper.xml`实现 ```xml ``` - `MusicController`实现 ```java //模糊查询 @RequestMapping("/findMusic") public ResponseBodyMessage> findMusic(@RequestParam(required =false) String musicName){ List musicList = new LinkedList<>(); if(musicName==null){ //查询名称为空,查询所有音乐返回 musicList = musicMapper.findMusic(); return new ResponseBodyMessage<>(0,"查询成功!",musicList); } //进行模糊查询! musicList = musicMapper.findMusicByName(musicName); return new ResponseBodyMessage<>(0,"查询成功!",musicList); } ``` ## 验证结果 ![image-20220801164808889](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801164808889.png) - 查询名称为空 ![image-20220801165026072](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801165026072.png) - 模糊查询 ![image-20220801165309472](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801165309472.png) # 收藏音乐模块 ## 添加音乐到收藏列表 ### 请求响应设计 ```java 请求: { post, /lovemusic/likeMusic data:user_id,music_id } ``` ```java 响应: { status:0, message:"收藏音乐成功", data:true } ``` ### 代码实现 > 我们要将一首音乐收藏分为2步 > > - 找到该音乐信息,判断是否收藏过(查询`lovemusic`表) > - 收藏该音乐(添加到`lovemusic`表) - 新增`LoveMusicMapper`接口 ```java package com.example.onlinemusic.mapper; import com.example.onlinemusic.model.LoveMusic; import org.apache.ibatis.annotations.Mapper; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-08-01 * Time: 19:54 */ @Mapper public interface LoveMusicMapper { /** * 通过用户id和音乐id查询喜欢音乐 * @param user_id * @param music_id * @return */ LoveMusic findLoveMusicByUidAndMusicId(int user_id, int music_id); /** * 添加音乐到收藏列表 * @param user_id * @param music_id * @return */ int insetLoveMusic(int user_id,int music_id); } ``` - `LoveMusicMapper.xml`实现接口 ```xml insert into lovemusic (user_id,music_id) values(#{user_id},#{music_id}) ``` - `LoveMusicController`代码实现 ```java package com.example.onlinemusic.controller; import com.example.onlinemusic.mapper.LoveMusicMapper; import com.example.onlinemusic.model.Contant; import com.example.onlinemusic.model.LoveMusic; import com.example.onlinemusic.model.User; import com.example.onlinemusic.tools.ResponseBodyMessage; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; /** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-08-01 * Time: 20:25 */ @RestController @RequestMapping("/lovemusic") public class LoveMusicController { //注入LoveMusicMapper @Resource private LoveMusicMapper loveMusicMapper; //收藏音乐 @RequestMapping("/likeMusic") public ResponseBodyMessage insertLoveMusic(@RequestParam Integer id,HttpServletRequest request){ //1.检查登入状态,未登入不创建回话 HttpSession session = request.getSession(false); if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){ //未登入状态 return new ResponseBodyMessage<>(-1,"请登入用户",false); } //登入状态,进行音乐收藏 //1.获取到用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); System.out.println("user_id:"+user_id+" music_id:"+id); //2.查询该歌曲是否已存在收藏列表 LoveMusic loveMusic = loveMusicMapper.findLoveMusicByUidAndMusicId(user_id,id); if(loveMusic!=null){ //该歌曲已收藏! System.out.println("lovemusic:"+loveMusic); return new ResponseBodyMessage<>(-1,"收藏失败,该歌曲收藏",false); } //未收藏,将其收藏! int flg = loveMusicMapper.insetLoveMusic(user_id,id); if(flg!=1){ return new ResponseBodyMessage<>(-1,"收藏失败",false); } return new ResponseBodyMessage<>(0,"收藏成功!",true); } } ``` ### 验证结果 ![image-20220801221914863](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801221914863.png) - 歌曲已收藏,收藏失败 ![image-20220801221743298](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801221743298.png) - 歌曲未收藏,收藏成功 ![image-20220801221849636](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801221849636.png) ## 查询喜欢的音乐列表 这里的查询喜欢列表和查询音乐模块类似! - 未给定歌曲名称 > 查询该用户所有收藏歌曲 - 给定歌曲名称参数 > 查询歌曲名称含有该参数的歌曲 ### 请求和响应设计 ```java 请求: { get /lovemusic/findloveMusic data:{musicName:musicName} } ``` ```java 响应: { status:0, message:"查询到收藏的音乐", data: { { id:1, title:"隆里电丝", singer:"大傻", time:"2022年8月2日", url:"/music/get?path=隆里电丝", user_id:2 } ... } } ``` ### 代码实现 - `LoveMusicMapper`接口新增方法 ```java /** * 查询该用户所有的收藏歌曲 * @param user_id * @return */ List findLoveMusic(int user_id); /** * 通过用户id和歌曲名称查询收藏歌曲支持模糊匹配 * @param user_id * @param musicName * @return */ List findLoveMusicByUidAndMusicName(int user_id,String musicName); ``` - `xml`实现接口方法 ```java ``` - `LoveMusicController`代码实现 ```java //通过音乐名称查询收藏列表 @RequestMapping("/findloveMusic") public ResponseBodyMessage> findLoveMusicByUidAndMusicName(@RequestParam(required = false) String musicName, HttpServletRequest request){ //1.检查登入状态,未登入不创建回话 HttpSession session = request.getSession(false); if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){ //未登入状态 return new ResponseBodyMessage<>(-1,"请登入用户",null); } //登入状态,进行音乐查询 //1.获取到用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); System.out.println("user_id:"+user_id+" musicName:"+musicName); //2.查询收藏列表 List musicList = null; if(musicName==null){ //2.1歌曲名称为空,查询该用户所有收藏歌曲! musicList = loveMusicMapper.findLoveMusic(user_id); return new ResponseBodyMessage<>(0,"查询到收藏列表",musicList); } //2.2 歌曲名称不为空,模糊查询 musicList = loveMusicMapper.findLoveMusicByUidAndMusicName(user_id,musicName); return new ResponseBodyMessage<>(0,"查询收藏列表成功",musicList); } ``` ### 验证结果 数据库信息: ![image-20220802002411320](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802002411320.png) 登入`user_di=10`的用户 - 查询该用户所有收藏歌曲 ![image-20220802002254427](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802002254427.png) - 模糊匹配查询指定歌曲 ![image-20220802002636445](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802002636445.png) ## 从喜欢列表移除音乐 ### 请求和响应设计 ```java 请求: { get, /lovemusic/deleteloveMusic, data:{id} } 响应: { status:0, message:"取消收藏成功", data:true } ``` ### 代码实现 - `LoveMusicMapper`接口新增方法 ```java /** * 通过用户id和音乐id取消音乐收藏 * @param user_id * @param music_id * @return */ int deleteLoveMusicByUidAndMusicId(int user_id, int music_id); /** * 通过音乐id删除lovemusic表中的信息 * @param music_id * @return */ int deleteLoveMusicByMusicId(int music_id); ``` - `xml`实现接口 ```xml delete from lovemusic where user_id = #{user_id} and music_id = #{music_id} delete from lovemusic where music_id = #{music_id} ``` - `LoveMusicMapperController`代码实现 ```java //通过收藏表中的id取消收藏歌曲信息 @RequestMapping("/deleteloveMusic") public ResponseBodyMessage deleteLoveMusic(@RequestParam Integer id,HttpServletRequest request){ //1.检查登入状态,未登入不创建回话 HttpSession session = request.getSession(false); if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){ //未登入状态 return new ResponseBodyMessage<>(-1,"请登入用户",false); } //登入状态,可进行取消音乐收藏功能 //1.获取到用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); //取消收藏! int ret = loveMusicMapper.deleteLoveMusicByUidAndMusicId(user_id,id); if(ret!=1){ return new ResponseBodyMessage<>(-1,"取消收藏失败",false); } return new ResponseBodyMessage<>(0,"取消收藏成功",true); } ``` ### 验证结果 数据库信息: ![image-20220802005252299](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802005252299.png) ![image-20220802083542038](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802083542038.png) - 歌曲不存在,移除失败 ![image-20220802083625829](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802083625829.png) - 歌曲存在,取消收藏成功 ![image-20220802083657166](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802083657166.png) # 完善删除音乐模块代码 > 我们对项目增添了收藏列表后,发现一个问题!我们上传的音乐删除后,收藏的音乐就不存在了,那么收藏列表中关于这首歌曲的信息也要删除,所以我们对我们的删除音乐代码进行完善! - 删除音乐时,要先检查该音乐是否在收藏列表中,如果在就将歌曲移除收藏列表 我们只需要添加一个通过`music_id`取消收藏音乐的方法即可 `LoveMusicMapper`新增方法 ```java /** * 通过音乐id删除lovemusic表中的信息 * @param music_id * @return */ int deleteLoveMusicByMusicId(int music_id); ``` `xml`实现 ```xml delete from lovemusic where music_id = #{music_id} ``` ```java //删除单个音乐 @RequestMapping("/delete") public ResponseBodyMessage deleteMusic(@RequestParam Integer id){ //1.首先找到该音乐信息 Music music = musicMapper.getMusicById(id); if(music==null){//音乐不存在 //未找到该音乐 return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false); } //2.进行音乐删除 //2.1 删除服务器下的音乐文件 //找到服务器下该音乐文件路径 File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3"); System.out.println("musicPath:"+file.getPath()); if(file==null){//服务器下不存在该文件 return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false); } //删除 if(!file.delete()){ //删除失败 return new ResponseBodyMessage<>(-1,"服务器删除失败",false); } //2.2 删除数据库下的音乐信息 //2.2.1删除music表中的音乐信息 int ret = musicMapper.deleteById(id); if(ret!=1){//数据库删除失败 return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false); } //2.2.2删除lovemusic表中的音乐信息 loveMusicMapper.deleteLoveMusicByMusicId(id); return new ResponseBodyMessage<>(0,"删除成功!",true); } //批量删除音乐 @RequestMapping("/deleteAll") public ResponseBodyMessage deleteMusicAll(@RequestParam(value = "id[]") List ids){ String message = ""; for (Integer id:ids) { //1.首先找到该音乐信息 Music music = musicMapper.getMusicById(id); if(music==null){//音乐不存在 //未找到该音乐 //保存这个音乐id信息 message += id+" "; continue; //return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false); } //2.进行音乐删除 //2.1 删除服务器下的音乐文件 //找到服务器下该音乐文件路径 File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3"); System.out.println("musicPath:"+file.getPath()); if(file==null){//服务器下不存在该文件 //保存这个id信息 message += id + ""; continue; //return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false); } //删除 if(!file.delete()){ //删除失败 message += id + ""; continue; //return new ResponseBodyMessage<>(-1,"服务器删除失败",false); } //2.2 删除数据库下的音乐信息 int ret = musicMapper.deleteById(id); if(ret!=1){//数据库删除失败 message += id+" "; continue; //return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false); } //取消该歌曲的所有收藏 loveMusicMapper.deleteLoveMusicByMusicId(id); } if(message==""){ return new ResponseBodyMessage<>(0,"删除成功!",true); } //部分删除失败 return new ResponseBodyMessage<>(0,"id:" + message+" 删除失败!",true); } ``` **验证结果:** 数据库信息: ![image-20220802085312234](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802085312234.png) 我们将`music_id`为51音乐删除! ![image-20220802085918389](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802085918389.png) ![image-20220802085753764](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220802085753764.png) # 前端设计 我们前端页面是在网上下载了一个静态页面的模板!我们需要在此基础上进行`js`代码的编写,从而实现前后端用户的接口 ## 登入逻辑 **核心逻辑:** ```html ``` **验证结果:** ![image-20220804111429478](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804111429478.png) ![image-20220804111501657](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804111501657.png) ## 注册逻辑 ```html ``` **验证结果:** ![image-20220804111333391](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804111333391.png) ' ![image-20220804111402057](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804111402057.png) ## 查询音乐逻辑 **核心代码逻辑:** ```html ``` **验证结果:** ![image-20220804125324656](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804125324656.png) ## 上传音乐逻辑 **核心代码逻辑** ```html
文件上传: 歌手名:
``` **验证结果:** ![image-20220804125629463](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804125629463.png) ![ ](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804125616918.png) ## 播放音乐 ```html
``` ```js //播放音乐 function playerSong(obj) { console.log(obj) var name = obj.substring(obj.lastIndexOf('=')+1); //obj:播放地址 name:歌曲或者视频名称 0:播放开始时间 false:点击后自动播放 SewisePlayer.toPlay(obj,name,0,true); } ``` **验证结果:** ![image-20220804134109484](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804134109484.png) ## 删除音乐逻辑 ```js //删除音乐 function deleteInfo(obj){ console.log(obj); $.ajax({ url:"/music/delete", type:'post', dataType:"json", data:{"id":obj}, success:function(body){ console.log(body); if(body.data==true){ //删除成功! alert("删除成功!"); window.location.href = "list.html"; }else{ alert("删除失败!"); } } }); } ``` **验证结果:** ![image-20220804135319716](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804135319716.png) ## 查询歌曲逻辑 ```js $(function(){ $('#submit1').click(function(){ var name = $("#exampleInputName2").val(); load(name); }); }); ``` **验证结果:** ![image-20220804140157554](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220804140157554.png) ## 删除选中的歌曲逻辑 ```js //删除选中逻辑在查询逻辑里 $(function(){ $('#submit1').click(function(){ var name = $("#exampleInputName2").val(); load(name); }); //当查询结束才可进行选中删除(有了音乐列表) $.when(load).done(function(){ //选中删除逻辑! $("#delete").click(function(){ var id = new Array(); var i = 0;//数组下标! //遍历input标签下的checkbox $("input:checkbox").each(function(){ //判断是否选中! if($(this).is(":checked")){ //获取input里面的id值! id[i] = $(this).attr('id'); i++; } }); console.log(id); $.ajax({//将获取到的id发送给服务器! url:"/music/deleteAll", data:{"id":id}, type:'post', success:function(body){ if(body.status==0){ alert("删除成功!"); window.location.href="list.html"; }else{ alert("删除失败!"); } } }); }); }); }); ``` **验证结果:** ![image-20220805231650640](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220805231650640.png) ​ ![image-20220805231750609](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220805231750609.png) ## 收藏音乐逻辑 ```js $(function(){ load(); }); //musicName可以传参,也可以不传 function load(musicName){ $.ajax({ url:"/lovemusic/findLoveMusic",//指定路径 type:"get", data:{"musicName":musicName}, dataType:"json", //设置服务器返回json数据 success:function(body){//回调函数 console.log(body); var s = ''; var data = body.data;//数组! for(var i = 0;i'; s += '' + data[i].singer + ''; s +=' ' + ''; s +=' ' + ''; s += ''; } $("#info").html(s);//把拼接好的页面放在info的id下 } }); } //播放音乐 function playerSong(obj) { console.log(obj) var name = obj.substring(obj.lastIndexOf('=')+1); //obj:播放地址 name:歌曲或者视频名称 0:播放开始时间 false:点击后自动播放 SewisePlayer.toPlay(obj,name,0,true); } //删除音乐 function deleteInfo(obj){ console.log(obj); $.ajax({ url:"/lovemusic/deleteloveMusic", type:'post', dataType:"json", data:{"id":obj}, success:function(body){ console.log(body); if(body.data==true){ //删除成功! alert("移除成功!"); window.location.href = "loveMusic.html"; }else{ alert("移除失败!"); } } }); } ``` **验证结果:** ![image-20220806003530836](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806003530836.png) ![image-20220806003515171](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806003515171.png) ## 实现收藏功能逻辑 ```js //在list.html文件中添加一个收藏歌曲函数即可! //收藏歌曲 function loveInfo(obj){ $.ajax({ url:"/lovemusic/likeMusic", type:"post", data:{"id":obj}, dataType:"json", success:function(body){ if(body.data==true){ alert("收藏成功!"); window.location.href="list.html"; }else{ alert("收藏失败!"); } } }) } ``` **验证结果:** ![image-20220805235115852](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220805235115852.png) # 配置拦截器 > 有些页面我们没有登入也可以进行访问,通过输入`url`到地址栏即可! 我们可以在项目中进行登入状态的检查,如果登入了就可以访问,否则不能,这就配置了拦截器,保证程序安全! - 我们首先在`config`包下自定义一个拦截器 **核心代码** ```java package com.example.onlinemusic.config; import com.example.onlinemusic.model.Contant; import com.example.onlinemusic.tools.ResponseBodyMessage; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * Created with IntelliJ IDEA. * Description:配置拦截器 * User: hold on * Date: 2022-08-06 * Time: 0:43 */ public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //检查是否登入! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return false; } return true; } } ``` - 然后将配置好的拦截器添加到`AppConfig`类中 ```java package com.example.onlinemusic.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Created with IntelliJ IDEA. * Description:将自定义拦截器添加到系统配置! * User: hold on * Date: 2022-07-27 * Time: 12:29 */ @Configuration public class AppConfig implements WebMvcConfigurer { //将BCrypetPasswordEncoder对象交给spring管理! @Bean public BCryptPasswordEncoder getBCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); } //添加拦截器! @Override public void addInterceptors(InterceptorRegistry registry) { LoginInterceptor loginInterceptor = new LoginInterceptor(); registry.addInterceptor(loginInterceptor) //拦截该项目目录下的所有文件! .addPathPatterns("/**") //排除无关文件 .excludePathPatterns("/js/**.js") .excludePathPatterns("/images/**") .excludePathPatterns("/css/**.css") .excludePathPatterns("/fronts/**") .excludePathPatterns("/player/**") //排除登入注册接口! .excludePathPatterns("/login.html") .excludePathPatterns("/user/login") .excludePathPatterns("/reg.html") .excludePathPatterns("/user/register"); } } ``` **验证结果:** ![image-20220806011222276](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806011222276.png) ![image-20220806011857589](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806011857589.png) # 部署到服务器 ## 准备 - 更改数据库配置 > 因为我的云服务器下的数据库用户密码信息和本地的一样,所以不用更改数据库配置! 我们在云服务器下创建好数据库即可! ![image-20220806013703105](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806013703105.png) - 更改上传音乐路径 ```properties # 云服务器下的地址路径! music.path.save = /root/javaweb部署环境/music ``` ![image-20220806013815069](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806013815069.png) ## 打包 - 项目打包 ![image-20220806014417266](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806014417266.png) 打包报错,我们需要统一编码UTF-8 ![image-20220806014706203](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806014706203.png) ![image-20220806020004651](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806020004651.png) 添加依赖 ```xml org.apache.maven.plugins maven-resources-plugin 3.1.0 ``` ![image-20220806020529697](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806020529697.png) ```xml org.apache.maven.plugins maven-surefire-plugin 2.22.1 true ``` ![image-20220806020622900](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806020622900.png) 打包成功! ## 部署 ​ 将打包好的项目上传到服务器! ![image-20220806021108082](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806021108082.png) **启动项目:** ```java java -jar onlinemusic.jar ``` 这里显示端口号`8080`被占用,那么我们就将端口号给`kill` ```java //查看端口 netstat -anp | grep 8080 //杀进程 kill -9 pcb值 ``` **验证结果:** ![image-20220806030042655](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806030042655.png) ![image-20220806030106868](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806030106868.png) ***这样虽然部署好了项目,但是这个只能支持前台运行,也就是说这个项目关闭了,项目就访问不了了!*** ### 后台运行SpringBoot项目 运行指令: ```java nohup java -jar onlinemusic.jar>>log.log& ``` > `nohup`:后台运行项目的指令 > > `>>log.log`:把控制台上的日志保存到log.log文件中!(未设置可默认生成) > > `&`:后台一直运行 ![image-20220806032205318](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220806032205318.png) 这样运行就支持后台运行了! # 后期项目维护更新 > ***如果后面我们觉得项目需要完善该如何进行服务器项目更新呢?*** - 将该项目的进制终止 `netstat -anp |grep 8080` ` kill -9 17303` ![image-20220807210340255](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220807210340255.png) 也可以使用`ps -ef | grep java` ``` kill 【进程ID】 命令说明: ps : Linux 当中查看进程的命令 -e 代表显示所有的进程 -f 代表全格式【显示全部的信息】 grep : 全局正则表达式 重新上传jar包 重新进行后台的启动 ``` - 更新项目,重新运行 ` nohup java -jar onlinemusic.jar>>log.log&` ![image-20220807210651629](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220807210651629.png) ![image-20220807214237459](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220807214237459.png) 更新了一下注册登入的前端页面! # 遇到的面试题总结 - 上传其他文件,然后将后缀改成`.mp3`,如何识别?是否可以正常播放? > 因为每种类型的文件都有自己的文件结构,都有自己特有的格式,我们根据`mp3`特有的文件格式,在倒数第`128`字节处,有有个`TAG`音乐文件标志,从而在上传时就检测一下是否是音频文件,如果不是音频文件无法上传! - 可以上传大文件嘛? > 不能,因为一首歌曲的大小不会很大,所以我已经在配置文件配置了每个文件的最大上传大小,以及单次请求的文件总数大小! > > ```properties > #配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb > spring.servlet.multipart.max-file-size = 15MB > spring.servlet.multipart.max-request-size=100MB > ``` - 为啥不用`HTML`的原生`audio`标签? > 因为我想通过使用开源的播放器,提升一下自己的学习能力,毕竟我们经常会在自己的项目中使用到其他的优秀开源项目,我们也需要具备这样的能力,学习使用大佬的优秀项目! > > 只要将开源播放代码换成原生`audio`即可! > > ```html > s += " "; > ``` > > ![image-20220807224032758](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220807224032758.png) > > 原生的`audio`标签和开源播放器的一首歌曲的下载时间如下: > > 开源播放器: > > ![image-20220807224553159](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220807224553159.png) > > 原生`audio`播放器: > > ![image-20220807224628368](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220807224628368.png) > > 可以看到同样一首歌曲在线播放后下载的时间不同,虽然2个都是边下载边播放,但是这里的开源播放器下载时间更短!