IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java项目--网页版音乐播放器(Spring Boot 后端逻辑) -> 正文阅读

[Java知识库]Java项目--网页版音乐播放器(Spring Boot 后端逻辑)

目录

一. 数据库设计

user 表

music 表

lovemusic表

二. 配置数据库

三. 登录模块设计

创建User类

创建Mapper

四. 实现登录逻辑

用户请求报文

响应报文

?编辑

密码加密

五. 上传音乐模块

服务器上传

数据库上传

六. 播放音乐模块

七. 删除音乐模块

删除单个音乐:

批量删除音乐

八. 查找音乐模块

九. 收藏音乐模块

十. 收藏音乐查询模块

十一. 取消收藏音乐模块

删除的一些细节


本篇博客带大家做一个网页版的音乐播放器项目

??

技术栈主要是SSM框架, 基本的创建项目就不写了, 直接上正题.

一. 数据库设计

我们的音乐播放器, 需要有登录注册功能, 所以 user 表不可少

其次, 还需要一个音乐库, 作为我们的首页内容

最后, 每个用户都有自己喜欢的音乐, 这是最后一个表

用户和音乐是多对多的关系, 用户可以喜欢多个音乐, 音乐可以被很多人喜欢?


user 表

User 表基础就是一个自增主键id, 一个用户名和一个密码

这里密码我们需要加密, 所以给较长的空间.

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("zhangsan","$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
);

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

? ??

二. 配置数据库

在 application.properites 文件下来配置文件

填入以下内容:

#配置数据库
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/musicserver?characterEncoding=utf8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=989811
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.musicsplayer.mapper=debug

#扫描的包:druid.sql.Statement类和frank包
logging.level.druid.sql.Statement=DEBUG
logging.level.com.example=DEBUG

? ??

三. 登录模块设计

创建User类

创建一个model包, 存放User

@Data 是 lombok 的一个注解, 加上这个我们就不用写Getter和Setter方法了

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

创建Mapper

创建一个mapper包, 用来存放数据库操作接口

初步内容:?

@Mapper
public interface UserMapper {
    User login(User user);
}

再在resources下创建mybatis包, 放置mybatis的配置文件和sql语句

配置内容:

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

? ? ? ??

四. 实现登录逻辑

用户请求报文

建controller包:

写一个登陆的逻辑:

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    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("登陆失败!");
        }
    }
}

启动项目, 调试一下看能否登录成功

登录成功就说明没有问题了.


响应报文

先创建一个tools包,放我们的JSON响应报文类

在ResponseBodyMessage类中, 就是我们的响应报文

@Data
public class ResponseBodyMessage <T> {

    private int status;//状态码(1成功, 0失败)

    private String message;//返回的信息

    private T data;//返回给前端的数据

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

接着我们的登录的逻辑就可以改了,返回值改为返回报文

并在登录成功时创建一个会话

为了以后方便调取这个会话,写一个静态属性包装一下会话名字:

public class Constant {
    public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}

最后修改好的UserController:?

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/login")
    public ResponseBodyMessage<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(Constant.USERINFO_SESSION_KEY, user);
            return new ResponseBodyMessage<>(1, "登陆成功!", userLogin);
        }else {
            System.out.println("登陆失败!");
            return new ResponseBodyMessage<>(0, "登陆失败!", userLogin);
        }
    }
}

我们还可以用 postman 抓个包:


密码加密

密码这块,我们肯定不能明文传输,否则抓个包就获取到了

我们这里采用 BCrypt 加密,它能够提供随机的盐值,不可逆,更加安全

先在 pom.xml?里添加 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>

接着在启动类的注解上加一行:

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

	public static void main(String[] args) {
		SpringApplication.run(MusicplayerApplication.class, args);
	}

}

因为我们用BCrypt加密时,导入了一个spring-security框架

这个框架有自己的配置,我们需要忽略这些配置,所以启动时加上这一行

接下来开始进行BCrypt加密:

我们先添加一个查找用户名是否存在的方法, 因为密码被加密了

如果用户名存在我们就进行解密操作, 再匹配密码正确与否

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

    User selectByName(String username);
}
<select id="selectByName" resultType="com.example.musicplayer.model.User">
    select * from user where username=#{username}
