# 在线音乐服务器 **Repository Path**: wangzhi430/online-music-server ## Basic Information - **Project Name**: 在线音乐服务器 - **Description**: 可以在线播放音乐的服务器 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2022-07-22 - **Last Updated**: 2024-09-25 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README @[TOC] # 1. 项目设计 **前端** : `HTML+CSS+JavaScript+JQuery` **后端** : `Spring MVC+Spring Boot+MyBatis`  # 2. 效果展示      # 3. 创建项目 配置文件 ## 3.1 创建项目     ## 3.2 配置文件 ### 3.2.1 在 application.properties 中添加配置文件 配置数据库 ```xml 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 ```xml mybatis.mapper-locations=classpath:mapper/**Mapper.xml ``` 配置文件上传大小 ```xml spring.servlet.multipart.max-file-size = 15MB spring.servlet.multipart.max-request-size=100MB ``` 配置上传的路径 ```xml upload.path=E:/logs/ ``` ### 3.2.2 在 resources 目录下创建mapper mapper下添加 目录 **.xml 并添加代码 ```xml ``` # 4. 数据库的设计与实现 这里设计数据库.  > 用户表 > > 1. 用户Id > 2. 用户账号 > 3. 用户密码 > 音乐表 > > 1. 音乐Id > 2. 音乐名 > 3. 音乐歌手 > 4. 上传时间 > 5. 存储地址 > 6. 用户Id > 收藏表 > > 1. 收藏Id > 2. 用户Id > 3. 音乐Id ```sql 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 设置统一响应类 > 这个类是用来让响应返回的格式统一的. ```java public class ResponseBodyMessage { 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需要通过这个字符串获取 . ```java 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使用示例 (加盐) 添加依赖 ```xml commons-codec commons-codec org.apache.commons commons-lang3 3.9 ``` 实现类 ```java 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使用示例 添加依赖 ```xml org.springframework.security spring-security-web org.springframework.security spring-security-config ``` 在springboot启动类添加: ``` @SpringBootApplication(exclude ={org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class}) ``` 创建BCryptTest测试类: ```java 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 对象 ```java @Configuration public class AppConfig implements WebMvcConfigurer { @Bean public BCryptPasswordEncoder getBCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } } ``` ## 6.5 添加拦截器 ### 6.5.1 LoginInterceptor 类 ```java 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 类 ```java @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 类 ```java @Data public class User { private int userId; private String username; private String password; } ``` ## 7.2 使用 Mybatis 操作数据库 > 这里登录 需要进行 数据库的查询. 查询是否存在当前 username 的用户. > > 所以要设计, 通过用户名查找用户信息 ### 7.2.1 在 UserServer 中添加代码 ```java public User selectByName(String username) { return userMapper.selectByName(username); } ``` ### 7.2.2 在 UserMapper 中添加代码 ```java /** * 通过用户名去查找用户信息, 用来对比登录信息. * @param username 用户名 * @return 对应用户名的用户信息 */ User selectByName(String username); ``` ### 7.2.3 在 UserMapper.xml 中添加代码 ```xml select * from user where username=#{username}; ``` ## 7.3 创建 UserConroller 添加代码 > 注意这里的登录. > > 1. 首先去数据库根据用户名查询是否存在当前用户. > 2. 如果不存在, 登录失败. > 3. 如果存在, 用输入的密码, 和数据库中的密码进行比较, 看是否相等. (注: 数据中的密码是加密的) > 4. 如果不相等, 登录失败. > 5. 如果相等, 创建 session, 并登录成功. ```java @RestController @RequestMapping("/user") public class UserController { @Autowired private UserServer userServer; @Autowired private BCryptPasswordEncoder bCryptPasswordEncoder; /** * 用户登录 * @param user * @param req * @return */ @RequestMapping("/login") public ResponseBodyMessage 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 前端代码  ```javascript 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 中添加代码 ```java public int addnewUser(User newUser) { return userMapper.addnewUser(newUser); } ``` ### 8.1.2 在 UserMapper 中添加代码 ```java /** * 注册新的用户 * @param newUser 新用户信息 * @return */ int addnewUser(User newUser); ``` ### 8.1.3 在 UserMapper.xml 中添加代码 ```xml insert into user(username,password) values (#{username},#{password}); ``` ## 8.2 向 UserController 中添加代码 > 1. 首先查看是否该用户是否存在 > 2. 存在, 就注册失败 > 3. 不存在, 就进行注册, 首先对当前密码进行加密. > 4. 加密之后对这个用户添加到数据库中. ```java /** * 注册用户 * @param user 用户信息 * @return */ @RequestMapping("/register") public ResponseBodyMessage 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 前端代码  ```javascript 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` ```java @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 实体类 ```java @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 中添加代码 ```java public int insert(String title, String author, String path, int userId){ return musicMapper.insert(title,author,path,userId); } public List selectByTitle(String title) { return musicMapper.selectByTitle(title); } ``` ### 10.2.2 在 MusicMapper 中添加代码 ```java /** * 上传音乐 * @param title 音乐名 * @param author 歌手 * @param path 对应的地址 * @param userId 上传的用户Id * @return 返回影响行数 */ int insert(String title, String author, String path, int userId); /** * 通过音乐名去查找歌曲. * @param title 音乐名 * @return 对应音乐名的所有歌曲 */ List selectByTitle(String title); ``` ### 10.2.3 在 MusicMapper.xml 中添加代码 ```xml insert into music(title,author,path,userId) values (#{title},#{author},#{path},#{userId}); select * from music where title = #{title}; ``` ## 10.3 向 MusicController 中添加代码 > 1. 这里首先对session判断, 判断是否存在session. (配置拦截器之后就不需要判断了) > 2. 去数据库中查询所有title相同的歌曲. 如果歌曲名相同,歌手也相同, 那么就上传失败. > 3. 创建文件夹. 将文件上传到文件夹中.(文件名是以歌手-歌名创建, 为了防止重名无法读取) > 4. 然后对该文件, 进行判断, 判断是不是 MP3 文件, 注意MP3文件, 字节码中有 字符"TAG" > > 5. 在数据库中上传数据. 注意这里的path. ```java @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 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 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 前端代码 ```html 文件上传: 歌手名: ``` ## 10.5 测试代码  # 11. 播放音乐模块 ## 11.1 向 MusicController 中添加代码 > 1. 获取存储路径的文件. > 2. 读取文件中的所有字节,读入内存, 如果不为空, 返回字节码回去. ```java /** * 播放音乐 * @param path * @return */ @RequestMapping("/play") public ResponseEntity 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` ```java public Music selectById(int musicId) { return musicMapper.selectById(musicId); } public int deleteById(int musicId) { return musicMapper.deleteById(musicId); } ``` `collectServer` ```java public int deleteLoveMusicById(int musicId){ return collectMapper.deleteLoveMusicById(musicId); } ``` ### 12.1.2 在 MusicMapper 和 CollectMapper 中添加代码 `musicMapper` ```java /** * 通过音乐Id去查找歌曲 * @param musicId 音乐Id * @return 查找到的音乐Id */ Music selectById(int musicId); /** * 删除对应音乐Id的歌曲 * @param musicId 音乐Id * @return 返回影响行数 */ int deleteById(int musicId); ``` `collectMapper` ```java /** * 删除收藏表中音乐Id为musicId的 * @param musicId 音乐Id * @return 返回受影响行数 */ int deleteLoveMusicById(int musicId); ``` ### 12.1.3 在 MusicMapper.xml 和 CollectMapper.xml 中添加代码 `MusicMapper.xml` ```xml select * from music where musicId = #{musicId}; delete from music where musicId = #{musicId}; ``` `CollectMapper.xml` ```xml delete from collect where musicId = #{musicId}; ``` ## 12.2 删除单一音乐功能 ### 12.2.1 向 MusicController 中添加代码 > 1. 首先查看要删除的 musicId 的音乐是否存在 > 2. 如果不存在就直接返回删除失败 > 3. 如果存在, 就删除, 首先删除数据库中的记录, 再删除服务器上的数据 > 4. 同时删除 collect 表中的 musicId 的数据 ```java /** * 删除音乐 * @param musicId * @return */ @RequestMapping("/delete") public ResponseBodyMessage 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 前端代码 ```javascript 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 中添加代码 > 1. 遍历传过来的 musicId的集合. 查询是否存在当前musicId 的音乐 > 2. 存在就删除数据库中的数据, 然后删除服务器上的数据, 再删除 collect 表中的数据 > 3. 都删除成功就计数. 如果和传来的集合的数据总数和计数的总数一样, 就返回删除成功. ```java /** * 删除多选音乐 * @param musicId * @return */ @RequestMapping("/deleteMore") public ResponseBodyMessage deleteMoreMusic(@RequestParam("musicId[]") List 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 前端代码 ```javascript $(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 实体类 ```java @Data public class Collect { private int collectId; private int userId; private int musicId; } ``` ## 13.2 使用 Mybatis 操作数据库 > 1. 首先要通过 musicId 和 userId去查找当前是否存在 collect 表中 > 2. 在通过 musicId 和 userId 去添加歌曲 ### 13.2.1 在 CollectServer 中添加代码 ```java 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 中添加代码 ```java /** * 查看对应用户是否已经收藏了该音乐 * @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 中添加代码 ```xml select * from collect where userId = #{userId} and musicId = #{musicId}; insert into collect(userId,musicId) values(#{userId},#{musicId}); ``` ## 13.3 向 CollectControll 中添加代码 > 1. 通过用户Id 和 musicId查看是否存在歌曲 > 2. 如果存在就返回收藏失败 > 3. 如果不存在, 就根据用户id和musicId 添加收藏 ```java /** * 点击收藏的时候, 收藏音乐 * @param musicId * @param request * @return */ @RequestMapping("/loveMusic") public ResponseBodyMessage 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 前端代码 ```javascript 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 操作数据库 > 1. 根据userId 和 musicId 删除歌曲 ### 14.1.1 在 CollectServer 中添加代码 ```java public int deleteLoveMusic(int userId,int musicId){ return collectMapper.deleteLoveMusic(userId,musicId); } ``` ### 14.1.2 在 CollectMapper 中添加代码 ```java /** * 删除用户收藏的对应的音乐Id * @param userId 用户Id * @param musicId 音乐Id * @return 受影响行数 */ int deleteLoveMusic(int userId,int musicId); ``` ### 14.1.3 在 CollectMapper.xml 中添加代码 ```xml delete from collect where userId = #{userId} and musicId = #{musicId} ``` ## 14.2 向 CollectControll 中添加代码 > 1. 这里登录之后去收藏页面,去删除歌曲. > 2. 通过 musicId 和 userId 去删除歌曲 ```java /** * 删除收藏的音乐 * @param musicId * @param request * @return */ @RequestMapping("/deleteLoveMusic") public ResponseBodyMessage 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 前端代码 ```javascript 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 操作数据库 > 1. 这里有空查询和模糊查询两种数据库操作 > 2. 空查询 不带 name > 3. 模糊查询带name ### 15.1.1 在 MusicServer 中添加代码 ```java public List findMusic() { return musicMapper.findMusic(); } public List findMusicByName(String name) { return musicMapper.findMusicByName(name); } ``` ### 15.1.2 在 MusicMapper 中添加代码 ```java /** * 查找所有的歌曲 * @return 所有的歌曲 */ List findMusic(); /** * 支持模糊查询的歌曲. * @param name 部分歌曲名 * @return 对应所有的歌曲 */ List findMusicByName(String name); ``` ### 15.1.3 在 MusicMapper.xml 中添加代码 ```xml select * from music; select * from music where title like concat('%',#{name},'%'); ``` ## 15.2 向 MusicController 中添加代码 > 这里判断前端传来的 name是否为空 > > 1. 不为空, 进入模糊查询 > 2. 为空, 进入空查询 ```java /** * 支持模糊查询, 支持空查询 * @param name * @return */ @RequestMapping("/findMusic") public ResponseBodyMessage> findMusic(@RequestParam(required = false) String name) { List list = null; if(name != null) { list = musicServer.findMusicByName(name); }else { list = musicServer.findMusic(); } return new ResponseBodyMessage<>(1,"查询完毕!",list); } ``` ## 15.3 前端代码 ```javascript $(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+= ''; s+= ''; s+= ''; s+= ''; s+= ''+list.title+''; s+= ''+list.author+'/'+DateFormat(list.uploadtime)+''; s+= ''; s+= ''; s+= ''; s+= ''; s+= ''; s+= ''; } $("#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 操作数据库 > 1. 这里有空查询和模糊查询两种数据库操作 > 2. 空查询 不带 name > 3. 模糊查询带name ### 16.1.1 在 CollectServer 中添加代码 ```java public List findLoveMusicByUserId(int userId){ return collectMapper.findLoveMusicByUserId(userId); } public List findLoveMusicByNameAndUserId(String name,int userId){ return collectMapper.findLoveMusicByNameAndUserId(name,userId); } ``` ### 16.1.2 在 CollectMapper 中添加代码 ```java /** * 查找用户收藏的所有音乐 * @param userId 用户Id * @return 返回查询到的所有音乐 */ List findLoveMusicByUserId(int userId); /** * 查找用户收藏音乐中名字带有 name的音乐 * @param name 部分名字 * @param userId 用户Id * @return 返回查询到的所有音乐 */ List findLoveMusicByNameAndUserId(String name,int userId); ``` ### 16.1.3 在 CollectMapper.xml 中添加代码 ```xml select m.* from collect c,music m where m.musicId = c.musicId and c.userId = #{userId}; select m.* from collect c,music m where m.musicId = c.musicId and c.userId = #{userId} and m.title like concat('%',#{name},'%'); ``` ## 16.2 向 CollectController 中添加代码 > 这里判断前端传来的 name是否为空 > > 1. 不为空, 进入模糊查询 > 2. 为空, 进入空查询 ```java /** * 1. 空查询, 查找所有的收藏音乐 * 2. 模糊查询, 查询包含部分 musicName 的所有收藏音乐 * @param musicName * @param request * @return */ @RequestMapping("findLoveMusic") public ResponseBodyMessage> 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 list = null; if(musicName == null) { list = collectServer.findLoveMusicByUserId(userId); }else{ list = collectServer.findLoveMusicByNameAndUserId(musicName,userId); } return new ResponseBodyMessage<>(1,"查询成功!",list); } ``` ## 16.3 前端代码 ```javascript $(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+= ''; s+= ''; s+= ''; s+= ''+list.title+''; s+= ''+list.author+'/'+DateFormat(list.uploadtime)+''; s+= ''; s+= ''; s+= ''; s+= ''; s+= ''; } $("#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; } ``` ## 16.4 测试代码 