基于SpringBoot的OnlineMusicPlayer项目

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介: 基于Spring,SpringMVC,SpringBoot,JQuery,HTML,CSS,JS技术的网页在线音乐播放器项目!

创建项目

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

image-20220726115032421

image-20220726115505183

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

image-20220726120252271

image-20220726121723445

数据库的设计

image-20220726122546219

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

image-20220726123715300

  • 创建onlinemusic 数据库

    -- 创建onlinemusic数据库
     drop database if exists onlinemusic;
     create database if not exists onlinemusic character set utf8;
    -- 使用onlinemusic
    use onlinemusic;
  • 创建user用户表

    -- 创建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音乐列表

    -- 创建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收藏音乐列表

    -- 创建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
    );

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

image-20220726132147784

3张表结构如下!

image-20220726132259078

整个db.sql文件

-- 创建onlinemusic数据库
 drop database if exists onlinemusic;
 create database if not exists onlinemusic character set utf8;
-- 使用onlinemusic
use 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=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#配置xml
mybatis.mapper-locations=classpath:mybatis/**Mapper.xml
#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb
spring.servlet.multipart.max-file-size = 15MB
spring.servlet.multipart.max-request-size=100MB
# 配置springboot日志调试模式是否开启
debug=true
# 设置打印日志的级别,及打印sql语句
#日志级别:trace,debug,info,warn,error
#基本日志
logging.level.root=INFO
logging.level.com.example.onlinemusic.mapper=debug
#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.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接口!

//UserMapper
package 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);
}

image-20220726141845600

创建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>

image-20220726141907881

实现登入

设置登入的请求和响应

  • 请求

    请求:
    { 
        post, //请求方法
        /user/login //请求的url
        data:{username,password} //传输的数据!
    }
  • 响应
响应:
{
    "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("登入失败!");
        }
    }
}

image-20220726203942253

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

pastman验证登入功能

image-20220726204051182

image-20220726204136503

image-20220726204151778

我们对照数据库中的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
 */
@Data
public 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;
    }
}

验证

image-20220726215005432

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会话响应

image-20220726221715118

未设置Session会话响应!

image-20220726221843242

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

Bcrypet加密原理

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

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

  • MD5加密
  • Bcrypet加密

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

MD5加密

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

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

image-20220726232223419

我们也可以对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"));
    }
}

image-20220726233230278

虽然这里的加盐操作使得加密后的密码长度更长了,但是还是解决不了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匹配算法通过密码和加密后的密文进行匹配!

image-20220726234759160

image-20220726234815961

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

MD5和BCrypet的异同

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

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

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

加密登入实现

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

image-20220727121658099

//加密后的登入方法!
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验证代码:

image-20220727122016630

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

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

image-20220727122834874

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

image-20220727122756040

创建config类,添加AppConfig类

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

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

image-20220727123829855

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

实现注册功能

约定注册请求和响应

请求:
{
    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验证

  • 用户存在

image-20220727132235399

image-20220727131627680

  • 用户不存在注册成功!

    image-20220727133836156

    image-20220727133851917

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

    image-20220727133954061

    上传音乐模块

上传音乐模块设计

上传音乐请求和响应

请求:
{
    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
 */
@Data
public 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验证

  • 未登录上传文件

image-20220728220551808

  • 登入后上传

image-20220728222451783

image-20220728221805447

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

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

可以通过后缀名.MP3嘛?

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

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

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

我们拿.MP3文件举例:

文件结构如下:

image-20220729133426738

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

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

image-20220729134018771

例如我们通过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);
    }

验证:

音乐文件:

非音乐文件:

image-20220801135011056

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

 //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);
            }

验证:

音乐文件上传成功:

非音乐文件:

image-20220801141902651

上传到数据库

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

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

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

image-20220728232037725

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
 */
@Mapper
public 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);

    }
}

验证:

image-20220729143336681

image-20220729143353645

查看数据库music

image-20220729143244779

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

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

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

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

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验证

image-20220729150224406

image-20220729150340098

批量上传歌曲

我们可以将参数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);
    }

验证:

image-20220801124134954

