前言:

这是我的第一个 SSM 项目 —— BY 音乐,所涉及到的技术:Spring、SpringBoot、SpringMVC、MyBatis、BCrypt 加密、自定义拦截器、HTML、CSS、JavaScrip、jquery、ajax …… 如项目有问题 or 改进方案随时下方留言,感谢支持 !!!

目录:

  • 1、创建 springBoot 项目
  • 2、设计数据库
  • 3、在 springboot 项目中的配置问价中引入基本配置
  • 4、实现登录功能
    • 4.1、实现 model 层
    • 4.2、定义操作数据库的抽象方法
    • 4.3、在 UserMapper.xml 文件中实现具体的数据库操作语句
    • 4.4、约定前后端接口
    • 4.5、使用一个单独的类封装响应 —— ResponseBodyMessage
    • 4.6、利用一个单独的类保存 session 中的 key
    • 4.7、实现登录框架
    • 4.8、有关密码加密问题
      • ①、MD5 加密
      • ②、BCrypt 加密
      • ③、二者对比
    • 4.9、前端代码
  • 5、实现注册功能
    • 5.1、定义操作数据库的抽象方法
    • 5.2、在 UserMapper.xml 文件中实现具体的数据库操作语句
    • 5.3、约定前后端接口
    • 5.4、实现注册框架
    • 5.5、前端代码
  • 6、实现修改密码功能
    • 6.1、定义操作数据库的抽象方法
    • 6.2、在 UserMapper.xml 文件中实现具体的数据库操作语句
    • 6.3、约定前后端接口
    • 6.4、实现修改密码框架
    • 6.5、前端代码
  • 7、实现退出功能
    • 7.1、实现退出框架
    • 7.2、前端代码
  • 8、实现上传音乐功能
    • 8.1、定义操作数据库的抽象方法
    • 8.2、在MusicMapper.xml 文件中实现具体的数据库操作语句
    • 8.3、约定前后端接口
    • 8.4、实现上传音乐框架 + mp3 文件校验
    • 8.5、前端代码
  • 9、实现搜索音乐功能(单个 or 全部)
    • 9.1、定义操作数据库的抽象方法
    • 9.2、在 MusicMapper.xml 文件中实现具体的数据库操作语句
    • 9.3、约定前后端接口
    • 9.4、实现搜索音乐框架
    • 9.5、前端代码
  • 10、实现播放音乐 + 功能
    • 10.1、约定前后端接口
    • 10.2、实现播放音乐框架
    • 10.3、前端代码
  • 11、实现删除单个 / 多个功能
    • 11.1、定义操作数据库的抽象方法
    • 11.2、在 MusicMapper.xml 文件中实现具体的数据库操作语句
    • 11.3、约定前后端接口
    • 11.4、实现删除单个 / 多个框架
    • 11.5、前端代码
  • 12、实现收藏功能
    • 12.1、定义操作数据库的抽象方法
    • 12.2、在 LoveMusicMapper.xml 文件中实现具体的数据库操作语句
    • 12.3、约定前后端接口
    • 12.4、实现收藏框架
    • 12.5、前端代码
  • 13、实现搜索收藏功能
    • 13.1、定义操作数据库的抽象方法
    • 13.2、在 LoveMusicMapper.xml 文件中实现具体的数据库操作语句
    • 13.3、约定前后端接口
    • 13.4、实现搜索收藏框架
    • 13.5、前端代码
  • 14、实现删除收藏功能
    • 14.1、定义操作数据库的抽象方法
    • 14.2、在 LoveMusicMapper.xml 文件中实现具体的数据库操作语句
    • 14.3、约定前后端接口
    • 14.4、实现删除收藏框架
    • 14.5、前端代码
  • 15、配置拦截器
  • 16、部署到云服务器

项目概览:




1、创建 springBoot 项目

⭐ 创建步骤如下:



2、设计数据库

⭐ 首先:数据库名命名为 ——> onlinemusic,此数据库中需要存储三张表分别是:

  • user 表 : 存储用户信息
  • music 表:存储音乐信息
  • lovemusic 表:存储收藏音乐的信息
drop database if exists onlinemusic;
create database if not exists onlinemusic character set utf8;
use onlinemusic;drop table if exists user;
create table user(id int primary key auto_increment, username varchar(20) not null, `password` varchar(255) not null);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);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);

3、在 springboot 项目中的配置问价中引入基本配置

⭐ 具体配置文件:(# 后的注释【部署到服务器上的配置】)

#配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.jdbc.Driver#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTC
#spring.datasource.username=root
#spring.datasource.password=@baiyang2001
#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
#基本日志
music.local.path=D:/music/
#music.local.path=/root/music/
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

4、实现登录功能

4.1、实现 model 层

⭐ 这里的 user 类中的属性名和数据库中的字段名保持一致

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

4.2、定义操作数据库的抽象方法

⭐ 创建实现操作的抽象方法:login

@Mapper
public interface UserMapper {public User login(User loginUser);
}

4.3、在 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.musicserver.mapper.UserMapper"><select id="login" resultType="com.example.musicserver.model.User">select * from user where username = #{username} and password = #{password}</select>
</mapper>

4.4、约定前后端接口

一、请求:

  • 1、请求方式、请求路径。
  • 2、响应的状态码(约定 0 为成功、-1 为失败)、登录提示、返回给前端的数据(数据类型为 json)


4.5、使用一个单独的类封装响应 —— ResponseBodyMessage

⭐ 这里使用的是泛型类:用于返回不同类型的 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;}
}

