一、核心功能
- 登录,注册,退出(加密操作)
- 上传,播放音乐
- 删除指定音乐
- 批量删除选中的音乐
- 查询你想要的音乐(支持模糊查询和全查询)
- 添加音乐至喜欢的列表
- 移除喜欢的音乐
二、效果演示
-
登录界面
-
注册界面
-
主页面
-
收藏页面
细节演示,后续说明 !!
三、创建项目
创建一个springboot项目,具体步骤与前面的博客记录一样,这里就不再重复赘述,大家自行参考以下博客:
1.springboot项目的基本创建
2.添加mybatis框架支持
四、数据库设计及配置数据库
4.1 数据库和表设计
思维导图如下:
具体创建:
- 创建数据库musicserver
-- 数据库
drop database if exists `onlinemusic`;
create database if not exists `musicserver` character set utf8;
-- 使用数据库
use `onlinemusic`;
- 创建表user
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`username` varchar(20) NOT NULL,
`password` varchar(255) NOT NULL
);
- 创建表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,
`userid` int(11) NOT NULL
);
title字段为歌曲名称,url字段为歌曲的路径
- 创建中间表lovemusic
DROP TABLE IF EXISTS `lovemusic`;
CREATE TABLE `lovemusic` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`music_id` int(11) NOT NULL
)
4.2 配置连接数据库
打开application.properties配置如下信息:
#配置数据库
#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&useSSL=false
#spring.datasource.username=root
#spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 上传服务器后,此处需要修改如下:外网IP ,服务器上的数据库没有密码
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 音乐上传路径 不推荐使用中文路径 且分隔符不能采用 \\ 因为 Linux中不支持
#music.local.path=F:/music/
# 上传服务器后,此处需要修改为服务器中的地址 注意最后一个 /
music.local.path=/root/savaonlinemusic/
#配置xml
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
# 配置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
#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB
五、创建配置类
创建 tools 工具包,放置全局可以使用的配置类
- 设置统一返回格式
package com.example.onlinemusic.tools;
import lombok.Data;
@Data
public class ResponseBodyMessage<T> {
private int status;//状态码
private String message;//状态描述信息[出错的原因? 没出错的原因?]
private T data;//返回的数据
public ResponseBodyMessage(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
}
注意: 将返回类 ResponseBodyMessage 设置为泛型,方便返回数据类型的设置
- Constant类,储存复杂的不变常量
如设置了session对象,此时的key值是一个字符串,将来在其他地方获取对应的session需要通过这个字符串获取,但是存在一定的写错的情况。所以,此时建议把他定义为一个常量
public class Constant {
public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}
六、具体功能实现
6.1 注册模块
- Controller 层实现
@Autowired
private UserService userService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
//注册
@RequestMapping("/reg")
public ResponseBodyMessage<Boolean> reg(@RequestParam String username, @RequestParam String password) {
User user1 = userService.selectByName(username);
if (user1 != null) {
return new ResponseBodyMessage<>(-1, "当前用户已存在!", false);
} else {
User user2 = new User();
user2.setUsername(username);
String password1 = bCryptPasswordEncoder.encode(password);
user2.setPassword(password1);
userService.addUser(user2);
return new ResponseBodyMessage<>(1, "注册成功!", true);
}
}
注意: 用户存在就不能再次注册
- Mapper 层实现
public int addUser(User user);
- xml 文件实现
<!-- 新增用户 -->
<insert id="addUser">
insert into user(username,password) values (#{username},#{password});
</insert>
- 请求和响应设计
请求
POST /user/reg
{username: “”,password: “”}
响应
{
status: 1/-1,
message: “”,
data: “”
}
6.2 登录模块
- 创建User类
在package com.example.musicserver.model包中创建User类
package com.example.onlinemusic.model;
import lombok.Data;
@Data
public class User {
private int id;
private String username;
private String password;
}
- 创建对应的Mapper和Service
2.1新建mapper包,新建UserMapper
package com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
public User login(User loginUser);
}
2.2创建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>
</mapper>
2.3创建Service类
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User login(User userlogin){
return userMapper.login(userlogin);
}
}
- 实现登录
3.1登录的请求和响应设计
请求
POST /user/login
{username: “”,password: “”}
响应
{
status: 0/-1,
message: “”,
data: “”
}
响应体设计字段解释:
{
status:状态码,为1代表成功,负数代表失败
message:状态描述信息,描述此次请求成功或者失败的原因
data:返回的数据,请求成功后,需要给前端的数据信息
}
3.2创建UserController类
@Autowired
private UserService userService;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@RequestMapping("/login1")
//将请求参数绑定到你控制器的方法参数上 。如果这个参数是非必传的可以写为:@RequestParam(required = false) ,默认是true
public ResponseBodyMessage<Boolean> login1(@RequestParam String username, @RequestParam String password, HttpServletRequest request) {
User user = userService.selectByName(username);
if (user != null) {
// bCryptPasswordEncoder 采用注入的方式
boolean flg = bCryptPasswordEncoder.matches(password, user.getPassword());
if (!flg) {
return new ResponseBodyMessage<>(-1, "密码错误 !", false);
} else {
System.out.println("登录成功 !");
// 登录成功,将用户信息写入Session中
request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY, user);
return new ResponseBodyMessage<>(1, "登录成功 !", true);
}
} else {
System.out.println("登录失败 !");
return new ResponseBodyMessage<>(-1, "登录失败 !", false);
}
}
拓展:登录注册加密(MD5,BCrypt)
- MD5 加密
MD5是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程不可逆; 但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。
彩虹表:彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对MD5算法的,各种算法的都有,有了它可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G以上。
不安全的原因:
- 暴力攻击速度很快
- 字典表很大
- 碰撞
更安全的做法是加盐或者长密码等做法,盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
但是,因为这里没有用随机盐值,固定盐值使得每次加密的密码都是固定的,也存在暴力破解风险。更安全的是,当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了,也就是下面介绍的 BCrypt
(重点参考)MD5详解:https://md5.cc/news1.aspx
- BCrypt 加密
Bcrypt就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理 。我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。Bcrypt生成的密文是60位的。而MD5的是32位的。Bcrypt破解难度更大。
在我们此项目中也是使用了 BCrypt 加密,具体操作如下
添加依赖:
<!-- security依赖包 (加密)-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
在 SpringBoot 启动了添加:
@SpringBootApplication(exclude = {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
解释: 因为在SpringBoot中,默认的Spring Security生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。此时通过上述配置,即可禁用默认的登录验证该项目本身没有那么复杂,并没有用到 Spring Security 框架,只是用到了其中的 BCryptPasswordEncoder 类
最后就是登录注册中分别调用的 encode 和 matches 方法:
encode方法:对用户密码进行加密
matches方法:参数一,待检验的未加密的密码 。参数二:从数据库中查询出的加密后密码
总结:
- 密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
- 使用BCrypt相比于MD5加密更好的一点在于,破解的难度上加大
- BCrypt的破解成本增加了,导致系统的运行成本也会大大的增加 。
- 回到本质的问题,你的数据库中的数据价值如何?如果你是银行类型的,那么使用BCrypt是不错的,一般情况使用MD5加盐,已经够用了。
BCrypt加密: 一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
MD5加密: 是不加盐的单向Hash,不可逆的加密算法,同一个密码经过hash的时候生成的是同一个hash值,在大多数的情况下,有些经过md5加密的方法将会被破解。
Bcrypt生成的密文是60位的。而MD5的是32位的。
目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全,但加密更慢。 虽然BCrpyt也是输入的字符串+盐,但是与MD5+盐的主要区别是:每次加的盐不同,导致每次生成的结果也不相同。无法比对!
6.3 退出模块
所谓退出即将Session中的用户信息删除掉,这样只有再次登录才能进行后续页面访问
- Controller 层实现
@RequestMapping("/logout")
public ResponseBodyMessage<Boolean> logout(HttpServletRequest request, HttpServletResponse response) {
//退出登录则清除session中的用户信息
HttpSession session = request.getSession(false);
if (session == null) {
System.out.println("您还未登录!");
return new ResponseBodyMessage<>(-1, "您还未登录!", false);
} else {
session.removeAttribute(Constant.USERINFO_SESSION_KEY);
if (session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("清除session中的用户信息成功!");
// try {
// response.sendRedirect("login.html");//此处要注意,只需要将后端数据传给前端,让前端来判断是否需要重定向即可
// } catch (IOException e) { //若后端也进行重定向,就可能出错 !!
// e.printStackTrace();
// }
return new ResponseBodyMessage<>(1, "退出成功!", true);
}
return new ResponseBodyMessage<>(-1, "退出失败!", false);
}
}
}
- 前端实现
<script>
function exit() {
// 2.发送 ajax 请求到后端
jQuery.ajax({
url: "/user/logout",
type: "POST",
dataTye: "json",
success: function (data) {
if (data.status == 1) {
alert("退出登录成功!");
window.location.href = "login.html";
} else {
alert("退出失败,请重试!");
}
}
});
}
$(function (){
$("#logout").click(function (){
exit();
});
});
</script>
6.4 上传音乐模块
- 请求响应设计
请求:
{
post,
/music/upload
{singer,MultipartFile file},
}
响应:
{
“status”: 1,
“message”: “上传成功!”,
“data”: true
}
- 新建Music类:
package com.example.onlinemusic.model;
import lombok.Data;
@Data
public class Music {
private int id;
private String title;
private String singer;
private String url;
private String time;
private int userid;
}
- 创建MusicController类
上传本地成功后,还要实现数据库上传,需要先进行数据库的查询,然后插入数据库
@RestController
@RequestMapping("/music")
public class MusicController {
@Value("${music.local.path}")
private String Save_path;
@Autowired
private UserService userService;
@RequestMapping("/upload")
// MultipartFile是Spring框架中处理文件上传的主要类
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer, @RequestParam("filename") MultipartFile file,
HttpServletRequest request, HttpServletResponse response) {
// 没有获取到 Session 不会创建
HttpSession httpSession = request.getSession(false);
// 1.验证是否登录,登录成功后才允许上传
if (httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录!");
return new ResponseBodyMessage<>(-1, "没有登陆,请登录后上传 !", false);
} else {
// 获取音乐文件名称及类型 如 xxx.mp3
String filenameAndType = file.getOriginalFilename();
System.out.println("filenameAndType--->>>>>>>>>>>>>>>>>" + filenameAndType);
// 设置文件保存路径 Save_path 采取从配置文件中读取的方式
String path = Save_path + filenameAndType;
//2.上传服务器 上传音乐到指定的文件目录
// 创建文件,该文件路径为 path
File dest = new File(path);
if (!dest.exists()) {
dest.mkdirs();
}
try {
// 将上传的文件保存在 dest 目录下
file.transferTo(dest);
//return new ResponseBodyMessage<>(1, "服务器上传音乐成功 !", true);
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-1, "服务器上传音乐失败 !", false);
}
//3.上传数据库
//3.1 先准备好歌曲的字段数据
int LastPointIndex = filenameAndType.lastIndexOf(".");//返回上传文件名称最后一个点的下标
//数据库当中存储的歌曲名称不包含.mp3. 所以需要进行截取
String title = filenameAndType.substring(0, LastPointIndex);// substring 截取的是左闭右开区间,我们只需要从0下标到最后一个点的下标
//SimpleDateFormat来格式化当前的系统时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String time = simpleDateFormat.format(new Date());
//这里的 url 会被用到播放音乐的模块
String url = "/music/get?path=" + title;
User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
int userid = user.getId();
// TODO:这里可以有一步:验证上传的文件在数据库中是否已经存在【判断title 和 singer 是否有和数据库中的数据一致的】
// 此处遇到的问题:1.端口被进程占用,如何处理! 先在cmd中查看被那个进程占用,再用命令杀掉该进程
// 2.mybatis xml文件中,查询语句的返回类型只能是对应的实体,不能是受影响的行数
Music music = userService.selectinsertMusic(title, singer);
if (music != null) {
return new ResponseBodyMessage<>(-1, "该音乐已存在,上传数据库失败!", false);
} else {
//3.2 将数据插入数据库
int ret = userService.insertMusic(title, singer, time, url, userid);
if (ret == 1) {
//Ctrl+alt+T 快速生产 try catch 包裹
try {
// 当上传数据库成功后,会跳转到音乐列表界面
response.sendRedirect("/list.html");
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseBodyMessage<>(1, "数据库上传音乐成功 !", true);
} else {
// 如果上传数据库失败,就会把服务器中的文件删除掉 !
dest.delete();
return new ResponseBodyMessage<>(-1, "数据库上传音乐失败,已删除服务器上传的音乐!", false);
}
}
}
}
- 使用@Value(“${music.local.path}”),获取到配置文件当中的值。不建议中文路径。
- MultipartFile类,在org.springframework.web.multipart包当中,是Spring框架中处理文件上传的主要类
主要方法介绍:
MultipartFile详解:https://www.jianshu.com/p/e3d798c906cd
- 定义接口MusicMapper
@Mapper
public interface MusicMapper {
//上传音乐
public int insertMusic(String title, String singer, String time, String url, int userid);
//上传音乐时,先查询数据库中是否已经存在该音乐
public Music selectinsertMusic(String title, String singer);
}
- xml 文件实现
<insert id="insertMusic">
insert into music(title, singer, time, url, userid)
values (#{title}, #{singer}, #{time}, #{url}, #{userid})
</insert>
<select id="selectinsertMusic" resultType="com.example.onlinemusic.model.Music">
select *
from music
where title = #{title}
and singer = #{singer}
</select>
知识拓展1:如何判断上传的文件是mp3
每个文件都存在其构成的方式【不能通过后缀名判断】
mp3文件格式:
由上图结构可知,每个Frame都由帧头和数据部分组成。具体参考以下链接:
1、https://blog.csdn.net/ffjffjffjffjffj/article/details/99691239
2、https://www.cnblogs.com/ranson7zop/p/7655474.html
3、https://blog.csdn.net/sunshine1314/article/details/2514322
知识拓展2:时间格式化的类:SimpleDateFormat
package com.example.onlinemusic.tools;
import java.text.SimpleDateFormat;
import java.util.Date;
public class SimpleDateFormatTest {
public static void main(String[] args) {
//获取当前年月日
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
String time=sf.format(new Date());
System.out.println(time);
//获取当前年月日 和 时分秒
SimpleDateFormat sf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time2=sf2.format(new Date());
System.out.println(time2);
}
}
6.5 播放音乐模块
- Controller 层实现
//播放音乐:发送一个请求来获取音乐资源字节文件
@RequestMapping("/get")
// ResponseEntity对象是Spring对请求响应的封装。它继承了HttpEntity对象,包含了Http的响应码httpstatus)、响应头(header)、响应体(body)三个部分
public ResponseEntity<byte[]> getMusic(String path) {
File file = new File(Save_path + path);// 请求时注意要加上.mp3
byte[] body = null;
try {
// Files.readAllBytes(String path) : 读取文件中的所有字节,读入内存 ,参数path是文件的路径
body = Files.readAllBytes(file.toPath());
if (body == null) {
// 如果 body 此时还为null,说明没有读取到文件,即前端发送了一个错误的请求,没有找到对应的音乐文件
return ResponseEntity.badRequest().build();// 错误请求,返回 400 状态码
}
return ResponseEntity.ok(body);//返回 200 状态码 以及对应的文件字节
} catch (IOException e) {
e.printStackTrace();
}
return ResponseEntity.badRequest().build();
}
代码解释:
- Files.readAllBytes(String path) : 读取文件中的所有字节,读入内存 ,参数path是文件的路径
- ResponseEntity:ResponseEntity对象是Spring对请求响应的封装。它继承了HttpEntity对象,包含了Http的响应码(httpstatus)、响应头(header)、响应体(body)三个部分。ResponseEntity类继承自HttpEntity类,被用于Controller层方法 。ResponseEntity.ok 方法有2个方法,分别是有参数和没有参数
源码如下:
//这个方法若被调用的话,返回OK状态
public static ResponseEntity.BodyBuilder ok(){
return status(HttpStatus.OK);
} /
/这个方法若被调用的话,返回body内容和OK状态
public static <T> ResponseEntity<T> ok(T body) {
ResponseEntity.BodyBuilder builder = ok();
//ResponseEntity可以通过这个builder返回任意类型的body内容
return builder.body(body);
- 与API中的描述一致,无参ok方法返回OK状态,有参ok方法返回body内容和OK状态
- body类型 是 泛型T,也就是我们不确定body是什么类型,可以向ok方法传递任意类型的值
- 有参ok方法其实有调用无参ok方法
参考链接:
1、使用ResponseEntity处理API返回
2、https://www.jianshu.com/p/1238bfb29ee1
3、https://blog.csdn.net/qq_43317193/article/details/100109136
难点:添加前端开源播放器控件
1.码云地址: https://gitee.com/jackzhang1204/sewise-player
2.GitHub地址:https://github.com/jackzhang1204/sewise-player
3.音频播放示例:https://gitee.com/jackzhang1204/sewise-player/blob/master/demos/audio.html
将该开源项目,下载到本地,取出player文件夹,放入static文件夹下
前端嵌入播放器:
<script type="text/javascript">
SewisePlayer.setup({
server: "vod",
type: "mp3",
//这里是默认的一个网址
videourl: "http://jackzhang1204.github.io/materials/where_did_time_go.mp3",
skin: "vodWhite",
//这里需要设置false
autostart: "false",
});
</script>
实现播放:
function playerSong(obj) {
console.log(obj)
var name = obj.substring(obj.lastIndexOf('=') + 1);
//obj:播放地址 name:歌曲或者视频名称 0:播放的时间 false:点击播放按钮时不开启自动播放
SewisePlayer.toPlay(obj, name, 0, true);
}
6.6 删除音乐模块
当删除主页面的音乐后,收藏列表对应的音乐也要被删除掉 !!
6.6.1 删除单个音乐
- 请求和响应设计
请求:
{
post,
/music/delete,
id
}
响应:
{
“status”: 1,
“message”: “删除成功!”,
“data”: true
}
- Service 层
public Music selectMusicById(int id){
return musicMapper.selectMusicById(id);
}
public int deleteMusicById(int id){
return musicMapper.deleteMusicById(id);
}
- Mapper 层
//根据id删除音乐,先查询判断数据库中是否存在该id的音乐
public Music selectMusicById(@RequestParam Integer id);
//如果查询到数据库中存在该id对应的音乐,即可以删除
public int deleteMusicById(@RequestParam Integer id);
- xml 文件实现
<select id="selectMusicById" resultType="com.example.onlinemusic.model.Music">
select *
from music
where id = #{id}
</select>
<delete id="deleteMusicById" parameterType="java.lang.Integer">
delete
from music
where id = #{id}
</delete>
- Controller 层实现
//根据单个id 删除单个音乐
//后期优化:根据id删除可能会出现数据库删除成功,服务器删除成功!但是此时数据库另一个id对应的音乐也是删除的这个音乐(音乐名字相同,歌手不同),
//这时该idList在数据库中的音乐就没有删除,但是在服务器上已经删除了该音乐
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusicByid(String id) {
int musicId = Integer.parseInt(id);// PS:如果参数直接是int类型,就会报错,是String类型,将其转换为int 就不会报错 原因不知
//1.先查询该数据库中该idList是否存在音乐
Music music = userService.selectMusicById(musicId);
if (music == null) {
return new ResponseBodyMessage<>(-1, "数据库中没有你想删除的音乐", false);
} else {
//2.删除数据库中的音乐
int ret = userService.deleteMusicById(musicId);
if (ret == 1) {
//3.数据库中删除成功,还要删除服务器中的音乐
String title = music.getTitle();
File file = new File(Save_path + title + ".mp3");
System.out.println("此时文件的路径:" + file.getPath());
Boolean flg = file.delete();
if (flg) {
//数据库和服务器中的音乐数据被删除后,收藏表中对应的音乐也应被删除
userService.deleteLoveMusicById(musicId);
return new ResponseBodyMessage<>(1, "数据库删除成功且服务器删除成功且删除收藏表对应的音乐 !", true);
} else {
return new ResponseBodyMessage<>(-1, "数据库删除成功但是服务器删除失败 !", false);
}
} else {
return new ResponseBodyMessage<>(-1, "数据库删除失败 !", false);
}
}
}
6.6.2 批量删除选中音乐
- 请求和响应设计
请求:
{
post,
/music/deletesel,
data:{“id”:id}
}
响应:
{
“status”: 1,
“message”: “批量删除成功”,
“data”: true
}
- Controller 层实现
//根据id批量删除 与单个删除逻辑一样,只是需要循环遍历集合来一个一个删除
@RequestMapping("/deletebatch")
public ResponseBodyMessage<Boolean> deleteBatchMusic(@RequestParam(value = "id[]") List<Integer> idList) {
int sum = 0;
for (int i = 0; i < idList.size(); i++) {
Music music = userService.selectMusicById(idList.get(i));
if (music == null) {
return new ResponseBodyMessage<>(-1, "数据库中没有你想删除的音乐", false);
} else {
//2.删除数据库中的音乐
int ret = userService.deleteMusicById(idList.get(i));
if (ret == 1) {
//3.数据库中删除成功,还要删除服务器中的音乐
String title = music.getTitle();
File file = new File(Save_path + title + ".mp3");
System.out.println("此时文件的路径:" + file.getPath());
Boolean flg = file.delete();
if (flg) {
//数据库和服务器中的音乐数据被删除后,收藏表中对应的音乐也应被删除
userService.deleteLoveMusicById(idList.get(i));
sum += ret;
// 注意:下面语句放开注释,整个功能逻辑就会出错 !! 花了半小时才找出来0.0
//return new ResponseBodyMessage<>(1, "数据库删除成功且服务器删除成功 !", true);
} else {
return new ResponseBodyMessage<>(-1, "数据库删除成功但是服务器删除失败 !", false);
}
} else {
return new ResponseBodyMessage<>(-1, "数据库删除失败 !", false);
}
}
}
if (sum == idList.size()) {
return new ResponseBodyMessage<>(1, "批量删除音乐成功 !", true);
} else {
return new ResponseBodyMessage<>(-1, "批量删除音乐失败 !", false);
}
}
6.7 查询音乐模块
当输入参数时进行模糊查询,参数为空时进行全部查询
- 请求和响应设计
请求:
{
get,
/music/findmusic,
data:{musicName:musicName},
}
响应:【不给musicName传参】
{
“status”: 1/-1,
“message”: “”,
“data”: “”
}
响应:【给musicName传参】
{
“status”: 1/-1,
“message”: “”,
“data”: “”
}
- Service 层实现
public List<Music> findMusicByMusicName(String name){
return musicMapper.findMusicByMusicName(name);
}
public List<Music> findMusic(){
return musicMapper.findMusic();
}
- Mapper 层实现
// 根据歌曲名字,查询音乐
List<Music> findMusicByMusicName(String name);
// 当参数为空时,查询所有音乐
List<Music> findMusic();
- xml 文件实现
<select id="findMusicByMusicName" resultType="com.example.onlinemusic.model.Music">
select * from music where title like concat('%',#{musicName},'%')
</select>
<select id="findMusic" resultType="com.example.onlinemusic.model.Music">
select * from music
</select>
注意: 进行模糊查询时,使用like语句的写法
- Controller 层实现
// 查询音乐
@RequestMapping("/findmusic")
//(required=false)可以不传入参数,默认为 true,不传入参数就会报错 !! //该参数要和前端参数对应
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName) {
List<Music> musicList = null;
if (musicName != null) {
//不为空就进行模糊查询
musicList = userService.findMusicByMusicName(musicName);
} else {
//参数为空就查询全部的音乐
musicList = userService.findMusic();
}
return new ResponseBodyMessage<>(1, "查询到了歌曲信息", musicList);
}
6.8 添加音乐至喜欢的列表模块
- 请求和响应设计
请求:
{
post,
/lovemusic/insertlikemusic
data: id//音乐id
}
响应:
{
“status”: 1,
“message”: “点赞音乐成功”,
“data”: true
}
- LoveMusicMapper接口
1、需要查询此次收藏音乐是否之前收藏过,收藏过则不能添加
2、没有收藏过,插入数据库中一条记录
@Mapper
public interface LoveMusicMapper {
//1.收藏音乐之前先查看该音乐是否已经收藏过了
public Music findLoveMusicByMusicIdAndUserId(@RequestParam Integer userId, @RequestParam Integer musicId);
//2.没有收藏该音乐的话就可以插入该音乐到收藏表中
public Boolean insertLoveMusic(@RequestParam Integer userId, @RequestParam Integer musicId);
}
- xml 文件实现
<select id="findLoveMusicByMusicIdAndUserId" resultType="com.example.onlinemusic.model.Music">
select *
from lovemusic
where user_id = #{userId}
and music_id = #{musicId}
</select>
<!-- 下面的返回类型不能是 Boolean -->
<insert id="insertLoveMusic">
insert into lovemusic(user_id, music_id)
values (#{userId}, #{musicId})
</insert>
- Controller 层实现
@RestController
@RequestMapping("/lovemusic")
public class LoveMusicController {
@Resource
private UserService userService;
@RequestMapping("/insertlikemusic")
public ResponseBodyMessage<Boolean> LoveMusic(@RequestParam String id, HttpServletRequest request) {
int musicId = Integer.parseInt(id);
HttpSession httpSession = request.getSession(false);
if (httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
return new ResponseBodyMessage<>(-1, "您还没有登录 !", false);
} else {
User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
int userId = user.getId();
//TODO:还可以扩展,该音乐是否已经上传,只有上传成功的音乐才能收藏
//先查询该音乐是否已经被收藏
Music music = userService.findLoveMusicByMusicIdAndUserId(userId, musicId);
if (music != null) {
System.out.println("该音乐已经被收藏 !");
return new ResponseBodyMessage<>(-1, "该音乐已经被收藏,重复收藏失败 !", false);
} else {
//没有收藏过就可以进行收藏操作
Boolean flg = userService.insertLoveMusic(userId, musicId);
if (flg) {
return new ResponseBodyMessage<>(1, "收藏音乐成功 !", true);
} else {
return new ResponseBodyMessage<>(-1, "收藏音乐失败 !", false);
}
}
}
}
6.9 查询收藏的音乐模块
功能实现和之前在主页面的查询一样 !! 注意细节修改
- 请求和响应设计
请求:
{
get,
/lovemusic/findlovemusic,
data:{musicName:musicName},
}
响应:【不给musicName传参】
{
“status”: 1/-1,
“message”: “”,
“data”: “”
}
响应:【给musicName传参】
{
“status”: 1/-1,
“message”: “”,
“data”: “”
}
- Mapper 层实现
//查询用户收藏的音乐:如果没有传入具体的歌曲名,显示当前用户收藏的所有音乐
public List<Music> findLoveMusicByUserId(int userId);
//根据某个用户的ID和歌曲名称查询某个用户收藏的音乐,支持模糊查询
public List<Music> findLoveMusicBykeyAndUID(String musicName, int userId);
- xml 文件实现(重要)
<select id="findLoveMusicByUserId" resultType="com.example.onlinemusic.model.Music">
select m.*
from lovemusic lm,
music m
where m.id = lm.music_id
and lm.user_id = #{userId}
</select>
<select id="findLoveMusicBykeyAndUID" resultType="com.example.onlinemusic.model.Music">
select m.*
from lovemusic lm,
music m
where m.id = lm.music_id
and lm.user_id = #{userId}
and title like concat('%', #{musicName}, '%')
</select>
- Controller 层
这里就不重复赘述了,大家参考前面 !!
6.10 删除收藏的音乐模块
- 请求和响应设计
请求:
{
post,
/lovemusic/deletelovemusic,
data:{id:id}
}
响应:
{
“status”: 1,
“message”: “取消收藏成功!”,
“data”: true
}
- Service 层
将上述 Service 层代码补齐
public Music findLoveMusicByMusicIdAndUserId(Integer userId,Integer musicId){
return loveMusicMapper.findLoveMusicByMusicIdAndUserId(userId,musicId);
}
public Boolean insertLoveMusic(Integer userId,Integer musicId){
return loveMusicMapper.insertLoveMusic(userId,musicId);
}
public List<Music> findLoveMusicByUserId(int userId){
return loveMusicMapper.findLoveMusicByUserId(userId);
}
public List<Music> findLoveMusicBykeyAndUID(String musicName, int userId){
return loveMusicMapper.findLoveMusicBykeyAndUID(musicName,userId);
}
public int deleteLoveMusic(int userId,int musicId){
return loveMusicMapper.deleteLoveMusic(userId,musicId);
}
public int deleteLoveMusicById(int musicId){
return loveMusicMapper.deleteLoveMusicById(musicId);
}
- Mapper 层
//移除自己收藏的音乐,但是不是删除音乐本身,只是从数据库中删除了记录而已
public int deleteLoveMusic(int userId,int musicId);
//删除音乐完善:当删除库中的音乐的时候,同步删除lovemusic中的数据
public int deleteLoveMusicById(int musicId);
- xml 文件实现
<delete id="deleteLoveMusic" parameterType="java.lang.Integer">
delete from lovemusic where user_id = #{userId} and music_id = #{musicId}
</delete>
<delete id="deleteLoveMusicById" parameterType="java.lang.Integer">
delete from lovemusic where music_id=#{musicId}
</delete>
七、配置拦截器
- 新建 LoginInterceptor ,实现HandlerInterceptor 接口
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 {
// 没有获取到 Session 不会创建
HttpSession httpSession = request.getSession(false);
// 1.验证是否登录,登录成功后才允许上传
if (httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录!");
response.sendRedirect("/login.html");
return false;
}
return true;
}
}
- 将拦截器注册到系统配置文件,并设置拦截规则
package com.example.onlinemusic.config;
import org.springframework.beans.factory.annotation.Autowired;
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();
}
@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("/reg.html")
.excludePathPatterns("/login.html")
//排除登录注册接口
.excludePathPatterns("/user/login1")
.excludePathPatterns("/user/reg");//注意里面的格式 / 不要掉
}
}
八、测试用例(了解即可)
九、项目部署
前提:拥有一台服务器,在服务器上安装必须的文件如,JDK,Mysql 等
-
将数据库在服务器上重新进行建表等操作,相应的数据也需要重新获取
-
修改项目中的路径,数据库密码等,匹配服务器即可
-
将打包好的jar包,通过xftp 上传到服务器上相应的文件目录下
-
使用java -jar xxxx.jar 启动项目【前台运行的方式】,再通过自己的外网IP访问
-
后台运行springboot项目:nohup java -jar xxx.jar >> log.log &
nohup:后台运行项目的指令
使用 >> log.log 将运行的日志记录到 log.log中,PS: log.log前缀名可以随意改变
& 表示 一直运行文章来源:https://www.toymoban.com/news/detail-438771.html
- 进行相应的功能检查,并修改
- springboot项目更新
1.先通过 ps -ef | grep java 查询当前的springboot项目的进程,然后 kill 【进程ID】kill掉
命令说明:
ps : Linux 当中查看进程的命令
-e 代表显示所有的进程
-f 代表全格式【显示全部的信息】
grep : 全局正则表达式
2.将原服务器上的jar包删除掉,重新上传新的jar包
3.重新进行后台的启动文章来源地址https://www.toymoban.com/news/detail-438771.html
十、项目拓展方向
- 检测文件是否为mp3文件
- 实现对音乐的评论,对评论的点赞和回复功能
- 新增MV功能
- 新增创建歌单的功能
- 自己实现一个流媒体服务器,使用流媒体协议对歌曲进行播放
到了这里,关于【 在线音乐平台(onlinemusic) 】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!