上传音乐模块总结

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

    image-20220729230213100

  • 上传服务器时要验证音乐是否重复上传,这里通过查询数据库中的信息进行验证
  • 在上传音乐时验证是否为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部分!

    image-20220801125519306

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

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

    image-20220801130437746

    ok静态方法:

    //这个方法若被调用的话,返回OK状态
    public static ResponseEntity.BodyBuilder ok(){
    return status(HttpStatus.OK);
    }
    //这个方法若被调用的话,返回body内容和OK状态
    public static <T> ResponseEntity<T> ok(T body) {
    ResponseEntity.BodyBuilder builder = ok();
    //ResponseEntity可以通过这个builder返回任意类型的body内容
    return builder.body(body);
    }

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

验证结果

音乐文件有TAG标志

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

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

image-20220801131314451

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

删除音乐模块

删除单个音乐

请求响应设计

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

代码实现

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

  • 将服务器下的文件进行删除
  • 将数据库中的文件信息删除

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

  • Mapper接口

    /**
         * 通过音乐id删除音乐!
         * @param id
         * @return
         */
        int deleteById(int id);
    
        /**
         * 通过id获取到音乐信息
         * @param id
         * @return
         */
        Music getMusicById(int id);
  • Mapper实现

image-20220801145400887

  • 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);
    }

验证结果

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

image-20220801145648988

  • 删除的音乐不存在

image-20220801145605376

  • 删除音乐存在

image-20220801145741138

image-20220801145811018

删除成功!

批量删除音乐

请求响应设计

请求:
{
    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);
    }

验证结果

  • 删除成功

image-20220801160935061

image-20220801161039645

  • 删除失败

image-20220801161253586

查询音乐模块

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

  • 查询给定名称的歌曲

    • 给定歌曲名称全

      查询到单个歌曲

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

      查询到多个歌曲

  • 未给定歌曲名

    就查询所有歌曲

请求响应设计

请求:
{
    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);
    }

验证结果

image-20220801164808889

  • 查询名称为空

image-20220801165026072

  • 模糊查询

    image-20220801165309472

收藏音乐模块

添加音乐到收藏列表

请求响应设计

请求:
{
    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
 */
@Mapper
public 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);
    }
}

验证结果

image-20220801221914863

  • 歌曲已收藏,收藏失败

image-20220801221743298

  • 歌曲未收藏,收藏成功

image-20220801221849636

查询喜欢的音乐列表

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

  • 未给定歌曲名称

    查询该用户所有收藏歌曲
  • 给定歌曲名称参数

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

请求和响应设计

请求:
{
    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);
    }

验证结果

数据库信息:

image-20220802002411320

登入user_di=10的用户

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

image-20220802002254427

  • 模糊匹配查询指定歌曲

image-20220802002636445

从喜欢列表移除音乐

### 请求和响应设计

请求:
{
    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);
    }

验证结果

数据库信息:

image-20220802005252299

image-20220802083542038

  • 歌曲不存在,移除失败

    image-20220802083625829

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

    image-20220802083657166

完善删除音乐模块代码

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

我们只需要添加一个通过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);
    }

验证结果:

数据库信息:

image-20220802085312234

我们将music_id为51音乐删除!

image-20220802085918389

image-20220802085753764

前端设计

我们前端页面是在网上下载了一个静态页面的模板!我们需要在此基础上进行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>

验证结果:

image-20220804111429478

image-20220804111501657

注册逻辑

 <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>

验证结果:

image-20220804111333391 '

image-20220804111402057

查询音乐逻辑

核心代码逻辑:

  <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>

验证结果:

image-20220804125324656

上传音乐逻辑

核心代码逻辑

<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>

验证结果:

image-20220804125629463

播放音乐

<!--嵌入播放器!-->
<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);
      }

验证结果:

image-20220804134109484

删除音乐逻辑

 //删除音乐
      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("删除失败!");
                }
            }
        });
      }

验证结果:

image-20220804135319716

查询歌曲逻辑

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

验证结果:

image-20220804140157554

删除选中的歌曲逻辑

 //删除选中逻辑在查询逻辑里
    $(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("删除失败!");
                        }
                    }
                });
            });
        });
      });

验证结果:

image-20220805231650640

image-20220805231750609

收藏音乐逻辑

 $(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("移除失败!");
                }
            }
        });
      }

验证结果:

image-20220806003530836

image-20220806003515171

实现收藏功能逻辑

//在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("收藏失败!");
                }
            }
        })
      }

验证结果:

image-20220805235115852

配置拦截器

有些页面我们没有登入也可以进行访问,通过输入 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
 */
@Configuration
public 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");
    }
}

验证结果:

image-20220806011222276

image-20220806011857589

