实现音乐播放器

目录

核心功能

演示效果

 创建项目

数据库设计

配置数据库和xml

登录模块设计

1 创建User类

2 创建对应的Mapper和Controller

2.1 创建接口UserMapper

2.2 创建UserMapper.xml

3 实现登录

3.1 登录的请求和响应设计

3.2 设计统一的响应体类工具类

3.3 创建UserController类

 3.4 使用postman验证登录功能

3.5 优化代码

3.6 验证登录

4 BCrypt加密的原理

4.1 MD5加密

4.2 BCrypt加密设计

5 加密登录实现

5.1 数据库插入数据

5.2 UserMapper类新增方法

5.3 UserMapper.xml配置

5.4 修改UserController类

5.5 创建包config,新建AppConfig类

5.6 spring-boot启动类注解  

5.7 验证加密登录

上传音乐模块设计

1 上传音乐的接口设计

2 创建MusicController类

3 如何判断上传的文件是mp3

4 实现数据库上传

5 验证整体文件上传

6 总结

6.1 MultipartFile类

6.2 如何判断是不是音乐文件?

6.3 相同的音乐是否可以上传成功?如何处理?

播放音乐模块设计

1 请求响应设计

 2 测试请求是否收到对应响应

删除音乐模块设计

删除单个音乐

1 请求响应设计

2 代码实现

3 验证结果

批量删除选中的音乐

1 请求响应设计

2 代码实现

3 验证结果

查询音乐模块设计

1 请求响应模块设计

2 代码实现

3 验证结果

喜欢/收藏音乐模块设计

添加音乐至喜欢的列表模块设计

1 请求响应模块设计

2 代码实现

3 验证结果

查询喜欢的音乐模块设计

1 请求响应设计

2 代码实现

3 验证结果

移除喜欢的音乐模块设计

1 请求响应设计

2 代码实现

3 验证结果

 删除音乐完善

前端页面实现

配置拦截器

项目部署


核心功能

1. 登录

2. 上传音乐

3. 删除指定音乐

4. 批量删除选中的音乐

5. 查询你想要的音乐

6. 添加音乐至喜欢的列表

7. 移除喜欢的音乐


演示效果

在线体验:http://43.138.233.192:8080/login.html  用户名:biaoge   密码:123456

项目Gitee链接:JavaEE: spring的项目学习 - Gitee.com

 

 

 创建项目

创建一个 springboot 项目

 

 

 

 

数据库设计

 创建数据库musicserver

-- 数据库
drop database if exists `musicserver`;
create database if not exists `musicserver` character set utf8;

-- 使用数据库
use `musicserver`;

创建表user

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
    `id` INT PRIMARY KEY AUTO_INCREMENT,
    `username` varchar(20) NOT NULL,
    `password` varchar(255) NOT NULL
);
INSERT INTO user(username,password)
VALUES("bit","$2a$10$Bs4wNEkledVlGZa6wSfX7eCSD7wRMO0eUwkJH0WyhXzKQJrnk85li");
创建表 music
DROP TABLE IF EXISTS `music`;
CREATE TABLE `music` (
    `id` int PRIMARY KEY AUTO_INCREMENT,
    `title` varchar(50) NOT NULL,
    `singer` varchar(30) NOT NULL,
    `time` varchar(13) NOT NULL,
    `url` varchar(1000) NOT NULL,
    `userid` int(11) NOT NULL
);
title 字段为歌曲名称, url 字段为歌曲的路径
创建中间表 lovemusic
DROP TABLE IF EXISTS `lovemusic`;
CREATE TABLE `lovemusic` (
    `id` int PRIMARY KEY AUTO_INCREMENT,
    `user_id` int(11) NOT NULL,
    `music_id` int(11) NOT NULL
);

配置数据库和xml

打开 application.properties 配置如下信息
#配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/onlinemusic?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=你的用户名
spring.datasource.password=你的密码
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

登录模块设计


1 创建User

package com.example.musicserver.model 包中创建 User
package com.example.musicserver.model;
import lombok.Data;

@Data
public class User {
    private int id;
    private String username;
    private String password;
}

2 创建对应的MapperController

2.1 创建接口UserMapper

新建 mapper 包,新建 UserMapper
@Mapper
public interface UserMapper {
    User login(User loginUser);
}

2.2 创建UserMapper.xml

resource 目录下,新建 mybatis 文件夹,新建 UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.musicserver.mapper.UserMapper">
   <select id="login" resultType="com.example.musicserver.model.User">
        select * from user where username=#{username} and password=#{password}
    </select>
</mapper>

3 实现登录

3.1 登录的请求和响应设计

请求:
{
    post,
    /user/login
    data:{username,password}
}
响应:
{
    "status": 0,
    "message": "登录成功",
    "data": {
        "id": xxxxx,
        "username": xxxxxx,
        "password": xxxxxxxx
    }
}
响应体设计字段解释:
{
    状态码,为0代表成功,负数代表失败
    状态描述信息,描述此次请求成功或者失败的原因
    返回的数据,请求成功后,需要给前端的数据信息
}

3.2 设计统一的响应体类工具类

创建tools包中创建ResponseBodyMessage类

@Data
public class ResponBodyMessage<T> {
    private int status;//状态码
    private String message;//状态描述信息
    private T data;//返回的数据

    public ResponBodyMessage(int status, String message, T data) {
        this.status = status;
        this.message = message;
        this.data = data;
    }
}

3.3 创建UserController

controller 包下,创建 UserController
package com.example.musicserver.controller;


