网站违规词处罚做网站的,哈尔滨网页设计制作,su搜索引擎优化,图书馆门户网站建设的意义文章目录1. 项目描述:2. 项目上线展现#xff1a;3. 项目具体实现#xff1a;1. 登录2. 注册3.退出系统4.添加音乐4.1前后端交互约定4.2上传文件业务逻辑#xff1a;4.3创建model包中的music类4.4在MusicMapper接口中#xff0c;声明insertMusic抽象方法4.5在mybatis包中添…
文章目录1. 项目描述:2. 项目上线展现3. 项目具体实现1. 登录2. 注册3.退出系统4.添加音乐4.1前后端交互约定4.2上传文件业务逻辑4.3创建model包中的music类4.4在MusicMapper接口中声明insertMusic抽象方法4.5在mybatis包中添加操作数据的SQL语句4.6 实现控制层controller中的接口方法4.7 在事务层中创建MusicService接口中的insertMusic抽象方法4.8 在业务层中service包中的impl子包中实现insertMusic抽象方法(业务框架)4.9使用postman进行测试4.10.前端代码5. 播放音乐5.1前后端交互约定5.2定义后端交互接口5.3 在MusicService中定义描述播放音乐的接口5.4在service包下的impl子包中实现接口中的getMusic抽象方法5.5ResponseEntiy类介绍5.6 播放音乐业务层实现逻辑5.7 使用postman进行测试5.8前端代码6. 删除音乐(单个删除批量删除)6.1 单个删除6.1.1 前后端交互约定6.1.2 删除业务实现逻辑描述6.1.3 在MusicMapper中声明删除单个音乐的接口和根据id查找音乐的接口6.1.4 在mybatis包中添加操作数据的SQL语句6.1.5后端实现接口6.1.6 在业务层中创建MusicService接口中的deleteMusic抽象方法6.1.7 在业务层中service包中的impl子包中实现deleteMusic抽象方法(业务框架)6.1.8 使用postman进行测试6.1.9前端代码6.2 实现批量删除6.2.1 前后端交互约定6.2.2 实现后端交互接口(是否批量删除成功)6.2.3 批量删除音乐逻辑描述6.2.4业务层中创建MusicService接口中的deletePartMusic抽象方法6.2.5在业务层中service包中的impl子包中实现deletePartMusic抽象方法(业务框架)6.2.6使用postman进行测试6.2.7 前端代码7. 查询音乐(支持模糊匹配)7.1 前后端交互约定7.2在MusicMapper中声明查询音乐的接口7.3 在mybatis包中添加操作数据的SQL语句7.4 后端实现接口7.5 在业务层中service包中的impl子包中实现findMusic抽象方法(业务框架)7.6 使用postman进行测试8. 添加收藏音乐8.1前后端交互约定8.2 在model层下添加loveMuisic实体类8.3 添加收藏音乐的具体逻辑实现8.4 在LoveMusicMapper中声明添加收藏音乐的接口8.5 在mybatis包中添加操作数据的SQL语句8.6 实现后端交互接口8.7 在业务层中创建LoveMusicService接口中的insertMusic抽象方法8.8 在业务层中service包中的impl子包中实现insertMusic抽象方法(业务框架)8.9 使用postman 进行测试8.10 前端代码9. 删除收藏音乐9.1 前后端交互约定9.2 在LoveMusicMapper中声明删除收藏音乐的接口9.3 在mybatis包中添加操作数据的SQL语句9.4 实现后端交互接口9.5在业务层中创建LoveMusicService接口中的insertMusic抽象方法9.6 在业务层中service包中的impl子包中实现insertMusic抽象方法(业务框架)9.7 使用postman 进行测试9.8 前端代码10.查询收藏音乐(模糊查询)10.1 前后端交互约定10.2在LoveMusicMapper中声明删除收藏音乐的接口10.3在mybatis包中添加操作数据的SQL语句10.4在业务层中创建LoveMusicService接口中的findMusic抽象方法10.5 使用postman进行测试11.代码完善11.设置登录拦截器12.使用服务器部署上线1. 项目描述:
主要业务注册登录注销新增查询删除播放歌曲。
在业务方面实现了基础的增删查改基于BCrypt对用户所传的密码进行加密实现了自定义登录拦截器使FileputStream,FileReader 判断上传文件是否是.mp3类型的文件
技术选型JavaSpringSpringMVCSpringBootAJAXMySQLMyBatishtmlcssJsredis
2. 项目上线展现
3. 项目具体实现
一. SpringBoot项目搭建
二. 设计数据库表 user表(存储用户信息) 用户Id——userId用户名——username用户密码——password drop database if exists onlinemusic;
create database if not exists onlinemusic character set utf8;
use onlinemusic;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表(存储音乐数据) 哪个用户上传的这首音乐得到这个用户的Id ——userId歌曲标题——title歌手——singer时间——time歌曲Id——idurl 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);lovemuisc表(存储用户收藏的音乐信息) 收藏音乐的id收藏音乐在music表中的music_id收藏这首歌的用户user_id 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);三. 配置文件
# 连接数据库
spring.datasource.urljdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncodingutf8serverTimezoneAsia/Shanghai
spring.datasource.usernameroot
spring.datasource.password123456
spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver# 配置xml
mybatis.mapper-locationsclasspath:mybatis/**Mapper.xml# 配置springboot上传文件的大小默认每个文件的配置最大为15Mb单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size15MB
spring.servlet.multipart.max-request-size100MB## 音乐上传后的路径
music.local.pathC:/work/local/music/## 设置日志级别
logging.level.rootINFO
logging.level.com.example.onlinemusic.mapperdebug
logging.level.druid.sql.StatementDEBUG
logging.level.com.exampleDEBUG1. 登录 定义model层中的实体类 根据数据库中的user表下的字段我们需要在实体类中 定义一个User类该类中的属性有userIdusernamepassword。 Data
public class User {public int id;public String username;public String password;
}
//使用注解Data 可以是代码简化Data中有类中的getter,setter,构造方法等功能前后端交互约定 定义操作数据库的抽象方法 创建一个mapper包在包下创建一个UserMapper接口在接口中定义一个login(String username,String password)的抽象方法。 /*** 根据用户名查找用户*/
User findByName(String username);首先通过username判断数据库中有没有这个用户如果没有那么就在返回体中说明该用户不存在。 /*** 判断密码是否正确*/User findUser(String username, String password);在UserMapper.xml中实现具体的操作user数据库的操作语句 select idfindByName resultTypecom.example.onlinemusic.model.Userselect * from user where username #{username}
/select使用一个类约定后端服务器向前端服务器发送的响应体(ResponseBodyMessage) 定义一个tools工具包在工具包中实现同一响应体响应体中包括:状态(status),返回信息(message),返回具体数据(data泛型类对象) package com.example.onlinemusic.tools;import lombok.Data;/*** 封装统一响应体*/Datapublic class ResponseBodyMessage T{private String message; //返回错误信息private int status; //返回状态码private T data; //返回给前端数据public ResponseBodyMessage(String message, int status, T data) {this.message message;this.status status;this.data data;}} 实现前后端交互接口: RestController
RequestMapping(/user)
public class UserController {Resourceprivate UserServiceImpl userService;/*** 注册*/PostMapping(/register)public ResponseBodyMessageUser register(RequestParam String username, RequestParam String password) {//判断传入参数是否为空if (StringUtils.isAnyBlank(username, password)) {return new ResponseBodyMessage(参数异常,0,null);}Boolean register userService.register(username, password);if(!register){return new ResponseBodyMessage(注册失败,-1,null);}return new ResponseBodyMessage(注册成功,1,null);}
} 各个注解回顾 RequestParam: RequestParam注解用于将方法的参数与Web请求的传递的参数进行绑定。使用 RequestParam可以轻松的访问HTTP请求参数的值。 简单的说就是这个字段命名前端叫什么我后端服务器就叫什么。 RestController:用于返回JSONXML等数据但不能返回HTML页面相当于注解ResponseBody和注解controller的结合 RequestMapper:如果用在类上则表示所有响应请求的方法都以该地址作为父路径 Resource:表示DI 注入对象 PostMapper:PostMapping注解用于处理HTTP POST请求并将请求映射到具体的处理方法中。PostMapping与GetMapping一样也是一个组合注解 密码加密介绍 使用MD5加密 MD5是一个安全的散列算法输入两个不同的明文不会得到相同的输出值根据输出值不能的带原始的明文那么这个过程就是不可逆的但是虽然是不可逆的但是这种方法也存在这个风险因为在后来因为彩虹表的出现这种MD5加密之后的面就没有那么有保密性了。 彩虹就是一个庞大的针对各种可能的自核预先极端号哈希值的集合不一定是针对MD5算法的各种算法都有现在的彩虹表都有100G以上的。 不安全的原因 暴力攻击的速度很快字典表很大碰撞 更安全的做法是加盐或者是把密码设置长一点让加密的字符串变长破解的时间就会变慢密码破解要结合它解密之后带来的经济效益。 我们这里就是一个小系统不至于人家那彩虹表给你破解密码。 MD5加密的简单用法 引入关于MD5的相关依赖 !-- md5 依赖 --
dependencygroupIdcommons-codec/groupIdartifactIdcommons-codec/artifactId
/dependency
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactIdversion3.9/version
/dependencypublic class MD5Util {public static final String str 1z2h3o4u5j6i7n;public static String md5(String str){return DigestUtils.md5DigestAsHex(str.getBytes());}public static String intPutPassFromNewPass(String password){String s str.charAt(0) str.charAt(5) str str.charAt(6) str.charAt(2) str.charAt(3);return md5(s);}public static void main(String[] args) {String s intPutPassFromNewPass(123456);System.out.println(s);String s1 intPutPassFromNewPass(s);System.out.println(s1);}
}同一个明文密码在进行两次加密之后得到的运行结果 568a03aca5deb1c401e2f9028d7fd150 568a03aca5deb1c401e2f9028d7fd150 我们可以看到两次加密的结果是一样的并且每次运行这个加密程序得到的都是同一种结果。 其实在我们的小系统中使用这中加密方式也是可以的毕竟我们的系统应该不会有人破解密码码一点利益价值都没有 使用BCrypt加密 BCrypt也是一个加密方式可以比较方便的实现数据的加密我们可以简单的理解在这个类中的加密方法有一种动态加盐的操作我们使用的MD5加密每次加密后的密文其实都是一样的这样方便了MD5通过大量数据方式进行破解我们这里的BCrypt生成的密文是60位的MD5生成的密文是32位的破解更难 添加依赖 !-- security依赖包 加密--
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-web/artifactId
/dependency
dependencygroupIdorg.springframework.security/groupIdartifactIdspring-security-config/artifactId
/dependency项目中没有使用到 spring security 这个框架、只是使用到了该框架下的一个类 SpringBootApplication(exclude {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
public class OnlinemusicApplication {public static void main(String[] args) {SpringApplication.run(OnlinemusicApplication.class, args);}
}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);boolean same_password_result bCryptPasswordEncoder.matches(password,newPassword);System.out.println(加密的密码和正确密码对比结果: same_password_result);boolean other_password_result bCryptPasswordEncoder.matches(987654,newPassword);System.out.println(加密的密码和错误的密码对比结果: other_password_result);}
}加密的密码为: $2a101010Xykg/goK.CKbDJJGqSgDp.q6a/MgZBbHU/2Vc27Y81OHnBSAMcmay 加密的密码和正确密码对比结果: true 加密的密码和错误的密码对比结果: false encode方法对用户密码进行加密 matches方法:第一个参数表示的是还没有加密的密码第二个参数表示的是从数据中查询到的加密之后的密码 总结 密码学的应用安全是家里在破解所要付出的成本远超过得到的利益上的使用BCrypt相比于MD5加密更好在于破解的难度上加大了BCrypt的破解成本增加了导致运行成本也大大的增加总之我们这里使用MD5就已经足够了 MD5和BCrypt之间的区别 BCrypt加密:一种加盐的单向hash不可逆的加密算法同一种明文每次加密后的密文都是不一样的并且不能反向破解生成明文破解难度很大MD5加密是不加盐的单向hash不可逆的加密算法同一个密码经过hash的时候生成同一个hash值在大多数的情况下有些进过MD5加密的方法会被破解 登录具体业务实现:
Slf4j
Service
public class UserServiceImpl implements UserService {BCryptPasswordEncoder bCryptPasswordEncoder new BCryptPasswordEncoder();Autowiredprivate UserMapper userMapper;Overridepublic User login(String username,String password, HttpServletRequest request) {User loginUser userMapper.findByName(username);//用户不存在if(loginUser null){return null;}//得到数据库中的符合username的user对象判断user此时已经加密的密文和此时用户输入的明文密码是否相同boolean flg bCryptPasswordEncoder.matches(password, loginUser.getPassword());//如果是false,就是登录失败if(!flg){return null;}User user new User();user.setUsername(username);user.setPassword(password);user.setId(loginUser.getId());request.getSession().setAttribute(USERINFO_SESSION_KEY,user);return loginUser;}Service
public interface UserService {/*** 登录*/User login(String username,String password, HttpServletRequest request);
}把用户的基本 信息添加到session中在服务器中保存用户的登录态(这部分在后期会使用redis进行更改) 为了方便标准在tools表下定义一个Constant类在类中使用一个静态变量表示用户的登录信息 public class Constant {public static final String USERINFO_SESSION_KEY user;
}使用postman测试 前端代码 !DOCTYPE html
html
headmeta http-equivContent-Type contenttext/html; charsetutf-8/title注册界面/titlemeta nameviewport contentwidthdevice-width,initial-scale1.0,user-scalablenolink relstylesheet hrefcss/bootstrap.csslink hreficonfont/style.css typetext/css relstylesheetstylebody {color: #fff;font-family: 微软雅黑;font-size: 14px;}.wrap1 {position: absolute;top: 0;right: 0;bottom: 0;left: 0;margin: auto}/*把整个屏幕真正撑开--而且能自己实现居中*/.main_content {background: url(images/main_bg.png) repeat;margin-left: auto;margin-right: auto;text-align: left;float: none;border-radius: 8px;}.form-group {position: relative;}.login_btn {display: block;background: #3872f6;color: #fff;font-size: 15px;width: 100%;line-height: 50px;border-radius: 3px;border: none;}.login_input {width: 100%;border: 1px solid #3872f6;border-radius: 3px;line-height: 40px;padding: 2px 5px 2px 30px;background: none;}.icon_font {position: absolute;bottom: 15px;left: 10px;font-size: 18px;color: #3872f6;}.font16 {font-size: 16px;}.mg-t20 {margin-top: 20px;}media (min-width: 200px) {.pd-xs-20 {padding: 20px;}}media (min-width: 768px) {.pd-sm-50 {padding: 50px;}}#grad {background: -webkit-linear-gradient(#4990c1, #52a3d2, #6186a3); /* Safari 5.1 - 6.0 */background: -o-linear-gradient(#4990c1, #52a3d2, #6186a3); /* Opera 11.1 - 12.0 */background: -moz-linear-gradient(#4990c1, #52a3d2, #6186a3); /* Firefox 3.6 - 15 */background: linear-gradient(#4990c1, #52a3d2, #6186a3); /* 标准的语法 */}/style/headbody stylebackground:url(images/bg.jpg) no-repeat;div classcontainer wrap1 styleheight:450px;h2 classmg-b20 text-centeronlineMusic登录页面/h2div classcol-sm-8 col-md-5 center-auto pd-sm-50 pd-xs-20 main_contentp classtext-center font16用户登录/pdiv classform-group mg-t20i classicon-user icon_font/iinput typetext classlogin_input idusername placeholder请输入用户名//divdiv classform-group mg-t20i classicon-lock icon_font/iinput typepassword classlogin_input idpassword placeholder请输入密码//divinput typesubmit classlogin_btn value登录 idsubmit/div
/div
script srchttp://libs.baidu.com/jquery/2.0.0/jquery.min.js/script
script$(function () {$(#submit).click(function () {//得到输入框中的输入内容let username $(#username).val();let password $(#password).val();//判断空值if (username.trim() || password.trim() ) {alert(输入内容不能为空!);return;}$.ajax({url:/user/login,//指定路径data:{username:username,password:password},type:POST,dataType:json,//服务器返回数据为jsonsuccess:function (data) {console.log(data);if(data.status1){alert(登录成功);window.location.hreflist.html;}else{alert(登录失败账号或密码错误请重试);$(#message).text(账号或密码错误请重试!);$(#user).val();$(#password).val();}}});})})
/script
/body
/html2. 注册
主要实现思想用户前端传来username和password,然后在业务层判断数据库中是否存在这个username,如果存在那么直接返回false,否则就把用户添加到数据库中返回true
❤️❤️在UserMapper接口中定义抽象方法register(String username,String password)****
/*** 注册用户信息*/
Boolean register(String username,String password);❤️❤️在resource 资源包下中的UserMapper.xml中添加SQL语句(在数据库中添加用户) insert idregister insert into user values(null,#{username},#{password});
/insert实现前后端交互接口中的register()方法如果传来的两个参数为空或者字符串的长度为0那么就返回参数异常如果在数据库查询这个注册用户已经存在了那么就返回-1如果注册失败就返回-2 PostMapping(/register)public ResponseBodyMessageUser register(RequestParam String username, RequestParam String password) {//判断传入参数是否为空if (StringUtils.isAnyBlank(username, password)) {return new ResponseBodyMessage(参数异常,0,null);}int register userService.register(username, password);if(register -1){return new ResponseBodyMessage(该用户已存在,-1,null);}else if(register -2){return new ResponseBodyMessage(注册失败,-2,null);}return new ResponseBodyMessage(注册成功,1,null);}
UserService中的register()抽象方法
/*** 注册*/
int register(String username,String password);UserService包下的impl包下的UserServiceImpl类实现register方法 Override
public int register(String username, String password) {//判断该用户在数据库中是否存在User user userMapper.findByName(username);if(user ! null){return -1;}//进行加密password bCryptPasswordEncoder.encode(password);Boolean register userMapper.register(username, password);if(!register){return -2;}return 1;
}
使用postman进行测试 前端代码
!DOCTYPE html
html
headmeta http-equivContent-Type contenttext/html; charsetutf-8/title注册界面/titlemeta nameviewport contentwidthdevice-width,initial-scale1.0,user-scalablenolink relstylesheet hrefcss/bootstrap.csslink hreficonfont/style.css typetext/css relstylesheetstylebody {color: #fff;font-family: 微软雅黑;font-size: 14px;}.wrap1 {position: absolute;top: 0;right: 0;bottom: 0;left: 0;margin: auto}/*把整个屏幕真正撑开--而且能自己实现居中*/.main_content {background: url(images/main_bg.png) repeat;margin-left: auto;margin-right: auto;text-align: left;float: none;border-radius: 8px;}.form-group {position: relative;}.login_btn {display: block;background: #3872f6;color: #fff;font-size: 15px;width: 100%;line-height: 50px;border-radius: 3px;border: none;}.login_input {width: 100%;border: 1px solid #3872f6;border-radius: 3px;line-height: 40px;padding: 2px 5px 2px 30px;background: none;}.icon_font {position: absolute;bottom: 15px;left: 10px;font-size: 18px;color: #3872f6;}.font16 {font-size: 16px;}.mg-t20 {margin-top: 20px;}media (min-width: 200px) {.pd-xs-20 {padding: 20px;}}media (min-width: 768px) {.pd-sm-50 {padding: 50px;}}#grad {background: -webkit-linear-gradient(#4990c1, #52a3d2, #6186a3); /* Safari 5.1 - 6.0 */background: -o-linear-gradient(#4990c1, #52a3d2, #6186a3); /* Opera 11.1 - 12.0 */background: -moz-linear-gradient(#4990c1, #52a3d2, #6186a3); /* Firefox 3.6 - 15 */background: linear-gradient(#4990c1, #52a3d2, #6186a3); /* 标准的语法 */}/style/headbody stylebackground:url(images/bg.jpg) no-repeat;div classcontainer wrap1 styleheight:450px;h2 classmg-b20 text-centeronlineMusic注册页面/h2div classcol-sm-8 col-md-5 center-auto pd-sm-50 pd-xs-20 main_contentp classtext-center font16用户注册/pdiv classform-group mg-t20i classicon-user icon_font/iinput typetext classlogin_input idusername placeholder请输入用户名//divdiv classform-group mg-t20i classicon-lock icon_font/iinput typepassword classlogin_input idpassword placeholder请输入密码//divdiv classform-group mg-t20i classicon-lock icon_font/iinput typepassword classlogin_input idconfirmPassword placeholder请输入确认密码//divinput typesubmit classlogin_btn value注册 idsubmit/div
/div
script srchttp://libs.baidu.com/jquery/2.0.0/jquery.min.js/script
script$(function () {$(#submit).click(function () {//得到输入框中的输入内容let username $(#username).val();let password $(#password).val();let confirmPassword $(#confirmPassword).val();//判断空值if (username.trim() || password.trim() || confirmPassword.trim() ) {alert(输入内容不能为空!);return;}if (password ! confirmPassword) {alert(密码和确认密码要相同!)return;}$.ajax({url: /user/register,data:{username:username,password:password},type:POST,dataType:json,//服务器返回数据为jsonsuccess:function (data) {console.log(data);if(data.status1){alert(注册成功);window.location.hreflogin.html;}else if(data.status -1){alert(该用户已存在);}else{alert(注册失败);}}});})})
/script
/body
/html3.退出系统
因为退出系统的业务条件比较简单我们此时在controller程序调用接口层进行实现 其实退出系统的业务非常简单就是把服务器中记录用户信息的sessionId 对应的信息给删除就行。
PostMapping(/logout)
public ResponseBodyMessageBoolean logout(HttpServletRequest request){//得到此时用户的登录态设置登录态为空request.getSession().removeAttribute(USERINFO_SESSION_KEY);return new ResponseBodyMessage(退出登录,0,true);
}使用postman 进行测试
前端代码
//退出登录
$(#logout).click(function (){$.ajax({url: /user/logout,type: post,success: function (result) {if (result) {alert(退出成功返回登录页面);window.location.assign(login.html);} else {alert(退出失败);}}})
})4.添加音乐
4.1前后端交互约定 4.2上传文件业务逻辑
首先用户在前端页面上传一个文件然后经过前端传递给后端。
判断当前用户是否是登录状态判断传来的文件是否在数据库中已经存在了如果存在就提醒用户该文件已存在。然后判断文件的类型此处的判断文件类型不能只判断文件是否已.mp3结尾我们要知道上传.mp3文件的标准是什么其实就是在mp3文件中字节码文件中用一个标志位使用TAG 表示的我们可以通过它判断文件的类型然后就是把文件上传到服务器中把文件添加到数据库中
4.3创建model包中的music类
因为此时要上传音乐所以我们在model包中创建一个music实体类
package com.example.onlinemusic.model;import lombok.Data;Data
public class Music {public int id; //音乐idpublic String title; //音乐标题public String singer; //歌手public String time; //时间public String url; //urlpublic int userId; //上传音乐用户的id
}4.4在MusicMapper接口中声明insertMusic抽象方法
❤️❤️在mapper包下的MusicMapper接口中声明一个用于把歌曲信息写到数据库中的抽象方法
/*** 添加音乐*/
int insertMusic(String title,String singer,String time,String url,int userId);4.5在mybatis包中添加操作数据的SQL语句
❤️❤️在mybatis包下添加一个用于在数据库中存储歌曲信息的SQL语句把歌曲名歌手时间url(播放歌曲的时候用得到)和上传这个用户的Id insert idinsertMusicinsert into music(title,singer,time,url,userId) values(#{title},#{singer},#{time},#{url},#{userId})
/insert4.6 实现控制层controller中的接口方法 PostMapping(/upload)
public ResponseBodyMessageBoolean insertMusic(RequestParam String singer,RequestParam(filename) MultipartFile multipartFile,HttpServletRequest request) throws IOException {if(StringUtils.isAnyBlank(singer)){return new ResponseBodyMessage(参数异常,-1,false);}int ret musicService.insertMusic(singer, multipartFile, request);if(ret -1){return new ResponseBodyMessage(用户还未登录请先登录,-1,false);}else if(ret 0){return new ResponseBodyMessage(歌曲已经上传过了无需再次上传,0,false);}else if(ret -2){return new ResponseBodyMessage(上传文件失败,-2,false);}else if(ret -3){return new ResponseBodyMessage(上传文件的格式不对,-3,false);}return new ResponseBodyMessage(文件存储成功,1,true);
}
4.7 在事务层中创建MusicService接口中的insertMusic抽象方法
接口层调用业务层创建一个MusicService接口在接口中声明一个添加音乐的抽象方法。在Service包中的Impl子包中添加一个MusicServiceImpl用于实现接口中的方法。
int insertMusic(String singer, MultipartFile multipartFile, HttpServletRequest request)4.8 在业务层中service包中的impl子包中实现insertMusic抽象方法(业务框架)
因为我们要把文件上传到服务器和数据库中因为我们现在是本地开发的那么此时就在本地设置一个存放音乐数据的地方其实我们在配置文件的时候已经执行的。把文件添加到 C:/work/local/music/ 使用Value(“${music.local.path}”)获得到配置文件中的值。
这里我们还要介绍一个类MultipartFile,是Spring框架中处理文件上传的主要类。
主要的方法有
Autowiredprivate MusicMapper musicMapper;//读取配置文件中的信息 --- 歌曲所在的盘福路径Value(${music.local.path})private String SAVE_PATH;//得到客户端发来的歌手歌曲和判断此时用户是否已经登录成功public int insertMusic(String singer, RequestParam(file)MultipartFile file, HttpServletRequest request) {//检查此时用户是否登录HttpSession session request.getSession(false);if (session null || session.getAttribute(USERINFO_SESSION_KEY) null) {return -1;}//判断传出的文件是否是mp3文件 使用得到字节数组中的最后一段字节码判断二进制字符串中是否存在TAGboolean isNotMp3Type false;try {InputStream is file.getInputStream();InputStreamReader isReader new InputStreamReader(is, StandardCharsets.UTF_8);BufferedReader br new BufferedReader(isReader);//循环逐行读取String line;while ((line br.readLine()) ! null) {if(line.contains(TAG)){isNotMp3Type true;}}br.close();} catch (IOException e) {e.printStackTrace();}if(!isNotMp3Type){return -3;}//把歌曲文件上传到服务器//可以得到上传文件的名称和类型String fileNameAndType file.getOriginalFilename();assert fileNameAndType ! null;int index fileNameAndType.lastIndexOf(.);String title fileNameAndType.substring(0, index);//判断数据库中是否存在和要即将添加的音乐重名并且歌手名相同如果相同就是重复的歌曲题型用户Music music musicMapper.findMusicNyTitleAndSinger(title, singer);//歌曲已经上传过了if(music ! null){return 0;}//得到存放音乐文件的路径//盘福路径 歌曲名称String path SAVE_PATH fileNameAndType;File file1 new File(path);//如果该目录不存在那么就重新创建一个if (!file1.exists()) {file1.mkdir();}try {//向指定目录中上传音乐file.transferTo(file1);} catch (IOException e) {e.printStackTrace();//服务器存储失败return -2;}//把文件上传的数据库中//文件标题//得到文件名中 “.”的位置截取到.这个位置就是title//得到userIdUser user (User) request.getSession().getAttribute(USERINFO_SESSION_KEY);int userId user.getId();//得到url 此处的url 用于播放String url /music/get?path title;//得到时间SimpleDateFormat simpleDateFormat new SimpleDateFormat(yyyy-MM-dd);String time simpleDateFormat.format(new Date());//如果此时的数据库存放文件失败那么此时的服务器中的文件也应该消失没有try{int ret musicMapper.insertMusic(title, singer, time, url, userId);//数据库存储失败if(ret ! 1){return -2;}return 1;}catch (BindingException e){//如果在把文件信息添加到数据库的时候发生了异常那么此时就把服务器中的文件也要删了//如果不删那么此时服务器和数据库中的文件数据就不一致了file1.delete(); //删除服务器中的文件return -2;}}
1.判断用户是否登录 //检查此时用户是否登录HttpSession session request.getSession(false);if (session null || session.getAttribute(USERINFO_SESSION_KEY) null) {return -1;}2.判断删除的文件是否满足mp3文件格式
我们不能是否文件的后缀来判断某个文件是否是.mp3文件因为谁知道那个老六会把其他类型的文件的后缀名改为.mp3文件。
其实每个文件都有自己的组成方式在每个文件的尾部长度为128字节有一个.mp3公有的特点就是有TAG标识
读取文件中的信息把这些信息转化成为utf-8类型然后进行逐行读取判断读取的每一行中是否有TAG字段
InputStream 只是一个抽象类要使用还需要具体的实现类。关于 InputStream 的实现类有很多基本可以认为不同的输入设备都可以对应一个 InputStream 类我们现在只关心从文件中读取所以使
用 FileInputStream
转化字符集类型
InputStreamReader(InputStream in, Charset cs) 创建一个使用给定字符集的InputStreamReader
BufferedReader 从字符输入流读取文本缓冲字符以提供字符数组和行的高效读取
//判断传出的文件是否是mp3文件 使用得到字节数组中的最后一段字节码判断二进制字符串中是否存在TAG
boolean isMp3Type false;
try {InputStream is file.getInputStream();InputStreamReader isReader new InputStreamReader(is, StandardCharsets.UTF_8);BufferedReader br new BufferedReader(isReader);//循环逐行读取String line;while ((line br.readLine()) ! null) {if(line.contains(TAG)){isMp3Type true;}}br.close();
} catch (IOException e) {e.printStackTrace();
}
if(!isMp3Type){return -3;
}3.判断数据库中是否已经存在这个文件
使用getOriginalFilename方法得到这个文件的名字和类型然后在得到.的下标然后再使用substring()方法得到0~.之间的字符串这就是文件的具体名字即title,然后根据这个title和singer判断数据库中是否存在这个singer所唱的title ,因为同一首歌会有许多人唱所以使用singer和title在数据库中匹配。
String fileNameAndType file.getOriginalFilename();
assert fileNameAndType ! null;
int index fileNameAndType.lastIndexOf(.);
String title fileNameAndType.substring(0, index);
//判断数据库中是否存在和要即将添加的音乐重名并且歌手名相同如果相同就是重复的歌曲题型用户
Music music musicMapper.findMusicNyTitleAndSinger(title, singer);
//歌曲已经上传过了
if(music ! null){return 0;
}4.如果此时在数据库中未找到这个音乐那么此时就把文件上传到服务器中。 此时得到服务器中要存放文件的盘符路径 和 这个文件的文件名和后缀拼接判断这个文件路径是否已经存在如果不存在使用mkdir创建一个。然后使用transferTo()方法把文件上传到指定目录。 //得到存放音乐文件的路径
//盘福路径 歌曲名称
String path SAVE_PATH fileNameAndType;
File file1 new File(path);
//如果该目录不存在那么就重新创建一个
if (!file1.exists()) {file1.mkdir();
}
try {//向指定目录中上传音乐file.transferTo(file1);
} catch (IOException e) {e.printStackTrace();//服务器存储失败return -2;
}5.把文件相关内容写到数据库中 此时我们要把相关这个文件的 title(文件名)userId(上传文件的userId),time(上传文件的时间),singer(歌手)url传到数据库。 //得到userId
User user (User) request.getSession().getAttribute(USERINFO_SESSION_KEY);
//根据已经登录的session信息得到此时用户的Id
int userId user.getId();
//得到url 此处的url 用于播放
String url /music/get?path title;
//得到时间
SimpleDateFormat simpleDateFormat new SimpleDateFormat(yyyy-MM-dd);
String time simpleDateFormat.format(new Date());
//如果此时的数据库存放文件失败那么此时的服务器中的文件也应该消失没有
try{int ret musicMapper.insertMusic(title, singer, time, url, userId);//数据库存储失败if(ret ! 1){return -2;}return 1;
}catch (BindingException e){//如果在把文件信息添加到数据库的时候发生了异常那么此时就把服务器中的文件也要删了//如果不删那么此时服务器和数据库中的文件数据就不一致了file1.delete(); //删除服务器中的文件return -2;
}4.9使用postman进行测试
同一个用户上传同一首歌曲是不能上传成功的
上传后缀名不是mp3的文件
上传后缀名是.mp3的文件但是该文件的本质不是一个mp3格式
4.10.前端代码
!DOCTYPE html
html langen
headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title上传音乐/title
/head
body
form action/music/upload methodpost enctypemultipart/form-data上传文件input typefile namefilenamelabel歌手名input typetext namesinger placeholder请输入歌手名/labelinput typesubmit value上传
/form
/body
/html5. 播放音乐
5.1前后端交互约定 5.2定义后端交互接口
GetMapping(/get)
public ResponseEntitybyte[] getMusic(RequestParam String path){//如果客户端传来的path为空或者字符串长度为0那么就是一个异常参数那么就是一个有问题的请求if(StringUtils.isAnyBlank(path)){return ResponseEntity.badRequest().build();}//返回字节类型的文件数据return musicService.getMusic(path);
}5.3 在MusicService中定义描述播放音乐的接口
ResponseEntitybyte[] getMusic(String path);5.4在service包下的impl子包中实现接口中的getMusic抽象方法
我们使用了Files.reasAllBytes(String path):读取文件中的所有字节读入内存参数path是文件的路径(绝对路径)
Override
public ResponseEntitybyte[] getMusic(String path) {File file new File(SAVE_PATH path);byte[] a null;try {//转化成为字节码数据a Files.readAllBytes(file.toPath());//如果a为nullif(a null){return ResponseEntity.badRequest().build();}return ResponseEntity.ok(a);} catch (IOException e) {e.printStackTrace();}return ResponseEntity.badRequest().build();
}5.5ResponseEntiy类介绍
ResponseEntity对象是Spring对请求响应的封装。它继承了HttpEntity对象包含了Http的响应码httpstatus、响应头header、响应体(body)三个部分。
ResponseEntity类继承自HttpEntity类被用于Controller层方法 。ResponseEntity.ok 方法有2个方法分别是有参数和没有参数。
//这个方法若被调用的话返回OK状态
public static ResponseEntity.BodyBuilder ok(){return status(HttpStatus.OK);
}
//这个方法若被调用的话返回body内容和OK状态
public static T ResponseEntityT ok(T body) {ResponseEntity.BodyBuilder builder ok();//ResponseEntity可以通过这个builder返回任意类型的body内容return builder.body(body);
}与API中的描述一致无参 ok方法 返回OK状态有参ok方法返回body内容和OK状态
body类型 是 泛型T也就是我们不确定body是什么类型可以向ok方法传递任意类型的值
有参ok方法其实有调用无参ok方法
5.6 播放音乐业务层实现逻辑
把传来的文件转化成为字节码文件然后把字节码文件返回给前端 //转化成为字节码数据a Files.readAllBytes(file.toPath());//如果a为nullif(a null){return ResponseEntity.badRequest().build();}5.7 使用postman进行测试 5.8前端代码
div stylewidth: 180px; height: 140px; position:absolute; bottom:10px; right:10pxscript typetext/javascript srcplayer/sewise.player.min.js/scriptscript typetext/javascriptSewisePlayer.setup({server: vod,type: mp3,//这里是默认的一个网址videourl: http://jackzhang1204.github.io/materials/where_did_time_go.mp3,skin: vodWhite,//这里需要设置falseautostart: false,});/script
/div//播放音乐
function playerSong(result) {console.log(result);//得到音乐的名字let musicName result.substring(result.lastIndexOf() 1);console.log(musicName);//retult 表示的是 url musicName 表示的是音乐名称 0 表示的是音乐从什么时候开始播放 false表示的是是否启动自动播放SewisePlayer.toPlay(result, musicName, 0, true);
}6. 删除音乐(单个删除批量删除)
6.1 单个删除
6.1.1 前后端交互约定 6.1.2 删除业务实现逻辑描述
根据前端传来的音乐id 知道了是哪一首歌曲如果根据这个id 在数据库中没有找到音乐文件那么就提醒用户删除文件不存在如果找到了该文件那么就把数据库和服务器中的该音乐文件都给删了
6.1.3 在MusicMapper中声明删除单个音乐的接口和根据id查找音乐的接口
/*** 根据id 查找音乐*/
Music findMusicById(int id);
/*** 删除单个音乐*/
int deleteMusicById(int id);6.1.4 在mybatis包中添加操作数据的SQL语句
添加根据id删除音乐文件和根据id查询音乐的SQL
select idfindMusicById resultTypecom.example.onlinemusic.model.Musicselect * from music where id #{id}
/selectdelete iddeleteMusicById parameterTypejava.lang.Integerdelete from music where id #{id}/delete6.1.5后端实现接口
PostMapping(/delete)
public ResponseBodyMessageBoolean deleteMusic(int id){if(id 0){return new ResponseBodyMessage(参数错误,-1,false);}int i musicService.deleteMusicById(id);if(i -1){return new ResponseBodyMessage(该音乐不存在,-1,false);}else if(i -2){return new ResponseBodyMessage(删除失败,-2,false);}return new ResponseBodyMessage(删除成功,1,true);
}6.1.6 在业务层中创建MusicService接口中的deleteMusic抽象方法
int deleteMusicById(int musicId);6.1.7 在业务层中service包中的impl子包中实现deleteMusic抽象方法(业务框架) 使用传来的音乐id在数据库中找到该文件如果该文件为空那么就没有id指向的文件那么直接返回-1 然后根据这个id删除数据库music表中id对应的文件。 但是我们在删除服务器中文件的时候就要注意了我们在数据库中可能不同的用户上传了同一首音乐同时服务器中的该音乐文件只有一个。那么此时我们需要使用该文件的title和singer在数据库中查看是否还存在着同名的歌曲如果存在那么就不删除服务器中的文件如果不存在那么就删除。 //根据music 中的title和singer判断数据库中是否还有同名同名的文件如果有就不删除服务器中的文件//如果这首歌在数据库中不存在了那么就删除服务器中的文件Music musicByTitleAndSinger musicMapper.findMusicByTitleAndSinger(music.title, music.singer);if(musicByTitleAndSinger ! null){return 1;}Overridepublic int deleteMusicById(int id) {Music music musicMapper.findMusicById(id);if(music null){return -1;}//删除数据库中的音乐int delete musicMapper.deleteMusicById(id);if(delete ! 1){return -2;}//删除服务器中的音乐//得到文件名int index music.getUrl().lastIndexOf();String filename music.getUrl().substring(index 1);String path SAVE_PATH filename .mp3;File file new File(path);System.out.println(file.toPath());//服务器删除失败if(!file.delete()){log.info(服务器删除失败);return -2;}return 1;}6.1.8 使用postman进行测试 6.1.9前端代码
//删除音乐
function deleteSong(id) {$.ajax({url: /music/delete,type: post,data: {id: id},dataType: json,success: function (result) {if (result) {alert(删除成功,重新加载页面);window.location.assign(list.html);} else {alert(删除失败);}}})
}6.2 实现批量删除
6.2.1 前后端交互约定 6.2.2 实现后端交互接口(是否批量删除成功)
PostMapping(/deletepart)
public ResponseBodyMessageBoolean deletePartMusic(RequestParam(id[]) ListInteger id){//判断id长度是否为空if(id.size() 0){return new ResponseBodyMessage(请选择歌曲,-1,false);}if(!musicService.deletePart(id)){return new ResponseBodyMessage(批量删除失败,-1,false);}return new ResponseBodyMessage(批量删除成功,1,true);
}6.2.3 批量删除音乐逻辑描述
前端传来一个List,在这个List中每个元素都代表的是对应音乐文件的id,删除List中所有Id对应的音乐文件。但是还是一样的删除服务器中的文件的时候要注意!!!
6.2.4业务层中创建MusicService接口中的deletePartMusic抽象方法
Boolean deletePart(ListInteger id);6.2.5在业务层中service包中的impl子包中实现deletePartMusic抽象方法(业务框架)
在数据库和服务器都把这个音乐给删了我们就记位一次。
Override
public Boolean deletePart(ListInteger id) {int sum 0;for(int i 0;i id.size();i) {int musicIndex id.get(i);Music music musicMapper.findMusicById(musicIndex);if (music null) {return false;}loveMusicMapper.deleteMusicById(music.getId());//删除数据库中的文件信息int ret musicMapper.deleteMusicById(musicIndex);//删除服务器中的文件int index music.getUrl().lastIndexOf();String filename music.getUrl().substring(index 1);String path SAVE_PATH filename .mp3;File file new File(path);System.out.println(file.toPath());if (file.delete()) {sum ret;}else {return false;}}return sum id.size();
}6.2.6使用postman进行测试 6.2.7 前端代码
//当页面加载完后 批量删除
$.when(load).done(function () {$(#delete).click(function () {let id new Array();let i 0;//遍历被选中$(input:checkbox).each(function () {if ($(this).is(:checked)) {id[i] $(this).attr(id);i;}})$.ajax({url: /music/deletepart,type: post,data: {id: id},dataType: json,success: function (result) {if (result) {alert(批量删除成功);window.location.assign(list.html);} else {alert(批量删除失败);}}})})7. 查询音乐(支持模糊匹配)
7.1 前后端交互约定 7.2在MusicMapper中声明查询音乐的接口 /*** 查询所有的音乐*/ListMusic findAllMusic();/*** 模糊匹配歌曲名*/ListMusic findMusicByFuzzyAndTitle(String title);7.3 在mybatis包中添加操作数据的SQL语句
select idfindAllMusic resultTypecom.example.onlinemusic.model.Musicselect * from music;
/select
select idfindMusicByFuzzyAndTitle resultTypecom.example.onlinemusic.model.Musicselect * from music where title like concat(%,#{title},%)
/select7.4 后端实现接口
GetMapping(/findmusic)
public ResponseBodyMessageListMusic findMusic(RequestParam(required false) String title){ListMusic musicList musicService.findMusic(title);if(musicList null){return new ResponseBodyMessage(查询列表为空,-1,null);}return new ResponseBodyMessage(查询成功,1,musicList);
}7.5 在业务层中service包中的impl子包中实现findMusic抽象方法(业务框架)
Override
public ListMusic findMusic(String title) {if(title null){return musicMapper.findAllMusic();}return musicMapper.findMusicByFuzzyAndTitle(title);
}7.6 使用postman进行测试 前端代码
function load(title) {$.ajax({//从服务器上得到数据url: /music/findmusic,type: get,dataType: json,data: {title: title},success: function (result) {if (result null) {alert(没有查询到这首歌);return;}console.log(result);//在这里result是一个数组在数组中的每个元素中包含每个歌曲的 id singer urllet data result.data;let s ;for (let i 0; i data.length; i) {let musicUrl data[i].url.mp3;s tr;s th input iddata[i].idtypecheckbox /th;s td data[i].title /td;s td data[i].singer /td;std button classbtn btn-primary οnclickplayerSong(\musicUrl\) 播放歌曲/button /td;std button classbtn btn-primary οnclickdeleteSong( data[i].id ) 删除/button button classbtn btn-primary οnclickloveSong( data[i].id ) 喜欢/button/td;s /tr;}$(#list).html(s);//把拼接好的页面添加到info的id下}})
}8. 添加收藏音乐
8.1前后端交互约定 8.2 在model层下添加loveMuisic实体类
在实体类中包括 id 收藏音乐的Idmusic_id 收藏音乐对应的在music表下到的Id还有就是user_id 表示的是那个用户收藏的在user表中的id
package com.example.onlinemusic.model;import lombok.Data;Data
public class LoveMusic {public int id;public int music_id;public int user_id;
}8.3 添加收藏音乐的具体逻辑实现 根据传来的musicId和从此时的登录态中得到的userId在数据库中的lovermusic表中查看这个音乐是否存在如果存在就提示用户此时无需添加已经收藏。如果lovmusic表中不存在这个文件那么就把这个音乐添加到lovmusic中其实就是把这个muiscId添加到了music表中。 8.4 在LoveMusicMapper中声明添加收藏音乐的接口 Music findLoveMusicByUserIdAndMusicId(int userId,int musicId);int insertMusic(int userId, int musicId);8.5 在mybatis包中添加操作数据的SQL语句
select idfindLoveMusicByUserIdAndMusicId resultTypecom.example.onlinemusic.model.Musicselect * from lovemusic where music_id #{musicId} and user_id #{userId}
/select
insert idinsertMusicinsert into lovemusic values(null,#{userId},#{musicId})
/insert8.6 实现后端交互接口
Autowired
private LoveMusicServiceImpl loveMusicService;
PostMapping(/insert)
public ResponseBodyMessageBoolean insertMusic(int musicId, HttpServletRequest request){if(musicId 0){return new ResponseBodyMessage(参数错误,-1,false);}int insert loveMusicService.insertMusic(musicId, request);if(insert -1){return new ResponseBodyMessage(该歌曲已收藏,-1,false);}else if(insert -2){return new ResponseBodyMessage(收藏失败,-2,false);}return new ResponseBodyMessage(添加成功,1,true);
}8.7 在业务层中创建LoveMusicService接口中的insertMusic抽象方法 int insertMusic(int musicId, HttpServletRequest request);8.8 在业务层中service包中的impl子包中实现insertMusic抽象方法(业务框架)
Override
public int insertMusic(int musicId, HttpServletRequest request) {//得到当前的登录态User user (User) request.getSession().getAttribute(USERINFO_SESSION_KEY);int userId user.getId();//查询音乐是否已存在Music music loveMusicMapper.findLoveMusicByUserIdAndMusicId(userId, musicId);if(music ! null){return -1;}int ret loveMusicMapper.insertMusic(userId, musicId);if(ret ! 1){return -2;}return 1;
}8.9 使用postman 进行测试 8.10 前端代码
function loveSong(musicId) {console.log(musicId);$.ajax({url: /lovemusic/insert,type: post,data: {musicId: musicId},dataType: json,success: function (result) {if (result.status -1) {alert(该歌曲已收藏!!!);} else if (result.status -2) {alert(收藏失败!!!)} else if(result.status 0 ){alert(参数错误!!!)}else{alert(收藏成功);window.location.assign(list.html);}}})
}9. 删除收藏音乐
9.1 前后端交互约定 9.2 在LoveMusicMapper中声明删除收藏音乐的接口
int deleteMusic(int userId,int musicId);9.3 在mybatis包中添加操作数据的SQL语句
delete iddeleteMusicdelete from lovemusic where user_id #{userId} and music_id #{musicId}
/delete9.4 实现后端交互接口
PostMapping(/deletemusic)
public ResponseBodyMessageBoolean deleteMusic(int musicId,HttpServletRequest request){if(musicId 0){return new ResponseBodyMessage(参数错误,-1,false);}int i loveMusicService.deleteMusic(musicId,request);if(i -1){return new ResponseBodyMessage(该音乐不存在,-1,false);}else if(i -2){return new ResponseBodyMessage(删除失败,-2,false);}return new ResponseBodyMessage(删除成功,1,true);
}9.5在业务层中创建LoveMusicService接口中的insertMusic抽象方法
int deleteMusic(int music,HttpServletRequest request);9.6 在业务层中service包中的impl子包中实现insertMusic抽象方法(业务框架)
Override
public int deleteMusic(int musicId, HttpServletRequest request) {User user (User) request.getSession().getAttribute(USERINFO_SESSION_KEY);Music music loveMusicMapper.findLoveMusicByUserIdAndMusicId(user.getId(),musicId);if(music null){return -1;}//删除数据库中的音乐int delete loveMusicMapper.deleteMusic(user.getId(),musicId);if(delete ! 1){return -2;}return 1;
}9.7 使用postman 进行测试 9.8 前端代码
删除收藏页面中的音乐逻辑和主页中的逻辑一致这里不过多叙述
10.查询收藏音乐(模糊查询)
10.1 前后端交互约定 GetMapping(/findmusic)
public ResponseBodyMessageListMusic findMusic(RequestParam(required false) String title,HttpServletRequest request){User user (User) request.getSession().getAttribute(USERINFO_SESSION_KEY);ListMusic musicList loveMusicService.findMusic(title,user.getId());if(musicList null){return new ResponseBodyMessage(查询列表为空,-1,null);}return new ResponseBodyMessage(查询成功,1,musicList);
}10.2在LoveMusicMapper中声明删除收藏音乐的接口
//查看user喜欢的所有音乐
ListMusic findAllMusic(int userId);
//查看user输入的title 也就是歌曲名相关的有关音乐
ListMusic findMusicByFuzzyAndTitle(String title,int userId);10.3在mybatis包中添加操作数据的SQL语句 resultMap idBaseMap1 typecom.example.onlinemusic.model.Musicid columnid propertyid /id columntitle propertytitle /id columnsinger propertysinger /id columntime propertytime /id columnurl propertyurl /id columnuserid propertyuserId /
/resultMap
select idfindAllMusic resultMapBaseMap1select m.* from lovemusic lm,music m where m.id lm.music_id and lm.user_id#{userId}
/selectresultMap idBaseMap2 typecom.example.onlinemusic.model.Musicid columnid propertyid /id columntitle propertytitle /id columnsinger propertysinger /id columntime propertytime /id columnurl propertyurl /id columnuserid propertyuserId /
/resultMap
select idfindMusicByFuzzyAndTitle resultMapBaseMap2select m.* from lovemusic lm,music m where m.id lm.music_id and lm.user_id#{userId} and title like concat(%,#{title},%)
/select10.4在业务层中创建LoveMusicService接口中的findMusic抽象方法 ListMusic findMusic(String title,int userId);10.5 在业务层中service包中的impl子包中实现findMusic抽象方法(业务框架)
Override
public ListMusic findMusic(String title,int userId) {if(title null){return loveMusicMapper.findAllMusic(userId);}return loveMusicMapper.findMusicByFuzzyAndTitle(title,userId);
}10.5 使用postman进行测试 11.代码完善
其实我们在删除的时候应当注意我们的music表中的数据应该和lovemusic表中的数据保持一致也就是如果我现在music页面和lovemusic页面有有同一首歌如果把music页面中的这一首歌给删了那么我们的这个lovmusic表中的这个音乐的数据那么也就没了。但是反过来是不一样的把lovemusic表中的数据无论我们怎么删。都是和music表是无关的。
只能是music ------ lovemusic
使用musicId删除lovemusic表中的数据
loveMusicMapper.deleteMusicById(music.getId());修改后的deleteMusicById()
Override
public int deleteMusicById(int id) {Music music musicMapper.findMusicById(id);if(music null){return -1;}//删除数据库中的音乐int delete musicMapper.deleteMusicById(id);if(delete ! 1){return -2;}//删除和这个音乐相关的在lovemusic中的歌曲loveMusicMapper.deleteMusicById(music.getId());//删除服务器中的音乐//得到文件名int index music.getUrl().lastIndexOf();String filename music.getUrl().substring(index 1);String path SAVE_PATH filename .mp3;File file new File(path);System.out.println(file.toPath());if (!file.delete()) {return -2;}return 1;
}修改后的deletePart()
Override
public Boolean deletePart(ListInteger id) {int sum 0;for(int i 0;i id.size();i) {int musicIndex id.get(i);Music music musicMapper.findMusicById(musicIndex);if (music null) {return false;}//删除数据库中的文件信息int ret musicMapper.deleteMusicById(musicIndex);//删除服务器中的文件int index music.getUrl().lastIndexOf();String filename music.getUrl().substring(index 1);String path SAVE_PATH filename .mp3;File file new File(path);System.out.println(file.toPath());if (file.delete()) {//删除和这个音乐相关的在lovemusic中的歌曲loveMusicMapper.deleteMusicById(music.getId());sum ret;}else {return false;}}return sum id.size();
}11.设置登录拦截器
首先在这里声明一点我们此时虽然已经完成了项目的大部分逻辑但是如果我现在直接使用访问用户收藏音乐界面是可以访问到的。因为我们还没有设置拦截器。
创建一个用于配置拦截器的包—webConfig包在这个包中在声明一个用户的登录拦截器。
Configuration
public class LoginInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//通过session中的信息我们就知道此时的这个用户是否是登录状态HttpSession session request.getSession(false);if(session ! null session.getAttribute(Constant.USERINFO_SESSION_KEY) ! null){System.out.println(登录成功);return true;}return false;}
}配置登录拦截器首先拦截所有的页面然后在逐一的把某些页面和路由解放出来如登录注册页面是不需要验证登录状态的还有对应的前端页面。
Configuration
public class AppConfig implements WebMvcConfigurer {Overridepublic void addInterceptors(InterceptorRegistry registry) {LoginInterceptor loginInterceptor new LoginInterceptor();registry.addInterceptor(loginInterceptor).addPathPatterns(/**).excludePathPatterns(/user/login).excludePathPatterns(/user/register).excludePathPatterns(/css/**.css).excludePathPatterns(/js/**.js).excludePathPatterns(/login.html).excludePathPatterns(/register.html).excludePathPatterns(/images/**).excludePathPatterns(/player/**).excludePathPatterns(/iconfont/**).excludePathPatterns(/source/**);}
12.使用服务器部署上线
我们为了让服务器上线要修改resource中的配置文件
其中要改变的就是此处的url,因为要连接linux中的NMySQL,还有就是关于MySQL的密码
记住在服务器上线的时候一定要在防火墙中打开MySQL的对应端口3306
music:local:path: /root/music/
spring:datasource:url: jdbc:mysql://124.223.222.249:3306/onlinemusic?useSSLfalseserverTimezoneUTCpassword: 123456username: rootdriver-class-name: com.mysql.cj.jdbc.Driverservlet:multipart.max-file-size: 15MBmultipart.max-request-size: 100MB# 开启 MyBatis SQL 打印
logging:level:root: infodruid:sql:Statement: debugcom:example: debug
mybatis:mapper-locations: classpath:mybatis/**Mapper.xml
debug: true项目部署步骤 在Linux中安装好关于Java 的环境依赖 jdk,Tomcat,还有就是MySQL数据库。 创建一个music文件用于存储上传的歌曲文件 相关命令 touch music 但是我们要记住此时一定要是在/root路径之下创建的music文件因为我们在配置文件中已经说明了。 然后在idea中中的maven栏中进行package进行打包。 然后将打包好的文件上传到linux中 看看此时有没有那个进程占用了8080端口 使用 netstat -aup | grep 8080然后使用 kill -9 进程Id 然后使用命令 java -jar onlinemusic-0.0.1-SNAPSHOT.jar 进行项目部署之后我们就可以访问了 访问 124.223.222.249:8080/register.html 但是我们会发现此时我们一旦关闭这个linux页面那么此时我们部署的项目就没了。 解决方法创建一个用于记录程序执行的日志信息的文件 相关命令 touch log.log 然后使用 命令 nohup java -jar onlinemusic-0.0.1-SNAPSHOT.jar log.log 进行项目步数这样我们在操作项目的时候相关的日志信息就展现在了log.log文件中。我们可以使用cat log.log命令可以查看对应的日志信息。此时即便我么退出了linux项目也是执行的。