</select>

接下来开始对 UserController 类修改

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private UserMapper userMapper;

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

        User user = userMapper.selectByName(username);

        if (user != null){
            boolean flag = bCryptPasswordEncoder.matches(password, user.getPassword());
            if (!flag){
                return new ResponseBodyMessage<>(0, "用户名或密码错误!", user);
            }
            System.out.println("登陆成功!");
            request.getSession().setAttribute(Constant.USERINFO_SESSION_KEY, user);
            return new ResponseBodyMessage<>(1, "登陆成功!", user);
        }else {
            System.out.println("登陆失败!");
            return new ResponseBodyMessage<>(0, "登陆失败!", user);
        }
    }
}

当然,为了方便起见,我们为加密类 BCryptPasswordEncoder 写一个注入:

创建 config 包,里面存放着很多 Bean

创建一个存放 Bean 的类,将加密类存放到 Spring 中

@Configuration
public class AppConfig {
    @Bean
    public BCryptPasswordEncoder getBCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

这样以后需要使用该类时,直接注入即可。

接着使用Postman抓包测试:

??

五. 上传音乐模块

服务器上传

首先, 上传音乐肯定用 post

结束了用户模块, 我们需要一个音乐模块

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

接着写music的操作方法:

第一步: 用会话session检查是否登录

第二步: 指定文件保存路径, 并保存

@RestController
@RequestMapping("/music")
public class MusicController {

    @Value("${music.local.path}")
    private String SAVE_PATH;

    @RequestMapping("/upload")
    public ResponseBodyMessage<Boolean> insertMusic(String singer, @RequestParam("filename") MultipartFile file, HttpServletRequest request){
        //检查是否登录
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null){
            return new ResponseBodyMessage<>(0, "请登录后上传", false);
        }

        String fileName = file.getOriginalFilename();// 获取文件名
        System.out.println("文件名: " + fileName);
        
        String path = SAVE_PATH + "/" + fileName;// 指定文件保存路径
        File destFile = new File(path);
        System.out.println("文件路径: " + destFile.getPath());
        
        if (!destFile.exists()){
            destFile.mkdirs();// 别少加了 s
        }
        