@RestController//@ResponseBody + @Controller合在一起的作用
@RequestMapping("/user")//使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求
public class UserController {
    @Autowired
    private UserMapper userMapper;


    @RequestMapping("/login")
    public ResponBodyMessage<User> login(@RequestParam String username, @RequestParam String password,
                      HttpServletRequest request) {
        User userLogin = new User();
        userLogin.setUsername(username);
        userLogin.setPassword(password);
        User user = userMapper.login(userLogin);
        if(user != null) {
            System.out.println("登录成功!");
            request.getSession().setAttribute("USERINFO_SESSION_KEY",user);
            return new ResponBodyMessage<>(0, "登录成功!",userLogin);
        }else {
            System.out.println("登录失败!");
            return new ResponBodyMessage<>(-1, "登录失败!", userLogin);
        }
    }
}
注解介绍:
@RestController @ResponseBody @Controller 合在一起的作用。 @Controller 注解,表明了这个类是一个控制器类 ,@ResponseBody 表示方法的返回值直接以指定的格式写入 Http response body 中 。
@RequestMapping :使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些 URL 请求
@RequestParam :将请求参数绑定到你控制器的方法参数上 。如果这个参数是非必传的可以写为: @RequestParam(required = false) ,默认是true

 3.4 使用postman验证登录功能

请求: http://localhost:8080/user/login?username=zhangsan&password=123

3.5 优化代码

上述代码中:
request.getSession().setAttribute("USERINFO_SESSION_KEY",userInfo);
设置了 session 对象,此时的 key 值是一个字符串,将来在其他地方获取对应的 session 需要通过这个字符串获取, 但是存在一定的写错的情况。所以,此时建议把他定义为一个常量。
tools 包中新建一个 Constant 类:
package com.example.musicserver.tools;

/**
 * Greated with IntelliJ IDEA.
 * Description:
 * User: 26524
 * Date: 2023-04-19
 * Time: 12:14
 */
public class Constant {
    public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}
修改刚刚设置 session 的代码为:
request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);

3.6 验证登录

重新启动项目,使用postman验证登录是否可以成功。

4 BCrypt加密的原理

4.1 MD5加密

MD5 是一个安全的散列算法,输入两个不同的明文不会得到相同的输出值,根据输出值,不能得到原始的明文,即 其过程不可逆; 但是虽然不可逆,但是不是说就是安全的。因为自从出现彩虹表后,这样的密码也" 不安全 "
彩虹表:彩虹表就是一个庞大的、针对各种可能的字母组合预先计算好的哈希值的集合,不一定是针对 MD5 算法的,各种算法的都有,有了它可以快速的破解各类密码。越是复杂的密码,需要的彩虹表就越大,现在主流的彩虹表都是100G 以上。

 不安全的原因:

  1. 暴力攻击速度很快
  2. 字典表很大
  3. 碰撞

 参考链接https://md5.cc/news1.aspx

更安全的做法是加盐或者长密码等做法,让整个加密的字符串变的更长,破解时间变慢。密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。

这里我们介绍加盐的做法:盐是在每个密码中加入一些单词来变成一个新的密码,存入数据库当中。
添加依赖:
<!-- 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>

在tools包下,新建MD5Util

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 inputPass
     * @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("对用户输入密码进行第3次加密:"+inputPassToDbPass("123456", "1b2i3t4e"));
    }
}
输出结果:

 不管运行多少次,这个密码是规定的。因为这里没有用随机盐值。当密码长度很大,盐值也是随机的情况下,密码的强度也加大了。破解成本也增加了。

4.2 BCrypt加密设计

Bcrypt 就是一款加密工具,可以比较方便地实现数据的加密工作。你也可以简单理解为它内部自己实现了随机加盐处理 。我们使用MD5 加密,每次加密后的密文其实都是一样的,这样就方便了 MD5 通过大数据的方式进行破解。 Bcrypt生成的密文是 60 位的。而 MD5 的是 32 位的。 Bcrypt 破解难度更大。
添加依赖:
<!-- 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>
这里虽然添加了security的依赖,但是没有用到security。而是security包底下的crypto.bcrypt.BCryptPasswordEncoder。
springboot 启动类添加:
(exclude =
		{org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
在tools包下创建 BCryptTest 测试类:
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

public class BCryptTest {
    public static void main(String[] args) {
        //模拟从前端获得的密码
        String password = "123456";
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String newPassword = bCryptPasswordEncoder.encode(password);
        System.out.println("加密的密码为: "+newPassword);
        
        //使用matches方法进行密码的校验
        boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword);
        
        //返回true
        System.out.println("加密的密码和正确密码对比结果: "+same_password_result);
        boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword);
        
        //返回false
        System.out.println("加密的密码和错误的密码对比结果: " + other_password_result);
    }
}

输出结果:【每次运行结果不一致,但是都能匹配成功】

 解析:

encode 方法:对用户密码进行加密
matches 方法:参数一,待检验的未加密的密码 。参数二:从数据库中查询出的加密后密码 

 总结:

  1.  密码学的应用安全,是建立在破解所要付出的成本远超出能得到的利益上的 。
  2. 使用BCrypt相比于MD5加密更好的一点在于,破解的难度上加大
  3.  BCrypt的破解成本增加了,导致系统的运行成本也会大大的增加 。
  4. 回到本质的问题,你的数据库中的数据价值如何?如果你是银行类型的,那么使用BCrypt是不错的,一般情况使用MD5加盐,已经够用了。

【总结】:

参考链接:https://blog.csdn.net/muyimo/article/details/118811514 

