目录
项目核心功能
项目前置工作
1.创建项目
2.数据库设计
3.配置文件中配置数据库和xml
核心功能设计
1.登录功能
2.注册功能
3.上传音乐至音乐列表功能
4.播放音乐功能
5.删除音乐列表音乐功能
5.1删除音乐列表单个音乐功能
5.2批量删除音乐列表选中音乐功能
6.查询音乐列表音乐功能
编辑
7.把音乐列表音乐添加到收藏音乐列表功能
8.查询收藏音乐列表的音乐功能
9.删除收藏音乐列表的音乐功能
10.对于删除音乐功能的完善
前端页面和逻辑
1.登录页面和登录逻辑
2.注册页面和逻辑
3.上传音乐页面和逻辑
4.音乐列表页面和逻辑
4.1页面
4.2逻辑
5.收藏音乐列表页面和逻辑
到此前端页面的代码与逻辑也完成了
最后收尾----配置拦截器
到此,完结撒花
项目核心功能
项目前置工作
1.创建项目
2.数据库设计
---创建数据库anitamusicspace
drop database if exists `anitamusicspace`;
create database if not exists `anitamusicspace` character set utf8;
---使用数据库
use `anitamusicspace`;
---创建表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
);
---在user表中插入数据(我),密码是123456的加密
INSERT INTO user(username,password)
VALUES("anita","$2a$10$Bs4wNEkledVlGZa6wSfX7eCSD7wRMO0eUwkJH0WyhXzKQJrnk85li");
---创建表music(title字段为歌曲名称,url字段为歌曲的路径)
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
);
--url:将来播放音乐的时候需要请求的地址
---创建中间表lovemusic
DROP TABLE IF EXISTS `lovemusic`;
CREATE TABLE `lovemusic` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`music_id` int(11) NOT NULL
);
3.配置文件中配置数据库和xml
##小梅需要修改端口号
server.port=8081
#配置数据库(两者选其一)
#1.这是dev的
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/anitamusicspace?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=111111
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.anitamusicspace.mapper=debug
#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG
核心功能设计
1.登录功能
在com.example.anitamusicspace.model包中创建User类
@Data
public class User {
private int id;
private String username;
private String password;
}
1.在com.example.anitamusicspace.mapper包中创建UserMapper接口
2.在resource下创建mybatis文件夹,在这个文件夹中创建UserMapper.xml
@Mapper
public interface UserMapper {
User selectByName(String username);
}
<?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.anitamusicspace.mapper.UserMapper">
<select id="selectByName" resultType="com.example.anitamusicspace.model.User">
select * from user where username=#{username}
</select>
</mapper>
在com.example.anitamusicspace.service包中创建UserService类
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public User selectByName(String username){
return userMapper.selectByName(username);
}
}
1.在com.example.anitamusicspace.controller包中创建UserController类
2.为了方便,我在com.example.anitamusicspace.tools中设置了:
(1)对响应体进行了统一设计
(2)对seesion对象进行了常量设置
3.登录涉及到了BCrypt加密和拦截器
(1)BCrypt加密需要:1.添加依赖 2.在springboot启动类添加一段代码 3.创建BCryptTest测试类 4.在com.example.anitamusicspace.config包中创建AppConfig类
(2)拦截器项目等最后设置
补充:
关于加密的介绍:关于加密---BCrypt和MD5_在上山的mei的博客-CSDN博客
对响应体进行了统一设计
package com.example.anitamusicspace.tools;
import lombok.Data;
@Data
public class ResponseBodyMessage<T>{
private int status;//状态码
private String message;//状态描述信息
private T data;//返回的数据
public ResponseBodyMessage(int status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
}
对seesion对象进行了常量设置
package com.example.anitamusicspace.tools;
public class Constant {
public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}
添加依赖
<!-- 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})
创建BCryptTest测试类
package com.example.anitamusicspace.tools;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
public class BCryptTest {
public static void main(String[] args) {
//模拟从前端获得的密码
String password = "123456";
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String newPassword = bCryptPasswordEncoder.encode(password);
System.out.println("加密的密码为: "+newPassword);
//使用matches方法进行密码的校验
boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword);
//返回true
System.out.println("加密的密码和正确密码对比结果: "+same_password_result);
boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword);
//返回false
System.out.println("加密的密码和错误的密码对比结果: " + other_password_result);
}
}
创建AppConfig类
@Configuration
public class AppConfig{
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
创建UserController类
@RestController//@RestController:@ResponseBody + @Controller合在一起的作用。@Controller注解,表明了这个类是一个控制器类 ,@ResponseBody表示方法的返回值直接以指定的格式写入Http response body中
@RequestMapping("/user")//使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求
public class UserController {
@Autowired
private UserService userService;
//登录
@Autowired//在自动装配之前,需要完成注入,之后在AppConfig中进行注入
private BCryptPasswordEncoder bCryptPasswordEncoder;
@RequestMapping("/login")
public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request) {
//关于@RequestParam的解释:将请求参数绑定到你控制器的方法参数上 。如果这个参数是非必传的可以写为@RequestParam(required = false) ,默认是true
User userInfo=userService.selectByName(username);
if(userInfo == null){
return new ResponseBodyMessage<>(-1, "用户名或者密码错误", userInfo);
}
else{
boolean flg=bCryptPasswordEncoder.matches(password,userInfo.getPassword());
if(!flg){
return new ResponseBodyMessage<>(-1, "用户名或者密码错误", userInfo);
}
request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,userInfo);//session的创建
return new ResponseBodyMessage<>(0, "登录成功", userInfo);
}
}
}
2.注册功能
注册这里的逻辑是:先根据要注册的username查询,如果数据库的user表中有这个username那么就不能注册,注册失败;如果没有就进行插入操作,注册成功
1.在UserMapper接口中添加代码
2.在UserMapper.xml中添加代码
int register(User registerUser);
<insert id="register">
insert into user(username,password)
values(#{username},#{password})
</insert>
在UserService中添加代码
public int register(User registerUser){
return userMapper.register(registerUser);
}
在UserController中添加代码
//注册
@RequestMapping("/register")
public ResponseBodyMessage<Boolean> register(@RequestParam String username,@RequestParam String password) throws IOException {
User user1 = userService.selectByName(username);
if (user1 != null) {
return new ResponseBodyMessage<>(-1, "当前用户已存在,注册失败", false);
} else {
User registerUser = new User();
registerUser.setUsername(username);
String registerPassword = bCryptPasswordEncoder.encode(password);
registerUser.setPassword(registerPassword);
userService.register(registerUser);
return new ResponseBodyMessage<>(0, "注册成功", true);
}
}
3.上传音乐至音乐列表功能
上传音乐的逻辑:
1.首先需要确认是否登录,如果没有需要先登录
2.查询数据库中是否有【歌曲名+歌手名】与要上传的【歌曲名+歌手名】一样的
(1)有就不能上传
(2)没有,那么就按照先上传到服务器(需要在配置文件中添加上传音乐的路径)--->判断上传的文件格式是否正确--->符合格式再上传到数据库,不符合就从服务器中删除并且不上传到数据库的逻辑顺序进行音乐的上传
补充:
1.关于MultipartFile类的参考介绍:深入理解MultipartFile,以更优雅的方式处理文件_Steafan_的博客-CSDN博客_multipartfile
2.关于MP3文件的介绍和参考:
mp3文件格式详解_oj_fang的博客-CSDN博客_mp3文件格式
MP3格式音频文件结构解析 - nigaopeng - 博客园
MP3文件格式解析_lspbeyond的博客-CSDN博客_mp3格式解析
3.由于music表中有一栏是上传时间,因此介绍下上传时间的格式:
时间格式化的类--->SimpleDateFormat
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");//比如2022-08-01
String time=sf.format(new Date());
SimpleDateFormat sf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//比如2022-08-01 20:00:00
String time2=sf2.format(new Date());
在com.example.anitamusicspace.model包中创建Music类
@Data
public class Music {
private int id;
private String title;
private String singer;
private String time;
private String url;
private int userid;
}
1.在com.example.anitamusicspace.mapper包中创建MusicMapper接口
2.在resource下创建mybatis文件夹,在这个文件夹中创建MusicMapper.xml
@Mapper
public interface MusicMapper {
//往数据库中上传音乐
int insert(String title,String singer,String time,String url,int userid);
//查询数据库中是否有要上传的音乐,如果数据库中有两次音乐就不再上传第二遍
List<Music> findMusicByMusicNameAndSinger(String title);
}
<?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.anitamusicspace.mapper.MusicMapper">
<!-- 往数据库中上传音乐-->
<insert id="insert">
insert into music(title,singer,time,url,userid)
values(#{title},#{singer},#{time},#{url},#{userid})
</insert>
<!-- 查询数据库中是否有要上传的音乐,如果数据库中有两次音乐就不再上传第二遍-->
<select id="findMusicByMusicNameAndSinger" resultType="com.example.anitamusicspace.model.Music">
select * from music where title=#{title}
</select>
在com.example.anitamusicspace.service包中创建MusicService类
@Service
public class MusicService {
@Resource
private MusicMapper musicMapper;
public int insert(String title,String singer,String time,String url,int userid){
return musicMapper.insert(title,singer,time,url,userid);
}
public List<Music> findMusicByMusicNameAndSinger(String title){
return musicMapper.findMusicByMusicNameAndSinger(title);
}
}
1.在com.example.anitamusicspace.controller包中创建MusicController类
2.在配置文件中配置上传音乐的路径,并在MusicController中进行注入
@RestController
@RequestMapping("/music")
public class MusicController {
@Value("${music.local.path}")
private String SAVE_PATH;
@Autowired
private MusicService musicService;
//上传音乐
@RequestMapping("/upload")
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer, @RequestParam("filename")
MultipartFile file, HttpServletRequest req, HttpServletResponse resp) throws IOException {
//1.检查是否登录了
HttpSession httpSession = req.getSession(false);
if (httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录,请先登录!");
return new ResponseBodyMessage<>(-1, "没有登录,请先登录!", false);
}
//2.查询数据库中是否有当前音乐[歌曲名和歌手都一样],有就不再上传
String filenameAndType = file.getOriginalFilename();//xxx.mp3
System.out.println("filenameAndType--->>>>>>>>>>>>>>>>>" + filenameAndType);
int index = filenameAndType.lastIndexOf(".");
String title = filenameAndType.substring(0, index);
List<Music> list = musicService.findMusicByMusicNameAndSinger(title);
if(list != null){
for(Music music : list) {
if(music.getSinger().equals(singer)){
return new ResponseBodyMessage<>(-1,"当前歌手的歌曲已经存在!",false);
}
}
}
//3.上传到服务器
String path = SAVE_PATH + "/" + filenameAndType;
File dest = new File(path);
System.out.println("dest:=>" + dest.getPath());
if (!dest.exists()) {
dest.mkdirs();
}
try {
file.transferTo(dest);//上传文件到目标
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-1, "服务器上传失败!", false);
}
//判断上传的文件是不是mp3文件,不是的话就从服务器删掉
File ff=new File(path);
byte[] res = null;
try {
res = Files.readAllBytes(ff.toPath());
String str = new String(res);
if(!str.contains("TAG")) {
ff.delete();
return new ResponseBodyMessage<>(-1,"当前上传的不是mp3文件,请上传mp3文件",false);
}
} catch (IOException e) {
e.printStackTrace();
return new ResponseBodyMessage<>(-1,"服务器出现问题", false);
}
//4.进行数据库的上传
//(1).准备数据 (2).调用insert
User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
int userid = user.getId();
String url = "/music/get?path=" + title;
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");//如果想获取的是精确到时分秒,yyyy-MM-dd HH:mm:ss
String time = sf.format(new Date());
try {
int ret = 0;
ret = musicService.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);
}
}
#音乐上传后的路径
#1.这是dev的
music.local.path=C:/Java/javacode/bigproject
4.播放音乐功能
关于ResponseEntity的介绍:
ResponseEntity的基本简介_胖橘爱吃小白粥的博客-CSDN博客_responseentity
https://www.jianshu.com/p/1238bfb29ee1
使用ResponseEntity处理API返回_半城风花半城雨的博客-CSDN博客
在MusicController中添加代码
@RequestMapping("/get")
public ResponseEntity<byte[]> get(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();
5.删除音乐列表音乐功能
删除功能的逻辑:先通过id查询要删除的音乐是否存在,存在就根据此id删除音乐。然后删除顺序是先删除数据库中此音乐,再删除服务器上此音乐
5.1删除音乐列表单个音乐功能
1.在MusicMapper接口中添加代码
2.在MusicMapper.xml中添加代码
//1.先查找要删除的音乐是否存在(通过id查看)
Music findMusicById(int id);
//2.如果存在就删除当前id的音乐
int deleteMusicById(int musicId);
<!-- 查询是否存在该id的音乐-->
<select id="findMusicById" resultType="com.example.anitamusicspace.model.Music">
select * from music where id=#{id}
</select>
<!-- 存在就根据该id进行删除-->
<delete id="deleteMusicById" parameterType="java.lang.Integer">
delete from music where id=#{id}
</delete>
在MusicService中添加代码
public Music findMusicById(int id){
return musicMapper.findMusicById(id);
}
public int deleteMusicById(int musicId){
return musicMapper.deleteMusicById(musicId);
}
在MusicController中添加代码
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusicById(@RequestParam String id) {
int iid = Integer.parseInt(id);
Music music = musicService.findMusicById(iid);
//看要删除的音乐的id是否存在
//不存在就提示没有要删除的音乐
if(music == null){
return new ResponseBodyMessage<>(-1,"没有你要删除的音乐",false);
}
//存在就要删除数据库和服务器上此音乐的数据
int ret = musicService.deleteMusicById(iid);
//ret=1就代表数据库中删除了一条,因此接下来接着删服务器上的数据
if(ret == 1) {
int index = music.getUrl().lastIndexOf("=");
String filename = music.getUrl().substring(index+1);
File file = new File(SAVE_PATH+"/"+filename+".mp3");
if(file.delete()) {
//同步删除lovemusic表中的数据
loveMusicService.deleteLoveMusicById(iid);
return new ResponseBodyMessage<>(0,"删除服务器音乐成功",true);
}else {
return new ResponseBodyMessage<>(-1, "删除服务器音乐失败", false);
}
//ret!=1就代表数据库没删除成功
}else{
return new ResponseBodyMessage<>(-1,"删除数据库中的音乐失败",false);
}
}
5.2批量删除音乐列表选中音乐功能
在MusicController中添加代码
//删除音乐----一次删除多个音乐
@RequestMapping("/deleteSel")
public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]") List<Integer> id) {
int sum = 0;
for (int i = 0; i < id.size(); i++) {
int musicId = id.get(i);
Music music = musicService.findMusicById(musicId);
int ret = musicService.deleteMusicById(musicId);
if(ret == 1) {
int index = music.getUrl().lastIndexOf("=");
String filename = music.getUrl().substring(index+1);
File file = new File(SAVE_PATH+"/"+filename+".mp3");
if(file.delete()) {
//同步删除lovemusic表中的数据
loveMusicService.deleteLoveMusicById(musicId);
sum += ret;
}else {
System.out.println("删除失败!");
return new ResponseBodyMessage<>(-1,"删除服务器上的音乐失败",false);
}
}else {
System.out.println("删除失败!");
return new ResponseBodyMessage<>(-1,"删除数据库中的音乐失败",false);
}
}
if(sum == id.size()) {
System.out.println("删除成功!");
return new ResponseBodyMessage<>(0,"批量删除成功",true);
}else {
System.out.println("删除失败!");
return new ResponseBodyMessage<>(0,"批量删除成功",false);
}
}
6.查询音乐列表音乐功能
查询音乐功能满足:
1.支持模糊查询:select * from music where title like concat('%',#{musicName},'%')
2.支持传入参数为空:@RequestParam(required=false)
1.在MusicMapper接口中新增代码
2.在MusicMapper.xml中添加代码
//查询音乐
//查询音乐分为两种
//一种是不给musicName传参,默认查询所有音乐
List<Music> findMusic();
//另一种是给musicName传参
List<Music> findMusicByMusicName(String name);
<!-- 查询音乐-->
<!-- 这个是没有传参的 -->
<select id="findMusic" resultType="com.example.anitamusicspace.model.Music">
select * from music
</select>
<!-- 这个是传参的 -->
<select id="findMusicByMusicName" resultType="com.example.anitamusicspace.model.Music">
select * from music where title like concat('%',#{musicName},'%')
</select>
在MusicService类中新增代码
public List<Music> findMusic(){
return musicMapper.findMusic();
}
public List<Music> findMusicByMusicName(String name){
return musicMapper.findMusicByMusicName(name);
}
在MusicController类中新增代码
//查询音乐
@RequestMapping("/findmusic")//(required=false)可以不传入参数
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required=false) String musicName) {
List<Music> musicList = null;
if(musicName != null) {
musicList = musicService.findMusicByMusicName(musicName);
}else {
//默认查询全部的音乐
musicList = musicService.findMusic();
}
return new ResponseBodyMessage<>(0,"查询到了歌曲的信息",musicList);
}
7.把音乐列表音乐添加到收藏音乐列表功能
此功能的逻辑是:
1、需要查询此次收藏音乐是否之前收藏过,收藏过则不能添加
2、没有收藏过,插入数据库中一条记录
1.在com.example.anitamusicspace.mapper包中创建LoveMusicMapper接口
2.在resource下创建mybatis文件夹,在这个文件夹中创建LoveMusicMapper.xml
@Mapper
public interface LoveMusicMapper {
//添加收藏音乐
//需要查询此次收藏音乐是否之前收藏过,收藏过则不能添加
Music findLoveMusicByMusicIdAndUserId(int userId, int musicId);
//没有收藏过,插入数据库中一条记录
boolean insertLoveMusic(int userId, int musicId);
}
<?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.anitamusicspace.mapper.LoveMusicMapper">
<!--收藏音乐,根据用户ID和音乐ID查询收藏的音乐 -->
<select id="findLoveMusicByMusicIdAndUserId" resultType="com.example.anitamusicspace.model.Music">
select * from lovemusic where user_id=#{userId} and music_id=#{musicId}
</select>
<insert id="insertLoveMusic">
insert into lovemusic(user_id,music_id) values(#{userId},#{musicId})
</insert>
</mapper>
在com.example.anitamusicspace.service包中创建LoveMusicService类
@Service
public class LoveMusicService {
@Resource
private LoveMusicMapper loveMusicMapper;
public Music findLoveMusicByMusicIdAndUserId(int userId, int musicId){
return loveMusicMapper.findLoveMusicByMusicIdAndUserId(userId,musicId);
}
public boolean insertLoveMusic(int userId, int musicId){
return loveMusicMapper.insertLoveMusic(userId,musicId);
}
}
在com.example.anitamusicspace.controller包中创建LoveMusicController类
@RestController
@RequestMapping("/lovemusic")
public class LoveMusicController {
@Resource
private LoveMusicService loveMusicService;
//添加到收藏音乐
@RequestMapping("/likeMusic")
public ResponseBodyMessage<Boolean> likeMusic(@RequestParam String id, HttpServletRequest req) {
int musicId = Integer.parseInt(id);
System.out.println("likeMusic->musicId: "+musicId);
//判断是否登录
HttpSession httpSession = req.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();
System.out.println("likeMusic->userID: "+userId);
//查询当前用户是否点赞过该音乐
Music music = loveMusicService.findLoveMusicByMusicIdAndUserId(userId,musicId);
if(music!=null) {
//之前收藏过,就不能再收藏了
return new ResponseBodyMessage<>(-1,"该用户点赞过该音乐",false);
}else {
boolean effect = loveMusicService.insertLoveMusic(userId,musicId);
if(effect) {
return new ResponseBodyMessage<>(0,"点赞音乐成功",true);
}else {
return new ResponseBodyMessage<>(-1,"点赞音乐失败",false);
}
}
}
8.查询收藏音乐列表的音乐功能
此功能需满足:
1. 支持模糊查询
2. 支持传入参数为空
1.在LoveMusicMapper接口中新增代码
2.在LoveMusicMapper.xml中添加代码
//查询收藏音乐
//如果没有传入具体的歌曲名,显示当前用户收藏的所有音乐
List<Music> findLoveMusicByUserId(int userId);
//根据某个用户的ID和歌曲名称查询,某个用户收藏的音乐
List<Music> findLoveMusicBykeyAndUID(String musicName, int userId);
<!-- 查询收藏的音乐-->
<select id="findLoveMusicByUserId" resultType="com.example.anitamusicspace.model.Music">
select m.* from lovemusic lm,music m where m.id = lm.music_id and lm.user_id=#{userId}
</select>
<select id="findLoveMusicBykeyAndUID" resultType="com.example.anitamusicspace.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>
在LoveMusicService类中新增代码
public List<Music> findLoveMusicByUserId(int userId){
return loveMusicMapper.findLoveMusicByUserId(userId);
}
public List<Music> findLoveMusicBykeyAndUID(String musicName, int userId){
return loveMusicMapper.findLoveMusicBykeyAndUID(musicName,userId);
}
在LoveMusicController类中新增代码
//查询收藏了的音乐
@RequestMapping("/findlovemusic")
public ResponseBodyMessage<List<Music>> findLoveMusic(@RequestParam(required=false)String musicName,
HttpServletRequest req) {
//判断是否登录
HttpSession httpSession = req.getSession(false);
if (httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
System.out.println("没有登录!");
return new ResponseBodyMessage<>(-1, "没有登录", null);
}
User user = (User) httpSession.getAttribute(Constant.USERINFO_SESSION_KEY);
int userid = user.getId();
List<Music> musics = new ArrayList<>();
if (musicName != null) {
musics = loveMusicService.findLoveMusicBykeyAndUID(musicName, userid);
} else {
musics = loveMusicService.findLoveMusicByUserId(userid);
}
return new ResponseBodyMessage<>(0,"查询到了所有的收藏的音乐",musics);
}
9.删除收藏音乐列表的音乐功能
注意此功能:删除收藏音乐仅删除的是收藏音乐列表中的音乐,不删除原音乐列表中的此音乐
1.在LoveMusicMapper接口中新增代码
2.在LoveMusicMapper.xml中添加代码
//取消音乐的收藏(把指定音乐从收藏音乐中删除)
int deleteLoveMusic(int userId,int musicId);
<!-- 取消收藏的音乐-->
<delete id="deleteLoveMusic" parameterType="java.lang.Integer">
delete from lovemusic where user_id=#{userId} and music_id=#{musicId}
</delete>
在LoveMusicService类中新增代码
public int deleteLoveMusic(int userId,int musicId){
return loveMusicMapper.deleteLoveMusic(userId,musicId);
}
在LoveMusicController类中新增代码
//取消音乐的收藏
@RequestMapping("/deletelovemusic")
public ResponseBodyMessage<Boolean> deleteLoveMusic(@RequestParam String id, HttpServletRequest req) {
int musicId = Integer.parseInt(id);
//判断是否登录
HttpSession httpSession = req.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();
int ret = loveMusicService.deleteLoveMusic(userid,musicId);
if(ret == 1) {
return new ResponseBodyMessage<>(0,"取消收藏成功!",true);
}else {
return new ResponseBodyMessage<>(0,"取消收藏失败!",false);
}
}
10.对于删除音乐功能的完善
到这里,完成了音乐列表中的一些功能,也完成了收藏音乐列表中的一些功能,现在需要完善一下音乐的删除功能,因为音乐列表和收藏音乐列表之间是有一个逻辑的:
删除音乐列表的音乐时,收藏音乐列表的中的此音乐也会被一并删除掉
1.在LoveMusicMapper接口中新增代码
2.在LoveMusicMapper.xml中添加代码
//当删除库中的音乐的时候,同步删除lovemusic中的数据
int deleteLoveMusicById(int musicId);
<!-- 当删除库中的音乐的时候,同步删除lovemusic中的数据-->
<delete id="deleteLoveMusicById" parameterType="java.lang.Integer">
delete from lovemusic where music_id=#{musicId}
</delete>
在LoveMusicService类中新增代码
public int deleteLoveMusicById(int musicId){
return loveMusicMapper.deleteLoveMusicById(musicId);
}
1.在MusicController类中的删除音乐方法中新增代码
2.在MusicController类中注入LoverMusicService
注意:删除单个音乐和批量删除音乐中都要新增这个代码
//之所以要注入收藏音乐的mapper是因为删除部分,当删除库中的音乐的时候,同步删除lovemusic中的数据
@Autowired
private LoveMusicService loveMusicService;
//删除单个音乐
int deleteLoveMusicById(int iid);
//批量删除音乐
loveMusicService.deleteLoveMusicById(musicId);
前端页面和逻辑
前端页面:1.登录页面2.注册页面3.上传音乐页面4.音乐列表页面5.收藏音乐列表页面
前端逻辑:(注意:前端逻辑使用ajax,那么就需要导入jQuery)
1.登录页面逻辑
关于jQuery的参考:jQuery 教程
这里放一些这个项目里用到的操作:
1.$(function(){ });用于存放操作DOM对象的代码,执行其中代码时DOM对象已存在
2.$("#submit").click(function () {}); 当按钮点击事件被触发时会调用一个函数。
3.$.ajax({}); 参考:jQuery ajax - ajax() 方法
4.url:发送请求的地址
5.data:发送到服务器的数据。将自动转换为请求字符串格式
6.type:默认值: "GET")。请求方式 ("POST" 或 "GET"), 默认为 "GET"。注意:其它 HTTP 请求方法,如 PUT和 DELETE 也可以使用,但仅部分浏览器支持。
7.dataType:预期服务器返回的数据类型
8.success:请求成功后的回调函数。参数为:由服务器返回,并根据 dataType 参数进行处理后的数据;描述状态的字符串
1.登录页面和登录逻辑
<script>
// 核心业务逻辑
$(function(){
$("#submit").click(function(){
var username = $("#user").val();
var password = $("#password").val();
if(username.trim() == "" || password.trim() == "") {
alert("用户名或者密码不能为空!");
return;
}
$.ajax({
url:"/user/login",
data:{"username":username,"password":password},
type:"POST",
dataType:"json",
success:function(data) {
console.log(data);
if(data.status == 0) {
alert("登录成功!");
//跳转到指定的页面了
window.location.href="list.html";
}else{
alert("登录失败,密码或者用户名错误!");
$("#user").val("");
$("#password").val("");
}
}
});
});
});
</script>
2.注册页面和逻辑
注册页面和登录页面几乎一致,因此照搬登录页面就可以
注册页面逻辑:
// 核心业务逻辑
$(function(){
$("#submit").click(function(){
var username = $("#user").val();
var password = $("#password").val();
if(username.trim() == "" || password.trim() == "") {
alert("用户名或者密码不能为空!");
return;
}
$.ajax({
url:"/user/register",
data:{"username":username,"password":password},
type:"POST",
dataType:"json",
success:function(data) {
console.log(data);
if(data.status == 0) {
alert(data.message);
//跳转到指定的页面了
window.location.href="login.html";
}else{
alert(data.message);
$("#user").val("");
$("#password").val("");
}
}
});
});
});
3.上传音乐页面和逻辑
注意:上传完音乐需要让页面跳转到音乐列表页面,因此此时我们需要在后端的MusicController中添加跳转页面的代码
resp.sendRedirect("/list.html");
4.音乐列表页面和逻辑
4.1页面
4.2逻辑
4.2.1展示所有音乐逻辑
$(function(){
load();
});
//musicName可以传参 也可以不传参数
function load(musicName) {
$.ajax({
url:"/music/findmusic",
data:{"musicName": musicName},
type:"GET",
dataType:"json",
success:function(obj) {
console.log(obj);
var data = obj.data;//数组
var s = '';
//data[i].id data[i].singer data[i].title
for(var i = 0; i < data.length;i++) {
var musicUrl = data[i].url+".mp3";
console.log(musicUrl);
s += '<tr>';
s += '<th > <input id= "'+data[i].id+'" type="checkbox"> </th>';
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>' +
'<button class = "btn btn-primary" onclick="loveInfo('+data[i].id+')"> 收藏 </button>' +'</td>';
s += "</tr>"
}
$("#info").html(s);
}
});
}
4.2.2播放音乐逻辑
这里采用的是开源的播放控件,将开源项目下载到本地,取出player文件夹,放入static文件夹下
播放音乐这里的逻辑:
1.嵌入播放器
2.播放音乐逻辑
1.嵌入播放器
<div style="width: 180px; height: 140px; position:absolute; bottom:10px; right:10px">
<script type="text/javascript" src="player/sewise.player.min.js"></script>
<script type="text/javascript">
SewisePlayer.setup({
server: "vod",
type: "mp3",
//这里是默认的一个网址
videourl:"http://jackzhang1204.github.io/materials/where_did_time_go.mp3",
skin: "vodWhite",
//这里需要设置false
autostart:"false",
});
</script>
</div>
2.播放音乐逻辑
//播放音乐
function playerSong(obj) {
var name = obj.substring(obj.lastIndexOf("=")+1);
//obj:播放的音乐的地址 name:播放的音乐的名称,0:播放的开始时间 false:不自动播放
SewisePlayer.toPlay(obj,name,0,true);
}
4.2.3删除指定音乐逻辑
//删除音乐
function deleteInfo(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 = "list.html";
}else{
alert("删除失败!");
}
}
});
}
4.2.4查询+删除选中音乐逻辑
//查询音乐+删除选中的音乐
$(function(){
//查询音乐
$("#submit1").click( function(){
var name = $("#exampleInputName2").val();
load(name);
});
//删除选中的音乐
$.when(load).done(function(){
$("#delete").click(function(){
var id = new Array();
var i = 0;//数组的下标
//
$("input:checkbox").each(function(){
//如果被选中,this代表发生事件的dom元素,<input>
if( $(this).is(":checked")) {
id[i] = $(this).attr("id");
i++;
}
});
console.log(id);
$.ajax({
url:"/music/deleteSel",
data:{"id":id},
dataType:"json",
type:"post",
success:function(obj){
if(obj.data == true) {
alert("删除成功!");
window.location.href = "list.html";
}else{
alert("删除失败!");
}
}
});
});
});
});
4.2.5收藏音乐逻辑
//收藏音乐
function loveInfo(obj) {
$.ajax({
url:"/lovemusic/likeMusic",
type:"POST",
data:{"id":obj},
dataType:"json",
success:function(val){
if(val.data == true) {
alert("收藏成功!");
window.location.href = "list.html";
}else{
alert("收藏失败!");
}
}
});
}
5.收藏音乐列表页面和逻辑
页面和音乐列表基本一致,照搬音乐列表页面就可以
注意:这个页面也要嵌入播放器,照搬音乐列表页面就可以
逻辑:播放删除音乐 | 删除收藏音乐 | 查询收藏音乐
// 核心代码实现
$(function(){
load();
});
//musicName可以传参 也可以不传参数
function load(musicName) {
$.ajax({
url:"/lovemusic/findlovemusic",
data:{"musicName": musicName},
type:"GET",
dataType:"json",
success:function(obj) {
console.log(obj);
var data = obj.data;//数组
var s = '';
//data[i].id data[i].singer data[i].title
for(var i = 0; i < data.length;i++) {
var musicUrl = data[i].url+".mp3";
console.log(musicUrl);
s += '<tr>';
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>' +'</td>';
s += "</tr>"
}
$("#info").html(s);
}
});
}
function playerSong(obj) {
var name = obj.substring(obj.lastIndexOf("=")+1);
//obj:播放的音乐的地址 name:播放的音乐的名称,0:播放的开始时间 false:不自动播放
SewisePlayer.toPlay(obj,name,0,true);
}
function deleteInfo(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 = "list.html";
}else{
alert("删除失败!");
}
}
});
}
$(function(){
$("#submit1").click( function(){
var name = $("#exampleInputName2").val();
load(name);
});
});
到此前端页面的代码与逻辑也完成了
最后收尾----配置拦截器
配置拦截器是为了防止除了注册和登录页面的其他页面在没有登录的时候能够访问
1.在com.example.anitamusicspace.config包中创建LoginInterceptor类
2.在com.example.anitamusicspace.config包中的AppConfig类中添加代码
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.USERINFO_SESSION_KEY)!=null) {
//此时是登录状态
return true;
}
return false;
}
}
2.AppConfig类
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
//添加拦截器,将自定义拦截器加入到系统配置
@Override
public void addInterceptors(InterceptorRegistry registry) {
//1、配置拦截规则
LoginInterceptor loginInterceptor = new LoginInterceptor();
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/**")
//排除所有的JS
.excludePathPatterns("/js/**.js")
//排除images下所有的元素
.excludePathPatterns("/images/**")
.excludePathPatterns("/css/**.css")
.excludePathPatterns("/fronts/**")
.excludePathPatterns("/player/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/register.html")
//排除登录接口
.excludePathPatterns("/user/register")
.excludePathPatterns("/user/login");
}
private void excludePathPatterns(String s) {
}
}
到此,完结撒花