# 部署到服务器

准备

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

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

image-20220806013703105

  • 更改上传音乐路径
# 云服务器下的地址路径!
music.path.save = /root/javaweb部署环境/music

image-20220806013815069

打包

  • 项目打包

image-20220806014417266

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

image-20220806014706203

image-20220806020004651

添加依赖

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

image-20220806020529697

 <!--添加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>

image-20220806020622900

打包成功!

部署

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

image-20220806021108082

启动项目:

java -jar onlinemusic.jar 

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

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

验证结果:

image-20220806030042655

image-20220806030106868

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

后台运行SpringBoot项目

运行指令:

nohup java -jar onlinemusic.jar>>log.log&
nohup:后台运行项目的指令

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

&:后台一直运行

image-20220806032205318

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

后期项目维护更新

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

netstat -anp |grep 8080

kill -9 17303

image-20220807210340255

也可以使用ps -ef | grep java

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

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

image-20220807210651629

image-20220807214237459

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

遇到的面试题总结

  • 上传其他文件,然后将后缀改成.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>";

image-20220807224032758

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

开源播放器:

image-20220807224553159

原生audio播放器:

image-20220807224628368

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

目录
相关文章
|
NoSQL Java 数据库连接
Idea创建SpringBoot多模块项目
我们可以定义一个维度,以此来划分模块,例如上述商城、可以划分成商品、库存和订单模块。也可以目录结构分层,`Controller`层,只不过没人这样做。这样就引申出了下一个问题`拆分策略`。
926 0
Idea创建SpringBoot多模块项目
|
Java 应用服务中间件 Maven
传统maven项目和现在spring boot项目的区别
Spring Boot:传统 Web 项目与采用 Spring Boot 项目区别
493 0
传统maven项目和现在spring boot项目的区别
|
XML Java 数据库连接
创建springboot项目的基本流程——以宠物类别为例
创建springboot项目的基本流程——以宠物类别为例
154 0
创建springboot项目的基本流程——以宠物类别为例
|
存储 机器学习/深度学习 IDE
SpringBoot 项目与被开发快速迁移|学习笔记
快速学习 SpringBoot 项目与被开发快速迁移
SpringBoot 项目与被开发快速迁移|学习笔记
|
Java Spring
自定义SpringBoot项目的启动Banner
``Banner``是``SpringBoot``框架一个特色的部分,其设计的目的无非就是一个框架的标识,其中包含了版本号、框架名称等内容,既然``SpringBoot``为我们提供了这个模块,它肯定也是可以更换的这也是``Spring``开源框架的设计理念。
|
前端开发 Java 应用服务中间件
基于springboot+mybatisplus+vue-科技项目评审及专家库管理系统
基于springboot+mybatisplus+vue-科技项目评审及专家库管理系统
261 0
基于springboot+mybatisplus+vue-科技项目评审及专家库管理系统
|
Java Spring
【Java】【Spring Boot】CP01:创建一个SpringBoot项目(Spring Initializr)
【Java】【Spring Boot】CP01:创建一个SpringBoot项目(Spring Initializr)
278 0
【Java】【Spring Boot】CP01:创建一个SpringBoot项目(Spring Initializr)
|
消息中间件 NoSQL Java
47K Star 的SpringBoot+MyBatis+docker电商项目,附超详细的文档
该项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现,采用Docker容器化部署。 前台商城系统:首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等模块。 后台管理系统:商品管理、订单管理、会员管理、促销管理、运营管理、内容管理、统计报表、财务管理、权限管理、设置等模块。 该项目使用现阶段主流技术实现。涵盖了SpringBoot 2.3.0、MyBatis 3.4.6、Elasticsearch 7.6.2、RabbitMQ 3.7.15、Redis 5.0、MongoDB 4.2.5、
|
安全 Java 关系型数据库
Mall电商实战项目全面升级!支持最新版SpringBoot,干掉循环依赖
技术栈升级 mall项目采用现阶主流技术实现,这些主流技术基本都升级了目前最新稳定版,具体升级内容大家可以参考下表。 技术版本说明
|
Java 应用服务中间件 Linux
springboot上传下载文件(1)(项目和文件资源放在同一个服务器上)
springboot上传下载文件(1)(项目和文件资源放在同一个服务器上)
325 0
springboot上传下载文件(1)(项目和文件资源放在同一个服务器上)

热门文章

最新文章

下一篇
无影云桌面