写点什么

基于 SpringBoot 的 OnlineMusicPlayer 项目

作者:bug郭
  • 2022 年 8 月 11 日
    江西
  • 本文字数:38050 字

    阅读完需:约 125 分钟

@TOC

项目演示

  • 项目部署链接地址:


OnlineMusicPlayer


  • 项目演示:



创建项目

因为我们的online-music-player项目是基于SpringBoot框架开发的,所以我们需要创建一个SpringBoot项目!




选择SpringBoot版本并初步导入依赖!



数据库的设计


根据我们的演示我们可以得知我们需要创建onlinemusic数据库,其下有 3 张结果!



  • 创建onlinemusic 数据库

  • 创建user用户表

  • 创建music音乐列表

  • 创建lovemusic收藏音乐列表

  • onlinemusic数据库下的 3 张表创建成功后!



3 张表结构如下!



整个db.sql文件


-- 创建onlinemusic数据库 drop database if exists onlinemusic; create database if not exists onlinemusic character set utf8;-- 使用onlinemusicuse onlinemusic;-- 创建user表drop table if exists user;create table user (    id int primary key auto_increment, -- 设置自增主键 id    username varchar(20) not null, -- 用户名不能为空!    password varchar(255) not null -- 这里密码不为空,长度255留有足够长度加密操作);-- 创建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, -- 歌曲路径    user_id int(11) not null);-- 创建lovemusic表drop table if exists lovemusic;create table lovemusic(    id int primary key auto_increment,    user_id int(11) not null, -- 用户id    music_id int(11) not null -- 音乐id);
复制代码

配置数据库和 xml

打开Application.properties文件进行配置


#配置数据库spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTCspring.datasource.username=rootspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.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
复制代码

登入注册模块设计

创建 User 类

我们先创建一个model包用来保存实体类


在其下创建User类!


package com.example.onlinemusic.model;import lombok.Data;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 13:37 */@Data //Data注解生成了setter/getter/tostring方法public class User { private int id; private String username; private String password;}
复制代码

创建对应的 Mapper 和 Controller

创建 UserMapper 接口

创建Mapper包保存 Mapper 接口!


//UserMapperpackage com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.User;import org.apache.ibatis.annotations.Mapper;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 13:38 */@Mapper //实现xml映射,无需通过其他的mapper映射文件!public interface UserMapper { //登入功能! User login(User loginUser);}
复制代码


创建 UserMapper.xml

resource包下创建mybatis包用于保存 mapper.xml 文件,再创建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">    <select id="login" resultType="com.example.onlinemusic.model.User">select * from user where username=#{username} and password=#{password}</select></mapper>
复制代码


实现登入

设置登入的请求和响应

  • 请求

  • 响应