BCrypt 加密: 一种加盐的单向 Hash ,不可逆的加密算法,同一种明文( plaintext ),每次加密后的密文都不一 样,而且不可反向破解生成明文,破解难度很大。
MD5 加密: 是不加盐的单向 Hash ,不可逆的加密算法,同一个密码经过 hash 的时候生成的是同一个 hash 值,在大多数的情况下,有些经过md5 加密的方法将会被破解。
Bcrypt 生成的密文是 60 位的。而 MD5 的是 32 位的。
目前, MD5 BCrypt 比较流行。相对来说, BCrypt MD5 更安全,但加密更慢。 虽然 BCrpyt 也是输入的字符串加盐,但是与 MD5加 盐的主要区别是:每次加的盐不同,导致每次生成的结果也不相同。无法比对!

5 加密登录实现

5.1 数据库插入数据

将上述 BCryptTest 运行生成的对 123456 加密后的结果,插入到数据库当中。同时设置一个用户名。

5.2 UserMapper类新增方法

@Mapper
public interface UserMapper {
    User login(User userLogin);

    //username用户名是唯一的
    User selectByName(String username);
}

5.3 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.musicserver.mapper.UserMapper">
    <select id="login" resultType="com.example.demo.model.User">
        select * from user where username=#{username} and password=#{password}
    </select>

    <select id="selectByName" resultType = "com.example.demo.model.User">
        select * from user where username=#{username}
    </select>
</mapper>

5.4 修改UserController

@RestController//@ResponseBody + @Controller合在一起的作用
@RequestMapping("/user")//使用 @RequestMapping 来映射请求,也就是通过它来指定控制器可以处理哪些URL请求
public class UserController {
    @Autowired
    private UserMapper userMapper;

    @Autowired//在自动装配之前,需要完成注入,我们再AppConfig中进行注入
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @RequestMapping("/login")
    public ResponBodyMessage<User> login(@RequestParam String username, @RequestParam String password,
                                         HttpServletRequest request) {

        User user = userMapper.selectByName(username);
        if(user != null) {
            boolean flg = bCryptPasswordEncoder.matches(password,user.getPassword());
            if (!flg) {
                return new ResponBodyMessage<>(-1, "用户名或者密码错误!", user);
            }
            System.out.println("登录成功!");
            request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY,user);
            return new ResponBodyMessage<>(0, "登录成功!",user);
        }else {
            System.out.println("登录失败!");
            return new ResponBodyMessage<>(-1, "用户名或者密码错误!", user);
        }
    }
}
注解介绍:
@Autowired :可以更准确地控制应该在何处以及如何进行自动装配。此注解用于在  setter 方法,构造函数,具有任意名称或多个参数的属性或方法上自动装配bean 。默认情况下,它是类型驱动的注入

5.5 创建包config,新建AppConfig

