目录
4、选择项目存放路径,就可以创建出一个 SpringBoot 项目
1、创建 config 包,在 config 包中创建 LoginInterceptor 类
4、在 UserMapper 接口中新增 selectByName 方法
4、在 UserController 类中添加 register 方法
2、在 MusicController 类中添加 playMusic 方法
4. 在 MusicController 类中添加 deleteByMusicId 方法
2. 在 MusicController 类中添加 deleteSelMusic 方法
4、在 MusicController 类中添加 findMusic 方法
4、在 LoveMusicController 类中添加 findLoveMusic 方法
4、在 LoveMusicController 类中添加 removeLoveMusic 方法
3、调整 MusicController 类中的 deleteMusicByMusicId 和 deleteSelMusic 方法
一、项目截图
二、创建 SpringBoot 项目
1、在 IDEA 中创建一个 SpringBoot 项目
2、设置项目名称
3、选择项目依赖
4、选择项目存放路径,就可以创建出一个 SpringBoot 项目
三、配置数据库和xml
1、打开application.properties
2、配置如下信息
#配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=你的用户名
spring.datasource.password=你的密码
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#配置xml
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
# 音乐上传后的路径
music.local.path=E:/SaveMusic/(填写存放歌曲的路径)
#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB
# 配置springboot日志调试模式是否开启
debug=true
# 设置打印日志的级别,及打印sql语句
#日志级别:trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
logging.level.com.example.onlinemusic.mapper=debug
#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG
四、数据库设计
-- 创建数据库
drop database if exists `onlinemusic`;
create database if not exists `onlinemusic` character set utf8;
-- 使用数据库
use `onlinemusic`;
-- 用户表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`userid` INT PRIMARY KEY AUTO_INCREMENT comment '用户id',
`username` varchar(20) NOT NULL comment '用户名',
`password` varchar(255) NOT NULL comment '密码'
);
-- 歌曲表
DROP TABLE IF EXISTS `music`;
CREATE TABLE `music` (
`musicid` int PRIMARY KEY AUTO_INCREMENT comment '歌曲id',
`title` varchar(50) NOT NULL comment '歌曲名称',
`singer` varchar(30) NOT NULL comment '歌手',
`time` varchar(13) NOT NULL comment '上传歌曲时间',
`url` varchar(1000) NOT NULL comment '存放歌曲的路径',
`userid` int(11) NOT NULL comment '上传歌曲的用户'
);
-- 歌曲收藏表
DROP TABLE IF EXISTS `lovemusic`;
CREATE TABLE `lovemusic` (
`loveid` int PRIMARY KEY AUTO_INCREMENT comment '收藏歌曲的id',
`user_id` int(11) NOT NULL comment '收藏歌曲的用户id',
`music_id` int(11) NOT NULL comment '歌曲id'
);
五、工具包
在 package com.example.musicserver 目录下创建一个 tools 包(工具包),在这个包中存放整个项目要使用的工具类。
1、ResponseBodyMessage 类
- 设计统一的响应体工具类,因为做任何操作时都需要响应,所以封装一个通用的响应工具类,这个工具类设计成一个泛型类。
package com.example.onlinemusic.tools;
import lombok.Data;
@Data
public class ResponseBodyMessage <T>{
private int status; //状态码
private String message; // 返回的信息(出错的原因等)
private T data; // 返回给前端的数据(因为返回的数据类型不确定,可能是 String,boolea,int ...,因此使用泛型)
public ResponseBodyMessage(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
}
2、Constant 类
- 这个类用来存储不变的常量。 例如:设置 session 对象中的 key 值,key 是一个不变的字符串。
- 如果在其他地方获取对应的 session 就可以通过这个类中的字符串进行获取。
package com.example.onlinemusic.tools;
public class Constant {
public static final String USER_SESSION_KEY= "USERINFO_SESSION_KEY"; // 设置 session 中的 key 值
}
3、数据加密
1. MD5 加密
MD5是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程不可逆; 但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。
- 彩虹表:彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有,有了它可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。
更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的。
- 加盐的做法:盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中。
MD5 的使用
(1)在 pom.xml 文件中添加依赖(添加到 <dependencies> </dependencies> 标签内)
<!-- md5 依赖 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
(2)在 tools 包中创建 MD5Util 类
package com.example.onlinemusic.tools;
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Util {
// 定义一个固定的盐值
private static final String salt = "1j2a3v4a5"; // 盐值可以自定义
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
/**
* 第一次加密 :模拟前端自己加密,然后传到后端
*
* @param inputPass
* @return
*/
public static String inputPassToFormPass(String inputPass) {
String str = "" + salt.charAt(1) + salt.charAt(3) + inputPass
+ salt.charAt(5) + salt.charAt(6);
return md5(str);
}
/**
* 第2次MD5加密
*
* @param formPass 前端加密过的密码,传给后端进行第2次加密
* @param salt 用户数据库当中的盐值
* @return
*/
public static String formPassToDBPass(String formPass, String salt) {
String str = "" + salt.charAt(0) + salt.charAt(2) + formPass + salt.charAt(5)
+ salt.charAt(4);
return md5(str);
}
/**
* 上面两个函数合到一起进行调用
*
* @param saltDB
* @return
* @paraminputPass
*/
public static String inputPassToDbPass(String inputPass, String saltDB) {
String formPass = inputPassToFormPass(inputPass);
String dbPass = formPassToDBPass(formPass, saltDB);
return dbPass;
}
public static void main(String[] args) {
System.out.println("对用户输入密码进行第1次加密:" + inputPassToFormPass("123456"));
System.out.println("对用户输入密码进行第2次加密:" + formPassToDBPass(inputPassToFormPass("123456"), salt));
System.out.println("对用户输入密码进行第2次加密:" + inputPassToDbPass("123456", salt));
}
}
运行结果
不管运行多少次,这个密码是规定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。
2. BCrypt 加密
- Bcrypt 就是一款加密工具,可以比较方便地实现数据的加密工作。也可以简单理解为它内部自己实现了随机加盐处理 。
- 使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。
- Bcrypt生成的密文是60位的,而MD5的是32位的,因此 Bcrypt 破解难度更大。
Bcrypt 的使用
(1)在 pom.xml 文件中添加依赖(添加到 <dependencies> </dependencies> 标签内)
<!-- security依赖包 (加密)-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
(2)在springboot启动类添加下面的内容
@SpringBootApplication(exclude =
{org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
(3)在 tools 包中创建 BCryptTest 测试类
package com.example.onlinemusic.tools;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptTest {
public static void main(String[] args) {
//模拟从前端获得的密码
String password = "123456";
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String newPassword = bCryptPasswordEncoder.encode(password);
System.out.println("加密的密码为: "+newPassword);
//使用matches方法进行密码的校验
boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword);
//返回true
System.out.println("加密的密码和正确密码对比结果: "+same_password_result);
boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword);
//返回false
System.out.println("加密的密码和错误的密码对比结果: " + other_password_result);
}
}
运行结果(每次运行的生成的密码都不一样)
- encode方法:对用户密码进行加密。
- matches方法:参数一,待检验的未加密的密码 。参数二:从数据库中查询出的加密后密码 。
3. BCrypt加密与MD5加密的区别:
- BCrypt加密: 一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
- MD5加密: 是不加盐的单向Hash,不可逆的加密算法,同一个密码经过hash的时候生成的是同一个hash值,在大多数的情况下,有些经过md5加密的方法将会被破解。
- Bcrypt生成的密文是60位的。而MD5的是32位的。
- 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全,但加密更慢。
- 虽然BCrpyt也是输入的字符串+盐,但是与MD5+盐的主要区别是:每次加的盐不同(BCrpyt 中的盐值是随即生成的),导致每次生成的结果也不相同。无法比对!
六、配置拦截器
- 未登录的情况下拦截其他页面,登录成功后才可以访问其他界面
1、创建 config 包,在 config 包中创建 LoginInterceptor 类
package com.example.onlinemusic.config;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
return false;
}
return true;
}
}
2、在 config 包中创建 AppConfig 类
package com.example.onlinemusic.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 添加拦截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 登录之后才可以访问其他页面
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor).
// 拦截所有的
addPathPatterns("/**")
//排除所有的JS
.excludePathPatterns("/js/**.js")
//排除images下所有的元素
.excludePathPatterns("/images/**")
.excludePathPatterns("/css/**.css")
.excludePathPatterns("/fronts/**")
.excludePathPatterns("/player/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/register.html")
//排除登录和注册接口
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/register");
}
}
七、实现登录模块
1、登录功能的请求和响应设计
请求:
{
post, // 使用 post 请求
/user/login // 请求路径
data:{ username, password } // 传入的数据
}
响应:
{
"status": 200,
"message": "登录成功",
"data": {
"id": xxxxx,
"username": xxxxxx,
"password": xxxxxxxx
}
}
响应设计字段解释:
{
状态码为 200 表示成功,-200表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息(返回用户id,用户名)
}
2、创建 User 类
- 在 package com.example.musicserver.model 包中创建User类
package com.example.onlinemusic.model;
import lombok.Data;
@Data
public class User {
private int userId; // 用户id
private String username; // 用户名
private String password; // 密码
}
3、创建对应的 Mapper 和 Controller
1. 创建接口 UserMapper
-
在 package com.example.musicserver.mapper 包中创建 UserMapper 接口
package com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
User login(User loginUser);
}
2. 创建 UserMapper.xml
- 在resource目录下,新建 mybatis 文件夹,新建 UserMapper.xml,在 UserMapper.xml 文件中添加配置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.UserMapper">
<select id="login" resultType="com.example.onlinemusic.model.User">
select * from user where username=#{username} and password=#{password}
</select>
<select id="selectByName" resultType="com.example.onlinemusic.model.User">
select * from user where username=#{username};
</select>
</mapper>
4、在 UserMapper 接口中新增 selectByName 方法
// 通过用户名查询用户是否存在(用户名是唯一的)
User selectByName(String username);
5、创建 UserController 类
- 在 package com.example.musicserver.controller 包下,创建 UserController 类
package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.UserMapper;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserMapper userMapper;
// 使用 BCrypt 对密码进行加密
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@RequestMapping("/login")
// 传入用户名和密码
public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){
// 调用 UserMapper 接口
User user = userMapper.selectByName(username);
// 判断是否登录成功
if(user != null){
System.out.println("登录成功"); // 可以在控制台中打印登录信息
// 判断当前用户输入的密码(password) 与 数据库中查询到的密码(加密的密码,getPassword())是否匹配
boolean flag = bCryptPasswordEncoder.matches(password,user.getPassword());
if(!flag){
// 密码不匹配,登录失败
return new ResponseBodyMessage<>(-200,"用户名或密码错误",user);
}
// 如果登录成功就将信息写入到 session 中(在 session 中存储了一个用户信息对象,此后可以随时从 session 中将这个对象取出来进行一些操作)
request.getSession().setAttribute(Constant.USER_SESSION_KEY,user);
// 状态码为200,表示登录成功,并返回用户信息
return new ResponseBodyMessage<>(200,"登录成功",user);
}else{
System.out.println("登录失败");
// 状态码为500,表示登录失败,并返回用户信息
return new ResponseBodyMessage<>(-200,"用户名或密码错误",user);
}
}
}
6、登录成功测试
-
在数据库中插入一条数据,启动项目,使用 postman 进行测试。
7、前端代码
<script>
$(function(){
$("#submit").click(function(){
// 点击登录按钮,获取用户名和密码
var username = $("#username").val();
var password = $("#password").val();
// 判断用户名和密码是否为空(使用 trim 方法,防止输入空格)
if(username.trim() == "" || password.trim() == ""){
alert("账号或密码不能为空");
return;
}
// 如果用户名和密码不为空,使用 Ajax 传入请求
$.ajax({
type:"POST",
url:"/user/login",
data:{
"username":username,
"password":password
},
// 服务器返回的数据类型
dataType:"json",
// 请求成功,服务器返回数据
success:function(data){
console.log(data);
// 如果状态码为 200,表示登录成功
if(data.status == 200){
alert("登录成功");
// 跳转到指定页面
window.location.href="list.html";
}else{
alert("登录失败,账号或密码错误");
// 登录失败,将用户名或密码置空
$("#username").val("");
$("#password").val("");
}
}
});
});
});
$(function () {
$("#register").click(function () {
window.location.href="register.html";
});
});
</script>
八、实现注册模块
1、注册功能的请求和响应设计
请求:
{
post, // 使用 post 请求
/user/register // 请求路径
data:{ username, password } // 传入的数据
}
响应:
{
"status": 200,
"message": "注册成功",
"data": {
"id": xxxxx,
"username": xxxxxx,
"password": xxxxxxxx
}
}
响应设计字段解释:
{
状态码为 200 表示成功,-200表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息(返回用户id,用户名)
}
2、在 UserMapper 接口新增方法
// 输入用户名和密码,注册账号
boolean insertInToValues(String username,String password);
3、UserMapper.xml 文件中添加代码
<insert id="insertInToValues" >
insert into user(username,password)
values(#{username},#{password});
</insert>
4、在 UserController 类中添加 register 方法
/**
* 用户注册
* @param username
* @param password
* @return
*/
@RequestMapping("/register")
public ResponseBodyMessage<Boolean> register(@RequestParam String username,@RequestParam String password) {
User user1 = userMapper.selectByName(username);
if(user1 != null) {
return new ResponseBodyMessage<>(-1,"当前用户已经存在",false);
}else {
String newPassword = bCryptPasswordEncoder.encode(password);
boolean flag = userMapper.insertInToValues(username,newPassword);
if(flag == true){
return new ResponseBodyMessage<>(200,"注册成功",true);
}else{
return new ResponseBodyMessage<>(-200,"注册失败",false);
}
}
}
5、注册功能测试
6、前端代码
<script>
$(function(){
$("#register").click(function(){
var username = $("#username").val();
var password = $("#password").val();
$.ajax({
url: "/user/register",
type: "POST",
data:{
"username":username,
"password":password
},
dataType:"json",
success: function(data){
console.log(data);
if(data.status == 200) {
alert("注册成功");
window.location.href="login.html";
}else{
alert("注册失败");
$("#username").val("");
$("#password").val("");
$("#repassword").val("");
}
}
})
})
});
</script>
九、实现上传音乐模块
1、上传音乐功能的请求和响应设计
请求:
{
post, // 使用 post 请求
/music/upload // 请求路径
{singer,MultipartFile file},//上传歌手,歌曲文件
}
响应:
{
"status": 200,
"message": "上传成功!",
"data": true
}
响应设计字段解释:
{
状态码为 200 表示成功,-200 表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息,true 表示上传成功,false 表示上传失败
}
2、创建 Music 类
- 在 package com.example.musicserver.model 包中创建 Music 类
package com.example.onlinemusic.model;
import lombok.Data;
@Data
public class Music {
private int musicId; // 歌曲id
private String title; // 歌曲名称
private String singer; //歌手
private String time; // 上传歌曲的时间
private String url; // 上传歌曲的路径
private int userId; // 上传歌曲的用户
}
3、创建接口 MusicMapper
- 在 package com.example.musicserver.mapper 包中创建 MusicMapper接口
package com.example.onlinemusic.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MusicMapper {
/**
* 插入音乐
* @param title
* @param singer
* @param time
* @param url
* @param userId
* @return
*/
int insert(String title,String singer,String time,String url,int userId);
/**
* 查询歌曲名
* @param title
* @return
*/
List<Music> selectBytitle(String title);
}
4、创建 MusicMapper.xml
- 在 package resources.mybatis 下创建 MusicMapper.xml 文件,在 MusicMapper.xml 文件中添加配置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.UserMapper">
<insert id="insert">
insert into music(title,singer,time,url,userId)
values(#(title),#(singer),#(time),#(url),#(userId));
</insert>
<select id="selectBytitle" resultType="com.example.onlinemusic.model.Music">
select * from music where title = #{title};
</select>
</mapper>
5、创建 MusicController 类
- 在 package com.example.musicserver.controller 包下,创建 MusicController 类
package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.LoveMusicMapper;
import com.example.onlinemusic.mapper.MusicMapper;
import com.example.onlinemusic.model.Music;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.apache.ibatis.binding.BindingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
@RestController
@RequestMapping("/music")
public class MusicController {
// 在配置文件中添加歌曲路径
@Value("${music.local.path}")
private String SAVE_PATH;
@Autowired
private MusicMapper musicMapper;
@Autowired
private LoveMusicMapper loveMusicMapper;
/**
* 上传音乐
* 请求路径:/music/upload
* @param singer 上传歌手
* @param file 上传歌曲
* @param request 请求,验证是否登录
* @return 返回true表示上传成功,返回false表示上传失败
*/
@RequestMapping("/upload")
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer, @RequestParam ("filename")MultipartFile file, HttpServletRequest request, HttpServletResponse response){
// 1. 检查是否登录
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
System.out.println("没有登录");
return new ResponseBodyMessage<>(-200,"请登录后再进行上传",false);
}
// 2. 获取的是文件的完整名称,包括文件名称+文件拓展名
String fileNameAndType = file.getOriginalFilename();
// 3. 查询数据库中是否存在当前要上传的音乐(歌曲名+歌手)
/**
* 获取标题(标题不包含后缀.mp3)
* 使用 lastIndexOf 从后向前找第一个 .
*/
int index = fileNameAndType.lastIndexOf(".");
String title = fileNameAndType.substring(0,index);
// 使用 list 存放歌曲,获取歌曲名
List<Music> list = musicMapper.selectBytitle(title);
if(list != null){
for(Music music : list){
// 判断当前上传的歌曲+歌手在数据库中是否存在,如果存在则上传失败(歌曲名+歌手 不能重复)
if(music.getSinger().equals(singer)){
return new ResponseBodyMessage<>(-200,"上传失败,数据库中存在此歌曲,不能重复上传",false);
}
}
}
// 2. 数据上传到服务器
// 上传文件路径
String path = SAVE_PATH+fileNameAndType;
// 上传文件
File dest = new File(path);
if(!dest.exists()){
//如果路径不存在就创建目录
dest.mkdir();
}
try {
// 将接收到的文件传输到给定目标路径
file.transferTo(dest);
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-200,"上传失败,服务器出现问题",false);
}
// 3. 判断上传的文件是否为mp3文件(判断是否存在 TAG 字符)
File file1 = new File(path);
byte[] bytes = null;
try {
bytes = Files.readAllBytes(file1.toPath());
if(bytes == null){
return new ResponseBodyMessage<>(-200,"上传失败,文件不存在",false);
}
String str = new String(bytes);
if(!str.contains("TAG")){
file1.delete();
return new ResponseBodyMessage<>(-200,"上传的文件不是mp3文件",false);
}
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-200,"上传失败,服务器出现问题",false);
}
// 4. 将数据上传到数据库中(1. 准备数据 2. 调用 insert)
/**
* 获取 userId
* 登录成功后将用户信息写到 session 中,通过 session 中key值(Constant.USERINFO) 就可以获取到对应的 value 值(用户信息)
*/
User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
// 获取用户Id
int userId = user.getUserId();
/**
* url 的作用: 播放音乐->发送 http 请求
*/
String url = "/music/get?path="+title; // 将 url 存入数据库时不用加后缀 .mp3,在取数据的时候加一个后缀就可以了
/**
* 获取上传的时间
* 将获取的时间格式化为:年-月-日 的形式
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String time = simpleDateFormat.format(new Date());
// 插入数据
try {
int ret = musicMapper.insert(title,singer,time,url,userId);
if(ret == 1){
// 数据插入成功
// 这里应该跳转到音乐列表页面
response.sendRedirect("/list.html");
return new ResponseBodyMessage<>(200,"数据库上传成功",true);
}else{
// 数据插入失败
return new ResponseBodyMessage<>(-200,"数据库上传失败",false);
}
}catch (BindingException | IOException e){
// 数据库上传失败,将上传到文件夹中的数据删除
dest.delete();
e.printStackTrace();
return new ResponseBodyMessage<>(-200,"数据库上传失败",false);
}
}
/**
* 播放音乐
* 请求路径:/music/get?get=xxx.mp3
* @param path
* @return
*/
@RequestMapping("/get")
public ResponseEntity<byte[]> playMusic(@RequestParam String path) {
File file = new File(SAVE_PATH+path);
byte[] bytes = null;
try {
bytes = Files.readAllBytes(file.toPath()); // 将文件路径中的文件以字节的形式读取,放到 bytes 数组中
if(bytes == null){
// 如果没有读取的文件,则返回状态码 400
return ResponseEntity.badRequest().build();
}
// 成功读取到文件
return ResponseEntity.ok(bytes);
} catch (IOException e) {
e.printStackTrace();
}
// 如果没有读取的文件,则返回状态码 400
return ResponseEntity.badRequest().build();
}
/**
* 删除单个音乐
* 请求路径:/music/delete?musicId=x
* @param musicId
* @return
*/
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusicByMusicId(@RequestParam String musicId){
/**
* 1. 检查待删除的音乐是否存在
* 2. 如果存在要删除的音乐
* 1. 删除数据库中的数据
* 2. 删除服务器上的数据
*/
// 检查待删除的音乐是否存在
Music music = musicMapper.findMusicById(Integer.parseInt(musicId));
if(music == null){
System.out.println("在控制台打印日志:没有要删除的音乐id");
return new ResponseBodyMessage<>(-200,"要删除的音乐不存在",false);
}else{
// 调用 musicMapper 接口中的 deleteMusicById 方法删除数据库中的数据
int ret = musicMapper.deleteMusicById(Integer.parseInt(musicId));
if(ret == 1){
// 成功删除数据库中的数据
/*int index = music.getUrl().lastIndexOf("=");
String fileName = music.getUrl().substring(index+1);*/
String fileName = music.getTitle();
// 根据存放音乐的路径删除服务器中的数据
File file = new File(SAVE_PATH+fileName+".mp3");
System.out.println("在控制台打印日志:当前音乐的路径:"+file.getPath());
//对删除服务器中的数据进行判断
if(file.delete()){
// 删除成功
return new ResponseBodyMessage<>(200,"音乐删除成功",true);
}else{
return new ResponseBodyMessage<>(-200,"服务器中的音乐删除失败",false);
}
}else{
return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
}
}
}
/**
* 批量删除选中的音乐
* 请求路径:/music/deleteSel
* @param musicId
* @return
*/
@RequestMapping("/deleteSel")
public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("musicId[]") List<Integer> musicId) {
System.out.println("在控制台打印日志:所有音乐的 Id:"+musicId);
int sum = 0; // 统计删除的音乐
for (int i = 0; i < musicId.size(); i++) {
Music music = musicMapper.findMusicById(musicId.get(i));
if (music == null) {
System.out.println("没有要删除的音乐id");
return new ResponseBodyMessage<>(-200, "要删除的音乐不存在", false);
}
int ret = musicMapper.deleteMusicById(musicId.get(i));
if (ret == 1) {
// 成功删除数据库中的数据
String fileName = music.getTitle();
// 根据存放音乐的路径删除服务器中的数据
File file = new File(SAVE_PATH + fileName + ".mp3");
System.out.println("当前音乐的路径:" + file.getPath());
//对删除服务器中的数据进行判断
if (file.delete()) {
// 成功删除一条数据,sum 就加上 ret(数据库中成功删除)
sum += ret;
} else {
return new ResponseBodyMessage<>(-200, "服务器中的音乐删除失败", false);
}
}else{
return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
}
}
if(sum == musicId.size()){
// 选中的数据全部删除成功
System.out.println("在控制台打印日志:选择中的歌曲删除成功");
return new ResponseBodyMessage<>(200,"音乐删除成功",true);
}else{
System.out.println("在控制台打印日志:选择中的歌曲删除失败");
return new ResponseBodyMessage<>(-200,"音乐删除失败",false);
}
}
/**
* 查询音乐
* @param musicName
* @return
*/
@RequestMapping("/findmusic")
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName){
List<Music> musicList = null;
if(musicName != null){
// 模糊查询,根据歌曲名查询指定的歌曲
musicList = musicMapper.findMusicByName(musicName);
}else{
// 查询所有的音乐
musicList = musicMapper.findMusic();
}
// 查询成功,返回查询到的音乐信息
return new ResponseBodyMessage<>(200,"查询成功",musicList);
}
}
6、上传音乐功能测试
7、前端代码
<div class="upload-container">
<form method="post" enctype="multipart/form-data" action="/music/upload">
<div class="upload-dialog">
<strong>上传本地音乐</strong>
<em>XiaoXiangYeYu's music upload_music</em>
<div class="row">
<span>音乐</span>
<input type="file" id="file" name="filename" placeholder="上传歌曲" class="upload_txtbx"/>
</div>
<div class="row">
<span>歌手</span>
<input type="text" id="singer" name="singer" placeholder="请输入歌手名" class="upload_txtbx"/>
</div>
<div class="row">
<input type="submit" id="submit" value="上传歌曲" class="submit_btn"/>
</div>
</div>
</form>
</div>
十、实现播放音乐模块
1、播放音乐功能的请求和响应设计
请求:
{
get, // 使用 get 请求
/music/get?path=xxx.mp3 // 请求路径(数据库中存储的 url)
}
响应:
{
音乐数据本身的字节信息 // 服务器将数据以字节的形式返回给客户端,客户端获取到信息后就可以进行解析,然后播放音乐
}
2、在 MusicController 类中添加 playMusic 方法
/**
* 播放音乐
* 请求路径:/music/get?paht=xxx.mp3
* @param path
* @return
*/
@RequestMapping("/get")
public ResponseEntity<byte[]> playMusic(@RequestParam String path) {
File file = new File(SAVE_PATH+path);
byte[] bytes = null;
try {
bytes = Files.readAllBytes(file.toPath()); // 将文件路径中的文件以字节的形式读取,放到 bytes 数组中
if(bytes == null){
// 如果没有读取的文件,则返回状态码 400
return ResponseEntity.badRequest().build();
}
// 成功读取到文件
return ResponseEntity.ok(bytes);
} catch (IOException e) {
e.printStackTrace();
}
// 如果没有读取的文件,则返回状态码 400
return ResponseEntity.badRequest().build();
}
3、播放音乐功能测试
4、前端代码
<script>
var audios = document.getElementsByTagName("audio");
// 暂停函数
function pauseAll() {
var self = this;
[].forEach.call(audios, function (i) {
// 将audios中其他的audio全部暂停
i !== self && i.pause();
})
}
// 给play事件绑定暂停函数
[].forEach.call(audios, function (i) {
i.addEventListener("play", pauseAll.bind(i));
});
</script>
十一、实现删除音乐模块
1、删除单个音乐
1. 删除单个音乐的请求和响应设计
请求:
{
post, // 使用 post 请求
/music/delete, // 请求路径
musicId // 要删除歌曲的 id
}
响应:
{
"status": 200,
"message": "删除成功!",
"data": true
}
响应设计字段解释:
{
状态码为 200 表示成功,-200 表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息,true 表示删除成功,false 表示删除失败
}
2. 在 MusicMapper 接口中添加代码
/**
* 通过音乐 Id 查询当前音乐是否存在
* @param musicId
* @return
*/
Music findMusicById(int musicId);
/**
* 通过当前音乐 Id 删除音乐
* @param musicId
* @return
*/
int deleteMusicById(int musicId);
3. MusicMapper.xml 文件中添加代码
<select id="findMusicById" resultType="com.example.onlinemusic.model.Music">
select * from music where musicid = #{musicid};
</select>
<delete id="deleteMusicById" parameterType="java.lang.Integer">
delete from music where musicid = #{musicid};
</delete>
4. 在 MusicController 类中添加 deleteByMusicId 方法
/**
* 删除单个音乐
* 请求路径:/music/delete?musicId=x
* @param musicId
* @return
*/
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteByMusicId(@RequestParam String musicId){
/**
* 1. 检查待删除的音乐是否存在
* 2. 如果存在要删除的音乐
* 1. 删除数据库中的数据
* 2. 删除服务器上的数据
*/
// 检查待删除的音乐是否存在
Music music = musicMapper.findMusicById(Integer.parseInt(musicId));
if(music == null){
System.out.println("在控制台打印日志:没有要删除的音乐id");
return new ResponseBodyMessage<>(-200,"要删除的音乐不存在",false);
}else{
// 调用 musicMapper 接口中的 deleteMusicById 方法删除数据库中的数据
int ret = musicMapper.deleteMusicById(Integer.parseInt(musicId));
if(ret == 1){
// 成功删除数据库中的数据
String fileName = music.getTitle();
// 根据存放音乐的路径删除服务器中的数据
File file = new File(SAVE_PATH+fileName+".mp3");
System.out.println("在控制台打印日志:当前音乐的路径:"+file.getPath());
//对删除服务器中的数据进行判断
if(file.delete()){
// 删除成功
return new ResponseBodyMessage<>(200,"音乐删除成功",true);
}else{
return new ResponseBodyMessage<>(-200,"服务器中的音乐删除失败",false);
}
}else{
return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
}
}
}
5. 删除单个音乐功能测试
6. 前端代码
function deleteInfo(obj){
console.log(obj);
$.ajax({
url:"/music/delete",
type:"POST",
data:{
"musicId":obj
},
dataType:"json",
success:function(data){
console.log(data);
if(data.data == true){
alert("删除成功,重新加载当前页面");
window.location.href = "list.html";
}else{
alert("删除失败");
}
}
});
}
2、批量删除选中的音乐
1. 批量删除选中的音乐的请求和响应设计
请求:
{
post, // 使用 post 请求
/music/deleteSel, // 请求路径
data:{
"id":musicId // 要删除的歌曲 id 的数组
}
}
响应:
{
"status": 200,
"message": "批量删除成功!",
"data": true
}
响应设计字段解释:
{
状态码为 200 表示成功,-200 表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息,true 表示删除成功,false 表示删除失败
}
2. 在 MusicController 类中添加 deleteSelMusic 方法
/**
* 批量删除选中的音乐
* 请求路径:/music/deleteSel
* @param musicId
* @return
*/
@RequestMapping("/deleteSel")
public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("musicId[]") List<Integer> musicId) {
System.out.println("在控制台打印日志:所有音乐的 Id:"+musicId);
int sum = 0; // 统计删除的音乐
for (int i = 0; i < musicId.size(); i++) {
Music music = musicMapper.findMusicById(musicId.get(i));
if (music == null) {
System.out.println("没有要删除的音乐id");
return new ResponseBodyMessage<>(-200, "要删除的音乐不存在", false);
}
int ret = musicMapper.deleteMusicById(musicId.get(i));
if (ret == 1) {
// 成功删除数据库中的数据
String fileName = music.getTitle();
// 根据存放音乐的路径删除服务器中的数据
File file = new File(SAVE_PATH + fileName + ".mp3");
System.out.println("当前音乐的路径:" + file.getPath());
//对删除服务器中的数据进行判断
if (file.delete()) {
// 成功删除一条数据,sum 就加上 ret(数据库中成功删除)
sum += ret;
} else {
return new ResponseBodyMessage<>(-200, "服务器中的音乐删除失败", false);
}
}else{
return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
}
}
if(sum == musicId.size()){
// 选中的数据全部删除成功
System.out.println("在控制台打印日志:整体删除成功");
return new ResponseBodyMessage<>(200,"音乐删除成功",true);
}else{
System.out.println("在控制台打印日志:整体删除失败");
return new ResponseBodyMessage<>(-200,"音乐删除失败",false);
}
}
}
3. 批量删除选中的音乐功能测试
4. 前端代码
$(function(){
$("#submit1").click(function(){
var name = $("#exampleInputName2").val();
load(name);
// window.location.href = "findMusic?musicName="+name;
});
$.when(load).done(function(){
$("#delete").click(function(){
var id = new Array(); // 音乐Id
var i = 0; // 数组下标
// 遍历checkbox
$("input:checkbox").each(function(){
// 如果被选中,this代表发生事件的dom元素,<input>
if($(this).is(":checked")){
id[i] = $(this).attr("id");
i++;
}
});
console.log(id);
$.ajax({
url:"/music/deleteSel",
data:{
"musicId":id
},
dataType:"json",
type:"POST",
success:function(obj){
if(obj.data == true){
alert("删除成功");
window.location.href = "list.html";
}else{
alert("删除失败");
}
}
});
});
});
});
十二、实现查询音乐模块
1、查询音乐的请求和响应设计
此处查询需要满足两个功能:
- 支持模糊查询
- 支持传入参数为空,当参数为空时默认查询到所有的音乐
请求:
{
get, // 使用 get 请求
/music/findmusic, // 请求路径
data:{musicName:musicName}, // 根据歌曲名进行查询
}
响应:【不给musicName传参】// 如果不传参时默认查询到所有的音乐
{
"status": 200,
"message": "查询到了歌曲的信息",
"data": [
{
"id": 19,
"title": "张靓颖 - 我的梦",
"singer": "张靓颖",
"url": "/music/get?path=张靓颖 - 我的梦",
"time": "2022-08-20",
"userid": 1
},
{
"id": 20,
"title": "纯音乐 - Victory",
"singer": "张三",
"url": "/music/get?path=纯音乐 - Victory",
"time": "2022-03-20",
"userid": 1
}]
}
响应:【给musicName传参】// 如果传入参数返回指定查询的歌曲
{
"status": 200,
"message": "查询到了歌曲的信息",
"data": [
{
"id": 19,
"title": "张靓颖 - 我的梦",
"singer": "张靓颖",
"url": "/music/get?path=张靓颖 - 我的梦",
"time": "2022-08-20",
"userid": 1
}]
}
响应设计字段解释:
{
状态码为 200 表示成功
状态描述信息,描述此次请求成功
返回的数据,请求成功后给前端的数据信息,返回查询到的音乐信息(歌曲id、歌曲名、歌手、存放歌曲的路径、上传时间、上传用户的id)
}
2、在 MusicMapper 接口中添加代码
/**
* 查询所有的音乐
* @return
*/
List<Music> findMusic();
/**
* 模糊查询,根据歌曲名查询指定的歌曲
* @param musicName
* @return
*/
List<Music> findMusicByName(String musicName);
3、MusicMapper.xml 文件中添加代码
<select id="findMusic" resultType="com.example.onlinemusic.model.Music">
select * from music ;
</select>
<select id="findMusicByName" resultType="com.example.onlinemusic.model.Music">
select * from music where title like concat('%', #{musicName}, '%');
</select>
4、在 MusicController 类中添加 findMusic 方法
/**
* 查询音乐
* @param musicName
* @return
*/
@RequestMapping("/findmusic")
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName){
List<Music> musicList = null;
if(musicName != null){
// 模糊查询,根据歌曲名查询指定的歌曲
musicList = musicMapper.findMusicByName(musicName);
}else{
// 查询所有的音乐
musicList = musicMapper.findMusic();
}
// 查询成功,返回查询到的音乐信息
return new ResponseBodyMessage<>(200,"查询成功",musicList);
}
5、查询音乐功能测试
1. 查询所有的音乐
2. 模糊匹配,查询指定的音乐
6、前端代码
<script type="text/javascript">
// 查询
$(function(){
load();
});
// musicName 可以传参(模糊匹配),也可以不传参(不传参默认传入的是所有的音乐)
function load(musicName){
$.ajax({
type:"GET",
url:"/music/findmusic",
data:{
"musicName":musicName
},
// 服务器返回的数据类型
dataType:"json",
// obj 查找存储的所有信息
success:function(obj){
console.log(obj);
// data数组,存放歌曲信息
var data = obj.data;
var s = '';
for(var i=0;i<data.length;i++){
var musicUrl = data[i].url+".mp3";
console.log(musicUrl);
s += '<tr>';
s += '<th> <input id="'+data[i].musicId+'"type="checkbox"> </th>';
s += '<td>'+ data[i].title +'</td>';
s += '<td>'+ data[i].singer +'</td>';
s += "<td <a href=\"\"> <audio src= \""+ musicUrl+"\" + controls=\"controls\" preload=\"auto\" loop=\"loop\" class=\"audio_btn\"> >" + "</audio> </a> </td>";
s += '<td> <button class = "btn btn-primary" onclick="loveInfo('+data[i].musicId+')"> 收藏歌曲 </button>' + '</td>';
s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+ data[i].musicId +')" >删除歌曲</button>'+'</td>';
s += '</tr>';
}
$("#info").html(s);
}
});
}
十三、实现收藏音乐模块
1、收藏音乐的请求和响应设计
请求:
{
post, // 使用 post 请求
/lovemusic/likeMusic // 请求路径
data: music_id //音乐id
}
响应:
{
"status": 0,
"message": "点赞音乐成功",
"data": true
}
响应设计字段解释:
{
状态码为 200 表示成功,-200 表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息,true 表示收藏成功,false 表示收藏失败
}
2、创建 LoveMusic 类
- 在 package com.example.musicserver.model 包中创建 LoveMusic 类
package com.example.onlinemusic.model;
import lombok.Data;
@Data
public class LoveMusic {
private int loveId;
private int userId;
private int musicId;
}
3、创建接口 LoveMusicMapper
- 在 package com.example.musicserver.mapper 包中创建 LoveMusicMapper 接口
package com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.LoveMusic;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LoveMusicMapper {
/**
* 查询是否已经收藏过该音乐
* @param userId
* @param musicId
* @return
*/
LoveMusic findLoveMusic(int userId, int musicId);
/**
* 收藏音乐
* @param userId
* @param musicId
* @return
*/
boolean insertLoveMusic(int userId,int musicId);
}
4、创建 LoveMusicMapper.xml
- 在 package resources.mybatis 下创建 LoveMusicMapper.xml 文件,在 LoveMusicMapper.xml 文件中添加配置。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.LoveMusicMapper">
<select id="findLoveMusic" resultType="com.example.onlinemusic.model.LoveMusic">
select * from lovemusic where user_id = #{userId} and music_id = #{musicId};
</select>
<insert id="insertLoveMusic">
insert into lovemusic(user_id,music_id) values(#{userId}, #{musicId});
</insert>
</mapper>
5、创建 LoveMusicController 类
- 在 package com.example.musicserver.controller 包下,创建 LoveMusicController 类
package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.LoveMusicMapper;
import com.example.onlinemusic.model.LoveMusic;
import com.example.onlinemusic.model.Music;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.Constant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.List;
@RestController
@RequestMapping("/lovemusic")
public class LoveMusicController {
@Autowired
private LoveMusicMapper loveMusicMapper;
/**
* 收藏音乐
* 请求路径:/lovemusic/likeMusic?music_id=x
* @param music_id 传入的音乐 id
* @param request
* @return
*/
@RequestMapping("/likeMusic")
public ResponseBodyMessage<Boolean> likeMusic(@RequestParam String music_id, HttpServletRequest request){
// 1. 检查是否登录
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
System.out.println("登录失败");
return new ResponseBodyMessage<>(-200,"请登录后再进行收藏",false);
}
// 2. 获取 usrId
User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
int userId = user.getUserId();
System.out.println("在控制台打印日志:user_id:"+userId);
// 3. 将传入的 music_id 转换成整型(获取 musicId)
int musicId = Integer.parseInt(music_id);
System.out.println("在控制台打印日志:music_id:"+musicId);
// 4. 判断当前歌曲是否被收藏过
LoveMusic loveMusic = loveMusicMapper.findLoveMusic(userId,musicId);
if(loveMusic != null){
// 之前收藏过这首歌曲
return new ResponseBodyMessage<>(-200,"该歌曲已被收藏,请勿重复收藏",false);
}else{
boolean ret = loveMusicMapper.insertLoveMusic(userId,musicId);
if(ret){
return new ResponseBodyMessage<>(200,"收藏成功",true);
}else{
return new ResponseBodyMessage<>(-200,"收藏失败",false);
}
}
}
}
6、收藏音乐功能测试
7、前端代码
function loveInfo(obj){
$.ajax({
type:"POST",
url:"/lovemusic/likeMusic",
data:{
"music_id":obj
},
dataType:"json",
success:function(data){
if(data.data == true){
alert("收藏成功");
window.location.href = "loveMusic.html";
}else{
alert("收藏失败");
}
}
});
}
十四、实现查询收藏的音乐模块
1、查询收藏音乐的请求和响应设计
此处查询需要满足两个功能:
- 支持模糊查询
- 支持传入参数为空,当参数为空时默认查询到所有的音乐
请求:
{
get, // 使用 get 请求
/lovemusic/findlovemusic, // 请求路径
data:{musicName:musicName}, // 根据歌曲名进行查询
}
响应:【不传入musicName参数】// 如果不传参时默认查询到所有的音乐
{
"status": 200,
"message": "查询到了所有的收藏的音乐",
"data": [
{
"id": 19,
"title": "张靓颖 - 我的梦",
"singer": "张靓颖",
"url": "/music/get?path=张靓颖 - 我的梦",
"time": "2022-08-20",
"userid": 1
},
{
"id": 20,
"title": "纯音乐 - Victory",
"singer": "张三",
"url": "/music/get?path=纯音乐 - Victory",
"time": "2022-03-20",
"userid": 1
}]
}
响应:【传入musicName参数】// 如果传入参数返回指定查询的歌曲
{
"status": 200,
"message": "查询到了收藏歌曲的信息",
"data": [
{
"id": 19,
"title": "张靓颖 - 我的梦",
"singer": "张靓颖",
"url": "/music/get?path=张靓颖 - 我的梦",
"time": "2022-08-20",
"userid": 1
}]
}
响应设计字段解释:
{
状态码为 200 表示成功
状态描述信息,描述此次请求成功
返回的数据,请求成功后给前端的数据信息,返回查询到的音乐信息(歌曲id、歌曲名、歌手、存放歌曲的路径、上传时间、上传用户的id)
}
2、在 LoveMusicMapper 接口中添加代码
/**
* 查询当前用户收藏的所有音乐
* @param userId
* @return
*/
List<Music> findLoveMusicByUserId(int userId);
/**
* 模糊查询,根据歌曲名查询当前用户指定的收藏歌曲
* @param musicName
* @param userId
* @return
*/
List<Music> findLoveMusicByMusicNameAndUserId(String musicName,int userId);
3、LoveMusicMapper.xml 文件中添加代码
<select id="findLoveMusicByUserId" resultType="com.example.onlinemusic.model.Music">
select music.*
from music,lovemusic
where music.musicid = lovemusic.music_id and user_id = #{userId};
</select>
<select id="findLoveMusicByMusicNameAndUserId" resultType="com.example.onlinemusic.model.Music">
select music.*
from music,lovemusic
where music.musicid = lovemusic.music_id and user_id = #{userId} and title like concat('%', #{musicName}, '%');
</select>
4、在 LoveMusicController 类中添加 findLoveMusic 方法
@RequestMapping("findlovemusic")
public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String musicName,HttpServletRequest request){
// 1. 检查是否登录
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
System.out.println("登录失败");
return new ResponseBodyMessage<>(-200,"请登录后再查找",null);
}
// 2. 获取 usrId
User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
int user_id = user.getUserId();
System.out.println("在控制台打印日志:user_id:"+user_id);
List<Music> musicList = null;
if(musicName == null){
//查询当前用户收藏的所有音乐
musicList = loveMusicMapper.findLoveMusicByUserId(user_id);
}else{
//模糊查询,根据歌曲名查询当前用户指定的收藏歌曲
musicList = loveMusicMapper.findLoveMusicByMusicNameAndUserId(musicName,user_id);
}
return new ResponseBodyMessage<>(200,"查询成功",musicList);
}
5、查询收藏音乐功能测试
1. 查询所有的收藏音乐
2. 模糊匹配,查询指定的收藏音乐
6、前端代码
$(function(){
load();
});
// musicName 可以传参(模糊匹配),也可以不传参(不传参默认传入的是所有的音乐)
function load(musicName){
$.ajax({
type:"GET",
url:"/lovemusic/findlovemusic",
data:{
"musicName":musicName
},
// 服务器返回的数据类型
dataType:"json",
// obj 查找存储的所有信息
success:function(obj){
console.log(obj);
// data数组,存放歌曲信息
var data = obj.data;
var s = '';
for(var i=0;i<data.length;i++){
var musicUrl = data[i].url+".mp3";
s += '<tr>';
s += '<td>'+ data[i].title +'</td>';
s += '<td>'+ data[i].singer +'</td>';
s += "<td <a href=\"\"> <audio src= \""+ musicUrl+"\" + controls=\"controls\" preload=\"auto\" loop=\"loop\" class=\"audio_btn\"> >" + "</audio> </a> </td>";
s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+ data[i].musicId +')">取消收藏</button>'+'</td>';
s += '</tr>';
}
$("#info").html(s);
}
});
}
十五、实现取消收藏音乐模块
1、取消(移除)收藏音乐的请求和响应设计
请求:
{
post, // 使用 post 请求
/lovemusic/removelovemusic, // 请求路径
data:{id:music_id} // 根据收藏列表中音乐的 id 进行移除
}
响应:
{
"status": 200,
"message": "取消收藏成功!",
"data": true
}
响应设计字段解释:
{
状态码为 200 表示成功,-200 表示失败
状态描述信息,描述此次请求成功或者失败的原因
返回的数据,请求成功后,需要给前端的数据信息,true 表示移除收藏成功,false 表示移除收藏失败
}
2、在 LoveMusicMapper 接口中添加代码
/**
* 移除某个用户收藏的引用
* @param userId 用户的 ID
* @param musicId 待移除音乐的 ID
* @return 受影响的行数
*/
int removeLoveMusic(int userId,int musicId);
3、LoveMusicMapper.xml 文件中添加代码
<delete id="removeLoveMusic" parameterType="java.lang.Integer">
delete from lovemusic where user_id = #{userId} and music_id = #{musicId};
</delete>
4、在 LoveMusicController 类中添加 removeLoveMusic 方法
/**
* 移除收藏的音乐
* @param music_id
* @param request
* @return
*/
@RequestMapping("/removelovemusic")
public ResponseBodyMessage<Boolean> removeLoveMusic(@RequestParam String music_id,HttpServletRequest request){
// 1. 检查是否登录
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USER_SESSION_KEY)==null){
System.out.println("登录失败");
return new ResponseBodyMessage<>(-200,"请登录后再移除",false);
}
// 2. 获取 usrId
User user = (User)session.getAttribute(Constant.USER_SESSION_KEY);
int userId = user.getUserId();
System.out.println("在控制台打印日志:user_id:"+userId);
// 3. 将传入的 music_id 转换成整型(获取 musicId)
int musicId = Integer.parseInt(music_id);
System.out.println("在控制台打印日志:music_id:"+musicId);
int ret = loveMusicMapper.removeLoveMusic(userId,musicId);
if(ret == 1){
return new ResponseBodyMessage<>(200,"取消收藏成功",true);
}else{
return new ResponseBodyMessage<>(-200,"取消收藏失败",false);
}
}
5、取消收藏音乐功能测试
6、前端代码
function deleteInfo(obj){
console.log(obj);
$.ajax({
url:"/lovemusic/removelovemusic",
type:"POST",
data:{
"music_id":obj
},
dataType:"json",
success:function(data){
console.log(data);
if(data.data == true){
alert("取消收藏成功,重新加载当前页面");
window.location.href = "loveMusic.html";
}else{
alert("取消收藏失败");
}
}
});
}
十六、完善删除音乐功能
前面所完成的功能中存在一个BUG,当成功删除音乐表(music)中的音乐后,收藏表(lovemusic)中的音乐不会被删除。
1、在 LoveMusicMapper 接口中添加代码
/**
* 根据音乐的ID进行删除
* @param musicId
* @return
*/
int deleteLoveMusicByMusicId(int musicId);
2、LoveMusicMapper.xml 文件中添加代码
<delete id="deleteLoveMusicByMusicId" parameterType="java.lang.Integer">
delete from lovemusic where music_id = #{musicId};
</delete>
3、调整 MusicController 类中的 deleteMusicByMusicId 和 deleteSelMusic 方法
// 在 MusicController 注入 LoveMusicMapper
@Autowired
private LoveMusicMapper loveMusicMapper;
// 调整后的 deleteMusicByMusicId 方法 和 deleteSelMusic 方法
/**
* 删除单个音乐
* 请求路径:/music/delete?musicId=x
* @param musicId
* @return
*/
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusicByMusicId(@RequestParam String musicId){
/**
* 1. 检查待删除的音乐是否存在
* 2. 如果存在要删除的音乐
* 1. 删除数据库中的数据
* 2. 删除服务器上的数据
*/
// 检查待删除的音乐是否存在
Music music = musicMapper.findMusicById(Integer.parseInt(musicId));
if(music == null){
System.out.println("在控制台打印日志:没有要删除的音乐id");
return new ResponseBodyMessage<>(-200,"要删除的音乐不存在",false);
}else{
// 调用 musicMapper 接口中的 deleteMusicById 方法删除数据库中的数据
int ret = musicMapper.deleteMusicById(Integer.parseInt(musicId));
if(ret == 1){
// 成功删除数据库中的数据
String fileName = music.getTitle();
// 根据存放音乐的路径删除服务器中的数据
File file = new File(SAVE_PATH+fileName+".mp3");
System.out.println("在控制台打印日志:当前音乐的路径:"+file.getPath());
//对删除服务器中的数据进行判断
if(file.delete()){
// 同步删除 lovemusic 表中的音乐
loveMusicMapper.deleteLoveMusicByMusicId(Integer.parseInt(musicId));
// 删除成功
return new ResponseBodyMessage<>(200,"音乐删除成功",true);
}else{
return new ResponseBodyMessage<>(-200,"服务器中的音乐删除失败",false);
}
}else{
return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
}
}
}
/**
* 批量删除选中的音乐
* 请求路径:/music/deleteSel
* @param musicId
* @return
*/
@RequestMapping("/deleteSel")
public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("musicId[]") List<Integer> musicId) {
System.out.println("在控制台打印日志:所有音乐的 Id:"+musicId);
int sum = 0; // 统计删除的音乐
for (int i = 0; i < musicId.size(); i++) {
Music music = musicMapper.findMusicById(musicId.get(i));
if (music == null) {
System.out.println("没有要删除的音乐id");
return new ResponseBodyMessage<>(-200, "要删除的音乐不存在", false);
}
int ret = musicMapper.deleteMusicById(musicId.get(i));
if (ret == 1) {
// 成功删除数据库中的数据
String fileName = music.getTitle();
// 根据存放音乐的路径删除服务器中的数据
File file = new File(SAVE_PATH + fileName + ".mp3");
System.out.println("当前音乐的路径:" + file.getPath());
//对删除服务器中的数据进行判断
if (file.delete()) {
// 同步删除 lovemusic 表中的音乐
loveMusicMapper.deleteLoveMusicByMusicId(musicId.get(i));
// 成功删除一条数据,sum 就加上 ret(数据库中成功删除)
sum += ret;
} else {
return new ResponseBodyMessage<>(-200, "服务器中的音乐删除失败", false);
}
}else{
return new ResponseBodyMessage<>(-200,"数据库中的音乐删除失败",false);
}
}
if(sum == musicId.size()){
// 选中的数据全部删除成功
System.out.println("在控制台打印日志:选择中的歌曲删除成功");
return new ResponseBodyMessage<>(200,"音乐删除成功",true);
}else{
System.out.println("在控制台打印日志:选择中的歌曲删除失败");
return new ResponseBodyMessage<>(-200,"音乐删除失败",false);
}
}