4.6、利用一个单独的类保存 session 中的 key

⭐ 单独使用一个类来表示 session 中的 key 防止 key 名字过长拼错,这样就可以在其他类直接调用不用手动拼写,减少错误发生

public class Constant {public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}

4.7、实现登录框架 —— UserController + UserService

⭐ 先实现 UserService 实现具体的登录实现功能,这里使用到了 BCrypt 加密

@Service
public class UserService {@Autowiredprivate UserMapper userMapper;BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();public ResponseBodyMessage login(@RequestParam String username, @RequestParam String password,HttpServletRequest request){User user = userMapper.selectByName(username);if(user == null){return new ResponseBodyMessage<>(-1,"登录失败",user);}else{boolean flg = bCryptPasswordEncoder.matches(password,user.getPassword());if(!flg){return new ResponseBodyMessage<>(-1,"用户名或密码错误",user);}request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);return new ResponseBodyMessage<>(0,"登录成功",user);}}
}

⭐UserController 只去实现方法调用即可:

@RequestMapping("/login")public ResponseBodyMessage login(@RequestParam String username, @RequestParam String password,HttpServletRequest request){return userService.login(username,password,request);}

4.8、有关密码加密问题

⭐ 对用户登录的密码进行加密:

①、MD5 加密

MD5是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程不可逆; MD5是一种算法,可以从任何密码,短语或文本中生成32个字符的十六进制字符串例如,如果您的密码是“ qwerty”(不好的主意),则在数据库中您将拥 d8578edf8458ce06fbc5bb76a58c5ca4。但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。

不安全的原因:

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

更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。这里我们介绍加盐的做法:盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中

⭐ 给密码加盐、可以是静态盐、也可以是动态盐(使用当前账户创建日期作为盐值这样就能保证盐值看起来是随机的),下面进行静态固定盐值的一个演示:

⭐需要引入 md5 依赖:

<!-- 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>
/**
* @Author GaoBo
* @Description:
*/
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 inputPass* @param saltDB
* @return
*/
public static String inputPassToDbPass(String inputPass, String saltDB) {String formPass = inputPassToFormPass(inputPass);
String dbPass = formPassToDBPass(formPass, saltDB);
return dbPass;
}
public static void main(String[] args) {System.out.println("对用户输入密码进行第1次加密:"+inputPassToFormPass("123456"));
System.out.println("对用户输入密码进行第2次加密:"+formPassToDBPass(inputPassToFormPass("123456"),
"1b2i3t4e"));
System.out.println("对用户输入密码进行第2次加密:"+inputPassToDbPass("123456", "1b2i3t4e"));
}
}

⭐不管运行多少次,这个密码是固定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。

②、Bcrypt加密数据

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

⭐在 pom.xml 中添加 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 启动类添加:(项目中没有使用到 spring security 这个框架、只是使用到了该框架下的一个类)

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

⭐ 总结:

  1. 密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
  2. 使用 BCrypt 相比于 MD5 加密更好的一点在于,破解的难度上加大
  3. BCrypt的破解成本增加了,导致系统的运行成本也会大大的增加 。
  4. 回到本质的问题,你的数据库中的数据价值如何?如果你是银行类型的,那么使用BCrypt是不错的,一般情况使用MD5加盐,已经够用了。
③、二者对比:
  • BCrypt加密: 一种加盐的单向Hash,不可逆的加密算法,同一种明文(plaintext),每次加密后的密文都不一样,而且不可反向破解生成明文,破解难度很大。
  • MD5加密: 是不加盐的单向Hash,不可逆的加密算法,同一个密码经过hash的时候生成的是同一个hash值,在大多数的情况下,有些经过md5加密的方法将会被破解。
  • Bcrypt生成的密文是60位的。而MD5的是32位的。目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全,但加密更慢。 虽然BCrpyt也是输入的字符串+盐,但是与MD5+盐的主要区别是:每次加的盐不同,导致每次生成的结果也不相同。无法比对!
⭐ postman 验证:



4.9、前端代码

⭐ 只展示 js 代码:

<script>$(function(){$("#submit").click(function(){var username=$("#user").val();var password=$("#password").val();$.ajax({url:"/user/login",//指定路径data:{"username":username,"password":password},type:"POST",dataType:"json",//服务器返回数据为jsonsuccess:function (data) {console.log(data);if(data.status==0){alert("登录成功!");window.location.href="index.html";}else{alert("用户名密码错误");$("#user").val("");$("#password").val("");}}})})})

5、实现注册功能

5.1、定义操作数据库的抽象方法

int insertUser(String username, String password);

5.2、在 UserMapper.xml 文件中实现具体的数据库操作语句

<insert id="insertUser">insert into user (username,password) values (#{username},#{password})</insert>

5.3、约定前后端接口


5.4、实现注册框架

⭐ 实现 service 层:

public ResponseBodyMessage<Boolean> register(@RequestParam String username, @RequestParam String password){User user = userMapper.selectByName(username);if(user == null){password = bCryptPasswordEncoder.encode(password);int ret = userMapper.insertUser(username,password);if(ret == 1){return new ResponseBodyMessage<>(0,"注册成功",true);}else{return new ResponseBodyMessage<>(-1,"注册失败",false);}}else{return new ResponseBodyMessage<>(-1,"该账户已经存在",false);}}

⭐ 实现 controller 层:

@RequestMapping("/register")public ResponseBodyMessage<Boolean> register(@RequestParam String username, @RequestParam String password){return userService.register(username,password);}

5.5、前端代码

$(function(){$("#rsb").click(function(){var username = $("#newUsername").val();var password = $("#newPassword").val();var repassword = $("#repassword").val();if(password != repassword){alert("两次输入的密码不一致");$("#newPassword").val("");$("#repassword").val("");return;}$.ajax({url:"/user/register",type:"post",data:{"username":username,"password":password},dataType:"json",success:function(val){console.log(val);if(val.data == true){alert("注册成功,请进行登录");window.location.href="login.html";}else{alert("该账户已存在");$("#newUsername").val("");$("#newPassword").val("");$("#repassword").val("");return;}}});})})

6、修改密码功能

6.1、定义操作数据库的抽象方法

int updatePassword(String username, String password);

6.2、在 UserMapper.xml 文件中实现具体的数据库操作语句

<update id="updatePassword">update user set password=#{password} where username = #{username}
</update>

6.3、约定前后端接口


6.4、实现修改密码框架

⭐ service 层,实现修改密码的功能:

public ResponseBodyMessage<Boolean> update(@RequestParam String username, @RequestParam String password){User user = userMapper.selectByName(username);if(user == null){return new ResponseBodyMessage<>(-1,"当前用户不存在无法修改",false);}else{password = bCryptPasswordEncoder.encode(password);int ret = userMapper.updatePassword(username,password);if(ret == 1){return new ResponseBodyMessage<>(0,"修改成功",true);}else{return new ResponseBodyMessage<>(-1,"无法修改",false);}}}

⭐ 实现 controller 层,调用方法:

@RequestMapping("/updatepassword")public ResponseBodyMessage<Boolean> update(@RequestParam String username, @RequestParam String password){return userService.update(username,password);}

6.5、前端代码

$(function(){$("#usb").click(function(){var username = $("#username").val();var password = $("#password").val();var newPassword = $("#newPassword").val();if(password != newPassword){alert("两次输入密码不一致");$("#password").val("");$("#newPassword").val("");return;}$.ajax({type:"post",url:"/user/updatepassword",data:{"username":username,"password":password},dataType:"json",success:function(val){if(val.data == true){alert("修改成功");window.location.href = "login.html";}else{alert("修改失败");$("#password").val("");$("#newPassword").val("");$("#username").val("");return;}}});})})

7、实现退出功能

7.1、实现退出框架

⭐ 删除服务器中存储的用户登录信息(session)后重定向到登录页面,先实现 service 层

public ResponseBodyMessage<Boolean> exit(HttpServletRequest request, HttpServletResponse response) throws IOException {HttpSession session = request.getSession(false);session.removeAttribute(Constant.USERINFO_SESSION_KEY);response.sendRedirect("/login.html");return new ResponseBodyMessage<>(0,"退出成功",true);}

⭐再实现 controller 层实现方法的调用:

@RequestMapping("/exit")public ResponseBodyMessage<Boolean> exit(HttpServletRequest request, HttpServletResponse response) throws IOException {return userService.exit(request,response);}

7.2、前端代码

⭐前端代码就是一个 a 标签:向目标服务器发送退出请求即可

<a href="/user/exit">

8、实现上传音乐功能

8.1、定义操作数据库的抽象方法

int insert(String title, String singer, String time, String url, int userId);
Music select(String title, String singer);

8.2、在 MusicMapper.xml 文件中实现具体的数据库操作语句

<insert id="insert">insert into music(title,singer,time,url,userId) values(#{title},#{singer},#{time},#{url},#{userId})</insert>

8.3、约定前后端交互接口


8.4、实现上传音乐框架 + MP3 文件校验

⭐先创建一个 music 实体类:

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

上传音乐有一个注意事项:需要分为服务器上传和数据库上传

⭐ 提前先注入配置文件中的文件保存路径:

@Value("${music.local.path}")private String SAVE_PATH;

①、服务器的上传(musicService 中完成)

先检查用户是否登录,如果为登录提示未登录,登录后再进行后续的判断:

HttpSession session = request.getSession(false);if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null){System.out.println("没有登录 !!! ");return new ResponseBodyMessage<>(-1,"请登录后上传",false);}

先获取到文件的文件名 + 后缀名:

//获取文件名 xx.mp3String fileNameAndType = file.getOriginalFilename();System.out.println("fileName:" + fileNameAndType);

进行文件名的拼接:和保存路径拼接成完整的路径名:

String path = SAVE_PATH + fileNameAndType;

去 new 这个文件:

File dest = new File(path);

如果 dest 不存在,就创建一个文件夹,存储此路径的文件夹就使用其即可

if(!dest.exists()){dest.mkdir();}

上传文件到目标位置:

try {file.transferTo(dest);return new ResponseBodyMessage<>(0,"上传成功",true);} catch (IOException e) {e.printStackTrace();}

如何判断上传的文件是 MP3 格式的

判断这个文件是不是 MP3 文件,每个文件都有自己的组成格式,可以通过代码判断上传的音频文件中总共的 128 字节中,有3 字节有一个 TAG 标签

具体实现:读取 file 中的数据、判断是否存在 “TAG” 标签:

InputStream is = file.getInputStream();InputStreamReader isReader = new InputStreamReader(is, StandardCharsets.UTF_8);BufferedReader br = new BufferedReader(isReader);//循环逐行读取String line;boolean flg = false;while ((line = br.readLine()) != null) {if(line.contains("TAG")){flg = true;}}br.close();if(flg == false){return new ResponseBodyMessage<>(-1, "文件格式错误", false);}

②、数据库的上传

创建上传接口:

@Mapper
public interface MusicMapper {//插入音乐int insert(String title, String singer, String time, String url, int userId);
}

实现 sql 语句:

<insert id="insert">insert into music(title,singer,time,url,userId) values(#{title},#{singer},#{time},#{url},#{userId})</insert>

得到各个属性:

int index = fileNameAndType.lastIndexOf(".");
String title = fileNameAndType.substring(0, index);
User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userId = user.getId();
String url = "/music/get?path=" + title;
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String time = simpleDateFormat.format(new Date());

进行插入:

int ret = 0;
ret = musicMapper.insert(title, singer, time, url, userId);
if (ret == 1) {return new ResponseBodyMessage<>(0, "上传成功", true);
} else {return new ResponseBodyMessage<>(-1, "上传失败", false);
}

⭐如果存在同名同歌手的情况下,不能进行插入:现在数据库中进行查询,同 singer 同 title 的歌曲有没有存在,如果存在就不进行插入:

<select id="select" resultType="com.example.musicserver.model.Music">select * from music where title = #{title} and singer = #{singer}</select>
Music music = musicMapper.select(title,singer);if(music != null){return new ResponseBodyMessage<>(-1, "已有同名同歌手的歌曲", false);}

⭐最后在controller 中进行调用:

@RequestMapping("/upload")public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer,@RequestParam(value = "filename") MultipartFile file,HttpServletRequest request,HttpServletResponse response) throws IOException {return musicService.insertMusic(singer,file,request,response);}

8.5、前端代码

<form action="/music/upload" method="post" enctype="multipart/form-data"><div class="login-dialog"><div><span style="margin-left: 165px;">文件上传</span><input type="file" name="filename" style="margin-left: 100px;margin-top: 15px;"></div><div class="row"><span style="margin-left: 90px;margin-top: 20px;">歌手名</span><label><input type="text" id="username" name="singer" placeholder="请输入歌手名" style="margin-top: 20px;"></label></div><div class="row"><input type="submit" id="submit" value="确认上传" style="margin-left: 30px;"></div></div></div></form>

9、实现搜索音乐功能(单个 or 全部)

9.1、定义操作数据库的抽象方法

⭐ 两种查询,第一种默认没有参数是查询所有音乐,第二种是进行模糊查询,查询带关键字的歌曲:

List<Music> findMusic();
List<Music> findMusicByName(String name);

9.2、在 UserMapper.xml 文件中实现具体的数据库操作语句

<select id="findMusic" resultType="com.example.musicserver.model.Music">select * from music;</select><select id="findMusicByName" resultType="com.example.musicserver.model.Music">select * from music where title like concat('%',#{name},'%')</select>

9.3、约定前后端交互接口

9.4、实现搜索音乐框架

⭐ 在 MusicService 中进行实现:两种查询音乐的方式

@RequestMapping("findmusic")public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String name){List<Music> music = null;if(name == null){music = musicMapper.findMusic();}else {music = musicMapper.findMusicByName(name);}return new ResponseBodyMessage<>(0,"查找成功",music);}


⭐ 最后在 MusicController 中调用方法:

@RequestMapping("/findmusic")public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String name){return musicService.findMusic(name);}

9.5、前端代码

⭐ 这里查询音乐(无论是多个还是单个都是需要动态去进行拼接的),所以这里采取 js 的原生拼接方法,去动态生成音乐列表的音乐:(两种 load 方式一个是有名字、一个没有名字)

$(function(){load();});function load(musicname){$.ajax({url:"/music/findmusic",data:{"name":musicname},dataType:"json",type:"get",success:function(obj){console.log(obj);var data = obj.data;var s = '';for(var i = 0; i < data.length; i++){s += '<div class="d-block d-md-flex podcast-entry bg-white mb-5" data-aos="fade-up">';s += '<img src="data:images/R-C.jfif" class="image">';s += '<div class="text">';s += '<input type="checkbox" class="checkbox" style="margin-left: 710px;" id="'+data[i].id+'">';s += '<h3 class="font-weight-light">' + data[i].title +'</h3>';s += '<div class="text-white mb-3"><span class="text-black-opacity-05"><small>' + data[i].singer + '</small><span class="sep">/</span>' + DateFormat(data[i].time)+'</small></span></div>';s += '<input type="button" class="btn btn-primary" style="margin-right: 20px;" value="收藏音乐" onclick="collectMusic(\''+ data[i].id + '\')">';s += '<input type="button" class="btn btn-primary" style="margin: 20px;" value="删除音乐" onclick="deleteMusic(\'' + data[i].id + '\')">';s += '<div class="player">';s += '<audio id="player2" preload="none" controls style="max-width: 100%">';s += '<source src="'+data[i].url + '.mp3' + '" type="audio/mp3">'s+= '</audio></div></div></div>';}$('#MusicList').html(s);}});}
$(function(){$("#search").click(function(){var name = $("#musictitle").val();load(name);});

10、 播放音乐模块设计

10.1、约定前后端接口


10.2、实现播放音乐框架

⭐借助 ResponseEntity<byte[]> 类实现:返回音乐的字节信息,前端传来文件的 path(文件名 + 文件类型),返回字节给前端,前端再进行解析进行音乐的播放

@RequestMapping("/get")public ResponseEntity<byte[]> func(String path){File file = new File(SAVE_PATH + "/" + path);byte[] a = null;try {a = Files.readAllBytes(file.toPath());if(a == null){return ResponseEntity.badRequest().build();}return ResponseEntity.ok(a);} catch (IOException e) {e.printStackTrace();}return ResponseEntity.badRequest().build();}

⭐ 验证文件为音乐文件:

⭐ 插入一个文本文件(通过修改 MP3 后缀名使得 txt 文件成文 MP3 文件),可以发现是查找不到 TAG 标签的

10.3、前端代码

s += '<source src="'+data[i].url + '.mp3' + '" type="audio/mp3">'

11、实现删除单个 / 多个功能

11.1、定义操作数据库的抽象方法

int deleteById(Integer id);
Music selectById(Integer id);

11.2、在 MusicMapper.xml 文件中实现具体的数据库操作语句

<delete id="deleteById">delete from music where id = #{id};</delete>

11.3、约定前后端交互接口


11.4、实现删除单个 / 多个框架

①、删除单个:

⭐ 先查询在数据库中有没有需要删除的歌曲 id:先判断需要删除的音乐是否存在

⭐ 在 MusicService 类里进行具体的实现:(注意这里删除,收藏音乐也是要跟着一起进行删除的)

 @RequestMapping("delete")public ResponseBodyMessage<Boolean> deleteMusicById(@RequestParam String id){int id1 = Integer.parseInt(id);Music music = musicMapper.selectById(id1);//检测要删除的 音乐 是否存在if(music == null){return new ResponseBodyMessage<>(-1,"没有此音乐,无法删除",false);}//进行数据库删除int ret = 0;ret = musicMapper.deleteById(id1);if(ret == 1){//数据库删除后 再进行服务器的删除File file = new File(SAVE_PATH + "/" + music.getTitle());if(file.delete()){return new ResponseBodyMessage<>(0,"服务器删除成功",true);}else{return new ResponseBodyMessage<>(0,"服务器删除失败",true);}}else{return new ResponseBodyMessage<>(-1,"删除失败",false);}}

⭐ 在 MusicController 类中调用方法:

@RequestMapping("/delete")public ResponseBodyMessage<Boolean> deleteMusicById(@RequestParam String id){return musicService.deleteMusicById(id);}
②、删除多个:(删除多个音乐是需要传入多个 id 进行删除,需要使用到一个 id 的集合,遍历这个集合、进行上面的单个删除操作,最后进行判断是否删除了约定集合中的 id 个数)
public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]") List<Integer> id){int sum = 0;for (int i = 0; i < id.size(); i++) {Music music = musicMapper.selectById(id.get(i));if(music == null){return new ResponseBodyMessage<>(-1,"没有此音乐,无法删除",false);}//进行数据库删除int ret = 0;ret = musicMapper.deleteById(id.get(i));if(ret == 1){//数据库删除后 再进行服务器的删除File file = new File(SAVE_PATH + "/" + music.getTitle() + ".mp3");if(file.delete()){sum += ret;loveMusicMapper.deleteLoveMusicBymusicId(music.getId());}else{return new ResponseBodyMessage<>(0,"批量删除失败",true);}}else{return new ResponseBodyMessage<>(-1,"批量删除失败",false);}}if(sum == id.size()){return new ResponseBodyMessage<>(0,"服务器删除成功",true);}else{return new ResponseBodyMessage<>(0,"批量删除失败",false);}}

⭐ 在 MusicController 中进行调用:

@RequestMapping("/deleteSel")public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]") List<Integer> id){return musicService.deleteSelMusic(id);}

11.5、前端代码

①、删除单个的前端代码:
function deleteMusic(obj){console.log(obj);$.ajax({url:"/music/delete",type:"post",data:{"id" : obj},dataType:"json",success:function(val){console.log(val);if(val.data == true){alert("删除成功,重新加载当前页面");window.location.href = "index.html";}else{alert("删除失败")}}});}
①、删除多个的前端代码:(需要遍历复选框),需要等待 load 加载完毕再可以进行删除,需要使用到 when,删除多选是一个单独的按钮,当点击删除多选后触发一个点击时间,把复选框勾住的 id 都放到一个数组里面
$.when(load).done(function(){$("#delete").click(function(){var id = new Array();var i = 0;$("input:checkbox").each(function(){if($(this).is(":checked")){id[i] = $(this).attr("id");i++;}});console.log(id);$.ajax({url:"/music/deleteSel",type:"post",data:{"id":id},dataType:"json",success:function(obj){if(obj.data == true){alert("批量删除成功");window.location.href = "index.html";}else{alert("批量删除失败");}}});});});});

12、实现收藏音乐的功能

12.1、定义操作数据库的抽象方法

⭐ 三个方法:查询收藏音乐是否在表中,如果在收藏列表进行收藏删除操作,如果不在进行收藏插入操作:

Music findLoveMusicByMusicIdAndUserId(int userId, int musicId);
boolean insertLoveMusic(int userId, int musicId);
boolean deleteLoveMusic(int userId, int musicId);

12.2、在 LoveMusicMapper.xml 文件中实现具体的数据库操作语句

<select id="findLoveMusicByMusicIdAndUserId" resultType="com.example.musicserver.model.Music">select * from lovemusic where music_id = #{musicId} and user_id = #{userId}</select>
<insert id="insertLoveMusic">insert into lovemusic (user_id,music_id) values(#{userId},#{musicId})</insert>
<delete id="deleteLoveMusic">delete from lovemusic where music_id = #{musicId} and user_id = #{userId}</delete>

12.3、约定前后端接口

12.4、实现收藏框架

⭐ 在 LoveMusicService 中实现:

public ResponseBodyMessage<Boolean> insertLoveMusic(@RequestParam String id, HttpServletRequest request){int musicId = Integer.parseInt(id);HttpSession session = request.getSession(false);if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null){return new ResponseBodyMessage<>(-1,"当前尚未登录",false);}User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);int userId = user.getId();Music music = loveMusicMapper.findLoveMusicByMusicIdAndUserId(userId,musicId);if(music != null){boolean flg = loveMusicMapper.deleteLoveMusic(userId,musicId);if(flg){return new ResponseBodyMessage<>(1,"取消收藏成功",true);}else{return new ResponseBodyMessage<>(-1,"取消收藏失败",false);}}else{boolean ret = loveMusicMapper.insertLoveMusic(userId,musicId);if(!ret){return new ResponseBodyMessage<>(-1,"当前歌曲收藏失败",false);}else{return new ResponseBodyMessage<>(1,"当前歌曲成功收藏",true);}}}

⭐ LoveMusicController 中进行调用:

@RequestMapping("/likemusic")public ResponseBodyMessage<Boolean> insertLoveMusic(@RequestParam String id, HttpServletRequest request){return loveMusicService.insertLoveMusic(id,request);}

⭐postman 验证:

12.5、前端代码

function collectMusic(obj){$.ajax({type:"post",url:"/lovemusic/likemusic",data:{"id":obj},dataType:"json",success:function(val){if(val.data == true){alert("收藏成功");window.location.href = "index.html";}else{console.log(val);alert("收藏失败");}}});}

13、实现搜索收藏功能

13.1、定义操作数据库的抽象方法

List<Music> findLoveMusicByName(String musicName, int userId);
List<Music> findLoveMusic(int userId);

13.2、在 LoveMusicMapper.xml 文件中实现具体的数据库操作语句

⭐ 需要进行一个多表联合查询:

<select id="findLoveMusicByName" resultType="com.example.musicserver.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><select id="findLoveMusic" resultType="com.example.musicserver.model.Music">select m.* from lovemusic lm,music m where m.id = lm.music_id and lm.user_id = #{userId}</select>

13.3、约定前后端交互接口

13.4、实现搜索收藏框架

public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String musicName, HttpServletRequest request){HttpSession session = request.getSession(false);if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null){return new ResponseBodyMessage<>(-1,"当前用户尚未登录",null);}User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);int id = user.getId();List<Music> music = null;if(musicName == null){music = loveMusicMapper.findLoveMusic(id);}else{music = loveMusicMapper.findLoveMusicByName(musicName,id);}return new ResponseBodyMessage<>(1,"查询成功",music);}

13.5、前端代码

⭐和音乐列表页代码基本一致:

 $(function(){load();});function load(musicname){$.ajax({url:"/lovemusic/findlovemusic",data:{"musicName":musicname},dataType:"json",type:"get",success:function(obj){console.log(obj);var data = obj.data;var s = '';for(var i = 0; i < data.length; i++){s += '<div class="d-block d-md-flex podcast-entry bg-white mb-5" data-aos="fade-up">';s += '<img src="data:images/R-C.jfif" class="image">';s += '<div class="text">';// s += '<input type="checkbox" class="checkbox" style="margin-left: 525px;" id="'+data[i].id+'">';s += '<h3 class="font-weight-light">' + data[i].title +'</h3>';s += '<div class="text-white mb-3"><span class="text-black-opacity-05"><small>' + data[i].singer + '</small><span class="sep">/</span>' + DateFormat(data[i].time)+'</small></span></div>';s += '<input type="button" class="btn btn-primary" style="margin-bottom: 20px;" value="移除收藏" οnclick="deleteMusic(\'' + data[i].id + '\')">';s += '<div class="player">';s += '<audio id="player2" preload="none" controls style="max-width: 100%">';s += '<source src="'+data[i].url + '.mp3' + '" type="audio/mp3">'s+= '</audio></div></div></div>';}$('#MusicList').html(s);}});}$(function(){$("#search").click(function(){var name = $("#musictitle").val();load(name);});});

14、实现删除收藏功能

14.1、实现删除收藏功能

int deleteLoveMusicBymusicId(int musicId);

14.2、在 LoveMusicMapper.xml 文件中实现具体的数据库操作语句

<delete id="deleteLoveMusicBymusicId">delete from lovemusic where music_id = #{musicId}</delete>

14.3、约定前后端交互接口

⭐ 和之前的删除一致,这里不过多展示


14.4、实现删除收藏框架

public ResponseBodyMessage<Boolean> deletelovemusicById(@RequestParam String id,HttpServletRequest request){int musicId = Integer.parseInt(id);
//没有session不创建HttpSession httpSession = request.getSession(false);if(httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {System.out.println("没有登录!");return new ResponseBodyMessage<>(-1,"没有登录",false);}User user = (User)httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);int userId = user.getId();boolean flg = loveMusicMapper.deleteLoveMusic(userId,musicId);if(flg){return new ResponseBodyMessage<>(1,"取消收藏成功",true);}else{return new ResponseBodyMessage<>(-1,"取消收藏失败",false);}}

⭐ 在 LoveMusicService 中调用:

@RequestMapping("/deletelovemusic")public ResponseBodyMessage<Boolean> deletelovemusicById(@RequestParam String id,HttpServletRequest request){return loveMusicService.deletelovemusicById(id,request);}

14.5、前端代码

function deleteMusic(obj){console.log(obj);$.ajax({url:"/lovemusic/deletelovemusic",type:"post",data:{"id" : obj},dataType:"json",success:function(val){console.log(val);if(val.data == true){alert("删除成功,重新加载当前页面");window.location.href = "contact.html";}else{alert("删除失败")}}});}

15、配置拦截器

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession(false);if(session != null && session.getAttribute(Constant.USERINFO_SESSION_KEY) != null){System.out.println("登录成功");return true;}response.sendRedirect("login.html");return false;}
}

⭐ 设置拦截规则:去除一些页面的 js 、 css 、还有一些不需要登录也可以进行访问的页面:

@Configuration
public class AppConfig implements WebMvcConfigurer {@Beanpublic BCryptPasswordEncoder getBCryptPasswordEncoder(){return new BCryptPasswordEncoder();}@Overridepublic 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("/user/login").excludePathPatterns("/user/register").excludePathPatterns("/user/updatepassword").excludePathPatterns("/modif.html");}
}

16、部署到服务器

⭐ 首先需要修改两个配置:连接 Linux 服务器上的数据库、修改音乐的保存位置

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=@baiyang2001
spring.datasource.driver-class-name=com.mysql.jdbc.Drivermusic.local.path=/root/music/

⭐ package 打包成 jar 包、拷贝到云服务下:


⭐ 把本地数据库初始化的 sql 文件内容拷贝到云服务上的数据库里


⭐ java -jar +你打包的 jar 包名字:运行你的 jar 文件,此时如果你关闭服务器项目通过外网 ip 就不能访问了。


⭐ 让项目在服务器后台运行,保证关闭服务器也能通过外网 ip 访问项目,通过以下指令:

nohup java -jar xxx.jar >> log.log &

在线音乐播放项目——BY音乐相关推荐

  1. php 在线调用音乐播放器,宅音乐播放器,HTML5网页播放器,带后台管理及API调用,使用thinkphp编写...

    宅音乐播放器 宅音乐播放器,HTML5网页播放器,集成后台管理及API调用,目前正在开发中,敬请关注 技术栈 后端:thinkphp 5.1 前端:layui 数据库:mysql 演示 整合依赖安装包 ...

  2. mac音乐播放器QQ音乐好用吗?vip绿钻的QQ音乐有哪些版本优势?

    mac音乐播放器QQ音乐好用吗?当然!QQ音乐是Mac平台用户体验极佳的音乐播放器,最新最热的排行榜.歌单.电台.MV天天推荐,智能音乐搜索.猜你喜欢帮你轻松发现音乐.更何况是不需要绿钻和付费包,VI ...

  3. 使用android studio时酷狗音乐,17 Android Studio开发实战:音乐播放器——浪花音乐...

    手机上的多媒体内容讲究声情并茂.悦目且悦耳,这样才能让用户的感官得到最大享受.影视播放器由于存在视频自身的画面,反而限制了开发者的施展空间:而音乐播放器允许定制播放画面,开发者有足够空间施展拳脚.本节 ...

  4. iOS开发之网络音乐播放器(SC音乐)(二)

    iOS开发之网络音乐播放器(SC音乐)(二) 前言 iOS开发之网络音乐播放器(SC音乐)(一)已经介绍完播放控制.音乐数据获取解析.歌词显示等.本文在上文的基础上介绍锁屏播放设置,后台播放设置,手势 ...

  5. 【毕业设计】28-基于单片机的音乐播放器简易音乐播放器设计(原理图+源代码+仿真工程+答辩PPT+答辩论文)

    typora-root-url: ./ [毕业设计]28-基于单片机的音乐播放器简易音乐播放器设计(原理图+源代码+仿真工程+答辩PPT+答辩论文) 文章目录 typora-root-url: ./ ...

  6. Android获取第三方音乐播放器的音乐信息

    最近在做Android手机获取第三方音乐播放器的音乐信息.一开始头疼的很,采集第三方的信息太难了,后面看了一遍博文是关于怎么监听系统的音乐播放信息,发现在播放下一首音乐的时候会发送广播,广播会包含下一 ...

  7. PC端网易云音乐播放云盘音乐时显示加载失败,自动调转下一首的解决方法

    PC端网易云音乐播放云盘音乐时显示加载失败,自动调转下一首解决方法 注意: 一定要看看是不是和你的情况一样,不一样不要用这种方法!!! 具体情况: 我们经常会下载歌曲存到电脑文件夹里,然后通过网易云音 ...

  8. php文件添加音乐播放器,window_win10系统自带Groove音乐播放器在哪?自带Groove音乐播放器添加音乐等功能的使用教程,   播放器在哪?1 - phpStudy...

    win10系统自带Groove音乐播放器在哪?自带Groove音乐播放器添加音乐等功能的使用教程 播放器在哪? 1.点击桌面的左下端"开始菜单"符号,然后在右上角,找到" ...

  9. html中加入音乐播放器,HTML网页添加音乐播放器做背景音乐代码-标签audio

    是 HTML 5 的一个新标签,定义声音,比如音乐或其他音频流. 调用格式: src="http://sc1.111ttt.com/2016/1/02/04/195040016323.mp3 ...

最新文章

  1. vscode中设置.mina语法高亮
  2. java画板抽象类_java 中的 抽象方法 抽象类 和 接口有啥瓜葛
  3. 计算机基础知识预备知识,计算机预备知识详解.ppt
  4. 脑洞大开!拿Transformer和CNN比较!犯错都像人类
  5. php redis type,redis中的几种常用基础对象介绍
  6. Web前端初学者,需用了解的7大HTML知识点
  7. 如何通过 Redis 实现分布式锁
  8. 手把手带你玩转Spark机器学习-使用Spark进行文本处理
  9. 【math】 向量运算:叉乘
  10. 计算机电路基础 - 1,计算机电路基础1.1.doc
  11. python实现图片切割及拼图游戏
  12. RL(Chapter 5): Monte Carlo Methods (MC) (蒙特卡洛方法)
  13. CentOS简单上手——第四篇
  14. matlab受力分析,基于Matlab的多支座蒸压釜的受力分析和强度计算
  15. E900V21C安装Linux系统(Armbian)
  16. 专升本管理学知识点总结——目标管理
  17. Eclipse中快捷键Ctrl + Alt + 向上箭头 或者 Ctrl + Alt + 向下箭头与Windows冲突
  18. 使用vue,实现前端导入excel数据
  19. 2009雷人语录最全
  20. zxing.net 中文乱码,重新设置字符集完美解决

热门文章

  1. 医院运维管理平台(模板)
  2. Axure 如何在页面加载时,设置文本框的内容为当前日期
  3. nginx 之 http 转 https (两种方式)
  4. js获取当前日期,并且转化为时间格式“yyyy-MM-dd HH:MM:SS”
  5. 惊心动魄修复U盘【另附U盘量产工具】(显示文件格式为RAW 需要格式化)
  6. MATLAB车道线识别
  7. HTML春节贺卡,HTML5+CSS3实现春节贺卡
  8. php微信一次性订阅消息demo,微信一次性订阅消息
  9. 唯一能够胜过对手的,只有你的学习能力
  10. 微信登录界面安卓代码_「微信多开神器」一键安排你的所有微信