        try {
            file.transferTo(destFile);// 保存文件到指定路径
            return new ResponseBodyMessage<>(1, "上传成功", true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        
        return new ResponseBodyMessage<>(0, "上传失败", false);
    }
}

对于上面代码中的 SAVE_PATH, 我们可以写到配置文件中, 方便调用

#音乐上传后的路径
music.local.path=D:/player/musics/

数据库上传

涉及到数据库, 都用mybatis, 我们创建一个 MusicMapper 类

先写一个插入方法

@Mapper
public interface MusicMapper {
    int insert(String title, String singer, String time, String url, String userid);
}
    <insert id="insert">
        insert into music(title, singer, time, url, userid)
        values(#{title}, #{singer}, #{time}, #{url}, #{userid})
    </insert>

获取除后缀名的文件名:

int index = fileName.lastIndexOf(".");
String title = fileName.substring(0, index);

获取userid:

User user = (User)session.getAttribute(Constant.USERINFO_SESSION_KEY);
int userid = user.getId();

获取音乐url:

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

获取时间:

SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String time = simpleDateFormat.format(new Date());

上传到数据库:

这里注意, 如果中间有问题没上传成功, 服务器需要把这个问题文件删除,?

        try {
            int ret = 0;
            ret = musicMapper.insert(title, singer, time, url, userid);
            if (ret == 1){
                //可以跳转到音乐列表界面
                return new ResponseBodyMessage<>(1, "服务器上传成功", true);
            }else {
                return new ResponseBodyMessage<>(0, "服务器上传失败", false);
            }
        }catch (BindingException e){
            destFile.delete();
            e.printStackTrace();
            return new ResponseBodyMessage<>(0, "服务器上传失败", false);
        }

当然, 我们还能添加一个功能, 上传歌曲时, 我们可以判断是否重复上传了

在 MusicMapper 里添加一个select方法就行

Music music = musicMapper.selectSame(title, singer);
if (music != null){
    return new ResponseBodyMessage<>(0, "请勿重复上传", false);
}

? ?

六. 播放音乐模块

播放音乐的格式采用GET, 直接在URL中体现: /get?path=xxx.mp3?

    @RequestMapping("/get")
    public ResponseEntity<byte[]> play(String path){
        File file = new File(SAVE_PATH + "/" + path);
        byte[] plays = null;
        try {
            // 找到路径下的文件进行读取
            plays = Files.readAllBytes(file.toPath());
            // 若未获取文件, 返回一个错误请求
            if (plays == null){
                return ResponseEntity.badRequest().build();
            }
            return ResponseEntity.ok(plays);// 正常播放
        } catch (IOException e) {
            e.printStackTrace();
        }
        return ResponseEntity.badRequest().build();
    }

ResponseEntity 对象是Spring对请求响应的封装. 它继承了HttpEntity对象,包含了Http的响应码
(httpstatus)、响应头(header)、响应体(body)三个部分。

如果是歌曲, 在抓包时可以看到, 字节码文件内有 TAG?

??

七. 删除音乐模块

删除单个音乐:

首先, 请求响应设计:

根据传输的 id 来删除音乐

服务器先查找是否有此音乐, 再操作数据库删除, 最后返回响应报文

接着设计Mapper, 首先要根据id查找是否有此书:

接着删除:

再写后端代码:

    @RequestMapping("/delete")
    public ResponseBodyMessage<Boolean> deleteMusicById(@RequestParam String id){
        int deleteId = Integer.parseInt(id);//前端拿到的都是String类型的
        Music deleteMusic = musicMapper.findById(deleteId);
        if (deleteMusic == null){
            //System.out.println("无此id的音乐");
            return new ResponseBodyMessage<>(0, "删除失败, 没有您要删除的音乐", false);
        }

        int flag = musicMapper.deleteMusicById(deleteId);
        if (flag == 0){
            return new ResponseBodyMessage<>(0, "删除失败, 未知原因", false);
        }

        //别忘了删除本地文件
        String title = deleteMusic.getTitle();
        File file = new File(SAVE_PATH + "/" + title + ".mp3");
        //System.out.println("当前路径:" + file.getPath());
        if (file.delete()){
            return new ResponseBodyMessage<>(1, "删除成功", true);
        }
        return new ResponseBodyMessage<>(0, "本地音乐删除失败", false);
    }

最后用postman验证即可.


批量删除音乐

和删除单个音乐类似, 是不过传一个 id 数组进来删除

    @RequestMapping("/deletes")
    public ResponseBodyMessage<Boolean> deleteMusics(@RequestParam("id[]") List<Integer> id){
        System.out.println("id[]" + id);
        int sum = 0;
        for (int i = 0; i < id.size(); i++) {
            Music deleteMusic = musicMapper.findById(id.get(i));
            if (deleteMusic == null){
                //System.out.println("无此id的音乐");
                return new ResponseBodyMessage<>(0, "删除失败, 没有您要删除的音乐", false);
            }
            int ret = musicMapper.deleteMusicById(id.get(i));
            if (ret == 0){
                return new ResponseBodyMessage<>(0, "数据库音乐删除失败", false);
            }
            String title = deleteMusic.getTitle();
            File file = new File(SAVE_PATH + "/" + title + ".mp3");
            if (file.delete()){
                //这里不能return, 因为要删很多
                sum += ret;
            }else {
                return new ResponseBodyMessage<>(0, "本地音乐删除失败", false);
            }
        }
        if (sum == id.size()){
            return new ResponseBodyMessage<>(1, "所选全部音乐删除成功", true);
        }
        return new ResponseBodyMessage<>(0, "本地音乐删除失败", false);
    }

postman测试:?

? ?

八. 查找音乐模块

查找的方式就是模糊查询

在参数为空时, 默认返回所有音乐

开始写Mapper:

一个是参数为空, 查找全部音乐:

另一个是用户传入以音乐名, 根据音乐名查找歌曲:

MusicController 逻辑:

    @RequestMapping("/find")
    public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required = false) String musicName){
        List<Music> musicList = null;
        if (musicName != null){
            musicList = musicMapper.findMusicByName(musicName);
        }else {
            musicList = musicMapper.findMusic();
        }
        return new ResponseBodyMessage<>(0, "查询到了所有音乐", musicList);
    }

?postman验证:

??

九. 收藏音乐模块

协议设计:

收藏音乐逻辑:

查询此音乐是否被收藏过, 如果没有, 则在收藏表中插入此数据.

Mapper设计:

Controller设计:

    @RequestMapping("/insert")
    public ResponseBodyMessage<Boolean> insertLoveMusic(HttpServletRequest request,
                                                        @RequestParam String musicId) {
        // 检查是否登录
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null){
            return new ResponseBodyMessage<>(0, "请登录后上传", false);
        }
        /*
            注意, userId可以通过session获取, 不用前端传参
         */
        User user = (User) session.getAttribute(Constant.USERINFO_SESSION_KEY);
        int userId = user.getId();

        // 检查是否重复收藏
        Music music = loveMusicMapper.findLoveMusic(userId, Integer.parseInt(musicId));
        if (music != null){
            return new ResponseBodyMessage<>(0, "请勿重复收藏", false);
        }

        // 插入音乐
        if (loveMusicMapper.insertLoveMusic(userId, Integer.parseInt(musicId))){
            return new ResponseBodyMessage<>(1, "收藏成功", true);
        }
        return new ResponseBodyMessage<>(0, "收藏失败", false);
    }

postman验证:

??

十. 收藏音乐查询模块

此模块与第八块类似

同样需要模糊查询和传参为空返回全部音乐

Mapper设计:

由于lovemusic表内只有ID字段, 所以需要多表查询, 根据userId来查

    <select id="findLoveMusicById" resultType="com.example.musicplayer.model.Music">
        select m.* from lovemusic lm, music m where lm.music_id=m.id and user_id=#{userId}
    </select>

    <select id="findLoveMusicByNameAndId" resultType="com.example.musicplayer.model.Music">
        select m.* from lovemusic lm, music m where lm.music_id=m.id and user_id=#{userId}
        and m.title like concat('%', #{musicname}, '%')
    </select>

Controller设计:

    @RequestMapping("/findlovemusic")
    public ResponseBodyMessage<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){
            return new ResponseBodyMessage<>(0, "请登录后查找", null);
        }

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

        //检查是否传参
        List<Music> musicList = null;
        if (musicName == null){
            musicList = loveMusicMapper.findLoveMusicById(userId);
        }else {
            musicList = loveMusicMapper.findLoveMusicByNameAndId(musicName, userId);
        }
        return new ResponseBodyMessage<>(1, "查询到了所有的歌曲信息", musicList);
    }

最后postman验证:

? ?

十一. 取消收藏音乐模块

请求与响应模块:

Mapper设计:

Controller设计:

    @RequestMapping("/deletelovemusic")
    public ResponseBodyMessage<Boolean> deleteLoveMusic(
            HttpServletRequest request,
            @RequestParam String id){
        // 检查是否登录
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute(Constant.USERINFO_SESSION_KEY) == null){
            return new ResponseBodyMessage<>(0, "请登录后查找", null);
        }

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

        int musicId = Integer.parseInt(id);
        boolean reg = loveMusicMapper.deleteLoveMusic(userId, musicId);
        if (reg){
            return new ResponseBodyMessage<>(1, "取消收藏音乐成功", true);
        }
        return new ResponseBodyMessage<>(0, "取消收藏音乐失败", false);
    }

postman验证:


删除的一些细节

注意, 不仅有一个lovemusic表, 还有一个music总表

在删除总表的音乐后, lovemusic表内的数据也要删除

所以在此要更改一下:

首先, 添加一个根据音乐ID删除的逻辑:

更改MusicController:

if (file.delete()){
    // 同步删除收藏音乐表内的数据
    loveMusicMapper.deleteLoveMusicById(deleteId);
    return new ResponseBodyMessage<>(1, "删除成功", true);
}
if (file.delete()){
    loveMusicMapper.deleteLoveMusicById(id.get(i));
    sum += ret;
}else {
    return new ResponseBodyMessage<>(0, "本地音乐删除失败", false);
}

? ??

谢谢你能看到这, 一起加油ヽ( ̄ω ̄( ̄ω ̄〃)ゝ

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:56:31  更:2022-09-04 01:01:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:33:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码