响应:{    "status":0, //status 为0表示登入成功,为负数表示登入失败!    "message":"登入成功", // 放回登入信息!    "data":{  // 登入成功后获取到相应的用户信息!        "id":xxxx,        "username":xxxx,        "password":xxxx    }}
复制代码

创建 UserController 类

创建一个controller包,在其包下创建一个 UserController 类


package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.UserMapper;import com.example.onlinemusic.model.User;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 15:12 */@RestController // @ResponseBody + @Controller@RequestMapping("/user") //设置路由 用来映射请求!public class UserController { //将UserMapper注入! @Resource private UserMapper userMapper;
@RequestMapping("/login") public void login(@RequestParam String username,@RequestParam String password){ User userLogin = new User(); userLogin.setUsername(username); userLogin.setPassword(password);
User user = userMapper.login(userLogin); //先初步测试一下,后面再完善 if(user!=null){//登入成功! System.out.println("登入成功!"); }else{ System.out.println("登入失败!"); } }}
复制代码



这里只是粗略的写一下登入逻辑,然后验证是否可行,我们再进行后续代码的完善!

pastman 验证登入功能




我们对照数据库中的user表中的数据!所以该登入请求成功!

封装响应

刚刚我们的登入逻辑是没有了问题,但是我们的服务器并没有给客户端返回响应,所以我们需要根据约定的响应,登入请求后分装并返回!


创建一个tools包统一保存一些通用的代码,创建响应类ResponseBodyMessage这里响应信息我们可以通过泛型,就可以变成通用的响应!


package com.example.onlinemusic.tools;import lombok.Data;/** * Created with IntelliJ IDEA. * Description:统一(泛型)的响应体 * User: hold on * Date: 2022-07-26 * Time: 21:32 */@Datapublic class ResponseBodyMessage <T>{    private int status;//状态码 0 表示成功,-1表示失败!    private String message;//响应信息描述    private T data; //返回的数据,这里采用泛型因为响应的数据的种类很多    public ResponseBodyMessage(int status, String message, T data) {        this.status = status;        this.message = message;        this.data = data;    }}
复制代码


验证


Session 创建

我们再对刚刚的登入功能创建Session


我们通过HttpServlet下的 getSession 方法获取到 Session,然后再通过SetAttribute方法设置会话,保存在服务器中!


//优化后的UserController类!package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.UserMapper;import com.example.onlinemusic.model.Contant;import com.example.onlinemusic.model.User;import com.example.onlinemusic.tools.ResponseBodyMessage;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 15:12 */@RestController // @ResponseBody + @Controller@RequestMapping("/user") //设置路由 用来映射请求!public class UserController { //将UserMapper注入! @Resource private UserMapper userMapper;
@RequestMapping("/login") //@RequestParam SpringMVC下的注解,表示该参数必须传入给服务器! //value = "前端参数名",required = true/false,defaultValue ="默认值" //这里required设置为ture表示该参数必传,默认为true,如果设置了defaultValue那默认required为false public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){ User userLogin = new User(); userLogin.setUsername(username); userLogin.setPassword(password); //调用mapper下的 login查询user! User user = userMapper.login(userLogin); //返回响应 if(user!=null){//登入成功! //登入成功就在服务器保存该Session会话! //这里我们的key值可以通过常量值设置,避免后面出错! //request.getSession().setAttribute("USERINFO_SESSION_KEY",user); // request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user); return new ResponseBodyMessage<User> (0,"登入成功",userLogin); }else{ return new ResponseBodyMessage<User> (-1,"登入失败",userLogin); } }}
复制代码


我们通过 Fiddler 抓包获取响应,


设置了Session会话响应



未设置Session会话响应!



我们可以看到设置了的返回的响应头中有 Session 信息,否则没有!

Bcrypet 加密原理

我们知道如果我们登入时传输的密码通过明文传输的话,就不安全,会被其他人盗取,所以我们要对密码进行加密!


目前主流的加密方式有 2 种


  • MD5 加密

  • Bcrypet 加密


我们先对这两种加密方式进行了解,便于后续我们对登入功能进行加密操作!

MD5 加密

MD5 加密是一个安全的散列算法(哈希算法),就是通过对某一密码进行哈希操作,然后得到哈希后的加密字符串密码,一般这里加密后密码的长度比原来密码长度长,我们这里的加密操作是不可逆的!所以当我们对一个密码进行 MD5 加密后得到的字符串,我们无法通过逆操作解密,所以相对安全.


MD5 的不足之处,在于我们每次对同一个密码加密后得到的结果都是固定值,这就是使得,我们可以通过彩虹表(密码本,里面记录了密码加密算法后得到结果的映射关系),我们通过彩虹表查询进行暴力破解,就可以拿到密码!



我们也可以对 MD5 加密进行优化,就是对密码进行加盐操作,再进行 MD5 散列算法,这里的加盐指的是对密码添加一些单词,也就是字符,通过加盐操作,使得密码长度更长也就更安全,MD5 加密后得到的结果也就更难破解!


如果我们要使用 MD5 加密,我们要在项目中导入 MD5 依赖


<!-- md5 依赖 -->        <dependency>            <groupId>commons-codec</groupId>            <artifactId>commons-codec</artifactId>        </dependency>        <dependency>            <groupId>org.apache.commons</groupId>            <artifactId>commons-lang3</artifactId>            <version>3.9</version>        </dependency>
复制代码


模拟 MD5 加密操作:


package com.example.onlinemusic.tools;
import org.apache.commons.codec.digest.DigestUtils;
/** * Created with IntelliJ IDEA. * Description:对密码加盐后再md5 * User: hold on * Date: 2022-07-26 * Time: 23:28 */public class MD5Util { //定义一个固定的盐值 private static final String salt = "1b2i3t4e"; 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); } /** * 第2次MD5加密 * @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 * @param saltDB * @return */ public static String inputPassToDbPass(String inputPass, String saltDB) { String formPass = inputPassToFormPass(inputPass); String dbPass = formPassToDBPass(formPass, saltDB); return dbPass; } public static void main(String[] args) { System.out.println("对用户输入密码进行第1次加密:"+inputPassToFormPass("123456")); System.out.println("对用户输入密码进行第2次加密:"+formPassToDBPass(inputPassToFormPass("123456"), "1b2i3t4e")); System.out.println("对用户输入密码进行第2次加密:"+inputPassToDbPass("123456", "1b2i3t4e")); }}
复制代码



虽然这里的加盐操作使得加密后的密码长度更长了,但是还是解决不了 md5 对一个密码加密得到的结果相同,除非我们这里采用随机盐!

BCrypet 加密

这里的BCrypet加密方式也是一种安全的不可逆的散列算法加密操作,BCrypet加密和MD5不同之处在于每次对同一个密码加密得到的结果都不相同,也就是在其内部实现了随机加盐处理.这就很好解决了MD5加密的缺点.所以BCrypet加密方式更加安全!并且BCrypet加密可以使加密得到的密文长度最大为 60 位,而 MD5 是 32 位,所以相对于 MD5 加密,BCrypet破解难度更大!


使用BCrypet加密:


引入依赖:


<!-- 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>
复制代码


//BCrypet加密使用演示package com.example.onlinemusic.tools;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-26 * Time: 23:42 */public class BCrypetTest { 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); }}
复制代码


这里BCrypet加密工具主要通过BCrypetPassWordEncoder对象下的encode加密方法对密码进行加密和matches匹配算法通过密码和加密后的密文进行匹配!




可以看到这里每次加密的结果都不一样,但是都能和正确密码匹配成功!


MD5 和 BCrypet 的异同


  • MD5:一种不加盐的单向 hash,不可逆的加密算法,对同一个密码每次 hash 加密得到的 hash 值结果都是一样的!所以大多数情况下可以破解!

  • BCrypet:一种加盐的单向 Hash,不可逆的加密算法,每次对同一个密码进行加密的结果不同,破解难度更高!


这 2 个都是目前主流的加密算法,BCrypet 更加安全,但是效率低! BCrypet 的加盐操作是加入的随机盐,所以每次的加密结果都不一样!


指的注意的是并没有什么密码是绝对安全的,无论那种加密方式都可以被破解的,只是破解的成本和时间问题!如果你的数据并没有价值,那么破解你的密码就毫无意义,也就很安全!

加密登入实现

因为我们 matches 通过前端传输过来的密码对比数据库中密码即可判断密码是否正确!我们知道每次 encode 后的密码都不一样,所以我们不能通过查询数据库中 username+password 验证!我们先通过 username 查询到数据库中的密码,然后通过 mathes 匹配判断是否登入成功!


  • UserMapper 添加查询用户方法!



//加密后的登入方法!public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){        User user = userMapper.selectUserByUserName(username);        //返回响应        if(user!=null){//查询到username用户!            BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();            //匹配验证密码是否正确!            boolean flg = bCryptPasswordEncoder.matches(password,user.getPassword());            if(flg){//登入成功!                request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user);                return new  ResponseBodyMessage<User> (0,"登入成功",user);            }else{//密码错误!                return new  ResponseBodyMessage<User> (0,"用户名或密码错误",user);            }
}else{//用户不存在! return new ResponseBodyMessage<User> (-1,"用户名或密码错误",user); } }
复制代码


pastman 验证代码:



这里我们发现失败了,因为我们引入BCrypet加密依赖时,导入的security框架,我们只是用了其下的一个类用于加密,并没有用到该框架的功能!而导入security框架后,该项目中的接口都需要身份验证和授权!所以这个我们就登入失败了!


我们在启动类上加上一行注解即可解决该问题!



@SpringBootApplication(exclude =        {org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
复制代码


创建 config 类,添加 AppConfig 类

刚刚BCrypetPasswordEncoder对象创建使用方式并不符合SpringIoC思想,所以我们通过Bean注解先将该对象注册到Spring中,然后通过Spring获取对象!


@Configurationpublic class AppConfig {    //将BCrypetPasswordEncoder对象交给spring管理!    @Bean    public BCryptPasswordEncoder getBCryptPasswordEncoder(){        return new BCryptPasswordEncoder();    }}
复制代码



这样我们的登入功能就完善好了!


实现注册功能


约定注册请求和响应


请求:{    post    url:user/register    data:{username,password}}
复制代码


响应:{    status:"状态码"    message:"响应信息"    data:{username,password}}
复制代码


UserMapper 类中添加一个注册接口!


//注册功能!    int register(String username,String password);
复制代码


Mapper.xml 下实现该接口


<!-- 注册添加用户-->    <insert id="register">        insert into user (username,password) values(#{username},#{password});    </insert>
复制代码


Controller 的 User 类下实现注册功能


  //注册功能    @RequestMapping("/register")    public ResponseBodyMessage<User>register(@RequestParam String username,@RequestParam String password,HttpServletRequest request){        //1.首先查询该用户是否存在!        User user = userMapper.selectUserByUserName(username);        if(user!=null){//查询到该用户,说明用户存在!            return new ResponseBodyMessage<User>(-1,"该用户已注册,请修改用户名重新注册",null);        }        //2.用户不存在,就注册该用户!        //对密码进行加密后保存在数据库中!        password = appConfig.getBCryptPasswordEncoder().encode(password);        //将用户注册到数据库中!        userMapper.register(username,password);        //将注册好的用户信息放回给客户端!        user = userMapper.selectUserByUserName(username);        request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user);        return new ResponseBodyMessage<User>(0,"注册成功!",user);    }
复制代码


postman 验证


  • 用户存在




  • 用户不存在注册成功!



  • 通过注册的用户验证一下登入功能


  • 上传音乐模块


上传音乐模块设计

上传音乐请求和响应

请求:{    post,    url:music/upload    data:{singer,MultipartFile file} //上传音乐的歌手名和音乐文件}
复制代码


响应:{    status:0,//0表示成功,-1失败!    message:"响应信息",    data:true //true表示成功    }
复制代码

Music 类

package com.example.onlinemusic.model;
import lombok.Data;
/** * Created with IntelliJ IDEA. * Description:Music实体类 * User: hold on * Date: 2022-07-27 * Time: 15:37 */@Datapublic class Music { private int id; private String title; private String singer; private String time; private String url; private int user_id;}
复制代码

MusicController 类

这里MusicController类中的上传方法,需要处理 2 部分内容


  • 将音乐文件上传到服务器下

  • 将上传的音乐信息上传到数据库中

上传到服务器

package com.example.onlinemusic.controller;
import com.example.onlinemusic.model.Contant;import com.example.onlinemusic.tools.ResponseBodyMessage;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import sun.util.logging.resources.logging;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import java.io.File;import java.io.IOException;

/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-27 * Time: 15:40 */@RestController@RequestMapping("/music")public class MusicController {
// //文件上传服务器后的路径地址!// public static final String SAVE_PATH = "D:/uploadmusicfile/";
//我们可以将该路径信息设置到配置文件中! @Value("${music.path.save}") private String SAVE_PATH; //这里的上传需要将音乐上传到服务器,还有就是需要将音乐信息上传到数据库! @RequestMapping("/upload") public ResponseBodyMessage<Boolean> UploadMusic(String singer, MultipartFile file, HttpServletRequest request){
//1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器!
//获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname); //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File desc = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!desc.exists()){ desc.mkdir(); } //将音乐文件上传到该目录下! try { file.transferTo(desc); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"上传失败",false); } //上传成功 return new ResponseBodyMessage<>(0,"上传成功",true); }}
复制代码


postman 验证


  • 未登录上传文件



  • 登入后上传




可以看到我们设置的服务器目录下就含有了该上传的音乐文件!

如何保证上传的文件是音乐文件

可以通过后缀名.MP3嘛?


虽然这是一种最简单的解决方案,但是显然不可以的,如果有人将不是.MP3文件改成后缀为.MP3的音乐文件,不过这种情况比较罕见!


那么我们如何检测用户上传的是音乐文件呢?


其实每一种文件都有自己特点的文件结构!

我们拿.MP3文件举例:

文件结构如下:

一个 MP3 文件结构分成 3 部分!

而确定是否是 MP3 文件可以通过MPEG音频标签

例如我们通过ID3V1128 字节中的前 3 个字节中的标签标志包含了字符TAG就可以判断该文件是MP3文件了!

增加音乐文件校验功能

我们先创建一个文件校验的接口,测试一下:


   //音乐文件验证    @RequestMapping("/ismp3")    public ResponseBodyMessage<String> ismp3(String path){        String str = null;        File file = null;        byte [] fileByte = null;        try {             file= new File(SAVE_PATH+File.separator+path);            //获取到这个文件的所有字节信息            fileByte = Files.readAllBytes(file.toPath());        } catch (IOException e) {            e.printStackTrace();        }        //将字节数组转成字符串        str = new String(fileByte);        //获取到最后128个字节的字符串信息!        str = str.substring(str.length()-128);
if(str.contains("TAG")){//最后128字节,含有音乐文件标志 return new ResponseBodyMessage<String>(0,"mp3文件",str); } //没有音乐文件标识 return new ResponseBodyMessage<String>(-1,"非mp3文件",str); }
复制代码


验证:


音乐文件:



非音乐文件:



测试该方法无误,我们就将其封装到上传音乐的模块中!


 //1.先验证是否为音乐文件!            boolean flg = false;            try {                //获取到后128位含有标志字节的字符串                String str = new String(file.getBytes());                String flgTAG = str.substring(str.length()-128);                if(flgTAG.contains("TAG")){                    //含有标志位,为音乐文件!                    flg = true;                }            } catch (IOException e) {                e.printStackTrace();            }            if(!flg){//不是音乐文件                return new ResponseBodyMessage<>(-1,"文件有误,并非mp3音乐文件",false);            }
复制代码


验证:


音乐文件上传成功:



非音乐文件:


上传到数据库

我们上传音乐信息到数据库,就是向数据库中的music表中插入数据!


我们首先要明确我们需要到数据库那些信息!


我们看一下我们music表结构!



id:自增主键不需要上传!title:歌曲名我们可以通过文件名去掉.MP3后缀获取! singer:歌手 我们请求信息中有!time:我们可以通过java中的SimpleDateFormat类获取到上传时间 url:音乐的url,因为我们上传的音乐就是用来后面播放的嘛,而我们数据的传输是通过http协议的,

我们后面通过这个url就可以找到该音乐的位置!

//先用这样的方式保存url
/music/get?title(歌曲名称)

user_id:我们可以通过session中获取上传用户id

MusicMapper

  • 接口


package com.example.onlinemusic.mapper;
import org.apache.ibatis.annotations.Mapper;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-28 * Time: 23:27 */@Mapperpublic interface MusicMapper { //上传音乐
/** * * @param title 文件名去后缀得到音乐名 * @param singer * @param time 通过SimpleDateFormat类获取到上传时间! * @param url 便于后面播放! * @param user_id 通过session获取 * @return */ int upload(String title,String singer,String time,String url,String user_id);}
复制代码


  • 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.MusicMapper">    <!--上传一条音乐信息到数据库-->    <insert id="upload">        insert into music (title,singer,time,url,user_id)        values(#{title},#{singer},#{url},#{user_id})    </insert></mapper>
复制代码

SimpleDateFormat 类和 Date 类获取系统时间并格式化

package com.example.onlinemusic.tools;
import java.text.SimpleDateFormat;import java.util.Date;
/** * Created with IntelliJ IDEA. * Description:SimpleDateFormat格式化时间类学习! * User: hold on * Date: 2022-07-28 * Time: 23:43 */public class GetTimeTest { public static void main(String[] args) { //我们可以通过 java.utilev包下的Date类获取到当前系统时间! Date currentTime = new Date(); System.out.println(currentTime);
//获取时间格式化类 //年月日 y M d //时分秒 H m s //通过构造方法传入需要设置的时间格式 SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //将当前时间设置成你需要的格式! String time = dateFormat.format(new Date()); System.out.println(time); }}
复制代码


  • new Date() 获取到当前系统时间

    SimpleDateFormat 类 对时间进行格式化处理

    yyyy-MM-dd HH:mm:ss //年-月-日 时:分:秒
    

MusicController 完善数据库上传

package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.MusicMapper;import com.example.onlinemusic.model.Contant;import com.example.onlinemusic.model.Music;import com.example.onlinemusic.model.User;import com.example.onlinemusic.tools.ResponseBodyMessage;import org.apache.ibatis.annotations.Mapper;import org.springframework.beans.factory.annotation.Value;import org.springframework.http.ResponseEntity;import org.springframework.jdbc.datasource.SimpleDriverDataSource;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;import org.springframework.web.multipart.MultipartFile;import sun.util.logging.resources.logging;
import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import java.io.File;import java.io.IOException;import java.nio.file.Files;import java.nio.file.Path;import java.text.SimpleDateFormat;import java.util.Date;

/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-07-27 * Time: 15:40 */@RestController@RequestMapping("/music")public class MusicController {
// //文件上传服务器后的路径地址!// private String SAVE_PATH = "D:/uploadmusicfile/";
//我们可以将该路径信息设置到配置文件中! @Value("${music.path.save}") private String SAVE_PATH;
//上传音乐到数据库的url前缀 @Value("${music.url}") private String URL_PRE;
@Resource //属性注入 private MusicMapper musicMapper;
//这里的上传需要将音乐上传到服务器,还有就是需要将音乐信息上传到数据库! @RequestMapping("/upload") //当我们没有传歌手信息时,就默认为未知歌手 public ResponseBodyMessage<Boolean> UploadMusic(@RequestParam(defaultValue = "未知歌手") String singer, @RequestParam(value = "filename") MultipartFile file, HttpServletRequest request){
//1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器!
//获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname);
//上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println(title+"已存在"); return new ResponseBodyMessage<>(-1,"重复上传,歌曲已存在",false); } //歌曲未上传 //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File dest = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!dest.exists()){ dest.mkdir(); System.out.println("mkdir"+dest); } //将音乐文件上传到该目录下! try { file.transferTo(dest); System.out.println(dest); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"服务器上传失败",false); } //服务器上传成功,我们就需要对数据库进行上传信息!
//1.数据准备 //1).title 通过文件名截取到歌名,验证上传重复上传时已获取title //String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //2).singer 直接获取用户上传的 singer //3).time SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String time = simpleDateFormat.format(new Date()); //4).url 可以通过拼接! eg: music/get?path=隆里电丝 String url = URL_PRE+title; //5).user_id 通过当前session获取,验证重复上传时已获取// User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);// int user_id = user.getId(); int ret = musicMapper.upload(title,singer,time,url,user_id); if(ret!=1){//上传失败! //我们数据库上传失败,那么就需要将服务器下的该音乐文件删除 System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件"); dest.delete(); return new ResponseBodyMessage<>(-1,"数据库上传失败",false); } return new ResponseBodyMessage<>(0,"上传成功",true); } //实现支持多个文件上传就将MultipartFile改成数组即可! @RequestMapping("/uploads") public ResponseBodyMessage<Boolean> UploadMusics(@RequestParam(defaultValue = "未知歌手") String singer, @RequestParam(value = "filename") MultipartFile[] files, HttpServletRequest request){
//1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器!
//保存上传失败的歌曲信息 StringBuilder uploadfailinfo = new StringBuilder(); for (MultipartFile file:files) {
//获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname);
//上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println(title+"已存在"); //保存歌曲信息,用于返回前端用户! uploadfailinfo.append(title+","); //进行下一首歌曲上传 continue; } //歌曲未上传 //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File dest = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!dest.exists()){ dest.mkdir(); System.out.println("mkdir"+dest); } //将音乐文件上传到该目录下! try { file.transferTo(dest); System.out.println(dest); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"服务器上传失败",false); } //服务器上传成功,我们就需要对数据库进行上传信息!
//1.数据准备 //1).title 通过文件名截取到歌名,验证上传重复上传时已获取title //String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //2).singer 直接获取用户上传的 singer //3).time SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String time = simpleDateFormat.format(new Date()); //4).url 可以通过拼接! eg: music/get?path=隆里电丝 String url = URL_PRE+title; //5).user_id 通过当前session获取,验证重复上传时已获取// User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);// int user_id = user.getId(); int ret = musicMapper.upload(title,singer,time,url,user_id); if(ret!=1){//上传失败! //我们数据库上传失败,那么就需要将服务器下的该音乐文件删除 System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件"); dest.delete(); return new ResponseBodyMessage<>(-1,"数据库上传失败",false); }
} if(uploadfailinfo.length()==0) { //说明全部歌曲上传成功! return new ResponseBodyMessage<>(0, "上传成功", true); } //部分歌曲上传失败 return new ResponseBodyMessage<>(0,"歌曲:"+uploadfailinfo+"已存在,上传失败,"+"其他歌曲上传成功",true);
}}
复制代码


验证:




查看数据库music


一个用户重复上传一首歌曲解决

显然我们的上传功能还有待优化,我们需要解决一个用户多次上传一首歌曲的行为!

在验证用户登入的行为后,再进行该用户上传的音乐文件是否已经上传过的验证


  • 我们通过查询数据库信息,从而验证


MusicMapper 接口


 /**     * 通过用户id和音乐信息验证是否重复上传     * @param title     * @param singer     * @param user_id     * @return     */    Music getMusicByUidAndMusicInfo(String title, String singer, int user_id);}
复制代码


xml 实现


 <select id="getMusicByUidAndMusicInfo" resultType="com.example.onlinemusic.model.Music">        select * from music where        title=#{title} and singer=#{singer} and user_id=#{user_id}    </select>
复制代码


Controller 重复上传验证


 //上传歌曲前验证歌曲是否已经存在!        String title = musicname.substring(musicname.lastIndexOf(".mp3"));        //获取到当前用户id        User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);        int user_id = user.getId();       Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id);       if(music!=null){ //说明该歌曲重复上传           System.out.println("重复上传");           return new ResponseBodyMessage<>(-1,"上传失败,歌曲已存在",false);       }
复制代码


postman 验证



批量上传歌曲

我们可以将参数file改成MulitspartFile的数组,就可以一次性批量上传多首歌曲


但是我们这里的歌手信息就无法全部上传咯!


 //实现支持多个文件上传就将MultipartFile改成数组即可!    @RequestMapping("/uploads")    public ResponseBodyMessage<Boolean> UploadMusics(@RequestParam(defaultValue = "未知歌手") String singer,                                                    @RequestParam(value = "filename") MultipartFile[] files,                                                    HttpServletRequest request){
//1.上传音乐前验证登入状态,如果未登入就不创建会话 //如果用户已登入,则允许上传音乐! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false); } //2.登入状态,可以将歌曲上传到服务器!
//保存上传失败的歌曲信息 StringBuilder uploadfailinfo = new StringBuilder(); for (MultipartFile file:files) {
//获取到音乐文件名 xxx.mp4 String musicname = file.getOriginalFilename(); System.out.println("musicfileAndtype:"+musicname);
//上传歌曲前验证歌曲是否已经存在! String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //获取到当前用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id); if(music!=null){ //说明该歌曲重复上传 System.out.println(title+"已存在"); //保存歌曲信息,用于返回前端用户! uploadfailinfo.append(title+","); //进行下一首歌曲上传 continue; } //歌曲未上传 //上传文件在服务器下保存的路径! String path = SAVE_PATH + musicname; //创建文件对象 File dest = new File(path); //该文件目录在磁盘中不存在,就创建该目录 if(!dest.exists()){ dest.mkdir(); System.out.println("mkdir"+dest); } //将音乐文件上传到该目录下! try { file.transferTo(dest); System.out.println(dest); } catch (IOException e) { e.printStackTrace(); //上传失败 return new ResponseBodyMessage<>(-1,"服务器上传失败",false); } //服务器上传成功,我们就需要对数据库进行上传信息!
//1.数据准备 //1).title 通过文件名截取到歌名,验证上传重复上传时已获取title //String title = musicname.substring(0,musicname.lastIndexOf(".mp3")); //2).singer 直接获取用户上传的 singer //3).time SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); String time = simpleDateFormat.format(new Date()); //4).url 可以通过拼接! eg: music/get?path=隆里电丝 String url = URL_PRE+title; //5).user_id 通过当前session获取,验证重复上传时已获取// User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);// int user_id = user.getId(); int ret = musicMapper.upload(title,singer,time,url,user_id); if(ret!=1){//上传失败! //我们数据库上传失败,那么就需要将服务器下的该音乐文件删除 System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件"); dest.delete(); return new ResponseBodyMessage<>(-1,"数据库上传失败",false); }
} if(uploadfailinfo.length()==0) { //说明全部歌曲上传成功! return new ResponseBodyMessage<>(0, "上传成功", true); } //部分歌曲上传失败 return new ResponseBodyMessage<>(0,"歌曲:"+uploadfailinfo+"已存在,上传失败,"+"其他歌曲上传成功",true); }
复制代码


验证:


上传音乐模块总结

  • 上传包括服务器和数据库上传

  • 这里上传文件用到了Spring框架中处理文件上传的主要类MulitspartFile类,这个类主要实现的是前端用表单的方式进行提交!


  • 上传服务器时要验证音乐是否重复上传,这里通过查询数据库中的信息进行验证

  • 在上传音乐时验证是否为MP3文件,我们通过MP3文件结构中的最后 128 个字节下有一个TAG标签即可验证

播放音乐模块设计

请求响应设计

请求:{    get    /music/get?path=xxx.mp3}
复制代码


响应:{    data:音乐数据本身的字节信息}
复制代码


可以看到我们这里请求的设计采用的是get方法,通过/music/get?path=xxx.mp3获取到响应的音乐信息,这也就和我们之前设计的保存音乐的url匹配上了!

而我们响应只要将对应的音乐字节信息返回给浏览器即可!

代码实现

   //播放音乐    @RequestMapping("/get")    public ResponseEntity<byte[]> get(String path){        //我们要先获取到该音乐保存在服务器下的路径信息!        try {            byte[] fileByte = null;            //获取到文件对象            File file = new File(SAVE_PATH +File.separator+ path);            //获取到该文件对象的路径信息           Path filepath =  file.toPath();            //读取文件中的所有字节            fileByte = Files.readAllBytes(filepath);            return ResponseEntity.ok(fileByte);        }catch (IOException e){            e.printStackTrace();        }        return ResponseEntity.badRequest().build();    }
复制代码


方法讲解


  • Files.readAllBytes(Path path);

  • 读取文件中的所有字节,参数是Path路径值!

  • File.separator

  • 与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串!

    就是在Windows系统下是\,而在Linux下是/

  • ReponseEntity

  • 这是Spring对请求响应的分装,继承了HttpEntity对象,包含Http响应码(HttpStatus),响应头(header),响应体(body)3 部分!


  • ResponseEntity类继承自HttpEntity类,被用于 Controller 层方法 !

  • 我们可以通过这个类下面提供的静态方法,封装一下响应返回给浏览器!


  • ok静态方法:

  • 我们代码里的ResponseEntity.ok(fileByte);就是将ok状态和fileByte音乐文件信息以body的形式返回给前端!

验证结果

音乐文件有TAG标志


 ![image-20220801131113688](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801131113688.png) 
复制代码


假如我们拿到的并不是mp3文件!



这里的隆里电丝.mp3我是通过png文件改成了这个,显然找不到这个音乐标志!

删除音乐模块

删除单个音乐

请求响应设计

请求:{    post    /music/delete    id}响应:{      status:0,    message:"删除成功"    data:true}
复制代码

代码实现

这里的删除操作需要分成 2 步


  • 将服务器下的文件进行删除

  • 将数据库中的文件信息删除


所以我们需要先查询到该 id 的音乐信息,再进行删除!


  • Mapper 接口

  • Mapper 实现



  • Controller 层代码实现


 //删除单个音乐    @RequestMapping("/delete")    public ResponseBodyMessage<Boolean> deleteMusic(@RequestParam Integer id){        //1.首先找到该音乐信息        Music music = musicMapper.getMusicById(id);        if(music==null){//音乐不存在            //未找到该音乐            return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);        }        //2.进行音乐删除        //2.1 删除服务器下的音乐文件            //找到服务器下该音乐文件路径            File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");            if(file==null){//服务器下不存在该文件                return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);            }            //删除            file.delete();        //2.2 删除数据库下的音乐信息       int ret = musicMapper.deleteById(id);       if(ret!=1){//数据库删除失败           return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);       }       return new ResponseBodyMessage<>(0,"删除成功!",true);    }
复制代码

验证结果

  • 先查看数据库下的音乐信息



  • 删除的音乐不存在



  • 删除音乐存在




删除成功!

批量删除音乐

请求响应设计

请求:{    post    /music/deleteAll    id[]}响应:{    status:0    message:"删除成功"    data:true}
复制代码

代码实现

我们只需要在删除单个音乐的基础上进行代码的修改即可!


直接增加Controller层代码即可!


 //批量删除音乐    @RequestMapping("/deleteAll")    public ResponseBodyMessage<Boolean> deleteMusicAll(@RequestParam(value = "id[]") List<Integer> ids){        String message = null;        for (Integer id:ids) {            //1.首先找到该音乐信息            Music music = musicMapper.getMusicById(id);            if(music==null){//音乐不存在                //未找到该音乐                //保存这个音乐id信息                message += id+" ";                continue;                //return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);            }            //2.进行音乐删除            //2.1 删除服务器下的音乐文件            //找到服务器下该音乐文件路径            File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");            System.out.println("musicPath:"+file.getPath());            if(file==null){//服务器下不存在该文件                //保存这个id信息                 message += id + "";                 continue;                //return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);            }            //删除            if(!file.delete()){                //删除失败                message += id + "";                continue;                //return new ResponseBodyMessage<>(-1,"服务器删除失败",false);            }            //2.2 删除数据库下的音乐信息            int ret = musicMapper.deleteById(id);            if(ret!=1){//数据库删除失败                message += id+" ";                continue;                //return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);            }        }        if(message==null){            return new ResponseBodyMessage<>(0,"删除成功!",true);           }        //部分删除失败        return new ResponseBodyMessage<>(0,"id:" + message+" 删除失败!",true);    }
复制代码

验证结果

  • 删除成功




  • 删除失败


查询音乐模块

我们的查询需要支持一下功能


  • 查询给定名称的歌曲

  • 给定歌曲名称全

  • 查询到单个歌曲

  • 给定名称字段(支持模糊匹配查询)

  • 查询到多个歌曲

  • 未给定歌曲名

  • 就查询所有歌曲

请求响应设计

请求:{    get    /music/findMusic    musicName}
复制代码


响应:{    status:0    message:"查询成功"    data:    {        {           id:2           title:"隆里电丝"           singer:"大傻"           time:"2022年8月1日"           url:/music/get?path="隆里电丝"        },       ....    }}
复制代码

代码实现

  • MusicMapper接口新增方法

  • 查询所有音乐

  • 模糊匹配查询某些音乐


    /**     *查询所有歌曲     * @return     */    List<Music> findMusic();
/** * 通过名称查询到歌曲信息,支持模糊查询 * @return */ List<Music> findMusicByName(String musicName);
复制代码


  • MusicMapper.xml实现


 <!--查询所有歌曲-->    <select id="findMusic" resultType="com.example.onlinemusic.model.Music">        select * from music;    </select>    <!--模糊查询指定歌曲-->    <select id="findMusicByName" resultType="com.example.onlinemusic.model.Music">        select * from music where title like concat('%',#{musicName},'%')    </select>
复制代码


  • MusicController实现


  //模糊查询    @RequestMapping("/findMusic")    public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required =false) String musicName){        List<Music> musicList = new LinkedList<>();        if(musicName==null){            //查询名称为空,查询所有音乐返回            musicList = musicMapper.findMusic();            return new ResponseBodyMessage<>(0,"查询成功!",musicList);        }        //进行模糊查询!        musicList = musicMapper.findMusicByName(musicName);         return new ResponseBodyMessage<>(0,"查询成功!",musicList);    }
复制代码

验证结果


  • 查询名称为空



  • 模糊查询


收藏音乐模块

添加音乐到收藏列表

请求响应设计

请求:{    post,    /lovemusic/likeMusic    data:user_id,music_id}
复制代码


响应:{    status:0,    message:"收藏音乐成功",    data:true}
复制代码

代码实现

我们要将一首音乐收藏分为 2 步

  • 找到该音乐信息,判断是否收藏过(查询lovemusic表)

  • 收藏该音乐(添加到lovemusic表)


  • 新增LoveMusicMapper接口


package com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.LoveMusic;import org.apache.ibatis.annotations.Mapper;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-08-01 * Time: 19:54 */@Mapperpublic interface LoveMusicMapper { /** * 通过用户id和音乐id查询喜欢音乐 * @param user_id * @param music_id * @return */ LoveMusic findLoveMusicByUidAndMusicId(int user_id, int music_id);
/** * 添加音乐到收藏列表 * @param user_id * @param music_id * @return */ int insetLoveMusic(int user_id,int music_id);}
复制代码


  • LoveMusicMapper.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.LoveMusicMapper">    <!--添加音乐到收藏列表-->    <insert id="insetLoveMusic">      insert into lovemusic (user_id,music_id) values(#{user_id},#{music_id})    </insert>    <!--通过用户id和音乐id查询收藏列表-->    <select id="findLoveMusicByUidAndMusicId" resultType="com.example.onlinemusic.model.LoveMusic">        select * from lovemusic where        user_id = #{user_id} and music_id = #{music_id}    </select></mapper>
复制代码


  • LoveMusicController代码实现


package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.LoveMusicMapper;import com.example.onlinemusic.model.Contant;import com.example.onlinemusic.model.LoveMusic;import com.example.onlinemusic.model.User;import com.example.onlinemusic.tools.ResponseBodyMessage;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestParam;import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;
/** * Created with IntelliJ IDEA. * Description: * User: hold on * Date: 2022-08-01 * Time: 20:25 */@RestController@RequestMapping("/lovemusic")public class LoveMusicController {
//注入LoveMusicMapper @Resource private LoveMusicMapper loveMusicMapper;
//收藏音乐 @RequestMapping("/likeMusic") public ResponseBodyMessage<Boolean> insertLoveMusic(@RequestParam Integer id,HttpServletRequest request){ //1.检查登入状态,未登入不创建回话 HttpSession session = request.getSession(false); if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){ //未登入状态 return new ResponseBodyMessage<>(-1,"请登入用户",false); } //登入状态,进行音乐收藏 //1.获取到用户id User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY); int user_id = user.getId(); System.out.println("user_id:"+user_id+" music_id:"+id); //2.查询该歌曲是否已存在收藏列表 LoveMusic loveMusic = loveMusicMapper.findLoveMusicByUidAndMusicId(user_id,id); if(loveMusic!=null){ //该歌曲已收藏! System.out.println("lovemusic:"+loveMusic); return new ResponseBodyMessage<>(-1,"收藏失败,该歌曲收藏",false); } //未收藏,将其收藏! int flg = loveMusicMapper.insetLoveMusic(user_id,id); if(flg!=1){ return new ResponseBodyMessage<>(-1,"收藏失败",false); } return new ResponseBodyMessage<>(0,"收藏成功!",true); }}
复制代码

验证结果


  • 歌曲已收藏,收藏失败



  • 歌曲未收藏,收藏成功


查询喜欢的音乐列表

这里的查询喜欢列表和查询音乐模块类似!


  • 未给定歌曲名称

  • 查询该用户所有收藏歌曲

  • 给定歌曲名称参数

  • 查询歌曲名称含有该参数的歌曲

请求和响应设计

请求:{    get    /lovemusic/findloveMusic    data:{musicName:musicName}}
复制代码


响应:{    status:0,    message:"查询到收藏的音乐",    data:    {        {            id:1,            title:"隆里电丝",            singer:"大傻",            time:"2022年8月2日",            url:"/music/get?path=隆里电丝",            user_id:2        }        ...    }}
复制代码

代码实现

  • LoveMusicMapper接口新增方法


   /**     * 查询该用户所有的收藏歌曲     * @param user_id     * @return     */    List<Music> findLoveMusic(int user_id);
/** * 通过用户id和歌曲名称查询收藏歌曲支持模糊匹配 * @param user_id * @param musicName * @return */ List<Music> findLoveMusicByUidAndMusicName(int user_id,String musicName);
复制代码


  • xml实现接口方法


 <!--在该用户id下通过歌曲名称查询歌曲(支持模糊查询)-->    <select id="findLoveMusicByUidAndMusicName" resultType="com.example.onlinemusic.model.Music">        select m.* from music as m, lovemusic as lm where m.id = lm.music_id            and lm.user_id = #{user_id} and m.title like concat('%',#{musicName},'%')    </select>    <!--通过id查询收藏列表-->    <select id="findLoveMusicById" resultType="com.example.onlinemusic.model.LoveMusic">        select * from lovemusic where id = #{id}    </select>
复制代码


  • LoveMusicController代码实现


   //通过音乐名称查询收藏列表    @RequestMapping("/findloveMusic")    public ResponseBodyMessage<List<Music>>    findLoveMusicByUidAndMusicName(@RequestParam(required = false) String musicName,                                   HttpServletRequest request){        //1.检查登入状态,未登入不创建回话        HttpSession session = request.getSession(false);        if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){            //未登入状态            return new ResponseBodyMessage<>(-1,"请登入用户",null);        }        //登入状态,进行音乐查询        //1.获取到用户id        User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);        int user_id = user.getId();        System.out.println("user_id:"+user_id+" musicName:"+musicName);        //2.查询收藏列表        List<Music> musicList = null;        if(musicName==null){            //2.1歌曲名称为空,查询该用户所有收藏歌曲!           musicList = loveMusicMapper.findLoveMusic(user_id);           return new ResponseBodyMessage<>(0,"查询到收藏列表",musicList);        }        //2.2 歌曲名称不为空,模糊查询        musicList = loveMusicMapper.findLoveMusicByUidAndMusicName(user_id,musicName);        return new ResponseBodyMessage<>(0,"查询收藏列表成功",musicList);    }
复制代码

验证结果

数据库信息:



登入user_di=10的用户


  • 查询该用户所有收藏歌曲



  • 模糊匹配查询指定歌曲


从喜欢列表移除音乐

请求和响应设计

请求:{    get,    /lovemusic/deleteloveMusic,    data:{id}}响应:{    status:0,    message:"取消收藏成功",    data:true}
复制代码

代码实现

  • LoveMusicMapper接口新增方法


  /**     * 通过用户id和音乐id取消音乐收藏     * @param user_id     * @param music_id     * @return     */    int deleteLoveMusicByUidAndMusicId(int user_id, int music_id);
/** * 通过音乐id删除lovemusic表中的信息 * @param music_id * @return */ int deleteLoveMusicByMusicId(int music_id);
复制代码


  • xml实现接口


   <!--通过用户id和音乐id移除收藏歌曲-->    <delete id="deleteLoveMusicByUidAndMusicId">        delete from lovemusic where user_id = #{user_id} and music_id = #{music_id}    </delete>    <!--通过musicId移除收藏列表-->    <delete id="deleteLoveMusicByMusicId">      delete from lovemusic where music_id = #{music_id}    </delete>
复制代码


  • LoveMusicMapperController代码实现


 //通过收藏表中的id取消收藏歌曲信息    @RequestMapping("/deleteloveMusic")    public ResponseBodyMessage<Boolean> deleteLoveMusic(@RequestParam Integer id,HttpServletRequest request){        //1.检查登入状态,未登入不创建回话        HttpSession session = request.getSession(false);        if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){            //未登入状态            return new ResponseBodyMessage<>(-1,"请登入用户",false);        }        //登入状态,可进行取消音乐收藏功能        //1.获取到用户id        User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);        int user_id = user.getId();        //取消收藏!        int ret = loveMusicMapper.deleteLoveMusicByUidAndMusicId(user_id,id);        if(ret!=1){            return new ResponseBodyMessage<>(-1,"取消收藏失败",false);        }        return new ResponseBodyMessage<>(0,"取消收藏成功",true);    }
复制代码

验证结果

数据库信息:




  • 歌曲不存在,移除失败



  • 歌曲存在,取消收藏成功


完善删除音乐模块代码

我们对项目增添了收藏列表后,发现一个问题!我们上传的音乐删除后,收藏的音乐就不存在了,那么收藏列表中关于这首歌曲的信息也要删除,所以我们对我们的删除音乐代码进行完善!


  • 删除音乐时,要先检查该音乐是否在收藏列表中,如果在就将歌曲移除收藏列表


我们只需要添加一个通过music_id取消收藏音乐的方法即可


LoveMusicMapper新增方法


/**     * 通过音乐id删除lovemusic表中的信息     * @param music_id     * @return     */    int deleteLoveMusicByMusicId(int music_id);
复制代码


xml实现


<!--通过musicId移除收藏列表-->    <delete id="deleteLoveMusicByMusicId">      delete from lovemusic where music_id = #{music_id}    </delete>
复制代码


   //删除单个音乐    @RequestMapping("/delete")    public ResponseBodyMessage<Boolean> deleteMusic(@RequestParam Integer id){        //1.首先找到该音乐信息        Music music = musicMapper.getMusicById(id);        if(music==null){//音乐不存在            //未找到该音乐            return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);        }        //2.进行音乐删除        //2.1 删除服务器下的音乐文件            //找到服务器下该音乐文件路径            File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");        System.out.println("musicPath:"+file.getPath());            if(file==null){//服务器下不存在该文件                return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);            }            //删除            if(!file.delete()){                //删除失败                return new ResponseBodyMessage<>(-1,"服务器删除失败",false);            }        //2.2 删除数据库下的音乐信息        //2.2.1删除music表中的音乐信息       int ret = musicMapper.deleteById(id);       if(ret!=1){//数据库删除失败           return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);       }       //2.2.2删除lovemusic表中的音乐信息       loveMusicMapper.deleteLoveMusicByMusicId(id);        return new ResponseBodyMessage<>(0,"删除成功!",true);    }
//批量删除音乐 @RequestMapping("/deleteAll") public ResponseBodyMessage<Boolean> deleteMusicAll(@RequestParam(value = "id[]") List<Integer> ids){ String message = ""; for (Integer id:ids) { //1.首先找到该音乐信息 Music music = musicMapper.getMusicById(id); if(music==null){//音乐不存在 //未找到该音乐 //保存这个音乐id信息 message += id+" "; continue; //return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false); } //2.进行音乐删除 //2.1 删除服务器下的音乐文件 //找到服务器下该音乐文件路径 File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3"); System.out.println("musicPath:"+file.getPath()); if(file==null){//服务器下不存在该文件 //保存这个id信息 message += id + ""; continue; //return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false); } //删除 if(!file.delete()){ //删除失败 message += id + ""; continue; //return new ResponseBodyMessage<>(-1,"服务器删除失败",false); } //2.2 删除数据库下的音乐信息 int ret = musicMapper.deleteById(id); if(ret!=1){//数据库删除失败 message += id+" "; continue; //return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false); } //取消该歌曲的所有收藏 loveMusicMapper.deleteLoveMusicByMusicId(id); } if(message==""){ return new ResponseBodyMessage<>(0,"删除成功!",true); } //部分删除失败 return new ResponseBodyMessage<>(0,"id:" + message+" 删除失败!",true); }
复制代码


验证结果:


数据库信息:



我们将music_id为 51 音乐删除!



前端设计

我们前端页面是在网上下载了一个静态页面的模板!我们需要在此基础上进行js代码的编写,从而实现前后端用户的接口

登入逻辑

核心逻辑:


<script>    //核心业务逻辑         ///$(function () {} 当DOM加载完毕后执行         $(function(){            //当点击这个按钮后就会执行function函数            $("#submit").click(function(){                //获取到用户名和密码                var username = $("#user").val();                var password = $("#password").val();                //判断用户名密码是否为空!                if(username.trim()==""||password.trim()==""){                    alert("请输入用户名和密码");                    return;                }                //这里就需要将请求前端数据返回给服务器!                $.ajax({                    url:"/user/login",//指定路径                    type:"post",                    data:{"username":username,"password":password},                    dataType:"json", //设置服务器返回json数据                    success:function(body){//回调函数                        console.log(body);                        if(body.status==0){//通过后端发送回来的status判断登入状态!                            alert("登入成功!");                            //登入成功后跳转到指定页面                            window.location.href("list.html");                        }else{                            alert("登入失败,账号或密码错误,请重试!");                            //将输入框清空!                            $("#message").text("");                            $("#user").val("");                            $("#password").val("");                        }                    }                });            });         });</script>
复制代码


验证结果:



注册逻辑

 <script>         //核心业务逻辑         ///$(function () {} 当DOM加载完毕后执行         $(function(){            //当点击这个按钮后就会执行function函数            $("#submit").click(function(){                //获取到用户名和密码                var username = $("#user").val();                var password = $("#password").val();                //判断用户名密码是否为空!                if(username.trim()==""||password.trim()==""){                    alert("请输入用户名和密码");                    return;                }                //这里就需要将请求前端数据返回给服务器!                $.ajax({                    url:"/user/login",//指定路径                    type:"post",                    data:{"username":username,"password":password},                    dataType:"json", //设置服务器返回json数据                    success:function(body){//回调函数                        console.log(body);                        if(body.status==0){//通过后端发送回来的status判断登入状态!                            alert("登入成功!");                            //登入成功后跳转到指定页面                            window.location.href = "list.html";                        }else{                            alert("登入失败,账号或密码错误,请重试!");                            //将输入框清空!                            $("#message").text("");                            $("#user").val("");                            $("#password").val("");                        }                    }                });            });         });      </script>
复制代码


验证结果:



'


查询音乐逻辑

核心代码逻辑:


  <script type="text/javascript">       $(function(){            load();       });       //musicName可以传参,也可以不传       function load(musicName){            $.ajax({                url:"/music/findMusic",//指定路径                type:"get",                data:{"musicName":musicName},                dataType:"json", //设置服务器返回json数据                success:function(body){//回调函数                    console.log(body);                    var s = '';                     var data = body.data;//数组!                    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>';                            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>' +                                '&nbsp;'+'<button class="btn btn-primary" onclick="loveInfo('+ data[i].id + ')" > 喜欢</button>'+                              '</td>';                        s +=  '</tr>';                    }                    $("#info").html(s);//把拼接好的页面放在info的id下                }            });       }    </script>
复制代码


验证结果:


上传音乐逻辑

核心代码逻辑


<form method="post" enctype="multipart/form-data" action="/music/upload">    文件上传:<input type="file" name="filename"/>    歌手名: <label>    <input type="text" name="singer" placeholder="请输入歌手名"/>    </label>    <input type="submit" value="上传"/></form>
复制代码


验证结果:



播放音乐

<!--嵌入播放器!--><div style="width: 180px; height: 140px; position:absolute; bottom:10px; right:10px">    <script type="text/javascript" src="player/sewise.player.min.js"></script>    <script type="text/javascript">        SewisePlayer.setup({        server: "vod",        type: "mp3",        //播放的地址        videourl:"http://jackzhang1204.github.io/materials/where_did_time_go.mp3",        //皮肤!        skin: "vodWhite",        //这里自动播放需要设置false        autostart:"false",        });    </script>    </div>
复制代码


  //播放音乐       function playerSong(obj) {        console.log(obj)        var name = obj.substring(obj.lastIndexOf('=')+1);        //obj:播放地址 name:歌曲或者视频名称 0:播放开始时间 false:点击后自动播放        SewisePlayer.toPlay(obj,name,0,true);      }
复制代码


验证结果:


删除音乐逻辑

 //删除音乐      function deleteInfo(obj){        console.log(obj);        $.ajax({            url:"/music/delete",            type:'post',            dataType:"json",            data:{"id":obj},            success:function(body){                console.log(body);                if(body.data==true){                    //删除成功!                    alert("删除成功!");                    window.location.href = "list.html";                }else{                    alert("删除失败!");                }            }        });      }
复制代码


验证结果:


查询歌曲逻辑

  $(function(){        $('#submit1').click(function(){            var name = $("#exampleInputName2").val();            load(name);        });      });
复制代码


验证结果:


删除选中的歌曲逻辑

 //删除选中逻辑在查询逻辑里    $(function(){        $('#submit1').click(function(){            var name = $("#exampleInputName2").val();            load(name);        });        //当查询结束才可进行选中删除(有了音乐列表)        $.when(load).done(function(){            //选中删除逻辑!            $("#delete").click(function(){                var id = new Array();                var i = 0;//数组下标!                //遍历input标签下的checkbox                $("input:checkbox").each(function(){                    //判断是否选中!                    if($(this).is(":checked")){                        //获取input里面的id值!                        id[i] = $(this).attr('id');                        i++;                    }                });                console.log(id);                $.ajax({//将获取到的id发送给服务器!                    url:"/music/deleteAll",                    data:{"id":id},                    type:'post',
success:function(body){ if(body.status==0){ alert("删除成功!"); window.location.href="list.html"; }else{ alert("删除失败!"); } } }); }); }); });
复制代码


验证结果:




收藏音乐逻辑

 $(function(){            load();       });       //musicName可以传参,也可以不传       function load(musicName){            $.ajax({                url:"/lovemusic/findLoveMusic",//指定路径                type:"get",                data:{"musicName":musicName},                dataType:"json", //设置服务器返回json数据                success:function(body){//回调函数                    console.log(body);                    var s = '';                     var data = body.data;//数组!                    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>';                    }                    $("#info").html(s);//把拼接好的页面放在info的id下                }            });       }        //播放音乐        function playerSong(obj) {        console.log(obj)        var name = obj.substring(obj.lastIndexOf('=')+1);        //obj:播放地址 name:歌曲或者视频名称 0:播放开始时间 false:点击后自动播放        SewisePlayer.toPlay(obj,name,0,true);      }      //删除音乐      function deleteInfo(obj){        console.log(obj);        $.ajax({            url:"/lovemusic/deleteloveMusic",            type:'post',            dataType:"json",            data:{"id":obj},            success:function(body){                console.log(body);                if(body.data==true){                    //删除成功!                    alert("移除成功!");                    window.location.href = "loveMusic.html";                }else{                    alert("移除失败!");                }            }        });      }
复制代码


验证结果:



实现收藏功能逻辑

//在list.html文件中添加一个收藏歌曲函数即可!//收藏歌曲      function loveInfo(obj){        $.ajax({            url:"/lovemusic/likeMusic",            type:"post",            data:{"id":obj},            dataType:"json",            success:function(body){                if(body.data==true){                    alert("收藏成功!");                    window.location.href="list.html";                }else{                    alert("收藏失败!");                }            }        })      }
复制代码


验证结果:


配置拦截器

有些页面我们没有登入也可以进行访问,通过输入url到地址栏即可!


我们可以在项目中进行登入状态的检查,如果登入了就可以访问,否则不能,这就配置了拦截器,保证程序安全!


  • 我们首先在config包下自定义一个拦截器


核心代码


package com.example.onlinemusic.config;
import com.example.onlinemusic.model.Contant;import com.example.onlinemusic.tools.ResponseBodyMessage;import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;
/** * Created with IntelliJ IDEA. * Description:配置拦截器 * User: hold on * Date: 2022-08-06 * Time: 0:43 */public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //检查是否登入! HttpSession session = request.getSession(false); if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){ //未登入状态! System.out.println("未登入"); return false; } return true; }}
复制代码


  • 然后将配置好的拦截器添加到AppConfig类中


package com.example.onlinemusic.config;
import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/** * Created with IntelliJ IDEA. * Description:将自定义拦截器添加到系统配置! * User: hold on * Date: 2022-07-27 * Time: 12:29 */@Configurationpublic class AppConfig implements WebMvcConfigurer { //将BCrypetPasswordEncoder对象交给spring管理! @Bean public BCryptPasswordEncoder getBCryptPasswordEncoder(){ return new BCryptPasswordEncoder(); }
//添加拦截器! @Override public void addInterceptors(InterceptorRegistry registry) { LoginInterceptor loginInterceptor = new LoginInterceptor(); registry.addInterceptor(loginInterceptor) //拦截该项目目录下的所有文件! .addPathPatterns("/**") //排除无关文件 .excludePathPatterns("/js/**.js") .excludePathPatterns("/images/**") .excludePathPatterns("/css/**.css") .excludePathPatterns("/fronts/**") .excludePathPatterns("/player/**") //排除登入注册接口! .excludePathPatterns("/login.html") .excludePathPatterns("/user/login") .excludePathPatterns("/reg.html") .excludePathPatterns("/user/register"); }}
复制代码


验证结果:



部署到服务器

准备

  • 更改数据库配置


因为我的云服务器下的数据库用户密码信息和本地的一样,所以不用更改数据库配置!


我们在云服务器下创建好数据库即可!



  • 更改上传音乐路径


# 云服务器下的地址路径!music.path.save = /root/javaweb部署环境/music
复制代码


打包

  • 项目打包



打包报错,我们需要统一编码 UTF-8




添加依赖


 <!--添加maven.plugins依赖 -->            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-resources-plugin</artifactId>                <version>3.1.0</version>            </plugin>
复制代码



 <!--添加maven-surefire-plugin依赖-->            <plugin>                <groupId>org.apache.maven.plugins</groupId>                <artifactId>maven-surefire-plugin</artifactId>                <version>2.22.1</version>                <configuration>                    <skipTests>true</skipTests>                </configuration>            </plugin>
复制代码



打包成功!

部署

​ 将打包好的项目上传到服务器!



启动项目:


java -jar onlinemusic.jar 
复制代码


这里显示端口号8080被占用,那么我们就将端口号给kill


//查看端口netstat -anp | grep 8080//杀进程kill -9 pcb值
复制代码


验证结果:




这样虽然部署好了项目,但是这个只能支持前台运行,也就是说这个项目关闭了,项目就访问不了了!

后台运行 SpringBoot 项目

运行指令:


nohup java -jar onlinemusic.jar>>log.log&
复制代码


nohup:后台运行项目的指令

>>log.log:把控制台上的日志保存到 log.log 文件中!(未设置可默认生成)

&:后台一直运行



这样运行就支持后台运行了!

后期项目维护更新

如果后面我们觉得项目需要完善该如何进行服务器项目更新呢?


  • 将该项目的进制终止


netstat -anp |grep 8080


kill -9 17303



也可以使用ps -ef | grep java


kill 【进程ID】命令说明:ps : Linux 当中查看进程的命令-e 代表显示所有的进程-f 代表全格式【显示全部的信息】grep : 全局正则表达式重新上传jar包重新进行后台的启动
复制代码


  • 更新项目,重新运行


nohup java -jar onlinemusic.jar>>log.log&




更新了一下注册登入的前端页面!

遇到的面试题总结

  • 上传其他文件,然后将后缀改成.mp3,如何识别?是否可以正常播放?


因为每种类型的文件都有自己的文件结构,都有自己特有的格式,我们根据mp3特有的文件格式,在倒数第128字节处,有有个TAG音乐文件标志,从而在上传时就检测一下是否是音频文件,如果不是音频文件无法上传!


  • 可以上传大文件嘛?


不能,因为一首歌曲的大小不会很大,所以我已经在配置文件配置了每个文件的最大上传大小,以及单次请求的文件总数大小!

#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB


  • 为啥不用HTML的原生audio标签?


因为我想通过使用开源的播放器,提升一下自己的学习能力,毕竟我们经常会在自己的项目中使用到其他的优秀开源项目,我们也需要具备这样的能力,学习使用大佬的优秀项目!

只要将开源播放代码换成原生audio即可!

 s += "<td <a href=\"\"> <audio src= \""+ musicUrl+"\" + controls=\"controls\" preload=\"none\" loop=\"loop\"> >" + "</audio> </a> </td>";

原生的audio标签和开源播放器的一首歌曲的下载时间如下:

开源播放器:

原生audio播放器:

可以看到同样一首歌曲在线播放后下载的时间不同,虽然 2 个都是边下载边播放,但是这里的开源播放器下载时间更短!


发布于: 2022 年 08 月 11 日阅读数: 50
用户头像

bug郭

关注

还未添加个人签名 2022.06.15 加入

还未添加个人简介

评论

发布
暂无评论
基于SpringBoot的OnlineMusicPlayer项目_签约计划第三季_bug郭_InfoQ写作社区