@Configuration
public class AppConfig {
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
注解介绍:
@Configuration :表明当前类是一个配置类,被注解的类内部包含有一个或多个被 @Bean 注解的方法,用于构建bean定义,初始化 Spring 容器。
@Bean 注解:用于告诉方法,产生一个 Bean 对象,然后这个 Bean 对象交给 Spring 管理。产生这个 Bean 对象的方法Spring 只会调用一次,随后这个 Spring 将会将这个 Bean 对象放在自己的 IOC 容器中。
SpringIOC 容器管理一个或者多个 bean ,这些 bean 都需要在 @Configuration 注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给 Spring 进行管理。

5.6 spring-boot启动类注解  

当启动类,没有加这个过滤的时候,我们发现不能进行登录。
@SpringBootApplication(exclude =
{org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
这是因为在 SpringBoot 中,默认的 Spring Security 生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。此时通过上述配置,即可禁用默认的登录验证。

5.7 验证加密登录

使用postman进行登录验证,加密功能是否正常。

上传音乐模块设计


1 上传音乐的接口设计

请求:
{
    post,
    /music/upload
    {singer,MultipartFile file},
}
响应:
{
    "status": 0,
    "message": "上传成功!",
    "data": true
}
在model包下新建 Music 类:
import lombok.Data;

@Data
public class Music {
    private int id;
    private String title;
    private String singer;
    private String url;
    private String time;
    private int userid;
}

2 创建MusicController

在controller包下创建MusicController

@RestController
@RequestMapping("/music")
public class MusicController {
    @Value("${music.local.path}")
    private String SAVE_PATH;
    @RequestMapping(value = "/upload")
    public ResponBodyMessage<Boolean> insertMusic(@RequestParam String singer, @RequestParam("filename")
        MultipartFile file, HttpServletRequest req, HttpServletResponse resp) throws IOException {
        //没有session不创建
        HttpSession httpSession = req.getSession(false);
        if(httpSession == null || httpSession.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            System.out.println("没有登录!");
            return new ResponBodyMessage<>(-1,"没有登录!",false);
        }
        String filenameAndType = file.getOriginalFilename();//xxx.mp3
        System.out.println("filenameAndType--->>>>>>>>>>>>>>>>>"+filenameAndType);
        String path = SAVE_PATH +"\\"+filenameAndType;
        File dest = new File( path);
        System.out.println("dest:=>" + dest.getPath());
        if(!dest.exists()) {
            dest.mkdirs();
        }
        try {
            file.transferTo(dest);//上传文件到目标
            return new ResponBodyMessage<>(0,"上传成功!",true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ResponBodyMessage<>(-1,"上传失败!",false);
    }
}
注解介绍:
  1. 使用@Value("${music.local.path}"),获取到配置文件当中的值。不建议中文路径。
    #音乐上传后的路径
    music.local.path=C:/work/local/music
  2. MultipartFile类,在org.springframework.web.multipart包当中,是Spring框架中处理文件上传的主要类。

主要方法介绍:

方法名
作用
String getOriginalFileName()
获取的是文件的完整名称,包括文件名称 + 文件拓展名。
String getContentType()
获取的是文件的类型,注意是文件的类型,不是文件的拓展名
boolean isEmpty()
用来判断传入的文件是否为空,如果为空则表示没有传入任何文件
long getSize()
获取文件的大小,单位是字节
void transferTo(File dest)
将接收到的文件传输到给定目标路径
测试:

3 如何判断上传的文件是mp3

每个文件都由其构成的方式【不能通过后缀名判断】
mp3 文件格式:

 由上图结构可知,每个Frame都由帧头和数据部分组成。我们来看每个帧头的数据格式。

 ID3V1部分

 

 参考链接:

https://blog.csdn.net/ffjffjffjffjffj/article/details/99691239
https://www.cnblogs.com/ranson7zop/p/7655474.html
https://blog.csdn.net/sunshine1314/article/details/2514322

4 实现数据库上传

上述实现只是实现了简单的本地上传文件,还未将数据插入到数据库当中,接下来我们实现数据库中数据的写入。 在mapper包下定义接口MusicMapper
package com.example.musicserver.mapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MusicMapper {
    int insert(String title, String singer, String time, String url,int userid);
}
定义 MusicMapper.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.musicserver.mapper.MusicMapper">
    <insert id="insert">
        insert into music(title,singer,time,url,userid) values(#{title},#{singer},#{time},#{url},#{userid})
    </insert>
</mapper>
MusicController 类中引入 MusicMapper
@Resource
private MusicMapper musicMapper;
一会儿进行数据库的插入,需要插入上传的时间,所以我们先了解一个时间格式化的类: SimpleDateFormat
在tools包下新建TestTime类
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestTime {
    public static void main(String[] args) {
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        String time = sf.format(new Date());
        System.out.println("当前的时间: "+time);

        SimpleDateFormat sf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time1 = sf1.format(new Date());
        System.out.println("当前的时间: "+time1);
    }
}
运行结果:
当前的时间: 2023-04-25
当前的时间: 2023-04-25 18:25:01
继续完成 insertMusic 方法:
前端页面完善后记得取消注解,上传成功后跳转到音乐列表页
    @RequestMapping("/upload")
    public ResponBodyMessage<Boolean> insertMusic(@RequestParam String singer,
                                                  @RequestParam("filename") MultipartFile file,
                                                  HttpServletRequest request,
                                                  HttpServletResponse response) {
        //检查是否登录了
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            System.out.println("没有登录!");
            return new ResponBodyMessage<>(-1, "请登陆后上传!", false);
        }
        //获取文件名
        String filename = file.getOriginalFilename();//xxx.mp3
        System.out.println("fileNameAndType: " + filename);
        //上传路径
        String path = SAVE_PATH + "/"+filename;

        //创建目录
        File dest = new File(path);
        if(!dest.exists()) {
            System.out.println("没有这个目录");
            dest.mkdir();
        }

        //上传文件
        try {
            file.transferTo(dest);
        } catch (IOException e) {
            e.printStackTrace();
            return new ResponBodyMessage<>(-1, "上传失败!", false);
        }
        //进行数据库上传
        int index = filename.lastIndexOf(".");
        String title = filename.substring(0, index);

        //获取id
        User user = (User)session.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userid = user.getId();

        //获取url
        String url = "music/get?path="+title;

        //获取time
        SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd");
        String time = sf.format(new Date());
        System.out.println("当前的时间: "+time);

        try {
            int ret = 0;
            ret = musicMapper.insert(title,singer,time,url,userid);
            if(ret == 1) {
                //后面完善前端页面后,上传成功会跳转到音乐列表页面
                //response.sendRedirect("/list.html");
                return new ResponBodyMessage<>(0, "数据库上传成功!",true);
            }else {
                return new ResponBodyMessage<>(-1, "数据库上传失败!",false);
            }
        }catch (BindingException e) {
            dest.delete();
            return new ResponBodyMessage<>(-1, "数据库上传失败!",false);
        }
    }

5 验证整体文件上传

 结果:

 

 

6 总结

6.1 MultipartFile

org.springframework.web.multipart 包当中,是 Spring 框架中处理文件上传的主要类。所以必须引入 Spring 框架。一般来讲使用MultipartFile 这个类主要是来实现以表单的形式进行文件上传功能 

参考链接:https://www.jianshu.com/p/e3d798c906cd 

方法名
作用
String getOriginalFileName()
获取的是文件的完整名称,包括文件名称 + 文件拓展名。
String getContentType()
获取的是文件的类型,注意是文件的类型,不是文件的拓展名
boolean isEmpty()
用来判断传入的文件是否为空,如果为空则表示没有传入任何文件
long getSize()
获取文件的大小,单位是字节
void transferTo(File dest)
将接收到的文件传输到给定目标路径

6.2 如何判断是不是音乐文件?

上述已经讲到

6.3 相同的音乐是否可以上传成功?如何处理?

需要先进行数据库的查询,然后上传文件,插入数据库。

播放音乐模块设计

1 请求响应设计

请求:
{
    get,
    /music/get?path=xxx.mp3
}
响应:
{
    音乐数据本身的字节信息
}
在MusicController类 新增方法get
@RequestMapping("/get")
public ResponseEntity<byte[]> get(String path) {
    File file = new File(SAVE_PATH+"/"+path);
    byte[] a = null;
    try {
        a = Files.readAllBytes(file.toPath());
        if(a == null) {
            return ResponseEntity.badRequest().build();
        }
        return ResponseEntity.ok(a);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return ResponseEntity.badRequest().build();
}
代码解释:
  1. Files.readAllBytes(String path) : 读取文件中的所有字节,读入内存 ,参数path是文件的路径
  2. ResponseEntity
ResponseEntity 对象是 Spring 对请求响应的封装。它继承了 HttpEntity 对象,包含了 Http 的响应码
httpstatus )、响应头( header )、响应体 (body) 三个部分。
ResponseEntity 类继承自 HttpEntity 类,被用于 Controller 层方法 。 ResponseEntity.ok 方法有 2 个方法,分别是有参数和没有参数。
//这个方法若被调用的话,返回OK状态
public static ResponseEntity.BodyBuilder ok(){
    return status(HttpStatus.OK);
}
//这个方法若被调用的话,返回body内容和OK状态
public static <T> ResponseEntity<T> ok(T body) {
    ResponseEntity.BodyBuilder builder = ok();
    //ResponseEntity可以通过这个builder返回任意类型的body内容
    return builder.body(body);
}
  • API中的描述一致,无参ok方法返回OK状态,有参ok方法返回body内容和OK状态
  • body类型 是 泛型T,也就是我们不确定body是什么类型,可以向ok方法传递任意类型的值
  • 有参ok方法其实有调用无参ok方法
@RequestMapping("/get")
public ResponseEntity<byte[]> func() {
    byte[] a = {97,98,99,100};
    //return ResponseEntity.internalServerError().build();
    return ResponseEntity.notFound().build();
    //return ResponseEntity.ok(a);
}

 参考链接:

  1. https://blog.csdn.net/imaginehero/article/details/102810792?spm=1001.2101.3001.6661.1&utm_medium=d
    istribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ETopBlog-1.topblog&depth_1-utm_sourc
    e=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7ECTRLIST%7ETopBlog-1.topblog&utm_relevant_in
    dex=1
  2. https://www.jianshu.com/p/1238bfb29ee1
  3. https://blog.csdn.net/qq_43317193/article/details/100109136

 2 测试请求是否收到对应响应

根据上述请求获取一个音乐文件:

 可以明显看到有TAG标签

获取一个图片伪造的 mp3 文件:没有 TAG 标签

删除音乐模块设计

删除单个音乐

1 请求响应设计

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

2 代码实现

MusicMapper.java
    /**
     * 根据ID删除音乐
     * @param musicId
     * @return
     */
    int deleteMusicById(int musicId);

    /**
     * 根据ID查询音乐
     * @param id
     * @return
     */
    Music findMusicById(int id);
MusicMapper.xml
<delete id="deleteMusicById" parameterType="java.lang.Integer">
    delete from music where id=#{id}
</delete>

<select id="findMusicById" resultType="com.example.musicserver.model.Music">
    select * from music where id=#{id}
</select>
MusicController 中实现:
    /**
     * 删除单个音乐
     * @param id
     * @param request
     * @return
     */
    @RequestMapping("/delete")
    public ResponBodyMessage<Boolean> deleteMusicById(@RequestParam String id,
                                                  HttpServletRequest request) {
        //1.先检查这个音乐是不是存在?
        int iid = Integer.parseInt(id);
        //2.如果存在要进行删除
        Music music = musicMapper.findMusicById(iid);
        if(music == null) {
            return new ResponBodyMessage<>(-1, "没有你要找的音乐!", false);
        }else {
            //2.1删除数据库
            int ret = musicMapper.deleteMusicById(iid);
            if(ret == 1) {
                //后面会增加这个,删除音乐的同时删除收藏音乐
                //loveMusicMapper.deleteLoveMusicByMusicId(iid);
                //2.2删除服务器上的数据
                int index = music.getUrl().lastIndexOf("=");
                String filename = music.getUrl().substring(index+1);

                File file = new File(SAVE_PATH+"/"+filename+".mp3");
                System.out.println("当前的路径:" + filename);
                if(file.delete()) {
                    return new ResponBodyMessage<>(0, "服务器删除成功!", true);
                }else {
                    return new ResponBodyMessage<>(-1, "服务器删除失败!", false);
                }
            }else {
                return new ResponBodyMessage<>(-1, "数据库中的音乐没有删除成功!", false);
            }
        }
    }

3 验证结果

 

 

批量删除选中的音乐

1 请求响应设计

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

2 代码实现

新增方法deleteSelMusic

    @RequestMapping("/deleteSel")
    public ResponBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]") List<Integer> id) {
        int sum = 0;
        for(int i = 0; i < id.size(); i++) {
            Music music = musicMapper.findMusicById(id.get(i));
            if(music == null) {
                System.out.println("没有这个id的音乐");
                return new ResponBodyMessage<>(-1, "没有你要删除的音乐!", false);
            }
            //2.1删除数据库
            int ret = musicMapper.deleteMusicById(id.get(i));
            if(ret == 1) {
                //后面会增加这个,删除音乐的同时删除收藏音乐
                //loveMusicMapper.deleteLoveMusicByMusicId(id.get(i));
                //2.2删除服务器上的数据
                int index = music.getUrl().lastIndexOf("=");
                String filename = music.getUrl().substring(index+1);

                File file = new File(SAVE_PATH+"/"+filename+".mp3");
                System.out.println("当前的路径:" + filename);
                if(file.delete()) {
                    sum+=ret;
                }else {
                    return new ResponBodyMessage<>(-1, "服务器删除失败!", false);
                }
            }else {
                return new ResponBodyMessage<>(-1,"数据库删除失败!",false);
            }

        }

        if(sum == id.size()) {
            System.out.println("整体删除成功!");
            return new ResponBodyMessage<>(0, "音乐删除成功!", true);
        }else {
            System.out.println("整体删除成功!");
            return new ResponBodyMessage<>(-1, "音乐删除失败!",false);
        }
    }

3 验证结果

查询音乐模块设计

1 请求响应模块设计

此处查询需要满足几个功能:
  1. 支持模糊查询
  1. 支持传入参数为空
请求:
{
    get,
    /music/findmusic,
    data:{musicName:musicName},
}
响应:【不给musicName传参】
{
    "status": 0,
    "data": [
        {
            "id": 64,
            "title": "G_E_M_ 邓紫棋 - 错过不错",
            "singer": "zjb",
            "url": "music/get?path=G_E_M_ 邓紫棋 - 错过不错",
            "time": "2023-04-25",
            "userid": 1
        },
        {
            "id": 65,
            "title": "陈奕迅 - 让我留在你身边",
            "singer": "zjb",
            "url": "music/get?path=陈奕迅 - 让我留在你身边",
            "time": "2023-04-25",
            "userid": 1
        },
        {
            "id": 66,
            "title": "G_E_M_ 邓紫棋 - 另一个童话",
            "singer": "zjb",
            "url": "music/get?path=G_E_M_ 邓紫棋 - 另一个童话",
            "time": "2023-04-25",
            "userid": 1
        },
        {
            "id": 68,
            "title": "广东爱情故事",
            "singer": "zjb",
            "url": "music/get?path=广东爱情故事",
            "time": "2023-04-25",
            "userid": 1
        },
        {
            "id": 69,
            "title": "凉凉",
            "singer": "zjb",
            "url": "music/get?path=凉凉",
            "time": "2023-04-25",
            "userid": 1
        }
    ],
    "message": "查询到所有音乐!"
}

2 代码实现

MusicMapper.java 接口新增方法
/**
 * 根据歌曲名字,查询音乐
 * @param musicName
 * @return
 */
List<Music> findMusicByMusicName(String musicName);

/**
 * 查询所有的音乐
 * @return
 */
List<Music> findMusic();
MusicMapper.xml 新增配置
<select id="findMusicByMusicName" resultType="com.example.musicserver.model.Music">
    select * from music where title like concat('%',#{musicName},'%')
</select>

<select id="findMusic" resultType="com.example.musicserver.model.Music">
    select * from music
</select>
注意事项:
select * from music where title like concat('%',#{musicName},'%')
MusicController 类新增方法:
    @RequestMapping("/findmusic")//(required=false)可以不传入参数
    public ResponBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName) {
        List<Music> musicList = null;
        if (musicName == null) {
            //默认查询全部的音乐
            musicList = musicMapper.findMusic();
        }else {
            musicList = musicMapper.findMusicByName(musicName);
        }
        return new ResponBodyMessage<>(0, "查询到所有音乐!", musicList);
    }
注意:
@RequestParam(required=false) :可以不传入参数。

3 验证结果

 

喜欢/收藏音乐模块设计

添加音乐至喜欢的列表模块设计

1 请求响应模块设计

请求:
{
    post,
    /lovemusic/likeMusic
    data: id//音乐id
}
响应:
{
    "status": 0,
    "message": "点赞音乐成功",
    "data": true
}

2 代码实现

实现LoveMusicMapper接口,收藏/喜欢音乐功能:

1、需要查询此次收藏音乐是否之前收藏过,收藏过则不能添加

2 、没有收藏过,插入数据库中一条记录
@Mapper
public interface LoveMusicMapper {
    /**
     * 检查是否已经收藏过该音乐
     * @param userId
     * @param musicId
     * @return
     */
    Music findLoveMusicByUserIdAndMusicId(int userId, int musicId);
    /**
     * 点赞/收藏音乐
     * @param userId
     * @param musicId
     * @return
     */
    boolean insertLoveMusic(int userId, int musicId);
}
实现 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.musicserver.mapper.LoveMusicMapper">
    <!--根据用户ID和音乐ID查询收藏的音乐 -->
    <select id="findLoveMusicByUserIdAndMusicId" resultType="com.example.musicserver.model.Music">
        select * from lovemusic where user_id=#{userId} and music_id=#{musicId}
    </select>

    <insert id="insertLoveMusic">
        insert into lovemusic(user_id,music_id) values(#{userId},#{musicId})
    </insert>
</mapper>
实现 LoveMusicController
@RestController
@RequestMapping("lovemusic")
public class LoveMusicController {

    @Autowired
    LoveMusicMapper loveMusicMapper;

    @RequestMapping("/likeMusic")
    public ResponBodyMessage<Boolean> likeMusic(@RequestParam String id,
                                                HttpServletRequest request) {
        int musicId = Integer.parseInt(id);
        System.out.println("musicId: " + musicId);
        //没有session不创建
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            System.out.println("没有登录!");
            return new ResponBodyMessage<>(-1, "没有登录!", false);
        }
        User user = (User)session.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();
        //查询当前用户是否收藏过该音乐
        Music music = loveMusicMapper.findLoveMusicByUserIdAndMusicId(userId, musicId);
        if(music != null) {
            return new ResponBodyMessage<>(-1, "该音乐已经收藏过了!",false);
        }else {
            boolean effer = loveMusicMapper.insertLoveMusic(userId, musicId);
            if(effer) {
                return new ResponBodyMessage<>(0, "收藏成功!", true);
            }else {
                return new ResponBodyMessage<>(-1, "收藏失败!",false);
            }
        }
    }
}

3 验证结果

 

查询喜欢的音乐模块设计

1 请求响应设计

此处查询需要满足几个功能:

1. 支持模糊查询
2. 支持传入参数为空
请求:
{
    get,
    /lovemusic/findlovemusic,
    data:{musicName:musicName}
}
{
    "status": 0,
    "data": [
        {
            "id": 77,
            "title": "凉凉",
            "singer": "zjb",
            "url": "music/get?path=凉凉",
            "time": "2023-04-26",
            "userid": 1
        },
        {
            "id": 78,
            "title": "G_E_M_ 邓紫棋 - 错过不错",
            "singer": "zjb",
            "url": "music/get?path=G_E_M_ 邓紫棋 - 错过不错",
            "time": "2023-04-26",
            "userid": 1
        },
        {
            "id": 79,
            "title": "G_E_M_ 邓紫棋 - 倒数",
            "singer": "zjb",
            "url": "music/get?path=G_E_M_ 邓紫棋 - 倒数",
            "time": "2023-04-26",
            "userid": 1
        },
        {
            "id": 80,
            "title": "陈奕迅 - 让我留在你身边",
            "singer": "zjb",
            "url": "music/get?path=陈奕迅 - 让我留在你身边",
            "time": "2023-04-26",
            "userid": 1
        }
    ],
    "message": "查询到所有的歌曲信息"
}

2 代码实现

实现 LoveMusicMapper 新增方法:
/**
 * 如果没有传入具体的歌曲名,显示当前用户收藏的所有音乐
 * @param userId
 * @return
 */

List<Music> findLoveMusicByUserId(int userId);
/**
 * 根据某个用户的ID和歌曲名称查询,某个用户收藏的音乐
 * @param musicName
 * @param userId
 * @return
 */
List<Music> findLoveMusicBykeyAndUID(String musicName, int userId);
实现 LoveMusicMapper.xml
    <select id="findLoveMusicByUserId" resultType="com.example.musicserver.model.Music">
        select m.* from lovemusic lm,music m where m.id = lm.music_id and lm.user_id=#{userId}
    </select>

    <select id="findLoveMusicBykeyAndUID" resultType="com.example.musicserver.model.Music">
        select m.* from lovemusic lm, music m where m.id=lm.music_id and lm.user_id = #{userId}
        and title like concat('%', #{musicName}, '%');
    </select>
关于 resultMap的使用: https://blog.csdn.net/qq_42780864/article/details/81429114
实现 LoveMusicController, 新增方法
    @RequestMapping("/findlovemusic")
    public ResponBodyMessage<List<Music>> findLoveMusic(@RequestParam(required = false) String musicName,
                                                        HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            System.out.println("没有登录!");
            return new ResponBodyMessage<>(-1, "请登陆后查找!", null);
        }
        User user = (User)session.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();
        List<Music> musicList = null;
        if(musicName == null) {
            musicList = loveMusicMapper.findLoveMusicByUserId(userId);
        }else {
            musicList = loveMusicMapper.findLoveMusicBykeyAndUID(musicName, userId);
        }
        System.out.println(musicList);
        return new ResponBodyMessage<>(0, "查询到所有的歌曲信息", musicList);
    }

3 验证结果

移除喜欢的音乐模块设计

1 请求响应设计

请求:
{
    post,
    /lovemusic/deletelovemusic,
    data:{id:id}
}
响应:
{
    "status": 0,
    "message": "取消收藏成功!",
    "data": true
}

2 代码实现

LoveMusicMapper 接口
/**
 * 移除自己收藏的音乐,但是不是删除音乐本身,只是从数据库中删除了记录而已
 * @param userId
 * @param musicId
 * @return
 */
int deleteLoveMusic(int userId,int musicId);
LoveMusicMapper.xml
<delete id="deleteLoveMusic" parameterType="java.lang.Integer">
    delete from lovemusic where user_id=#{userId} and music_id=#{musicId}
</delete>
LoveMusicController
    @RequestMapping("/deletelovemusic")
    public ResponBodyMessage<Boolean> deleteLoveMusic(@RequestParam String id,
                                                      HttpServletRequest request) {
        int musicId = Integer.parseInt(id);
        HttpSession session = request.getSession(false);
        if(session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null) {
            System.out.println("没有登录!");
            return new ResponBodyMessage<>(-1, "请登陆后删除!", null);
        }
        User user = (User)session.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();

        int ret = loveMusicMapper.deleteLoveMusic(userId, musicId);
        if (ret == 1) {
            return new ResponBodyMessage<>(0, "取消收藏成功!", true);
        }else {
            return new ResponBodyMessage<>(-1, "取消收藏失败!",false);
        }
    }

3 验证结果

 

 删除音乐完善

上述的删除音乐,不会 lovemusic 表中的数据,此时我们需要同步删除
LoveMusicMapper 接口新增方法:
/**
 * 当删除库中的音乐的时候,同步删除lovemusic中的数据
 * @param musicId
 * @return
 */
int deleteLoveMusicByMusicId(int musicId);
重写 LoveMusicMapper.xml
<delete id="deleteLoveMusicByMusicId" parameterType="java.lang.Integer">
    delete from lovemusic where music_id=#{musicId}
</delete>
添加:
 @Autowired
 private LoveMusicMapper loveMusicMapper;

重写MusicController.java文件中的deleteMusicByIddeleteSelMusic两个方法

添加loveMusicMapper.deleteLoveMusicByMusicId();

        @RequestMapping("/delete")
        public ResponBodyMessage<Boolean> deleteMusicById(@RequestParam String id,
                                                      HttpServletRequest request) {
            //1.先检查这个音乐是不是存在?
            int iid = Integer.parseInt(id);
            //2.如果存在要进行删除
            Music music = musicMapper.findMusicById(iid);
            if(music == null) {
                return new ResponBodyMessage<>(-1, "没有你要找的音乐!", false);
            }else {
                //2.1删除数据库
                int ret = musicMapper.deleteMusicById(iid);
                if(ret == 1) {
    /* -------> */  loveMusicMapper.deleteLoveMusicByMusicId(iid);
                    //2.2删除服务器上的数据
                    int index = music.getUrl().lastIndexOf("=");
                    String filename = music.getUrl().substring(index+1);

                    File file = new File(SAVE_PATH+"/"+filename+".mp3");
                    System.out.println("当前的路径:" + filename);
                    if(file.delete()) {
                        return new ResponBodyMessage<>(0, "服务器删除成功!", true);
                    }else {
                        return new ResponBodyMessage<>(-1, "服务器删除失败!", false);
                    }
                }else {
                    return new ResponBodyMessage<>(-1, "数据库中的音乐没有删除成功!", false);
                }
            }
        }

    @RequestMapping("/deleteSel")
    public ResponBodyMessage<Boolean> deleteSelMusic(@RequestParam("id[]") List<Integer> id) {
        int sum = 0;
        for(int i = 0; i < id.size(); i++) {
            Music music = musicMapper.findMusicById(id.get(i));
            if(music == null) {
                System.out.println("没有这个id的音乐");
                return new ResponBodyMessage<>(-1, "没有你要删除的音乐!", false);
            }
            //2.1删除数据库
            int ret = musicMapper.deleteMusicById(id.get(i));
            if(ret == 1) {
                //2.2删除服务器上的数据
/* -------> */  loveMusicMapper.deleteLoveMusicByMusicId(id.get(i));
                int index = music.getUrl().lastIndexOf("=");
                String filename = music.getUrl().substring(index+1);

                File file = new File(SAVE_PATH+"/"+filename+".mp3");
                System.out.println("当前的路径:" + filename);
                if(file.delete()) {
                    sum+=ret;
//                        return new ResponBodyMessage<>(0, "服务器删除成功!", true);
                }else {
                    return new ResponBodyMessage<>(-1, "服务器删除失败!", false);
                }
            }else {
                return new ResponBodyMessage<>(-1,"数据库删除失败!",false);
            }

        }

前端页面实现

直接在百度上搜索 " 免费网页模板 ", 能找到很多免费模板网站 . 可以直接基于现成的漂亮的页面进行修改 .
tips: 做减法比做加法更容易 .
例如: http://www.cssmoban.com/preview/index.html?url=http://demo.mxyhn.xyz:8020/cssthemes6/p
odca-gh-pages/index.html&id=10615&tid=20125072756579

将网页模板解压缩, 拷贝到项目的 static目录中. 

此处提供本项目前端代码:

JavaEE: spring的项目学习 - Gitee.com

配置拦截器

在config包下创建LoginInterceptor类

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        HttpSession httpSession = request.getSession(false);
        if(httpSession!=null &&
            httpSession.getAttribute(Constant.USERINFO_SESSION_KEY)!=null) {
            //此时是登录状态
            return true;
        }
        return false;
    }
}

AppConfig类中实现WebMvcConfigurer接口,再添加重写的addInterceptors方法

    /**
     * 添加拦截器,将自定义拦截器加入到系统配置
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //1、配置拦截规则
        LoginInterceptor loginInterceptor = new LoginInterceptor();
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**")
                //排除所有的JS
                .excludePathPatterns("/js/**.js")
                //排除images下所有的元素
                .excludePathPatterns("/images/**")
                .excludePathPatterns("/css/**.css")
                .excludePathPatterns("/fronts/**")
                .excludePathPatterns("/player/**")
                .excludePathPatterns("/login.html")
                //排除登录接口
                .excludePathPatterns("/user/login");
    }
此时如果直接访问 list.html 等页面,不会进行页面展示和跳转
  • addPathPatterns:表示需要拦截的 URL“**”表示拦截任意方法(也就是所有方法)。
  • excludePathPatterns:表示需要排除的 URL

项目部署

1. 将数据库在服务器上重新进行建表等操作

2. 修改项目中的路径,数据库密码等,匹配服务器即可

注意上传文件目录

#本地目录
music.local.path=C:/work/local/music1
#云服务器下
#music.local.path=/root/music

 数据库用户密码

#云服务器上的用户名和密码
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

3. 使用java -jar xxxx.jar 启动项目【前台运行的方式】

4. http://你的公网地址:8080/login.html

5. 后台运行springboot项目:nohup java -jar xxx.jar >> log.log &

nohup :后台运行项目的指令
使用 >> log.log 将运行的日志记录到 log.log
& 表示 一直运行

 6.进行功能的检查

 7.如果项目端口被占用:

  1. 查看端口状态:netstat -anp | grep "端口号"
  2. 然后kill -9 18506杀掉进程
  3. 重新部署

8.如果想部署多个项目:

打开你的云服务器:

 

 

 在项目的application.properties下添加

server.port=9090

重新部署

就此结束,

文章过长,

如果发现其中错误,

希望即使指正,

谢谢

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值