文章目录
- 1. 项目设计
- 2. 效果展示
- 3. 创建项目 配置文件
-
- 3.1 创建项目
- 3.2 配置文件
-
- 3.2.1 在 application.properties 中添加配置文件
- 3.2.2 在 resources 目录下创建mapper
- 4. 数据库的设计与实现
- 5. 交互接口的设计
- 6. 工具包
-
- 6.1 设置统一响应类
- 6.2 Constant类
- 6.3 了解 MD5 加密 和 BCrypt 加密
-
- MD5使用示例 (加盐)
- BCrypt使用示例
- 6.4 在Config中 注入 BCryptPasswordEncoder 对象
- 6.5 添加拦截器
-
- 6.5.1 LoginInterceptor 类
- 6.5.2 AppConfig 类
- 7. 登录模块
-
- 7.1 创建 User 实体类
- 7.2 使用 Mybatis 操作数据库
-
- 7.2.1 在 UserServer 中添加代码
- 7.2.2 在 UserMapper 中添加代码
- 7.2.3 在 UserMapper.xml 中添加代码
- 7.3 创建 UserConroller 添加代码
- 7.4 前端代码
- 8. 注册模块
-
- 8.1 使用 Mybatis 操作数据库
-
- 8.1.1 在 UserServer 中添加代码
- 8.1.2 在 UserMapper 中添加代码
- 8.1.3 在 UserMapper.xml 中添加代码
- 8.2 向 UserController 中添加代码
- 8.3 前端代码
- 9. 退出功能
-
- 9.1 向 UserController 中添加代码
- 9.2 登录注册测试.
- 10. 上传音乐模块
-
- 10.1 创建 Music 实体类
- 10.2 使用 Mybatis 操作数据库
-
- 10.2.1 在 MusicServer 中添加代码
- 10.2.2 在 MusicMapper 中添加代码
- 10.2.3 在 MusicMapper.xml 中添加代码
- 10.3 向 MusicController 中添加代码
- 10.4 前端代码
- 10.5 测试代码
- 11. 播放音乐模块
-
- 11.1 向 MusicController 中添加代码
- 11.2 测试代码
- 12. 删除音乐模块
-
- 12.1 使用 Mybatis 操作数据库
-
- 12.1.1 在 MusicServer 和 CollectServer 中添加代码
- 12.1.2 在 MusicMapper 和 CollectMapper 中添加代码
- 12.1.3 在 MusicMapper.xml 和 CollectMapper.xml 中添加代码
- 12.2 删除单一音乐功能
-
- 12.2.1 向 MusicController 中添加代码
- 12.2.2 前端代码
- 12.2.3 测试代码
- 12.3 删除多个音乐功能
-
- 12.3.1 向 MusicConroller 中添加代码
- 12.3.2 前端代码
- 12.3.3 测试代码
- 13. 收藏音乐模块
-
- 13.1 创建 Collect 实体类
- 13.2 使用 Mybatis 操作数据库
-
- 13.2.1 在 CollectServer 中添加代码
- 13.2.2 在 CollectMapper 中添加代码
- 13.2.3 在 CollectMapper.xml 中添加代码
- 13.3 向 CollectControll 中添加代码
- 13.4 前端代码
- 13.5 测试代码
- 14. 取消收藏音乐模块
-
- 14.1 使用 Mybatis 操作数据库
-
- 14.1.1 在 CollectServer 中添加代码
- 14.1.2 在 CollectMapper 中添加代码
- 14.1.3 在 CollectMapper.xml 中添加代码
- 14.2 向 CollectControll 中添加代码
- 14.3 前端代码
- 14.4 测试代码
- 15. 主页面 - 查询模块
-
- 15.1 使用 Mybatis 操作数据库
-
- 15.1.1 在 MusicServer 中添加代码
- 15.1.2 在 MusicMapper 中添加代码
- 15.1.3 在 MusicMapper.xml 中添加代码
- 15.2 向 MusicController 中添加代码
- 15.3 前端代码
- 15.4 测试代码
- 16. 收藏页面 - 查询模块
-
- 16.1 使用 Mybatis 操作数据库
-
- 16.1.1 在 CollectServer 中添加代码
- 16.1.2 在 CollectMapper 中添加代码
- 16.1.3 在 CollectMapper.xml 中添加代码
- 16.2 向 CollectController 中添加代码
- 16.3 前端代码
- 16.4 测试代码
1. 项目设计
前端 : HTML+CSS+JavaScript+JQuery
后端 : Spring MVC+Spring Boot+MyBatis
2. 效果展示
3. 创建项目 配置文件
3.1 创建项目
3.2 配置文件
3.2.1 在 application.properties 中添加配置文件
配置数据库
spring.datasource.url=jdbc:mysql://localhost:3306/onlinemusicserver?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
配置 Mybatis
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
配置文件上传大小
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB
配置上传的路径
upload.path=E:/logs/
3.2.2 在 resources 目录下创建mapper
mapper下添加 目录 **.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.onlinemusicserver.mapper."对应的Mapper"">
</mapper>
4. 数据库的设计与实现
这里设计数据库.
用户表
- 用户Id
- 用户账号
- 用户密码
音乐表
- 音乐Id
- 音乐名
- 音乐歌手
- 上传时间
- 存储地址
- 用户Id
收藏表
- 收藏Id
- 用户Id
- 音乐Id
drop database if exists `onlinemusicserver`;
create database `onlinemusicserver`;
use `onlinemusicserver`;
drop table if exists `user`;
create table `user`(
`userId` int primary key auto_increment,
`username` varchar(20) unique,
`password` varchar(255) not null
);
drop table if exists `music`;
create table `music`(
`musicId` int primary key auto_increment,
`title` varchar(100) not null,
`author` varchar(20) not null,
`uploadtime` timestamp default CURRENT_TIMESTAMP,
`path` varchar(1000) not null,
`userId` int not null
);
drop table if exists `collect`;
create table `collect`(
`collectId` int primary key auto_increment,
`userId` int not null,
`musicId` int not null
);
5. 交互接口的设计
上传音乐
请求
POST /music/upload HTTP/1.1
{singer, MultipartFile file}
响应
{
status: 1/-1 (1 为成功, -1 为失败),
message: "对应信息",
data: "内容"
}
收藏功能
请求
POST /collect/loveMusic HTTP/1.1
{musicId: 1}
响应
{
status: 1/-1,
message: "",
data: ""
}
取消收藏功能
请求
POST /collect/deleteLoveMusic HTTP/1.1
{musicId: 1}
响应
{
status: 1/-1,
message: "",
data: ""
}
收集页面 — 空查询 模糊查询
请求
POST /collect/findLoveMusic HTTP/1.1
{musicName: "可以为空可以不为空, 为空的时候,查询所有, 不为空的时候, 模糊查询"}
响应
{
status: 1/-1,
message: "",
data: {
{
musicId: "",
title: "",
author: "",
uploadtime: "",
path: "",
userId: "",
}
...
}
}
主页页面 — 空查询 模糊查询
请求
POST /music/findMusic HTTP/1.1
{musicName: "可以为空可以不为空, 为空的时候,查询所有, 不为空的时候, 模糊查询"}
响应
{
status: 1/-1,
message: "",
data: {
{
musicId: "",
title: "",
author: "",
uploadtime: "",
path: "",
userId: "",
}
...
}
}
删除单个音乐
请求
POST /music/delete HTTP/1.1
{musicId: ""}
响应
{
status: 1/-1,
message: "",
data: ""
}
删除多个音乐
请求
POST /music/deleteMore HTTP/1.1
{musicId: "1 2 3 4 5"(数组)}
响应
{
status: 1/-1,
message: "",
data: ""
}
播放音乐
请求
GET /music/play?path="..." HTTP/1.1
响应
{
音乐的字节信息
}
登录功能
请求
POST /user/login HTTP/1.1
{username: "",password: ""}
响应
{
status: 1/-1,
message: "",
data: ""
}
注销功能
请求
GET /user/logout HTTP/1.1
响应
HTTP/1.1 200
注册功能
请求
POST /user/register HTTP/1.1
{username: "",password: ""}
响应
{
status: 1/-1,
message: "",
data: ""
}
6. 工具包
6.1 设置统一响应类
这个类是用来让响应返回的格式统一的.
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;
}
}
6.2 Constant类
这个类是用来存储不变的常量的. 例如设置了session对象 , 是一个字符串. 不变的字符串.将来在其他地方获取对应的session需要通过这个字符串获取 .
public class Constant {
public static final String USER_SESSION_KEY = "user";
}
6.3 了解 MD5 加密 和 BCrypt 加密
MD5
是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即其过程不可逆; 但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也"不安全"。
更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。
Bcrypt
就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理 。我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。
Bcrypt生成的密文是60位的。而MD5的是32位的。Bcrypt破解难度更大。
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>
实现类
public class MD5Util {
private static final String salt = "1q2w3e4r5t";//可任意设置
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);
}
/**
* 第二次加密
* @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;
}
}
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})
创建BCryptTest测试类:
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);
}
运行结果: (每次加密的密码都不同)
6.4 在Config中 注入 BCryptPasswordEncoder 对象
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
6.5 添加拦截器
6.5.1 LoginInterceptor 类
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession httpSession = request.getSession(false);
if(httpSession != null && httpSession.getAttribute(Constant.USER_SESSION_KEY) != null) {
return true;
}
response.sendRedirect("/login.html");
return false;
}
}
6.5.2 AppConfig 类
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/**/login.html")
.excludePathPatterns("/**/css/**.css")
.excludePathPatterns("/**/images/**")
.excludePathPatterns("/**/fonts/**")
.excludePathPatterns("/**/js/**.js")
.excludePathPatterns("/**/scss/**")
.excludePathPatterns("/**/user/login")
.excludePathPatterns("/**/user/register")
.excludePathPatterns("/**/user/logout");
}
}
7. 登录模块
7.1 创建 User 实体类
创建 model 包, 然后创建 User 类
@Data
public class User {
private int userId;
private String username;
private String password;
}
7.2 使用 Mybatis 操作数据库
这里登录 需要进行 数据库的查询. 查询是否存在当前 username 的用户.
所以要设计, 通过用户名查找用户信息
7.2.1 在 UserServer 中添加代码
public User selectByName(String username) {
return userMapper.selectByName(username);
}
7.2.2 在 UserMapper 中添加代码
/**
* 通过用户名去查找用户信息, 用来对比登录信息.
* @param username 用户名
* @return 对应用户名的用户信息
*/
User selectByName(String username);
7.2.3 在 UserMapper.xml 中添加代码
<select id="selectByName" resultType="com.example.onlinemusicserver.model.User">
select * from user where username=#{username};
</select>
7.3 创建 UserConroller 添加代码
注意这里的登录.
- 首先去数据库根据用户名查询是否存在当前用户.
- 如果不存在, 登录失败.
- 如果存在, 用输入的密码, 和数据库中的密码进行比较, 看是否相等. (注: 数据中的密码是加密的)
- 如果不相等, 登录失败.
- 如果相等, 创建 session, 并登录成功.
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserServer userServer;
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
/**
* 用户登录
* @param user
* @param req
* @return
*/
@RequestMapping("/login")
public ResponseBodyMessage<User> login(@RequestBody User user, HttpServletRequest req) {
User truUser = userServer.selectByName(user.getUsername());
if(truUser != null) {
System.out.println("登陆成功");
System.out.println(user.getPassword() + " " + truUser.getPassword());
boolean flg = bCryptPasswordEncoder.matches(user.getPassword(),truUser.getPassword());
if(!flg) {
return new ResponseBodyMessage<>(-1,"当前账号密码错误!",user);
}
HttpSession session = req.getSession(true);
session.setAttribute(Constant.USER_SESSION_KEY,truUser);
return new ResponseBodyMessage<>(1,"登录成功!",truUser);
}else{
System.out.println("登录失败");
return new ResponseBodyMessage<>(-1,"当前账号密码错误!",user);
}
}
}
7.4 前端代码
let loginButton = document.querySelector('#loginButton');
loginButton.onclick = function() {
let username = document.querySelector('#loginUsername');
let password = document.querySelector('#loginPassword');
if (username.value.trim() == ""){
alert('请先输入用户名!');
username.focus();
return;
}
if (password.value.trim() == ""){
alert('请先输入密码!');
password.focus();
return;
}
$.ajax({
url: "user/login",
method: "POST",
data: JSON.stringify({username: username.value.trim(), password: password.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data, status) {
if(data.status == 1) {
location.assign("index.html");
}else{
alert(data.message);
username.value="";
password.value="";
username.focus();
}
}
})
}
8. 注册模块
8.1 使用 Mybatis 操作数据库
这里注册, 需要查看当前用户是否存在, 存在就不能注册, 通过用户查找, 这里已经实现.
注册一个新用户还需要 向数据库中添加一个新的用户信息.
8.1.1 在 UserServer 中添加代码
public int addnewUser(User newUser) {
return userMapper.addnewUser(newUser);
}
8.1.2 在 UserMapper 中添加代码
/**
* 注册新的用户
* @param newUser 新用户信息
* @return
*/
int addnewUser(User newUser);
8.1.3 在 UserMapper.xml 中添加代码
<insert id="addnewUser">
insert into user(username,password) values (#{username},#{password});
</insert>
8.2 向 UserController 中添加代码
- 首先查看是否该用户是否存在
- 存在, 就注册失败
- 不存在, 就进行注册, 首先对当前密码进行加密.
- 加密之后对这个用户添加到数据库中.
/**
* 注册用户
* @param user 用户信息
* @return
*/
@RequestMapping("/register")
public ResponseBodyMessage<Boolean> register(@RequestBody User user) {
User user1 = userServer.selectByName(user.getUsername());
if(user1 != null) {
return new ResponseBodyMessage<>(-1,"当前用户已经存在",false);
}else {
User newUser = new User();
newUser.setUsername(user.getUsername());
String newPassword = bCryptPasswordEncoder.encode(user.getPassword());
newUser.setPassword(newPassword);
userServer.addnewUser(newUser);
return new ResponseBodyMessage<>(1,"注册成功",true);
}
}
8.3 前端代码
let Reg = document.querySelector('#Reg');
Reg.onclick = function() {
let username = document.querySelector('#RegUsername');
let password1 = document.querySelector('#RegPassword1');
let password2 = document.querySelector('#RegPassword2');
if(!$('#checkbox').is(':checked')) {
alert("请勾选条款");
return;
}
if(username.value.trim() == ""){
alert("请先输入用户名!");
username.focus();
return;
}
if(password1.value.trim() == ""){
alert('请先输入密码!');
password1.focus();
return;
}
if(password2.value.trim() == ""){
alert('请再次输入密码!');
password2.focus();
return;
}
if(password1.value.trim() != password2.value.trim()) {
alert('两次输入的密码不同!');
passwrod1.value="";
password2.value="";
return;
}
$.ajax({
url: "user/register",
method: "POST",
data: JSON.stringify({username: username.value.trim(), password: password1.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status){
if(data.status == 1) {
alert(data.message);
location.assign("login.html");
}else{
alert(data.message);
username.value="";
password1.value="";
password2.value="";
username.focus();
}
}
})
}
9. 退出功能
这里点击退出之后, 直接删除 对应 的 session 即可
9.1 向 UserController 中添加代码
直接删除对应session 为
Constant.USER_SESSION_KEY
, 然后跳转到login.html
@RequestMapping("/logout")
public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(false);
// 拦截器的拦截, 所以不可能出现session为空的情况
session.removeAttribute(Constant.USER_SESSION_KEY);
response.sendRedirect("login.html");
}
9.2 登录注册测试.
10. 上传音乐模块
10.1 创建 Music 实体类
@Data
public class Music {
private int musicId;
private String title;
private String author;
private Timestamp uploadtime;
private String path;
private int userId;
private String srcPath;
}
10.2 使用 Mybatis 操作数据库
上传音乐, 要上传 音乐名, 音乐歌手, 音乐地址, 上传作者Id. (音乐上传时间, 已经默认设置了. 不需要传也可以)
通过音乐名去查找歌曲, 这里用来对当前歌曲判断, 是否出现歌曲和歌手都相同的情况.
10.2.1 在 MusicServer 中添加代码
public int insert(String title, String author, String path, int userId){
return musicMapper.insert(title,author,path,userId);
}
public List<Music> selectByTitle(String title) {
return musicMapper.selectByTitle(title);
}
10.2.2 在 MusicMapper 中添加代码
/**
* 上传音乐
* @param title 音乐名
* @param author 歌手
* @param path 对应的地址
* @param userId 上传的用户Id
* @return 返回影响行数
*/
int insert(String title, String author, String path, int userId);
/**
* 通过音乐名去查找歌曲.
* @param title 音乐名
* @return 对应音乐名的所有歌曲
*/
List<Music> selectByTitle(String title);
10.2.3 在 MusicMapper.xml 中添加代码
<insert id="insert">
insert into music(title,author,path,userId) values (#{title},#{author},#{path},#{userId});
</insert>
<select id="selectByTitle" resultType="com.example.onlinemusicserver.model.Music">
select * from music where title = #{title};
</select>
10.3 向 MusicController 中添加代码
这里首先对session判断, 判断是否存在session. (配置拦截器之后就不需要判断了)
去数据库中查询所有title相同的歌曲. 如果歌曲名相同,歌手也相同, 那么就上传失败.
创建文件夹. 将文件上传到文件夹中.(文件名是以歌手-歌名创建, 为了防止重名无法读取)
然后对该文件, 进行判断, 判断是不是 MP3 文件, 注意MP3文件, 字节码中有 字符"TAG"
在数据库中上传数据. 注意这里的path.
@RestController
@RequestMapping("/music")
public class MusicController {
@Autowired
private MusicServer musicServer;
@Value("${upload.path}")
public String SAVE_PATH;
/**
* 上传音乐
* @param singer
* @param file
* @param request
* @return
*/
@RequestMapping("/upload")
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer,
@RequestPart("filename") MultipartFile file,
HttpServletRequest request,
HttpServletResponse response) {
// 检测登录
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute(Constant.USER_SESSION_KEY) == null) {
System.out.println("当前未登录!");
return new ResponseBodyMessage<>(-1,"请登录后上传",false);
}
// 文件的类型
String fileNameAndType = file.getOriginalFilename();
// 防止出现重复的相同歌曲和相同歌手.可以出现相同歌曲不同歌手
String title = fileNameAndType.substring(0,fileNameAndType.lastIndexOf('.'));
// 可能出现多首名称相同的歌曲, 所以用 List
List<Music> list = musicServer.selectByTitle(title);
if(list != null){
for(Music music : list) {
if(music.getAuthor().equals(singer)){
return new ResponseBodyMessage<>(-1,"当前歌手的歌曲已经存在!",false);
}
}
}
// 创建文件
String path = SAVE_PATH +singer+"-"+fileNameAndType;
File dest = new File(path);
if(!dest.exists()) {
dest.mkdirs();
}
try {
file.transferTo(dest);
//return new ResponseBodyMessage<>(1,"上传成功!",true);
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-1,"服务器上传失败!",false);
}
// 这里对是不是 MP3 文件进行判断. 主要是判断是否存在 TAG 这个字符
File file1 = new File(path);
byte[] res = null;
try {
res = Files.readAllBytes(file1.toPath());
if(res == null) {
return new ResponseBodyMessage<>(-1,"当前文件不存在",false);
}
String str = new String(res);
if(!str.contains("TAG")) {
file1.delete();
return new ResponseBodyMessage<>(-1,"当前不是mp3文件",false);
}
}catch (IOException e){
e.printStackTrace();
return new ResponseBodyMessage<>(-1,"服务器出现问题", false);
}
// 在数据库中上传数据
User user = (User) session.getAttribute(Constant.USER_SESSION_KEY);
// 这里传递的 path 没有带 `.MP3` 后期在前端进行设置
String uploadPath = "/music/play?path="+singer+"-"+title;
try {
int ret = musicServer.insert(title,singer,uploadPath,user.getUserId());
if(ret == 1) {
response.sendRedirect("/index.html");
return new ResponseBodyMessage<>(1,"上传成功",true);
}else {
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
}catch (BindingException | IOException e) {
dest.delete();
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
}
}
10.4 前端代码
<form method="post" enctype="multipart/form-data" action="music/upload">
文件上传: <input type="file" name="filename" id="filename" />
歌手名: <label>
<input type="text" name="singer" placeholder="请输入歌手名" id="singer" />
</label>
<input type="submit" value="上传" id="submit"/>
</form>
10.5 测试代码
11. 播放音乐模块
11.1 向 MusicController 中添加代码
- 获取存储路径的文件.
- 读取文件中的所有字节,读入内存, 如果不为空, 返回字节码回去.
/**
* 播放音乐
* @param path
* @return
*/
@RequestMapping("/play")
public ResponseEntity<byte[]> playMusic(@RequestParam String path){
File file = new File(SAVE_PATH + path);
byte[] res = null;
try {
res = Files.readAllBytes(file.toPath());
if (res == null) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(res);
} catch (IOException e) {
e.printStackTrace();
return ResponseEntity.badRequest().build();
}
}
11.2 测试代码
观察字节码可以看出, 有 TAG 这个字符
12. 删除音乐模块
12.1 使用 Mybatis 操作数据库
删除音乐, 主要是两个删除, 一个是删除单个, 根据单个musicId 删除. 另一个是删除多个, 根据多个 musicId 删除.
这里根据 musicId 删除, 需要去数据库里查找 是否存在当前 musicId 的歌曲. 存在删成功, 不存在删失败.
注意, 删除的时候, 不仅要删除 music表里的歌曲. 也要删除 collect 表里的歌曲.
12.1.1 在 MusicServer 和 CollectServer 中添加代码
musicServer
public Music selectById(int musicId) {
return musicMapper.selectById(musicId);
}
public int deleteById(int musicId) {
return musicMapper.deleteById(musicId);
}
collectServer
public int deleteLoveMusicById(int musicId){
return collectMapper.deleteLoveMusicById(musicId);
}
12.1.2 在 MusicMapper 和 CollectMapper 中添加代码
musicMapper
/**
* 通过音乐Id去查找歌曲
* @param musicId 音乐Id
* @return 查找到的音乐Id
*/
Music selectById(int musicId);
/**
* 删除对应音乐Id的歌曲
* @param musicId 音乐Id
* @return 返回影响行数
*/
int deleteById(int musicId);
collectMapper
/**
* 删除收藏表中音乐Id为musicId的
* @param musicId 音乐Id
* @return 返回受影响行数
*/
int deleteLoveMusicById(int musicId);
12.1.3 在 MusicMapper.xml 和 CollectMapper.xml 中添加代码
MusicMapper.xml
<select id="selectById" resultType="com.example.onlinemusicserver.model.Music">
select * from music where musicId = #{musicId};
</select>
<delete id="deleteById">
delete from music where musicId = #{musicId};
</delete>
CollectMapper.xml
<delete id="deleteLoveMusicById">
delete from collect where musicId = #{musicId};
</delete>
12.2 删除单一音乐功能
12.2.1 向 MusicController 中添加代码
- 首先查看要删除的 musicId 的音乐是否存在
- 如果不存在就直接返回删除失败
- 如果存在, 就删除, 首先删除数据库中的记录, 再删除服务器上的数据
- 同时删除 collect 表中的 musicId 的数据
/**
* 删除音乐
* @param musicId
* @return
*/
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusic(@RequestParam String musicId) {
// 1. 检测音乐是不是存在
Music music = musicServer.selectById(Integer.parseInt(musicId));
// 2. 不存在直接返回, 存在就删除
if (music == null) {
System.out.println("该音乐不存在");
return new ResponseBodyMessage<>(-1,"没有你要删除的音乐",false);
}
// 2.1 删除数据库中的记录
int ret = musicServer.deleteById(Integer.parseInt(musicId));
if(ret == 1) {
// 2.2 删除服务器上的数据
int index = music.getPath().lastIndexOf("=");
String PathName = music.getPath().substring(index+1);
File file = new File(SAVE_PATH + PathName+".mp3");
if(file.delete()){
collectServer.deleteLoveMusicById(Integer.parseInt(musicId));
return new ResponseBodyMessage<>(1,"删除成功!",true);
}else{
return new ResponseBodyMessage<>(-1,"服务器删除失败!",false);
}
}else {
return new ResponseBodyMessage<>(-1,"数据库删除失败!",false);
}
}
12.2.2 前端代码
function deleteMusic(musicId) {
$.ajax({
url: "music/delete",
method: "post",
data:{"musicId":musicId},
dataType: "json",
success:function(data,status) {
if(data.status == 1) {
alert(data.message);
location.assign("index.html");
}else{
alert(data.message);
}
}
})
}
12.2.3 测试代码
12.3 删除多个音乐功能
12.3.1 向 MusicConroller 中添加代码
- 遍历传过来的 musicId的集合. 查询是否存在当前musicId 的音乐
- 存在就删除数据库中的数据, 然后删除服务器上的数据, 再删除 collect 表中的数据
- 都删除成功就计数. 如果和传来的集合的数据总数和计数的总数一样, 就返回删除成功.
/**
* 删除多选音乐
* @param musicId
* @return
*/
@RequestMapping("/deleteMore")
public ResponseBodyMessage<Boolean> deleteMoreMusic(@RequestParam("musicId[]") List<Integer> musicId) {
int sum = 0;
for (int i = 0; i < musicId.size(); i++) {
Music music = musicServer.selectById(musicId.get(i));
if(music == null) {
return new ResponseBodyMessage<>(-1,"没有你要删除的音乐",false);
}
int ret = musicServer.deleteById(musicId.get(i));
if (ret == 1) {
int index = music.getPath().lastIndexOf("=");
String PathName = music.getPath().substring(index+1);
File file = new File(SAVE_PATH + PathName+".mp3");
if(file.delete()){
collectServer.deleteLoveMusicById(musicId.get(i));
sum += ret;
}else{
return new ResponseBodyMessage<>(-1,"服务器删除失败!",false);
}
}else {
return new ResponseBodyMessage<>(-1,"数据库删除失败!",false);
}
}
if(sum == musicId.size()) {
return new ResponseBodyMessage<>(1,"音乐删除成功!",true);
}else{
return new ResponseBodyMessage<>(-1,"音乐删除失败!",false);
}
}
12.3.2 前端代码
$(function(){
$.when(load).done(function() {
$("#deleteMore").click(function(){
let musicId = new Array();
let i =0;
$("input:checkbox").each(function(){
if($(this).is(":checked")) {
musicId[i] = $(this).attr("id");
i++;
}
});
$.ajax({
url: "music/deleteMore",
method: "post",
data:{"musicId":musicId},
dataType:"json",
success:function(data,status) {
if(data.status == 1) {
alert(data.message);
location.assign("index.html");
}else{
alert(data.message);
}
}
})
})
})
})
12.3.3 测试代码
13. 收藏音乐模块
13.1 创建 Collect 实体类
@Data
public class Collect {
private int collectId;
private int userId;
private int musicId;
}
13.2 使用 Mybatis 操作数据库
- 首先要通过 musicId 和 userId去查找当前是否存在 collect 表中
- 在通过 musicId 和 userId 去添加歌曲
13.2.1 在 CollectServer 中添加代码
public Collect findCollectMusic(int userId, int musicId) {
return collectMapper.findCollectMusic(userId,musicId);
}
public int insertLoveMusic(int userId, int musicId) {
return collectMapper.insertLoveMusic(userId, musicId);
}
13.2.2 在 CollectMapper 中添加代码
/**
* 查看对应用户是否已经收藏了该音乐
* @param userId 用户Id
* @param musicId 音乐Id
* @return 收藏歌单
*/
Collect findCollectMusic(int userId, int musicId);
/**
* 收藏音乐
* @param userId 用户Id
* @param musicId 音乐Id
* @return 返回影响行数
*/
int insertLoveMusic(int userId, int musicId);
13.2.3 在 CollectMapper.xml 中添加代码
<select id="findCollectMusic" resultType="com.example.onlinemusicserver.model.Collect">
select * from collect where userId = #{userId} and musicId = #{musicId};
</select>
<insert id="insertLoveMusic">
insert into collect(userId,musicId) values(#{userId},#{musicId});
</insert>
13.3 向 CollectControll 中添加代码
- 通过用户Id 和 musicId查看是否存在歌曲
- 如果存在就返回收藏失败
- 如果不存在, 就根据用户id和musicId 添加收藏
/**
* 点击收藏的时候, 收藏音乐
* @param musicId
* @param request
* @return
*/
@RequestMapping("/loveMusic")
public ResponseBodyMessage<Boolean> AddLoveMusic(@RequestParam String musicId, HttpServletRequest request) {
int music_Id = Integer.parseInt(musicId);
HttpSession session = request.getSession(false);
User user = (User) session.getAttribute(Constant.USER_SESSION_KEY);
int userId = user.getUserId();
Collect collect = collectServer.findCollectMusic(userId,music_Id);
if (collect != null) {
return new ResponseBodyMessage<>(-1, "当前已经收藏了",false);
}else{
int ret = collectServer.insertLoveMusic(userId,music_Id);
if(ret == 1) {
return new ResponseBodyMessage<>(1, "收藏成功!",true);
}else{
return new ResponseBodyMessage<>(-1,"收藏失败",false);
}
}
}
13.4 前端代码
function collectMusic(musicId) {
$.ajax({
url: "collect/loveMusic",
method: "post",
data:{"musicId":musicId},
dataType: "json",
success:function(data,status){
if(data.status == 1) {
alert(data.message);
location.assign("collect.html");
}else{
alert(data.message);
}
}
})
}
13.5 测试代码
14. 取消收藏音乐模块
14.1 使用 Mybatis 操作数据库
- 根据userId 和 musicId 删除歌曲
14.1.1 在 CollectServer 中添加代码
public int deleteLoveMusic(int userId,int musicId){
return collectMapper.deleteLoveMusic(userId,musicId);
}
14.1.2 在 CollectMapper 中添加代码
/**
* 删除用户收藏的对应的音乐Id
* @param userId 用户Id
* @param musicId 音乐Id
* @return 受影响行数
*/
int deleteLoveMusic(int userId,int musicId);
14.1.3 在 CollectMapper.xml 中添加代码
<delete id="deleteLoveMusic">
delete from collect where userId = #{userId} and musicId = #{musicId}
</delete>
14.2 向 CollectControll 中添加代码
- 这里登录之后去收藏页面,去删除歌曲.
- 通过 musicId 和 userId 去删除歌曲
/**
* 删除收藏的音乐
* @param musicId
* @param request
* @return
*/
@RequestMapping("/deleteLoveMusic")
public ResponseBodyMessage<Boolean> deleteLoveMusic(@RequestParam String musicId,HttpServletRequest request) {
HttpSession session = request.getSession(false);
if(session == null) {
return new ResponseBodyMessage<>(-1,"当前未登录",false);
}
User user = (User) session.getAttribute(Constant.USER_SESSION_KEY);
int userId = user.getUserId();
int ret = collectServer.deleteLoveMusic(userId,Integer.parseInt(musicId));
if(ret == 1) {
return new ResponseBodyMessage<>(1,"取消收藏成功!",true);
}else{
return new ResponseBodyMessage<>(-1,"取消收藏失败!",false);
}
}
14.3 前端代码
function deleteLoveMusic(musicId) {
$.ajax({
url: "collect/deleteLoveMusic",
method: "post",
data:{"musicId":musicId},
dataType: "json",
success:function(data,status) {
if(data.status == 1) {
alert(data.message);
location.assign("collect.html");
}else{
alert(data.message);
}
}
})
}
14.4 测试代码
15. 主页面 - 查询模块
15.1 使用 Mybatis 操作数据库
- 这里有空查询和模糊查询两种数据库操作
- 空查询 不带 name
- 模糊查询带name
15.1.1 在 MusicServer 中添加代码
public List<Music> findMusic() {
return musicMapper.findMusic();
}
public List<Music> findMusicByName(String name) {
return musicMapper.findMusicByName(name);
}
15.1.2 在 MusicMapper 中添加代码
/**
* 查找所有的歌曲
* @return 所有的歌曲
*/
List<Music> findMusic();
/**
* 支持模糊查询的歌曲.
* @param name 部分歌曲名
* @return 对应所有的歌曲
*/
List<Music> findMusicByName(String name);
15.1.3 在 MusicMapper.xml 中添加代码
<select id="findMusic" resultType="com.example.onlinemusicserver.model.Music">
select * from music;
</select>
<select id="findMusicByName" resultType="com.example.onlinemusicserver.model.Music">
select * from music where title like concat('%',#{name},'%');
</select>
15.2 向 MusicController 中添加代码
这里判断前端传来的 name是否为空
- 不为空, 进入模糊查询
- 为空, 进入空查询
/**
* 支持模糊查询, 支持空查询
* @param name
* @return
*/
@RequestMapping("/findMusic")
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String name) {
List<Music> list = null;
if(name != null) {
list = musicServer.findMusicByName(name);
}else {
list = musicServer.findMusic();
}
return new ResponseBodyMessage<>(1,"查询完毕!",list);
}
15.3 前端代码
$(function(){load()});
function load(musicName) {
$.ajax({
url: 'music/findMusic',
method: 'POST',
data: {"name":musicName},
dataType: "json",
success: function(data,status) {
if(data.data!=null){
createMusic1(data.data);
let audios = document.querySelectorAll('#player2');
for(let audio of audios) {
new MediaElementPlayer(audio, {
pluginPath: 'https://cdn.jsdelivr.net/npm/mediaelement@4.2.7/build/',
shimScriptAccess: 'always',
success: function () {
let play = document.querySelector('.player');
play.style = "visibility: visible;";
}
});
}
}
}
})
}
function createMusic1(lists) {
let s = '';
for(let list of lists) {
s+= '<div class="d-block d-md-flex podcast-entry bg-white mb-5" data-aos="fade-up">';
s+= '<img src="images/img_2.jpg" class="image">';
s+= '<div class="text">';
s+= '<input id="'+list.musicId+'" type="checkbox" class="checkbox">';
s+= '<h3 class="font-weight-light">'+list.title+'</h3>';
s+= '<div class="text-white mb-3"><span class="text-black-opacity-05"><small>'+list.author+'</small><span class="sep">/</span><small>'+DateFormat(list.uploadtime)+'</small></span></div>';
s+= '<input type="button" class="btn btn-primary" style="margin: 5px;" value="收藏音乐" οnclick="collectMusic(\''+list.musicId+'\')">';
s+= '<input type="button" class="btn btn-primary" style="margin: 5px;" value="删除音乐" οnclick="deleteMusic(\''+list.musicId+'\')">';
s+= '<div class="player">';
s+= '<audio id="player2" preload="none" controls style="max-width: 100%">';
s+= '<source src="'+ list.path+'.mp3'+'" type="audio/mp3">';
s+= '</audio></div></div></div>';
}
$("#list23").html(s);
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
$(function(){
$("#submit1").click( function(){
var name = $("#exampleInputName2").val();
load(name);
});
});
15.4 测试代码
16. 收藏页面 - 查询模块
16.1 使用 Mybatis 操作数据库
- 这里有空查询和模糊查询两种数据库操作
- 空查询 不带 name
- 模糊查询带name
16.1.1 在 CollectServer 中添加代码
public List<Music> findLoveMusicByUserId(int userId){
return collectMapper.findLoveMusicByUserId(userId);
}
public List<Music> findLoveMusicByNameAndUserId(String name,int userId){
return collectMapper.findLoveMusicByNameAndUserId(name,userId);
}
16.1.2 在 CollectMapper 中添加代码
/**
* 查找用户收藏的所有音乐
* @param userId 用户Id
* @return 返回查询到的所有音乐
*/
List<Music> findLoveMusicByUserId(int userId);
/**
* 查找用户收藏音乐中名字带有 name的音乐
* @param name 部分名字
* @param userId 用户Id
* @return 返回查询到的所有音乐
*/
List<Music> findLoveMusicByNameAndUserId(String name,int userId);
16.1.3 在 CollectMapper.xml 中添加代码
<select id="findLoveMusicByUserId" resultType="com.example.onlinemusicserver.model.Music">
select m.* from collect c,music m where m.musicId = c.musicId and c.userId = #{userId};
</select>
<select id="findLoveMusicByNameAndUserId" resultType="com.example.onlinemusicserver.model.Music">
select m.* from collect c,music m where m.musicId = c.musicId and c.userId = #{userId} and m.title like concat('%',#{name},'%');
</select>
16.2 向 CollectController 中添加代码
这里判断前端传来的 name是否为空
- 不为空, 进入模糊查询
- 为空, 进入空查询
/**
* 1. 空查询, 查找所有的收藏音乐
* 2. 模糊查询, 查询包含部分 musicName 的所有收藏音乐
* @param musicName
* @param request
* @return
*/
@RequestMapping("findLoveMusic")
public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String musicName,HttpServletRequest request) {
HttpSession session = request.getSession(false);
if(session == null) {
return new ResponseBodyMessage<>(-1,"当前未登录",null);
}
User user = (User) session.getAttribute(Constant.USER_SESSION_KEY);
int userId = user.getUserId();
List<Music> list = null;
if(musicName == null) {
list = collectServer.findLoveMusicByUserId(userId);
}else{
list = collectServer.findLoveMusicByNameAndUserId(musicName,userId);
}
return new ResponseBodyMessage<>(1,"查询成功!",list);
}
16.3 前端代码
$(function(){load()});
function load(musicName) {
$.ajax({
url: 'collect/findLoveMusic',
method: 'POST',
data: {"musicName":musicName},
dataType: "json",
success: function(data,status) {
if(data.data!=null){
createMusic1(data.data);
let audios = document.querySelectorAll('#player2');
for(let audio of audios) {
new MediaElementPlayer(audio, {
pluginPath: 'https://cdn.jsdelivr.net/npm/mediaelement@4.2.7/build/',
shimScriptAccess: 'always',
success: function () {
let play = document.querySelector('.player');
play.style = "visibility: visible;";
}
});
}
}
}
})
}
$(function(){
$("#submit1").click( function(){
var name = $("#exampleInputName2").val();
load(name);
});
});
function createMusic1(lists) {
let s = '';
for(let list of lists) {
s+= '<div class="d-block d-md-flex podcast-entry bg-white mb-5" data-aos="fade-up">';
s+= '<img src="images/img_1.jpg" class="image">';
s+= '<div class="text">';
s+= '<h3 class="font-weight-light">'+list.title+'</h3>';
s+= '<div class="text-white mb-3"><span class="text-black-opacity-05"><small>'+list.author+'</small><span class="sep">/</span><small>'+DateFormat(list.uploadtime)+'</small></span></div>';
s+= '<input type="button" class="btn btn-primary" style="margin: 5px;" value="取消收藏" οnclick="deleteLoveMusic(\''+list.musicId+'\')">';
s+= '<div class="player">';
s+= '<audio id="player2" preload="none" controls style="max-width: 100%">';
s+= '<source src="'+ list.path+'.mp3'+'" type="audio/mp3">';
s+= '</audio></div></div></div>';
}
$("#list23").html(s);
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}