本项目主要是实现了在线播放音乐的功能,用户可以随时登陆听自己喜欢的音乐
项目实现的业务功能
项目功能展示
注册:
登陆:
音乐列表
查询功能:
收藏列表:
收藏列表的查询功能:
我已经将项目部署到到服务器上了,感兴趣的小伙伴可以访问以下链接:个人在线音乐播放器
通过上面的业务功能可知,需要一张用户表保存用户信息,需要一张音乐表保存用户添加的音乐信息,还需要一张收藏表来保存每个用户收藏的音乐
用户表:只保存用户基本信息即主键,用户名,密码
create table user(id int primary key auto_increment,username varchar(20) not null,password varchar(255) not null
);
音乐表:音乐表有主键,音乐名称,歌手,上传时间,因为要播放音乐,所以此处还需要保存音乐地址的信息,多个用户都可以上传音乐,所以还需要一个用户id字段
create table music(id int primary key auto_increment,title varchar(20) not null,singer varchar(20) not null,`time` varchar(13) not null,url varchar(100) not null,user_id int not null
);
收藏表:此表将用户和喜欢的音乐关联起来,所以字段为主键,用户id,音乐id
create table lovemusic(id int primary key auto_increment,user_id int not null,music_id int not null
);
创建拦截器对验证用户是否登陆功能做统一处理
创建拦截器:创建一个类实现HandlerInterceptor接口重写preHandle方法
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {HttpSession session = request.getSession();if(session == null || session.getAttribute("user") == null){return false;}return true;}
}
添加拦截器:创建一个类实现WebMvcConfigurer接口,重写addInterceptors方法添加拦截规则
@Configuration
public class AppConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {LoginInterceptor loginInterceptor = new LoginInterceptor();registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login.html").excludePathPatterns("/register.html").excludePathPatterns("/user/register").excludePathPatterns("/user/login").excludePathPatterns("/css/**.css").excludePathPatterns("/fonts/**").excludePathPatterns("/images/**").excludePathPatterns("/js/**.js").excludePathPatterns("/player/**");}
}
注意:登陆页面,注册页面和所有的静态样式都要开放出来
对返回前端的数据进行统一封装
返回给前端的数据应该包含状态码,提示信息,数据
@Data
public class ResponseMessage {private int status;private String message;private Object data;public ResponseMessage(int status, String message, Object data) {this.status = status;this.message = message;this.data = data;}
}
前端设计
对于注册功能,用户输入用户名,密码,并且必须确认密码之后才可注册,所以在前端就对输入内容进行了判空操作,也对确认密码进行了校验,获取到输入框的用户名和密码,发送POST请求,待后端返回数据后,如果注册成功,跳转到登陆页面,如果注册参数有误,则清空输入框,让用户重新注册
后端设计
前端传来的参数有用户名,密码,首先判断用户名是否存在,根据用户名在用户表中查询,看能否查询出信息,如果查询到了,说明该账号已被注册过了,如果查询不到信息,说名该账号没有被注册,可以添加用户名和密码到数据库中了
//注册@RequestMapping("/register")public ResponseMessage register(@RequestParam String username,@RequestParam String password){//判断用户名是否存在User user = userService.getUserByName(username);if(user != null){//用户名存在,直接返回错误信息给前端return new ResponseMessage(-1,"用户名已存在",false);}//用户名不存在,往用户表插入注册数据int n = userService.insertUser(username,password);if(n == 1){return new ResponseMessage(1,"注册成功",true);}else {return new ResponseMessage(-1,"注册失败",false);}}
查询和插入sql:
insert into user (username,password) values(#{username},#{password})
前端设计
获取到输入框的用户名和密码后,对数据进行判空后,然后发送POST请求,待后端返回响应后,如果登陆成功,则跳转到音乐列表页面,如果用户名或密码不正确,提示用户错误信息
后端设计
前端传来的参数有用户名,密码,首先校验用户名是否存在,如果用户名不存在返回给前端错误信息,如果用户名存在,再校验密码是否正确,如果正确,创建session保存用户信息
//登陆@RequestMapping("/login")public ResponseMessage login(@RequestParam String username, @RequestParam String password, HttpServletRequest req){//校验用户是否存在User user = userService.getUserByName(username);if(user == null){return new ResponseMessage(-1,"用户名不存在","");}else {//校验密码是否正确if(!password.equals(user.getPassword())){return new ResponseMessage(-1,"密码错误","");}//创建session保存用户信息HttpSession session = req.getSession(true);session.setAttribute("user",user);return new ResponseMessage(1,"登陆成功",user);}}
查询sql:
前端设计
页面一加载就要查询该用户所有的音乐,并且用户也可以手动的输入音乐昵称进行模糊查询,所以前端创建函数的时候,该函数的参数可以为空,也可以是音乐名称,当参数为空的时候就要在页面加载完成后立即调用,当参数不为空的时候,就要由用户手动点击按钮进行调用
后端设计
当不输入任何名称时候,默认查找所有音乐,也就是当访问到该页面时自动进行了不带任何名称的查询,所以一加载到页面就能看到全部的音乐信息,当输入名称时,根据输入的名称模糊匹配,在进行查询的时候默认带上用户id,因为数据库中的音乐是多个用户拥有的
//根据歌名查询音乐,当歌名为空时,查询全部音乐(根据歌名和用户id查询)@RequestMapping("/findmusic")public ResponseMessage findMusic(String musicName,HttpServletRequest req){//获取到用户id,根据用户id查询该用户的音乐HttpSession session = req.getSession();User user = (User)session.getAttribute("user");int userId = user.getId();//当查询内容为空时,查询该用户所有音乐if(musicName == null){List allMusics = musicService.getAllMusic(userId);return new ResponseMessage(1,"查询成功",allMusics);}//当查询内容不为空时,根据查询内容模糊匹配List musics = musicService.findByName(musicName,userId);return new ResponseMessage(1,"查询成功",musics);}
查询sql:
前端设计
上传的音乐包含音乐文件和歌手,所以前端要对参数进行判空校验,参数校验完后,就发送body格式为FormData格式的数据,因为文件上传需要使用改格式,待后端返回响应后,如果上传成功就跳转到音乐列表页面,如果上传失败就提示用户错误信息
后端设计
上传音乐的参数有音乐文件和歌手,后端接收文件的类型为MultipartFile,需要注意的是必须添加@RequestPart
注解,在获取到音乐文件后,首先要判断该音乐存在不存在,这里我们认为歌手和音乐文件的名称相同的音乐是同一首音乐,所以得先获取上传文件的名称,根据这个名称和歌手在数据中校验该音乐是否存在,由于同一首音乐可以被多个用户上传,所以此处还必须带上用户id这个参数,当该音乐存在时,返回前端错误信息,当不存在时在执行后边逻辑
HttpSession session = req.getSession();User user = (User) session.getAttribute("user");int userId = user.getId();//检查数据库是否已经有了该音乐(音乐名称+歌手)String fileName = file.getOriginalFilename(); //获取文件名称,xxx.mp3String title = fileName.substring(0,fileName.lastIndexOf(".")); //获取音乐名称Music getMusic = musicService.getMusic(title,singer,userId);if(getMusic != null){return new ResponseMessage(-1,"此音乐已经存在",false);}
查询sql:
如果该音乐不存在,先保存音乐到服务器,此时我的电脑就是服务器,所以保存在本地磁盘,如果部署到linux上,linux就是服务器主机,需要保存到linux,在保存的时候如果出现异常,就保存失败,返回错误信息给前端
注意:保存文件的时候,应该给每个用户有一个单独的文件来保存音乐,保证每个用户在删除音乐文件时,别的用户的音乐文件不受干扰
//保存文件到服务器//SAVE_PATH为配置的本机路径String path = SAVE_PATH+userId+"/"+fileName;File dest = new File(path);//目录不存在,则创建目录if(!dest.exists()){dest.mkdirs();}try {file.transferTo(dest);} catch (IOException e) {e.printStackTrace();return new ResponseMessage(-1,"上传失败",false);}
将文件保存在本地后,就要将该音乐保存到数据中中,此时需要先获取插入需要的数据,因为用户id,歌手,音乐名称在前面都获取过了,所以此处只需获取时间和url,此处的url为播放音乐时的路径
//保存音乐的url,该url为播放音乐时的后端路径String url = "/music/get?path="+fileName;//获取上传的时间SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");String time = df.format(new Date());
在获取到所有字段的信息后,就将这些数据组装到Music对象中,往数据库中插入这个对象
Music music = new Music();music.setSinger(singer);music.setTitle(title);music.setTime(time);music.setUserId(user.getId());music.setUrl(url);int n = musicService.insertMusic(music);if(n == 1){return new ResponseMessage(1,"上传成功",true);}else {dest.delete();return new ResponseMessage(-1,"上传失败",false);}
插入sql:
insert into music (title,singer,time,url,user_id) values(#{title},#{singer},#{time},#{url},#{userId})
对于播放音乐功能,用户点击播放按钮,后端应该返回给前端音乐文件的内容,我们可以使用ResponseEntityFiles.readAllBytes(file.toPath())
将文件内容读到字节数组中返回给前端
//播放音乐@RequestMapping("/get")public ResponseEntity get(String path,HttpServletRequest req){ //返回值为body内容HttpSession session = req.getSession();User u = (User) session.getAttribute("user");int userId = u.getId();//获取文件路径,需要用户id,因为每个用户的音乐文件路径相互独立File file = new File(SAVE_PATH+userId+"/"+path);byte[] bytes = null; //将文件的内容读到字节数组中try {bytes = Files.readAllBytes(file.toPath());if(bytes != null){return ResponseEntity.ok(bytes); //返回body内容和ok状态}} catch (IOException e) {e.printStackTrace();}return ResponseEntity.badRequest().build(); //400}
前端设计
点击某个音乐的收藏按钮,就会携带该音乐的id发送请求,后端接受到音乐id后,将该音乐保存在收藏表中,待后端返回响应,提示成功或失败信息
后端设计
对于收藏功能来说,需要知道收藏的音乐,以及是哪个用户,即建立音乐id和用户id之间对应的关系,但是在往收藏表中添加数据时,需要校验该用户是否已经收藏过该音乐,如果收藏过了就无需再次收藏,如果没有收藏,则插入数据
//收藏音乐@RequestMapping("/likemusic")public ResponseMessage likeMusic(String id, HttpServletRequest req){HttpSession session = req.getSession();User user = (User) session.getAttribute("user");//获取到音乐id和用户idint musicId = Integer.parseInt(id);int userId = user.getId();//查看该用户是否收藏过该音乐LoveMusic loveMusic = loveMusicService.getLoveMusic(userId,musicId);if(loveMusic != null){//收藏过,返回错误信息return new ResponseMessage(-1,"该音乐已被收藏",false);}//未收藏过,往收藏表中插入数据int n = loveMusicService.insertLoveMusic(userId,musicId);if(n == 1){return new ResponseMessage(1,"收藏成功",true);}else {return new ResponseMessage(-1,"收藏失败",false);}}
查询与插入sql:
insert into lovemusic (user_id,music_id) values (#{userId},#{musicId})
删除功能提供了两种,一是每次只删除一个音乐,二是可以选中多个音乐,一次性删除,但是删除时都需要校验该音乐是否存在,因为只有音乐存在才能删除成功
注意: 删除时需要注意一个问题,就是删除音乐时如果用户收藏了这个音乐,那么收藏表中的数据也因该被删除
前端设计
点击某个音乐对应的删除按钮,携带音乐id发送请求,后端会将该音乐的数据库中数据和本地保存的音乐文件删掉,待后端返回响应,如果删除成功书信音乐列表页面,如果删除失败提示错误信息
后端设计
前端点击删除,传递给后端的参数是音乐id,后端需要从session中获取到用户id,因为用户点击删除按钮,删除的是自己拥有的音乐,所以需要结合用户id删除,否则就会把其他用户拥有的音乐误删,删除完数据库中数据后,还需要删除本地保存的音乐文件,每个用户都有自己的文件保存路径,所以删除本地路径的时候,删除用户对应路径的音乐文件
//删除单个音乐@RequestMapping("/delete")public ResponseMessage deleteById(String id,HttpServletRequest req){HttpSession session = req.getSession();User user = (User) session.getAttribute("user");//获取到用户idint userId = user.getId();//获取音乐idInteger musicId = Integer.parseInt(id);//校验数据库是否存在要删除的音乐Music music = musicService.getById(musicId,userId);if(music == null){//如果不存在返回给前端错误信息return new ResponseMessage(-1,"该音乐不存在",false);}//如果存在则删除数据库中音乐int n = musicService.deleteById(musicId,userId);//删除服务器路径保存的音乐String path = music.getTitle(); //获取音乐名称String url = music.getUrl();String suffix = url.substring(url.lastIndexOf(".")); //获取后缀path = SAVE_PATH+userId+"/"+path+suffix; //拼接路径File file = new File(path);if(file.delete()){//同步查看收藏的音乐是否有该音乐,如果有也要删除收藏的该音乐int m = loveMusicService.deleteLoveMusicByMusicId(musicId,userId);return new ResponseMessage(1,"删除成功",true);}else {return new ResponseMessage(-1,"删除失败",false);}}
前端设计
与上述删除一个音乐类似,只是请求携带的数据是音乐id数组,该数组通过遍历复选框得到,如果遍历的复选框被选中则将此对应的音乐id添加到数组中
后端设计
批量删除的逻辑与上面删除一个的逻辑相同,只是前端传递的是一个音乐id数组,遍历该数组执行与上述相同的逻辑即可
//批量删除@RequestMapping("/deletepart")public ResponseMessage deletePart(@RequestParam("id[]") List ids,HttpServletRequest req){HttpSession session = req.getSession();User user = (User) session.getAttribute("user");int userId = user.getId();int sum = 0;//拿到每一个id,先查再删for(int i = 0;i < ids.size();i++){int musicId = ids.get(i);Music music = musicService.getById(musicId,userId);if(music == null){return new ResponseMessage(-1,"要删除的音乐不存在",false);}int n = musicService.deleteById(ids.get(i),userId);String path = music.getTitle();String url = music.getUrl();String suffix = url.substring(url.lastIndexOf("."));path = SAVE_PATH+userId+"/"+path+suffix;File file = new File(path);if(file.delete()){int m = loveMusicService.deleteLoveMusicByMusicId(musicId,userId);sum += n;}else {return new ResponseMessage(-1,"删除失败",false);}}if(sum == ids.size()){return new ResponseMessage(1,"删除成功",true);}else {return new ResponseMessage(-1,"删除失败",false);}}
查询和删除sql:
delete from music where id=#{id} and user_id=#{userId} delete from lovemusic where music_id=#{musicId} and user_id=#{userId}
前端设计
查询收藏音乐的功能与前面在音乐列表查询的功能类似,页面一加载就要发送请求,获取该用户全部收藏的音乐此时的查询是查询内容为空时的查询,当用户输入名称点击查询按钮时,就要根据该用户输入的名称进行模糊查询
后端设计
在跳转到收藏列表后,后端就要查询到该用户所有的收藏音乐返回给前端,当然用户页可以通过音乐名称来查询收藏的音乐,所以在查询的时候需要判断用户输入的音乐名称是否为空,如果为空就查询当前用户所有收藏的音乐,如果不为空,就根据用户输入的音乐名称进行模糊匹配
//查询当前用户收藏的音乐@RequestMapping("/findlovemusic")public ResponseMessage findLoveMusic(String musicName,HttpServletRequest req){HttpSession session = req.getSession();User user = (User) session.getAttribute("user");//获取到该用户idint userId = user.getId();//如果传递的参数为空,就查询所有音乐if(musicName == null){List musics = loveMusicService.getLoveMusicByUserId(userId);return new ResponseMessage(1,"查询成功",musics);}//传递参数不为空,根据查询内容模糊匹配List musics = loveMusicService.getLoveMusicByMusicName(musicName,userId);return new ResponseMessage(1,"查询成功",musics);}
查询sql:
前端设计
用户点击某音乐的对应的收藏按钮时,携带该音乐id发送请求,后端接收到该音乐id后就会将该音乐从收藏表中删掉,待后端返回响应后,如果取消收藏成功,就刷新当前用户收藏列表页面,如果取消失败,提示错误信息
后端设计
取消收藏只是将音乐从收藏表中删除,不会从音乐表中删除,也不会删除该音乐文件
//取消当前用户收藏的某个音乐@RequestMapping("/deletelovemusic")public ResponseMessage deleteLoveMusic(@RequestParam String id, HttpServletRequest req){//获取音乐idInteger musicId = Integer.parseInt(id);User user = (User)req.getSession().getAttribute("user");//获取用户idInteger userId = user.getId();//根据用户id和音乐id,删除收藏表中对应的数据int n = loveMusicService.deleteLoveMusic(userId,musicId);if(n == 1){return new ResponseMessage(1,"取消收藏成功",true);}return new ResponseMessage(-1,"取消失败",false);}
删除sql:
delete from lovemusic where user_id=#{userId} and music_id=#{musicId}
用户点击注销按钮,删除session中保存的用户信息,重定向到登陆页面
//注销@RequestMapping("/logout")public void logout(HttpServletRequest req,HttpServletResponse resp) throws IOException {HttpSession session = req.getSession();session.removeAttribute("user");resp.sendRedirect("../login.html");}
上一篇:【深度学习】第三章:卷积神经网络
下一篇:Vue面试题-答案、例子