发布时间:2025-12-10 11:20:01 浏览次数:19
用户 id,用户名 username,用户密码 password
创建 music 表:
音乐 id,音乐名称 title,歌手名称 singer,时间 time,歌曲路径 url,对应上传音乐的用户 userid
创建 lovemusic 表(中间表):
音乐 id,对应的用户id user_id,对应的音乐id music_id
-- 数据库drop database if exists `onlinemusic`;create database if not exists `onlinemusic` character set utf8;-- 使用数据库use `onlinemusic`;-- 创建 user表DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`id` INT PRIMARY KEY AUTO_INCREMENT,`username` varchar(20) NOT NULL,`password` varchar(255) NOT NULL);-- 创建 music 表DROP TABLE IF EXISTS `music`;CREATE TABLE `music` (`id` int PRIMARY KEY AUTO_INCREMENT,`title` varchar(50) NOT NULL,`singer` varchar(30) NOT NULL,`time` varchar(13) NOT NULL,`url` varchar(1000) NOT NULL,`userid` int(11) NOT NULL);-- 创建 lovemusicDROP 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);打开application.properties配置如下信息:
#配置数据库spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&useSSL=falsespring.datasource.username=rootspring.datasource.password=你的密码spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver#配置xmlmybatis.mapper-locations=classpath:mybatis/**Mapper.xml#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mbspring.servlet.multipart.max-file-size = 15MBspring.servlet.multipart.max-request-size=100MB# 配置springboot日志调试模式是否开启debug=true# 设置打印日志的级别,及打印sql语句#日志级别:trace,debug,info,warn,error#基本日志logging.level.root=INFOlogging.level.com.example.onlinemusic.mapper=debug#扫描的包:druid.sql.Statement类和frank包logging.level.druid.sql.Statement=DEBUGlogging.level.com.example=DEBUG在package com.example.onlinemusic.model包中创建User类
@Datapublic class User {private int id;private String username;private String password;}1.新建mapper包,在mapper包下新建UserMapper
@Mapperpublic interface UserMapper {User login(User loginUser);}2.在resource目录下,新建mybatis文件夹,新建UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.onlinemusic.mapper.UserMapper"></mapper>UserMapper 中:
@Mapperpublic interface UserMapper {// 登录功能,查询操作User login(User userLogin);// login 方法,返回为 User 对象}UserMapper.xml中:
<!-- 登录操作,查询 username 和 password,后面查询那个,就目录就是谁的,比如这里查询是User--><select id="login" resultType="com.example.onlinemusic.model.User">select * from user where username=#{username} and password=#{password};</select>加了响应体之后,登录成功,我们再存储一下 Session
完整代码如下:
@RestController // 这个注解是 @Controller 和 @RequestBody 的组合注解@RequestMapping("/user") // 一级路由public class UserController {@Autowiredprivate UserMapper userMapper; // 注入 UserMapper// 后面要用 UserMapper里面的方法,去进行数据查询// 登录功能@RequestMapping("/login")// 传递两个参数 一个是 username 一个是 password// 加了统一响应体类之后 返回值 不在是 void ,而是 我们的 ResponseBodyMessagepublic ResponseBodyMessage<User> login(@RequestParam String username,@RequestParam String password,HttpServletRequest request) {// @RequestParam 注解,可以H后端参数重命名,也可以制定传参// 拿到 userLogin 对象,设置 用户名和密码User userLogin = new User();userLogin.setUsername(username);userLogin.setPassword(password);// 调用 userMapper的 login 方法 在数据库中 进行查询,返回 UserUser user = userMapper.login(userLogin);if(user != null ) {System.out.println("登录成功!");// 存储 session// request.getSession().setAttribute("USERINFO_SESSION_KEY",user);// 做出优化后的代码,我们将 USERINFO_SESSION_KEY 写在我们的工具包中request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);/*** request.getSession.setAttribute()是获得当前会话的session* 然后再setAttribute到session里面去,有效范围是session而不是request。*/// 返回响应体return new ResponseBodyMessage<>(0,"登录成功老铁!",userLogin);}else {System.out.println("登录失败!");// 登录失败,做出响应// 在这里规定 status -1 为失败 0为成功return new ResponseBodyMessage<>(-1,"登录失败老铁!",userLogin);}}}Bcrypt就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理 。我们使用MD5加密,每次加密后的密文其实都是一样的,这样就方便了MD5通过大数据的方式进行破解。Bcrypt生成的密文是60位的。而MD5的是32位的。Bcrypt破解难度更大
添加如下依赖到 pom.xml:
<!-- security依赖包 (加密)--><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId></dependency>在 SpringBoot 启动类添加如下代码:
@SpringBootApplication(exclude ={org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})为什么要加 spring-boot 启动类注解:
当启动类,没有加这个过滤的时候,我们发现不能进行登录。
这是因为在SpringBoot中,默认的Spring Security生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。此时通过上述配置,即可禁用默认的登录验证。
实质我们并没有用到 Security 这个框架,而是用到了里面其中的一个类,仅此而以
创建 BcryptTest 测试类:
public class BcryptTest {public static void main(String[] args) {//模拟从前端获得的密码// String password = "123456";// 获取 BCrypt 对象BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();// encode 对我们输入的密码进行加密处理 得到新的密码String newPassword = bCryptPasswordEncoder.encode(password);System.out.println("加密的密码为: " + newPassword);//使用matches方法进行密码的校验boolean same_password_result = bCryptPasswordEncoder.matches(password, newPassword);//返回trueSystem.out.println("加密的密码和正确密码对比结果: " + same_password_result);boolean other_password_result = bCryptPasswordEncoder.matches("987654", newPassword);//返回falseSystem.out.println("加密的密码和错误的密码对比结果: " + other_password_result);}}逻辑如下:
1.根据用户名名称 查询 当前是否存在这样的用户[用户名:默认是唯一的]
2.取出当前用户的密码,进行匹配,查看密码是否是一样的,一样就登录成功
这里为什么要建一个 AppConfig 类,是为了方便我们后面的对象注入,比如我 我们要在 加密登录中 注入 BCryptPasswordEncoder,我们就需要先创建一个对象,通过 @Bean 注解,将它输入到 Spring 容器中
当然直接在 UserController 当中进行 实例化创建 BCryptPasswordEncoder 对象也是一样的
//@Configuration:表明当前类是一个配置类,被注解的类内部包含有一个或多个被@Bean注解的方法,用于构建//bean定义,初始化Spring容器。//@Bean注解:用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方//法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。//SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用//@Bean注解就表明这个方法需要交给Spring进行管理。@Configurationpublic class AppConfig {@Beanpublic BCryptPasswordEncoder getBCryptPasswordEncoder() {return new BCryptPasswordEncoder();}}UserMapper 代码如下:
每个种类的文件都要自己的格式,检测当前你上传的格式,判断这个文件的格式是不是 mp3 文件
不能通过后缀名进行判断,后缀名可以进行更改的
data: 成功 or 失败
2.MusicMapper.xml 内容如下
3.MusicController 内容如下:
@RequestMapping("/delete")public ResponseBodyMessage<Boolean> deleteMusicById(@RequestParam String id) {// 1. 先检查音乐是否存在int iid = Integer.parseInt(id);// 2. 如果存在进行删除Music music = musicMapper.findMusicById(iid);if(music == null) {return new ResponseBodyMessage<>(-1,"没有你要删除的音乐!",false);}else {// 2.1 删除数据库int ret = musicMapper.deleteMusicById(iid);// 如果 ret 等于1 说明 数据库数据删除成功if(ret == 1) {// 2.2 删除服务器上的数据(file)// 这里需要拿到 title,可以通过 url 去拿,也可以直接 music.getTitleint index = music.getUrl().lastIndexOf("=");String fileName = music.getUrl().substring(index+1);File file = new File(SAVE_PATH + fileName + ".mp3");System.out.println("当前的路径: " + file.getPath());if(file.delete()){return new ResponseBodyMessage<>(0,"服务器当中的音乐删除成功!",true);}else {return new ResponseBodyMessage<>(-1,"服务器当中的音乐删除失败!",false);}}else {return new ResponseBodyMessage<>(-1,"数据库当中的音乐没有删除成功",false);}}}约定前后端相互接口:
MusicController 内容如下:
/*** 批量删除* id[1,3,5,7,9]*/@RequestMapping("/deleteSel")public ResponseBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]")List<Integer> id) {System.out.println("所有的id:" + id);int sum = 0;// 因为是批量删除,这里用数组存储我们要删除的 音乐 idfor (int i = 0; i < id.size(); i++) {// 1. 查询 音乐是否存在Music music = musicMapper.findMusicById(id.get(i));if (music == null) {System.out.println("没有这个id的音乐");return new ResponseBodyMessage<>(-1, "没有你要删除的音乐", false);}// 2. 如果音乐存在我们进行删除// 2.1 进行数据库删除int ret = musicMapper.deleteMusicById(id.get(i));if (ret == 1) {// 说明 数据库删除成功,进行服务器删除// 先拿到 titleint index = music.getUrl().lastIndexOf("=");String fileName = music.getUrl().substring(index + 1);File file = new File(SAVE_PATH + fileName + ".mp3");System.out.println("当前路径:" + file.getPath());if (file.delete()) {sum += ret;} else {return new ResponseBodyMessage<>(-1, "服务器当中的音乐删除失败!", false);}}else {return new ResponseBodyMessage<>(-1,"数据库当中音乐删除失败",false);}}// 判断 sum 的值是否等于 id.size 如果等于说明 删完啦if(sum == id.size()) {System.out.println("整体删除成功!");return new ResponseBodyMessage<>(0,"音乐删除成功",true);}else {System.out.println("整体删除失败!");return new ResponseBodyMessage<>(-1,"音乐删除失败",false);}}此处查询需要满足几个功能:
请求和响应设计:
模糊查询:
select * from music where title like concat('%',#{musicName},'%');
请求和响应设计:
此处查询需要满足几个功能:
请求和响应设计,和查询音乐模块是一样的,这里不在做过多的阐述
支持模糊查询,多表联合查询
请求和响应设计
当删除 music 表 当中的 musicId 为 5的这首音乐的时候,请问 lovemusic这张表中,是不是应该也被删除?
music 表
lovemusic表
我们在删除 音乐模块中,进行删除,需要同步删除我们 喜欢的音乐(lovemuisc),因此,我们对 MusicController 中进行优化和完善。
在 MusicController 中,删除 单个音乐中 添加以下内容:
在 批量删除中 添加以下内容:
以上,后端逻辑全部完善~~~~,接下来是后端模块实现。
将前端页面模板,导入到我们 resources 底下的 static 中
jquery参考手册
JS核心代码如下:
<script>// 1. 登录核心业务逻辑 //原本是$(document).ready(function(){}) 表示当这个页面的dom树加载完成后才会执行这个,而后面的(document).ready可以省略,因此这里就是这样写也是可以的$(function(){//此时设置提交按钮的click事件,通过id选择器来获取$("#submit").click(function(){//此时就需要获取到用户名和密码的值let username = $("#user").val();let password = $("#password").val();//检查一下用户名和密码是否为空,以及去掉空格使用trim方法if(username.trim() == "" || password.trim() == ""){alert("用户名或密码不能为空!");return;}//如果都不为空,就需要使用ajax来发送请求到后端,然后处理这个请求及返回响应$.ajax({type:"POST", // 请求url:"/user/login", // 指定路径// 返回数据data:{"username":username,"password":password},//服务器返回数据类型dataType:"json",success:function(data){//看状态码if(data.status == 0){console.log(data);alert("登录成功,点击进行跳转!");//登录成功,这里就可以进行页面的跳转window.location.href="list.html";}else{alert("登录失败,用户名或密码错误!");$("#password").val("");}}});});});</script>当我们跳转到 list.html 以后,我们要像服务器发起请求,查询到所有的音乐信息,动态的生成表格
JS代码如下:
script type="text/javascript">// 核心代码实现// 1. 上传音乐// <!-- 核心代码实现 -->$(function(){load();});// musicName 可以 默认传参 和 不传参// 不传参 匹配的就是所有的 音乐function load(musicName) {$.ajax({type:"GET",url:"/music/findmusic",//数据data:{"musicName":musicName},//服务器返回数据类型dataType:"json",// 如果服务器返回成功,会返回我们的回调函数success:function(obj) {console.log(obj);// obj包含了所有的返回信息,然后其中的data就包含了所有的音乐信息,可以先获取到var data = obj.data;var s = ''; // 最原始的拼接方式// data[i].id data[i].singer data[i].title 这种形式来获取 obj里面的信息for(var i = 0; i < data.length;i++) {var musicUrl = data[i].url + ".mp3";s += '<tr>';s += '<th> <input id= "' + data[i].id + '" type="checkbox"> </th>';// <th> <input id="1" type="checkbox"></th>s += '<td>' + data[i].title + '</td>';s += '<td>' + data[i].singer + '</td>';//s += "<td <a href=\"\"> <audio src= \""+ musicUrl+"\" + controls=\"controls\" preload=\"none\" loop=\"loop\"> >" + "</audio> </a> </td>";s += '<td> <button class="btn btn-primary" onclick="playerSong(\''+musicUrl+'\')"> 播放歌曲 </button>' + '</td>';s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+data[i].id+')"> 删除 </button> <button class="btn btn-primary" onclick="loveInfo('+data[i].id+')"> 喜欢 </button>' + '</td>';s += '</tr>';}//然后将所有的数据放到tbody里面$('#info').html(s);}});}</script>这里播放歌曲,我们采用开源的播放控件:
码云地址
GitHub
将该开源项目,下载到本地,取出player文件夹,放入static文件夹下
JS 核心代码,就是调用 播放器的 toPlay() 方法
toPlay(url,title,startTime,autoPlay) 这里的 autoPlay 设置为 true 代表 我们点击播放的时候,它才会播放
// 实现音乐播放function playerSong(obj) {// toPlay(url,title,startTime,autoPlay)//obj: http://localhost:8080/music/get?path=xxx.mp3// 从等号下标 下一个位置开始截取 [)var title = obj.substring(obj.lastIndexOf("=") + 1);SewisePlayer.toPlay(obj,title,0,true);}之前约定好的 前后端交互接口
前端传入的数据和后端返回的响应,响应的 data 为 true or false
JS 核心代码如下:
// 删除音乐 传入的参数是 idfunction deleteInfo(obj){console.log(obj);$.ajax({type:"POST",url:"/music/delete",data:{"id":obj},dataType:"json",success:function(body){console.log(body);//判断是否删除成功if(body.data == true){//表示删除成功alert("删除成功了老铁!,重新加载当前页面哈!");window.location.href="list.html";}else{alert("删除失败了老铁!");}}});}思路很简单,我们前面实现音乐列表页的时候 完成了 load 查询函数,这里我们只需要点击 按钮,执行我们的回调函数,拿到我们的 输入框的信息,即可,进行查询
JS核心代码如下:
// 查询和删除// 查询~~~~~~$(function(){// 点击 提交按钮执行 回调函数$("#submit1").click( function(){//这里的功能就是进行查询而框里面不传参数就是默认的查询所有音乐//传了参数就进行模糊查询var name = $("#exampleInputName2").val();load(name);});});这里的删除,我们的删除逻辑为,点击删除,如图:
我们需要获取到 每一行 中的 input 标签中的 checkbox
首先获取到我们需要删除的音乐id并存储起来,其次再通过ajax和后端建立联系,进行删除。
JS 核心代码如下:
// 删除音乐/*** 1. 先拿到 需要删除的 id* 拿到 需要删除的 id 就存储起来* */// when 当执行完 load函数,则执行 done 当中的回调函数$.when(load).done(function() {//删除选中的回调事件$("#delete").click(function(){//由于这里存储的不止一条数据,因此这里需要将这些选中的存储下来var id = new Array();var i = 0; //然后遍历所有的checkbox标签$("input:checkbox").each(function(){//看有没有被选中,被选中了就记录下来,没有选中就不用管//this 发生事件的 demo元素,checked表示看是否选中了if($(this).is(":checked")){id[i] = $(this).attr("id");i++;}});console.log(id);/*** 2. 找到 id以后我们通过 ajax 给后端发送请求,传递给后端我们需要* 删除的音乐* */$.ajax({url:"/music/deleteSel",type:"POST",data:{"id":id},dataType:"json",// 执行成功 回调这个函数success:function(obj){// 看是否删除成功if(obj.status == 0) {alert("删除成功,重新加载此页面!");window.location.href="list.html";}else{alert("删除失败!");}}});收藏音乐列表页,和 list.html 基本一样,但是需要注意我们需要将ajax中的 url 进行修改:
列入如下:
loveMusci.html 如下:
<script type="text/javascript">function load(musicName) {$.ajax({type:"GET",url:"/lovemusic/findlovermusic",//数据data:{"musicName":musicName},//服务器返回数据类型dataType:"json",// 如果服务器返回成功,会返回我们的回调函数success:function(obj) {console.log(obj);// obj包含了所有的返回信息,然后其中的data就包含了所有的音乐信息,可以先获取到var data = obj.data;var s = ''; // 最原始的拼接方式// data[i].id data[i].singer data[i].title 这种形式来获取 obj里面的信息for(var i = 0; i < data.length;i++) {var musicUrl = data[i].url + ".mp3";s += '<tr>';s += '<td>' + data[i].title + '</td>';s += '<td>' + data[i].singer + '</td>';s += '<td> <button class="btn btn-primary" onclick="playerSong(\''+musicUrl+'\')"> 播放歌曲 </button>' + '</td>';s += '<td> <button class="btn btn-primary" onclick="deleteInfo('+data[i].id+')"> 移除 </button>' + '</td>';s += '</tr>';}//然后将所有的数据放到tbody里面$('#info').html(s);}});}// 实现音乐播放function playerSong(obj) {// toPlay(url,title,startTime,autoPlay)//obj: http://localhost:8080/music/get?path=xxx.mp3// 从等号下标 下一个位置开始截取 [)var title = obj.substring(obj.lastIndexOf("=") + 1);SewisePlayer.toPlay(obj,title,0,true);}// 删除喜欢音乐 传入的参数是 idfunction deleteInfo(obj){console.log(obj);$.ajax({type:"POST",url:"/lovemusic/deletelovemusic",data:{"id":obj},dataType:"json",success:function(body){console.log(body);//判断是否删除成功if(body.data == true){//表示删除成功alert("删除成功了老铁!,重新加载当前页面哈!");window.location.href="list.html";}else{alert("删除失败了老铁!");}}});}</script>在 list.html 中 实现如下核心代码:
// 添加喜欢的音乐/收藏音乐function loveInfo(obj){//直接发送请求$.ajax({type:"POST",url:"/lovemusic/likeMusic",data:{"id":obj},dataType:"json",success:function(body){//然后这里判断看是否收藏成功了if(body.data == true){alert("收藏成功,^^,厉害了老铁!");window.location.href="loveMusic.html";}else{alert("收藏失败,^^,咋回事儿呢!");}}});}当我们直接访问列表页的时候,我们需要用拦截器拦截,否则 不登录也能直接访问,这是不行的
修改音乐存放路径:
在配置文件当中,修改如下:
输入指令 mysql,进入数据库:
将如下内容添加进去:
-- 数据库drop database if exists `onlinemusic`;create database if not exists `onlinemusic` character set utf8;-- 使用数据库use `onlinemusic`;-- 创建 user表DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`id` INT PRIMARY KEY AUTO_INCREMENT,`username` varchar(20) NOT NULL,`password` varchar(255) NOT NULL);-- 创建 music 表DROP TABLE IF EXISTS `music`;CREATE TABLE `music` (`id` int PRIMARY KEY AUTO_INCREMENT,`title` varchar(50) NOT NULL,`singer` varchar(30) NOT NULL,`time` varchar(13) NOT NULL,`url` varchar(1000) NOT NULL,`userid` int(11) NOT NULL);-- 创建 lovemusicDROP TABLE IF EXISTS `lovemusic`;CREATE TABLE `lovemusic` (`id` int PRIMARY KEY AUTO_INCREMENT,`user_id` int(11) NOT NULL,`music_id` int(11) NOT NULL);输入指令 exit 退出数据库
4. 上传
云服务器配置防火墙端
启动项目
刚开始可以使用 java -jar xxxx.jar 启动项目【前台运行的方式】
运行好之后没有问题,我们可以使用下面的命令来对项目进行运行
nohup java -jar xxx.jar >> log.log &
nohup :后台运行项目的指令
使用 >> log.log 将运行的日志记录到 log.log 中
& 表示 一直运行
tips:重新部署
如果更新了项目,先将 jar删除
输入如下指令:
rm -ri xxx.jar
查看进程
ps -ef | grep java
kill [ID] 即可删除进程