文章目录
- 效果演示
- 1. 创建SpringBoot项目
- 2. 数据库设计
- 3. 配置数据库和xml
- 4. 登录模块设计
- 4.1 创建User类
- 4.2 创建对应的Mapper和Controller
- 5. 实现登录
- 5.1 登录的请求和响应设计
- 5.2 请求实现
- 5.3 响应实现
- 5.31 设置统一的响应体类工具类
- 5.32 创建常量工具类
- 5.33 优化后完整代码
- 6. 实现加密登录
- 6.1 Bcrypt加密设计
- 6.2 加密登录实现
- 6.21 创建包config,新建AppConfig类
- 6.3 加密登录完整实现
- 7. 上传音乐模块设计
- 7.1 上传音乐的请求和响应设计
- 7.2 新建 music 实体类
- 7.3 实现服务器上传
- 创建 MusicController 类
- 7.4 如何判断上传的文件是mp3
- 7.5 实现数据库上传
- 实现 MusicMapper
- 实现 MusicMapper.xml
- 进行 数据库上传
- 完整代码
- 8. 播放音乐模块设计
- 8.1 实现 ResponseEntity 类
- 9. 删除音乐模块设计
- 9.1 删除单个音乐
- 9.2 批量删除
- 10. 查询音乐模块设计
- 10.1 实现 MusicMapper
- 10.2 实现 MusicMapper.xml
- 10.3 实现 MusicController
- 11. 收藏/喜欢音乐模块设计
- 11.1 实现 LoveMusicMapper
- 11.2 实现 LoveMusicMapper.xml
- 11.3 实现 LoveMusicController
- 12. 查询收藏/喜欢音乐模块设计
- 12.1 实现 LoveMusicMapper
- 12.2 实现 LoveMusicMapper.xml
- 12.3 实现 LoveMusicController
- 13. 取消音乐收藏模块设计
- 13.1 实现 LoveMusicMapper
- 13.2 实现 LoveMusicMapper.xml
- 13.3 实现 LoveMusicController
- 13.4 存在BUG分析
- 14. 删除音乐完善
- 15. 注册实现
- 前端页面实现
- 16. 实现 登录界面 login.html
- 17. 实现上传音乐 upload.html
- 18. 实现音乐列表页 list.html
- 19. 实现播放歌曲
- 20. 实现删除单个音乐
- 21. 实现查询音乐功能
- 22. 实现删除选中音乐功能
- 23. 实现喜欢音乐列表页功能
- 24. 实现收藏音乐列表功能
- 25 实现注册功能
- 26 配置拦截器
- 项目部署
效果演示
1. 创建SpringBoot项目
2. 数据库设计
创建 user 表:
用户 id,用户名 username,用户密码 password
创建 music 表:
音乐 id,音乐名称 title,歌手名称 singer,时间 time,歌曲路径 url,对应上传音乐的用户 userid
创建 lovemusic 表(中间表):
音乐 id,对应的用户id user_id,对应的音乐id music_id
-- 数据库
drop database if exists `onlinemusic`;
create database if not exists `onlinemusic` 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
);
-- 创建 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
);
3. 配置数据库和xml
打开application.properties配置如下信息:
#配置数据库
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
#配置xml
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB
# 配置springboot日志调试模式是否开启
debug=true
# 设置打印日志的级别,及打印sql语句
#日志级别:trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
logging.level.com.example.onlinemusic.mapper=debug
#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG
4. 登录模块设计
4.1 创建User类
在package com.example.onlinemusic.model包中创建User类
@Data
public class User {
private int id;
private String username;
private String password;
}
4.2 创建对应的Mapper和Controller
1.新建mapper包,在mapper包下新建UserMapper
@Mapper
public interface UserMapper {
User login(User loginUser);
}
2.在resource目录下,新建mybatis文件夹,新建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">
</mapper>
5. 实现登录
5.1 登录的请求和响应设计
5.2 请求实现
@RestController // 这个注解是 @Controller 和 @RequestBody 的组合注解
@RequestMapping("/user") // 一级路由
public class UserController {
@Autowired
private UserMapper userMapper; // 注入 UserMapper
// 后面要用 UserMapper里面的方法,去进行数据查询
// 登录功能
@RequestMapping("/login")
// 传递两个参数 一个是 username 一个是 password
public void login(@RequestParam String username,
@RequestParam String password) { // @RequestParam 注解,可以H后端参数重命名,也可以制定传参
// 拿到 userLogin 对象,设置 用户名和密码
User userLogin = new User();
userLogin.setUsername(username);
userLogin.setPassword(password);
// 调用 userMapper的 login 方法 在数据库中 进行查询,返回 User
User user = userMapper.login(userLogin);
if(user != null ) {
System.out.println("登录成功!");
}else {
System.out.println("登录失败!");
}
}
}
UserMapper 中:
@Mapper
public interface UserMapper {
// 登录功能,查询操作
User login(User userLogin);// login 方法,返回为 User 对象
}
UserMapper.xml中:
<!-- 登录操作,查询 username 和 password,后面查询那个,就目录就是谁的,比如这里查询是User-->
<select id="login" resultType="com.example.onlinemusic.model.User">
select * from user where username=#{username} and password=#{password};
</select>
5.3 响应实现
5.31 设置统一的响应体类工具类
@Data
// 这是统一响应体工具类
public class ResponseBodyMessage <T> {
private int status; // 状态码
private String message; // 返回的信息[出错或者没出错的原因]
private T data; // 返回给前端的数据,有可能是 boolean 类型,类型很多这里直接用 泛型
// 构造方法
public ResponseBodyMessage(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
}
5.32 创建常量工具类
5.33 优化后完整代码
加了响应体之后,登录成功,我们再存储一下 Session
完整代码如下:
@RestController // 这个注解是 @Controller 和 @RequestBody 的组合注解
@RequestMapping("/user") // 一级路由
public class UserController {
@Autowired
private UserMapper userMapper; // 注入 UserMapper
// 后面要用 UserMapper里面的方法,去进行数据查询
// 登录功能
@RequestMapping("/login")
// 传递两个参数 一个是 username 一个是 password
// 加了统一响应体类之后 返回值 不在是 void ,而是 我们的 ResponseBodyMessage
public ResponseBodyMessage<User> login(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request) {
// @RequestParam 注解,可以H后端参数重命名,也可以制定传参
// 拿到 userLogin 对象,设置 用户名和密码
User userLogin = new User();
userLogin.setUsername(username);
userLogin.setPassword(password);
// 调用 userMapper的 login 方法 在数据库中 进行查询,返回 User
User user = userMapper.login(userLogin);
if(user != null ) {
System.out.println("登录成功!");
// 存储 session
// request.getSession().setAttribute("USERINFO_SESSION_KEY",user);
// 做出优化后的代码,我们将 USERINFO_SESSION_KEY 写在我们的工具包中
request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);
/**
* request.getSession.setAttribute()是获得当前会话的session
* 然后再setAttribute到session里面去,有效范围是session而不是request。
*/
// 返回响应体
return new ResponseBodyMessage<>(0,"登录成功老铁!",userLogin);
}else {
System.out.println("登录失败!");
// 登录失败,做出响应
// 在这里规定 status -1 为失败 0为成功
return new ResponseBodyMessage<>(-1,"登录失败老铁!",userLogin);
}
}
}
6. 实现加密登录
6.1 Bcrypt加密设计
Bcrypt就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理 。我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。Bcrypt生成的密文是60位的。而MD5的是32位的。Bcrypt破解难度更大
添加如下依赖到 pom.xml:
<!-- 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})
为什么要加 spring-boot 启动类注解:
当启动类,没有加这个过滤的时候,我们发现不能进行登录。
这是因为在SpringBoot中,默认的Spring Security生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。此时通过上述配置,即可禁用默认的登录验证。
实质我们并没有用到 Security 这个框架,而是用到了里面其中的一个类,仅此而以
创建 BcryptTest 测试类:
public class BcryptTest {
public static void main(String[] args) {
//模拟从前端获得的密码
// String password = "123456";
// 获取 BCrypt 对象
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
// encode 对我们输入的密码进行加密处理 得到新的密码
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.2 加密登录实现
逻辑如下:
1.根据用户名名称 查询 当前是否存在这样的用户[用户名:默认是唯一的]
2.取出当前用户的密码,进行匹配,查看密码是否是一样的,一样就登录成功
6.21 创建包config,新建AppConfig类
这里为什么要建一个 AppConfig 类,是为了方便我们后面的对象注入,比如我 我们要在 加密登录中 注入 BCryptPasswordEncoder,我们就需要先创建一个对象,通过 @Bean 注解,将它输入到 Spring 容器中
当然直接在 UserController 当中进行 实例化创建 BCryptPasswordEncoder 对象也是一样的
//@Configuration:表明当前类是一个配置类,被注解的类内部包含有一个或多个被@Bean注解的方法,用于构建
//bean定义,初始化Spring容器。
//@Bean注解:用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方
//法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。
//SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用
//@Bean注解就表明这个方法需要交给Spring进行管理。
@Configuration
public class AppConfig {
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserMapper 代码如下:
UserMapper.xml 内容如下:
6.3 加密登录完整实现
/**
* 实现加密登录
*/
@RequestMapping("/login")
// 传递两个参数 一个是 username 一个是 password
// 加了统一响应体类之后 返回值 不在是 void ,而是 我们的 ResponseBodyMessage
public ResponseBodyMessage<User> login(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request) {
// User userLogin = new User();
// userLogin.setUsername(username);
// userLogin.setPassword(password);
// User user = userMapper.login(userLogin);
// 1. 先去查询我们 用户,是否存在
User user = userMapper.selectByName(username);
if(user != null ) {
// 2. 如果用户存在,找到当前用户的密码,通过 Bcrypt 中的 matches 方法 判断密码是否一致
// password 为原来的密码,user.getPassword 为加密后的密码
boolean flg = bCryptPasswordEncoder.matches(password,user.getPassword());
if(!flg) {
// 说明密码不匹配
return new ResponseBodyMessage<>(-1,"登录失败,用户名或密码错误",user);
}
System.out.println("登录成功!");
request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);
return new ResponseBodyMessage<>(0,"登录成功老铁!",user);
}else {
System.out.println("登录失败!");
return new ResponseBodyMessage<>(-1,"登录失败,没找到用户!",user);
}
}
7. 上传音乐模块设计
7.1 上传音乐的请求和响应设计
7.2 新建 music 实体类
@Data
public class Music {
private int id;
private String title;
private String singer;
private String time;
private String url;
private int userId;
}
7.3 实现服务器上传
创建 MusicController 类
@RestController // 组合注解 @Controller + @ResponseBody
@RequestMapping("/music")
public class MusicController {
/**
* 上传音乐
* @return
*/
// 从配置文件中 将 路径 读取出来
@Value("${music.local.path}")
private String SAVE_PATH;
@RequestMapping("/upload")
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer,
@RequestParam("filename") MultipartFile file,
HttpServletRequest request) {
// 1. 检查是否登录
// 获取 session
HttpSession session = request.getSession(false);
// 判空
if(session == null || session.getAttribute
(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录!");
return new ResponseBodyMessage<>(-1,"请登录后上传!",false);
}
// 2. 上传到了服务器 ---- 拿到完整的文件名称 xxx.mp3
String fileNameAndType = file.getOriginalFilename(); // 获取完整文件名称
System.out.println("fileNameAndType" + fileNameAndType);
String path = SAVE_PATH + fileNameAndType;
File dest = new File(path); // dest 目录
if(!dest.exists()) {
dest.mkdir(); // 如果 dest(目录)不存在 创建目录
}
// 如果存在,通过 file.transferTo 将文件上传
try {
file.transferTo(dest);
return new ResponseBodyMessage<>(0,"上传成功!",true);
} catch (IOException e) {
e.printStackTrace();
}
return new ResponseBodyMessage<>(-1,"上传失败!",false);
}
}
7.4 如何判断上传的文件是mp3
每个种类的文件都要自己的格式,检测当前你上传的格式,判断这个文件的格式是不是 mp3 文件
不能通过后缀名进行判断,后缀名可以进行更改的
7.5 实现数据库上传
实现 MusicMapper
实现 MusicMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 这里的目录 对于的是 mapper 所对应的目录-->
<mapper namespace="com.example.onlinemusic.mapper.MusicMapper">
<insert id="insert" >
insert into music(title,singer,time,url,userid)
values(#{title},#{singer},#{time},#{url},#{userid});
</insert>
</mapper>
进行 数据库上传
分别获取需要的数据:
tilte,singer,userid,url,time
/**
* 进行 数据库 上传
*/
// 1. 先准备需要数据
// 1.1 获取title xxx.mp3 -- > title = xxx
int index = fileNameAndType.lastIndexOf(".");
String title = fileNameAndType.substring(0,index); // 拿到 xxx
// 1.2 获取 singer 已经确定,我们传参已经传了
// 1.3 获取 userid
User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userid = user.getId();
// 1.4 获取 url,播放音乐-》http请求
String url = "/music/get?path="+title;
// 1.5 获取 年-月-日
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
// 把当前的日期格式化为 time format()格式化
String time = sf.format(new Date());
// 2. 调用 insert
int ret = 0; // 插入影响的是行数,这里我们初始化一下,看看他等不等于1,等于1说明插入成功
ret = musicMapper.insert(title,singer,time,url,userid);
if(ret ==1 ) {
return new ResponseBodyMessage<>(1,"数据库上传成功",true);
}else {
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
}
完整代码
@RestController // 组合注解 @Controller + @ResponseBody
@RequestMapping("/music")
public class MusicController {
/**
* 上传音乐
* @return
*/
@Autowired
private MusicMapper musicMapper;
// 从配置文件中 将 路径 读取出来
@Value("${music.local.path}")
private String SAVE_PATH;
@RequestMapping("/upload")
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer,
@RequestParam("filename") MultipartFile file,
HttpServletRequest request) {
// 1. 检查是否登录
// 获取 session
HttpSession session = request.getSession(false);
// 判空
if(session == null || session.getAttribute
(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录!");
return new ResponseBodyMessage<>(-1,"请登录后上传!",false);
}
// 先查询数据库当中是否有当前音乐[歌曲名 + 歌手]
// TODD:
// 2. 上传到了服务器 ---- 拿到完整的文件名称 xxx.mp3
String fileNameAndType = file.getOriginalFilename(); // 获取完整文件名称
System.out.println("fileNameAndType" + fileNameAndType);
String path = SAVE_PATH + fileNameAndType;
File dest = new File(path); // dest 目录
if(!dest.exists()) {
dest.mkdir(); // 如果 dest(目录)不存在 创建目录
}
// 如果存在,通过 file.transferTo 将文件上传
try {
file.transferTo(dest);
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-1,"服务器上传失败!",false);
}
/**
* 进行 数据库 上传
*/
// 1. 先准备需要数据
// 1.1 获取title xxx.mp3 -- > title = xxx
int index = fileNameAndType.lastIndexOf(".");
String title = fileNameAndType.substring(0,index); // 拿到 xxx
// 1.2 获取 singer 已经确定,我们传参已经传了
// 1.3 获取 userid
User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userid = user.getId();
// 1.4 获取 url,播放音乐-》http请求
String url = "/music/get?path="+title;
// 1.5 获取 年-月-日
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
// 把当前的日期格式化为 time format()格式化
String time = sf.format(new Date());
try {
// 2. 调用 insert
int ret = 0; // 插入影响的是行数,这里我们初始化一下,看看他等不等于1,等于1说明插入成功
ret = musicMapper.insert(title,singer,time,url,userid);
if(ret ==1 ) {
return new ResponseBodyMessage<>(0,"数据库上传成功",true);
}else {
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
}catch (BindingException e) {
dest.delete();
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
// 另外一个问题: 如果重复上传一首歌曲 能否上传成功? 可以
}
}
8. 播放音乐模块设计
8.1 实现 ResponseEntity 类
/**
* 播放音乐模块设计
*/
// 播放音乐的时候,路径:/music/get?path=xxx.mp3
@RequestMapping("/get")
public ResponseEntity<byte[]> get(String path) {
File file = new File(SAVE_PATH + path);
byte[] a = null;
try {
a = Files.readAllBytes(file.toPath());
//Files.readAllBytes(String path) :
// 读取文件中的所有字节,读入内存 ,参数path是文件的路径
if(a == null) {
// 无参ok方法返回OK状态
// 有参ok方法返回body内容和OK状态
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(a);
} catch (IOException e) {
e.printStackTrace();
}
return ResponseEntity.badRequest().build();
// return ResponseEntity.internalServerError().build();
// return ResponseEntity.notFound().build();
}
9. 删除音乐模块设计
9.1 删除单个音乐
请求和响应设计:
data: 成功 or 失败
1.MusicMapper 内容如下:
2.MusicMapper.xml 内容如下
3.MusicController 内容如下:
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusicById(@RequestParam String id) {
// 1. 先检查音乐是否存在
int iid = Integer.parseInt(id);
// 2. 如果存在进行删除
Music music = musicMapper.findMusicById(iid);
if(music == null) {
return new ResponseBodyMessage<>(-1,"没有你要删除的音乐!",false);
}else {
// 2.1 删除数据库
int ret = musicMapper.deleteMusicById(iid);
// 如果 ret 等于1 说明 数据库数据删除成功
if(ret == 1) {
// 2.2 删除服务器上的数据(file)
// 这里需要拿到 title,可以通过 url 去拿,也可以直接 music.getTitle
int index = music.getUrl().lastIndexOf("=");
String fileName = music.getUrl().substring(index+1);
File file = new File(SAVE_PATH + fileName + ".mp3");
System.out.println("当前的路径: " + file.getPath());
if(file.delete()){
return new ResponseBodyMessage<>(0,"服务器当中的音乐删除成功!",true);
}else {
return new ResponseBodyMessage<>(-1,"服务器当中的音乐删除失败!",false);
}
}else {
return new ResponseBodyMessage<>(-1,"数据库当中的音乐没有删除成功",false);
}
}
}
9.2 批量删除
约定前后端相互接口:
MusicController 内容如下:
/**
* 批量删除
* id[1,3,5,7,9]
*/
@RequestMapping("/deleteSel")
public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]")
List<Integer> id) {
System.out.println("所有的id:" + id);
int sum = 0;
// 因为是批量删除,这里用数组存储我们要删除的 音乐 id
for (int i = 0; i < id.size(); i++) {
// 1. 查询 音乐是否存在
Music music = musicMapper.findMusicById(id.get(i));
if (music == null) {
System.out.println("没有这个id的音乐");
return new ResponseBodyMessage<>(-1, "没有你要删除的音乐", false);
}
// 2. 如果音乐存在我们进行删除
// 2.1 进行数据库删除
int ret = musicMapper.deleteMusicById(id.get(i));
if (ret == 1) {
// 说明 数据库删除成功,进行服务器删除
// 先拿到 title
int index = music.getUrl().lastIndexOf("=");
String fileName = music.getUrl().substring(index + 1);
File file = new File(SAVE_PATH + fileName + ".mp3");
System.out.println("当前路径:" + file.getPath());
if (file.delete()) {
sum += ret;
} else {
return new ResponseBodyMessage<>(-1, "服务器当中的音乐删除失败!", false);
}
}else {
return new ResponseBodyMessage<>(-1,"数据库当中音乐删除失败",false);
}
}
// 判断 sum 的值是否等于 id.size 如果等于说明 删完啦
if(sum == id.size()) {
System.out.println("整体删除成功!");
return new ResponseBodyMessage<>(0,"音乐删除成功",true);
}else {
System.out.println("整体删除失败!");
return new ResponseBodyMessage<>(-1,"音乐删除失败",false);
}
}
10. 查询音乐模块设计
此处查询需要满足几个功能:
- 支持模糊查询
- 支持传入参数为空
请求和响应设计:
10.1 实现 MusicMapper
10.2 实现 MusicMapper.xml
模糊查询:
select * from music where title like concat('%',#{musicName},'%');
10.3 实现 MusicController
@RequestMapping("/findmusic")
// 这里有一个小细节,我们可能不传参那么 我们的 @RequestParam 这里 需要 required = false
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false)
String musicName) {
List<Music> musicList = null;
if(musicName != null) {
// 不为空 查询指定音乐
musicList = musicMapper.findMusicByName(musicName);
}else {
// 为空 查询所有音乐
musicList = musicMapper.findMusic();
}
return new ResponseBodyMessage<>(0,"查询到了所有的音乐",musicList);
}
11. 收藏/喜欢音乐模块设计
请求和响应设计:
11.1 实现 LoveMusicMapper
11.2 实现 LoveMusicMapper.xml
11.3 实现 LoveMusicController
/**
* 收藏音乐,需要获取到 userId 和 musicId,musicId 传入的参数,userId 通过 session 获取
*/
@RequestMapping("/likeMusic")
public ResponseBodyMessage<Boolean> likeMusic(@RequestParam String id, HttpServletRequest request) {
// 字符串变为整数
int musicId = Integer.parseInt(id);
System.out.println("musicId:" + musicId);
// 1. 检查是否登录
HttpSession session = request.getSession(false); // 获取 session
// 判断 session 是否为空
if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录!");
return new ResponseBodyMessage<>(-1,"请先登录!",false);
}
// 如果不为空 获取 session 信息
User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userId = user.getId();
System.out.println("userId:" + userId);
Music music = loveMusicMapper.findLoveMusic(userId,musicId);
if(music != null) {
// 如果不等于null,说明之前收藏过当前的音乐,不能进行收藏 TODD:加一个取消收藏的音乐
return new ResponseBodyMessage<>(-1,"之前收藏过这个音乐",false);
}
// 如果等于空,收藏这首音乐 (插入)
boolean effect = loveMusicMapper.insertLoveMusic(userId,musicId);
if(effect) {
return new ResponseBodyMessage<>(0,"收藏成功",true);
}else {
return new ResponseBodyMessage<>(-1,"收藏失败",false);
}
}
12. 查询收藏/喜欢音乐模块设计
此处查询需要满足几个功能:
- 支持模糊查询
- 支持传入参数为空
请求和响应设计,和查询音乐模块是一样的,这里不在做过多的阐述
12.1 实现 LoveMusicMapper
12.2 实现 LoveMusicMapper.xml
支持模糊查询,多表联合查询
12.3 实现 LoveMusicController
@RequestMapping("/findlovermusic")
public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String musicName,
HttpServletRequest request) {
// 1. 检查是否登录
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录");
return new ResponseBodyMessage<>(-1,"没有登录",null);
}
// 如果不为空,进行查询
// 先获取 userId
User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userId = user.getId();
List<Music> musicList = null;
if(musicName == null) {
// 如果等于空,查询所有的音乐
musicList = loveMusicMapper.findLoveMusicByUserId(userId);
}else {
// 如果不等于空,查询 指定收藏的音乐
musicList = loveMusicMapper.findLoveMusicByKeyAndUid(musicName,userId);
}
return new ResponseBodyMessage<>(0,"查询到了所有的歌曲信息",musicList);
}
13. 取消音乐收藏模块设计
请求和响应设计
13.1 实现 LoveMusicMapper
13.2 实现 LoveMusicMapper.xml
13.3 实现 LoveMusicController
@RequestMapping("/deletelovemusic")
public ResponseBodyMessage<Boolean> deleteLoveMusic(@RequestParam String id,
HttpServletRequest request) {
// 将 id 转为 整数
int musicId = Integer.parseInt(id);
// 拿到 useId,先检查是否 登录
HttpSession session = request.getSession(false);
if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录");
return new ResponseBodyMessage<>(-1,"没有登录",false);
}
// 如果 session 不等于 空 拿到 use 信息
User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userId = user.getId();
// 获取到 userId 和 musicId 后调用 mapper 中的方法 去删除 ,首先判断是否存在
int ret = loveMusicMapper.deleteLoveMusic(userId,musicId);
if(ret == 1) {
System.out.println("取消收藏成功!");
return new ResponseBodyMessage<>(0,"取消收藏成功",true);
}else {
System.out.println("取消收藏失败");
return new ResponseBodyMessage<>(-1,"取消收藏失败",false);
}
}
13.4 存在BUG分析
当删除 music 表 当中的 musicId 为 5的这首音乐的时候,请问 lovemusic这张表中,是不是应该也被删除?
music 表
lovemusic表
14. 删除音乐完善
我们在删除 音乐模块中,进行删除,需要同步删除我们 喜欢的音乐(lovemuisc),因此,我们对 MusicController 中进行优化和完善。
在 MusicController 中,删除 单个音乐中 添加以下内容:
在 批量删除中 添加以下内容:
15. 注册实现
@RequestMapping("/logon")
public ResponseBodyMessage<User> logon(@RequestParam String username,@RequestParam String password,
HttpServletRequest request) {
// 1. 先对 输入的 password 进行加密
String newPassword = bCryptPasswordEncoder.encode(password);
// 创建 User 对象
User user = new User();
// 设置账号和 账号 和 密码
user.setUsername(username);
user.setPassword(newPassword);
// 在数据库中进行查询 看用户是否存在
User user1 = userMapper.selectByName(username);
if(user1 != null) {
System.out.println("用户已注册");
return new ResponseBodyMessage<>(-1,"用户已注册,换个其他的吧",user);
}
// 注册,往数据库中插入 新的 用户
int ret = userMapper.insertUser(user);
if(ret == 1) {
System.out.println("注册成功啦,我的老baby,一起来听音乐吧!");
return new ResponseBodyMessage<>(0,"注册成功啦,我的老baby,一起来听音乐吧!",user);
}else {
System.out.println("注册失败!达咩~");
return new ResponseBodyMessage<>(-1,"注册失败!达咩~",user);
}
}
以上,后端逻辑全部完善~~~~,接下来是后端模块实现。
前端页面实现
将前端页面模板,导入到我们 resources 底下的 static 中
jquery参考手册
16. 实现 登录界面 login.html
JS核心代码如下:
<script>
// 1. 登录核心业务逻辑
//原本是$(document).ready(function(){}) 表示当这个页面的dom树加载完成后才会执行这个,而后面的(document).ready可以省略,因此这里就是这样写也是可以的
$(function(){
//此时设置提交按钮的click事件,通过id选择器来获取
$("#submit").click(function(){
//此时就需要获取到用户名和密码的值
let username = $("#user").val();
let password = $("#password").val();
//检查一下用户名和密码是否为空,以及去掉空格使用trim方法
if(username.trim() == "" || password.trim() == ""){
alert("用户名或密码不能为空!");
return;
}
//如果都不为空,就需要使用ajax来发送请求到后端,然后处理这个请求及返回响应
$.ajax({
type:"POST", // 请求
url:"/user/login", // 指定路径
// 返回数据
data:{"username":username,"password":password},
//服务器返回数据类型
dataType:"json",
success:function(data){
//看状态码
if(data.status == 0){
console.log(data);
alert("登录成功,点击进行跳转!");
//登录成功,这里就可以进行页面的跳转
window.location.href="list.html";
}else{
alert("登录失败,用户名或密码错误!");
$("#password").val("");
}
}
});
});
});
</script>
17. 实现上传音乐 upload.html
- 修改后端代码,上传音乐成功后,跳转到 音乐 列表页
- 前端代码 upload.html 实现:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
</head>
<body>
<!--enctype="multipart/form-data" action 提交 url 地址-->
<form method="POST" enctype="multipart/form-data" action="/music/upload">
文件上传:<input type="file" name="filename"/>
歌手名: <label>
<input type="text" name="singer" placeholder="请输入歌手名"/>
</label>
<input type="submit" value="上传"/>
</form>
</body>
</html>
18. 实现音乐列表页 list.html
当我们跳转到 list.html 以后,我们要像服务器发起请求,查询到所有的音乐信息,动态的生成表格
之前我们前面说到,当我们 查询音乐的时候,如果不传参数,那么查询到所有的音乐,传递参数,查询到指定的音乐
JS代码如下:
script type="text/javascript">
// 核心代码实现
// 1. 上传音乐
// <!-- 核心代码实现 -->
$(function(){
load();
});
// musicName 可以 默认传参 和 不传参
// 不传参 匹配的就是所有的 音乐
function load(musicName) {
$.ajax({
type:"GET",
url:"/music/findmusic",
//数据
data:{"musicName":musicName},
//服务器返回数据类型
dataType:"json",
// 如果服务器返回成功,会返回我们的回调函数
success:function(obj) {
console.log(obj);
// obj包含了所有的返回信息,然后其中的data就包含了所有的音乐信息,可以先获取到
var data = obj.data;
var s = ''; // 最原始的拼接方式
// data[i].id data[i].singer data[i].title 这种形式来获取 obj里面的信息
for(var i = 0; i < data.length;i++) {
var musicUrl = data[i].url + ".mp3";
s += '<tr>';
s += '<th> <input id= "' + data[i].id + '" type="checkbox"> </th>';// <th> <input id="1" type="checkbox"></th>
s += '<td>' + data[i].title + '</td>';
s += '<td>' + data[i].singer + '</td>';
//s += "<td <a href=\"\"> <audio src= \""+ musicUrl+"\" + controls=\"controls\" preload=\"none\" loop=\"loop\"> >" + "</audio> </a> </td>";
s += '<td> <button class="btn btn-primary" onclick="playerSong(\''+musicUrl+'\')"> 播放歌曲 </button>' + '</td>';
s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+data[i].id+')"> 删除 </button> <button class="btn btn-primary" onclick="loveInfo('+data[i].id+')"> 喜欢 </button>' + '</td>';
s += '</tr>';
}
//然后将所有的数据放到tbody里面
$('#info').html(s);
}
});
}
</script>
19. 实现播放歌曲
这里播放歌曲,我们采用开源的播放控件:
码云地址
GitHub
将该开源项目,下载到本地,取出player文件夹,放入static文件夹下
JS 核心代码,就是调用 播放器的 toPlay() 方法
toPlay(url,title,startTime,autoPlay) 这里的 autoPlay 设置为 true 代表 我们点击播放的时候,它才会播放
// 实现音乐播放
function playerSong(obj) {
// toPlay(url,title,startTime,autoPlay)
//obj: http://localhost:8080/music/get?path=xxx.mp3
// 从等号下标 下一个位置开始截取 [)
var title = obj.substring(obj.lastIndexOf("=") + 1);
SewisePlayer.toPlay(obj,title,0,true);
}
20. 实现删除单个音乐
之前约定好的 前后端交互接口
前端传入的数据和后端返回的响应,响应的 data 为 true or false
JS 核心代码如下:
// 删除音乐 传入的参数是 id
function deleteInfo(obj){
console.log(obj);
$.ajax({
type:"POST",
url:"/music/delete",
data:{"id":obj},
dataType:"json",
success:function(body){
console.log(body);
//判断是否删除成功
if(body.data == true){
//表示删除成功
alert("删除成功了老铁!,重新加载当前页面哈!");
window.location.href="list.html";
}else{
alert("删除失败了老铁!");
}
}
});
}
21. 实现查询音乐功能
思路很简单,我们前面实现音乐列表页的时候 完成了 load 查询函数,这里我们只需要点击 按钮,执行我们的回调函数,拿到我们的 输入框的信息,即可,进行查询
JS核心代码如下:
// 查询和删除
// 查询~~~~~~
$(function(){
// 点击 提交按钮执行 回调函数
$("#submit1").click( function(){
//这里的功能就是进行查询而框里面不传参数就是默认的查询所有音乐
//传了参数就进行模糊查询
var name = $("#exampleInputName2").val();
load(name);
});
});
22. 实现删除选中音乐功能
这里的删除,我们的删除逻辑为,点击删除,如图:
我们需要获取到 每一行 中的 input 标签中的 checkbox
首先获取到我们需要删除的音乐id并存储起来,其次再通过ajax和后端建立联系,进行删除。
JS 核心代码如下:
// 删除音乐
/**
* 1. 先拿到 需要删除的 id
* 拿到 需要删除的 id 就存储起来
* */
// when 当执行完 load函数,则执行 done 当中的回调函数
$.when(load).done(function() {
//删除选中的回调事件
$("#delete").click(function(){
//由于这里存储的不止一条数据,因此这里需要将这些选中的存储下来
var id = new Array();
var i = 0;
//然后遍历所有的checkbox标签
$("input:checkbox").each(function(){
//看有没有被选中,被选中了就记录下来,没有选中就不用管
//this 发生事件的 demo元素,checked表示看是否选中了
if($(this).is(":checked")){
id[i] = $(this).attr("id");
i++;
}
});
console.log(id);
/**
* 2. 找到 id以后我们通过 ajax 给后端发送请求,传递给后端我们需要
* 删除的音乐
* */
$.ajax({
url:"/music/deleteSel",
type:"POST",
data:{"id":id},
dataType:"json",
// 执行成功 回调这个函数
success:function(obj){
// 看是否删除成功
if(obj.status == 0) {
alert("删除成功,重新加载此页面!");
window.location.href="list.html";
}else{
alert("删除失败!");
}
}
});
23. 实现喜欢音乐列表页功能
收藏音乐列表页,和 list.html 基本一样,但是需要注意我们需要将ajax中的 url 进行修改:
列入如下:
loveMusci.html 如下:
<script type="text/javascript">
function load(musicName) {
$.ajax({
type:"GET",
url:"/lovemusic/findlovermusic",
//数据
data:{"musicName":musicName},
//服务器返回数据类型
dataType:"json",
// 如果服务器返回成功,会返回我们的回调函数
success:function(obj) {
console.log(obj);
// obj包含了所有的返回信息,然后其中的data就包含了所有的音乐信息,可以先获取到
var data = obj.data;
var s = ''; // 最原始的拼接方式
// data[i].id data[i].singer data[i].title 这种形式来获取 obj里面的信息
for(var i = 0; i < data.length;i++) {
var musicUrl = data[i].url + ".mp3";
s += '<tr>';
s += '<td>' + data[i].title + '</td>';
s += '<td>' + data[i].singer + '</td>';
s += '<td> <button class="btn btn-primary" onclick="playerSong(\''+musicUrl+'\')"> 播放歌曲 </button>' + '</td>';
s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+data[i].id+')"> 移除 </button>' + '</td>';
s += '</tr>';
}
//然后将所有的数据放到tbody里面
$('#info').html(s);
}
});
}
// 实现音乐播放
function playerSong(obj) {
// toPlay(url,title,startTime,autoPlay)
//obj: http://localhost:8080/music/get?path=xxx.mp3
// 从等号下标 下一个位置开始截取 [)
var title = obj.substring(obj.lastIndexOf("=") + 1);
SewisePlayer.toPlay(obj,title,0,true);
}
// 删除喜欢音乐 传入的参数是 id
function deleteInfo(obj){
console.log(obj);
$.ajax({
type:"POST",
url:"/lovemusic/deletelovemusic",
data:{"id":obj},
dataType:"json",
success:function(body){
console.log(body);
//判断是否删除成功
if(body.data == true){
//表示删除成功
alert("删除成功了老铁!,重新加载当前页面哈!");
window.location.href="list.html";
}else{
alert("删除失败了老铁!");
}
}
});
}
</script>
24. 实现收藏音乐列表功能
在 list.html 中 实现如下核心代码:
// 添加喜欢的音乐/收藏音乐
function loveInfo(obj){
//直接发送请求
$.ajax({
type:"POST",
url:"/lovemusic/likeMusic",
data:{"id":obj},
dataType:"json",
success:function(body){
//然后这里判断看是否收藏成功了
if(body.data == true){
alert("收藏成功,^^,厉害了老铁!");
window.location.href="loveMusic.html";
}else{
alert("收藏失败,^^,咋回事儿呢!");
}
}
});
}
25 实现注册功能
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
<title>注册页面</title>
<!-- 1. 导入CSS的全局样式 -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- 2. jQuery导入,建议使用1.9以上的版本 -->
<script src="js/jquery-3.1.1.min.js"></script>
<script src="js/md5.min.js"></script>
<!-- 3. 导入bootstrap的js文件 -->
<!--<script src="js/bootstrap.min.js"></script>-->
<script type="text/javascript"></script>
<style>
#body{
background-image: url("images/rose3.png");
background-repeat: repeat;
background-position: 0 20%;
background-size: contain;
}
</style>
<script>
// 登录核心业务逻辑
//原本是$(document).ready(function(){}) 表示当这个页面的dom树加载完成后才会执行这个,而后面的(document).ready可以省略,因此这里就是这样写也是可以的
$(function(){
//此时设置提交按钮的click事件,通过id选择器来获取
$("#submit").click(function(){
//此时就需要获取到用户名和密码的值
let username = $("#user").val();
let password = $("#password").val();
//检查一下用户名和密码是否为空,以及去掉空格使用trim方法
if(username.trim() == "" || password.trim() == ""){
alert("用户名或密码不能为空!");
return;
}
//如果都不为空,就需要使用ajax来发送请求到后端,然后处理这个请求及返回响应
$.ajax({
type:"POST",
url:"/user/logon",
//数据
data:{"username":username,"password":password},
//服务器返回数据类型
dataType:"json",
success:function(data){
//看状态码
if(data.status == 0){
console.log(data);
alert("注册成功了我的老宝贝,要跳转了哦!");
//登录成功,这里就可以进行页面的跳转
window.location.href="login.html";
}else{
alert("注册失败,用户名已存在,--!");
}
}
});
});
});
</script>
</head>
<body id="body">
<div class="container" style="width: 400px;margin-top: 110px;background-color: rgba(255,255,255,0.9)">
<h3 style="text-align: center;">回溯</h3>
<!-- <form action="login" method="post">-->
<div class="form-group" >
<label for="user">用户名:</label>
<input type="text" name="username" class="form-control" id="user" placeholder="请输入用户名"/>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" name="password" class="form-control" id="password" placeholder="请输入密码"/>
</div>
<hr/>
<div class="form-group" style="text-align: center;"><!--class="form-group"-->
<input style="width: 200px;height: 40px" id="submit" class="btn btn btn-primary" type="button" value="注册" >
</div>
<!-- </form>-->
<!-- 出错显示的信息框 -->
</div>
</body>
</html>
26 配置拦截器
当我们直接访问列表页的时候,我们需要用拦截器拦截,否则 不登录也能直接访问,这是不行的
以上所有功能全部实现完毕。
项目部署
- 修改配置文件
修改音乐存放路径:
在配置文件当中,修改如下:
- 将数据库在服务器上重新进行建表等操作
输入指令 mysql,进入数据库:
将如下内容添加进去:
-- 数据库
drop database if exists `onlinemusic`;
create database if not exists `onlinemusic` 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
);
-- 创建 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
);
输入指令 exit 退出数据库
- 导包
4. 上传
-
云服务器配置防火墙端
-
启动项目
刚开始可以使用 java -jar xxxx.jar 启动项目【前台运行的方式】
运行好之后没有问题,我们可以使用下面的命令来对项目进行运行
nohup java -jar xxx.jar >> log.log &
nohup :后台运行项目的指令
使用 >> log.log 将运行的日志记录到 log.log 中
& 表示 一直运行
tips:重新部署
如果更新了项目,先将 jar删除
输入如下指令:
rm -ri xxx.jar
查看进程
ps -ef | grep java
kill [ID] 即可删除进程