基于 SpringBoot 的 OnlineMusicPlayer 项目
- 2022 年 8 月 11 日 江西
本文字数:38050 字
阅读完需:约 125 分钟
@TOC
项目演示
项目部署链接地址:
项目演示:
创建项目
因为我们的online-music-player
项目是基于SpringBoot
框架开发的,所以我们需要创建一个SpringBoot
项目!
选择SpringBoot
版本并初步导入依赖!
数据库的设计
根据我们的演示我们可以得知我们需要创建onlinemusic
数据库,其下有 3 张结果!
创建
onlinemusic
数据库创建
user
用户表创建
music
音乐列表创建
lovemusic
收藏音乐列表onlinemusic
数据库下的 3 张表创建成功后!
3 张表结构如下!
整个db.sql
文件
-- 创建onlinemusic数据库
drop database if exists onlinemusic;
create database if not exists onlinemusic character set utf8;
-- 使用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);
}
创建 UserMapper.xml
在resource
包下创建mybatis
包用于保存 mapper.xml 文件,再创建UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.onlinemusic.mapper.UserMapper">
<select id="login" resultType="com.example.onlinemusic.model.User">
select * from user where username=#{username} and password=#{password}
</select>
</mapper>
实现登入
设置登入的请求和响应
请求
响应
响应:
{
"status":0, //status 为0表示登入成功,为负数表示登入失败!
"message":"登入成功", // 放回登入信息!
"data":{ // 登入成功后获取到相应的用户信息!
"id":xxxx,
"username":xxxx,
"password":xxxx
}
}
创建 UserController 类
创建一个controller
包,在其包下创建一个 UserController 类
package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.UserMapper;
import com.example.onlinemusic.model.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* Created with IntelliJ IDEA.
* Description:
* User: hold on
* Date: 2022-07-26
* Time: 15:12
*/
@RestController // @ResponseBody + @Controller
@RequestMapping("/user") //设置路由 用来映射请求!
public class UserController {
//将UserMapper注入!
@Resource
private UserMapper userMapper;
@RequestMapping("/login")
public void login(@RequestParam String username,@RequestParam String password){
User userLogin = new User();
userLogin.setUsername(username);
userLogin.setPassword(password);
User user = userMapper.login(userLogin);
//先初步测试一下,后面再完善
if(user!=null){//登入成功!
System.out.println("登入成功!");
}else{
System.out.println("登入失败!");
}
}
}
这里只是粗略的写一下登入逻辑,然后验证是否可行,我们再进行后续代码的完善!
pastman 验证登入功能
我们对照数据库中的user
表中的数据!所以该登入请求成功!
封装响应
刚刚我们的登入逻辑是没有了问题,但是我们的服务器并没有给客户端返回响应,所以我们需要根据约定的响应,登入请求后分装并返回!
创建一个tools
包统一保存一些通用的代码,创建响应类ResponseBodyMessage
这里响应信息我们可以通过泛型,就可以变成通用的响应!
package com.example.onlinemusic.tools;
import lombok.Data;
/**
* Created with IntelliJ IDEA.
* Description:统一(泛型)的响应体
* User: hold on
* Date: 2022-07-26
* Time: 21:32
*/
@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;
}
}
验证
Session 创建
我们再对刚刚的登入功能创建Session
我们通过HttpServlet
下的 getSession 方法获取到 Session,然后再通过SetAttribute
方法设置会话,保存在服务器中!
//优化后的UserController类!
package com.example.onlinemusic.controller;
import com.example.onlinemusic.mapper.UserMapper;
import com.example.onlinemusic.model.Contant;
import com.example.onlinemusic.model.User;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* Created with IntelliJ IDEA.
* Description:
* User: hold on
* Date: 2022-07-26
* Time: 15:12
*/
@RestController // @ResponseBody + @Controller
@RequestMapping("/user") //设置路由 用来映射请求!
public class UserController {
//将UserMapper注入!
@Resource
private UserMapper userMapper;
@RequestMapping("/login")
//@RequestParam SpringMVC下的注解,表示该参数必须传入给服务器!
//value = "前端参数名",required = true/false,defaultValue ="默认值"
//这里required设置为ture表示该参数必传,默认为true,如果设置了defaultValue那默认required为false
public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){
User userLogin = new User();
userLogin.setUsername(username);
userLogin.setPassword(password);
//调用mapper下的 login查询user!
User user = userMapper.login(userLogin);
//返回响应
if(user!=null){//登入成功!
//登入成功就在服务器保存该Session会话!
//这里我们的key值可以通过常量值设置,避免后面出错!
//request.getSession().setAttribute("USERINFO_SESSION_KEY",user);
// request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user);
return new ResponseBodyMessage<User> (0,"登入成功",userLogin);
}else{
return new ResponseBodyMessage<User> (-1,"登入失败",userLogin);
}
}
}
我们通过 Fiddler 抓包获取响应,
设置了Session
会话响应
未设置Session
会话响应!
我们可以看到设置了的返回的响应头中有 Session 信息,否则没有!
Bcrypet 加密原理
我们知道如果我们登入时传输的密码通过明文传输的话,就不安全,会被其他人盗取,所以我们要对密码进行加密!
目前主流的加密方式有 2 种
MD5 加密
Bcrypet 加密
我们先对这两种加密方式进行了解,便于后续我们对登入功能进行加密操作!
MD5 加密
MD5 加密是一个安全的散列算法(哈希算法),就是通过对某一密码进行哈希操作,然后得到哈希后的加密字符串密码,一般这里加密后密码的长度比原来密码长度长,我们这里的加密操作是不可逆的!所以当我们对一个密码进行 MD5 加密后得到的字符串,我们无法通过逆操作解密,所以相对安全.
MD5 的不足之处,在于我们每次对同一个密码加密后得到的结果都是固定值,这就是使得,我们可以通过彩虹表(密码本,里面记录了密码加密算法后得到结果的映射关系),我们通过彩虹表查询进行暴力破解,就可以拿到密码!
我们也可以对 MD5 加密进行优化,就是对密码进行加盐操作,再进行 MD5 散列算法,这里的加盐指的是对密码添加一些单词,也就是字符,通过加盐操作,使得密码长度更长也就更安全,MD5 加密后得到的结果也就更难破解!
如果我们要使用 MD5 加密,我们要在项目中导入 MD5 依赖
<!-- md5 依赖 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
模拟 MD5 加密操作:
package com.example.onlinemusic.tools;
import org.apache.commons.codec.digest.DigestUtils;
/**
* Created with IntelliJ IDEA.
* Description:对密码加盐后再md5
* User: hold on
* Date: 2022-07-26
* Time: 23:28
*/
public class MD5Util {
//定义一个固定的盐值
private static final String salt = "1b2i3t4e";
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
/**
* 第一次加密 :模拟前端自己加密,然后传到后端
* @param inputPass
* @return
*/
public static String inputPassToFormPass(String inputPass) {
String str = ""+salt.charAt(1)+salt.charAt(3) + inputPass
+salt.charAt(5) + salt.charAt(6);
return md5(str);
}
/**
* 第2次MD5加密
* @param formPass 前端加密过的密码,传给后端进行第2次加密
* @param salt 用户数据库当中的盐值
* @return
*/
public static String formPassToDBPass(String formPass, String salt) {
String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5)
+ salt.charAt(4);
return md5(str);
}
/**
* 上面两个函数合到一起进行调用
* @param
* @param saltDB
* @return
*/
public static String inputPassToDbPass(String inputPass, String saltDB) {
String formPass = inputPassToFormPass(inputPass);
String dbPass = formPassToDBPass(formPass, saltDB);
return dbPass;
}
public static void main(String[] args) {
System.out.println("对用户输入密码进行第1次加密:"+inputPassToFormPass("123456"));
System.out.println("对用户输入密码进行第2次加密:"+formPassToDBPass(inputPassToFormPass("123456"),
"1b2i3t4e"));
System.out.println("对用户输入密码进行第2次加密:"+inputPassToDbPass("123456", "1b2i3t4e"));
}
}
虽然这里的加盐操作使得加密后的密码长度更长了,但是还是解决不了 md5 对一个密码加密得到的结果相同,除非我们这里采用随机盐!
BCrypet 加密
这里的BCrypet
加密方式也是一种安全的不可逆的散列算法加密操作,BCrypet
加密和MD5
不同之处在于每次对同一个密码加密得到的结果都不相同,也就是在其内部实现了随机加盐处理.这就很好解决了MD5
加密的缺点.所以BCrypet
加密方式更加安全!并且BCrypet
加密可以使加密得到的密文长度最大为 60 位,而 MD5 是 32 位,所以相对于 MD5 加密,BCrypet
破解难度更大!
使用BCrypet
加密:
引入依赖:
<!-- security依赖包 (加密)-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
//BCrypet加密使用演示
package com.example.onlinemusic.tools;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
/**
* Created with IntelliJ IDEA.
* Description:
* User: hold on
* Date: 2022-07-26
* Time: 23:42
*/
public class BCrypetTest {
public static void main(String[] args) {
//模拟从前端获得的密码
String password = "123456";
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String newPassword = bCryptPasswordEncoder.encode(password);
System.out.println("加密的密码为: "+newPassword);
//使用matches方法进行密码的校验
boolean same_password_result = bCryptPasswordEncoder.matches(password,newPassword);
//返回true
System.out.println("加密的密码和正确密码对比结果: "+same_password_result);
boolean other_password_result = bCryptPasswordEncoder.matches("987654",newPassword);
//返回false
System.out.println("加密的密码和错误的密码对比结果: " + other_password_result);
}
}
这里
BCrypet
加密工具主要通过BCrypetPassWordEncoder
对象下的encode
加密方法对密码进行加密和matches
匹配算法通过密码和加密后的密文进行匹配!
可以看到这里每次加密的结果都不一样,但是都能和正确密码匹配成功!
MD5 和 BCrypet 的异同
MD5:一种不加盐的单向 hash,不可逆的加密算法,对同一个密码每次 hash 加密得到的 hash 值结果都是一样的!所以大多数情况下可以破解!
BCrypet:一种加盐的单向 Hash,不可逆的加密算法,每次对同一个密码进行加密的结果不同,破解难度更高!
这 2 个都是目前主流的加密算法,BCrypet 更加安全,但是效率低! BCrypet 的加盐操作是加入的随机盐,所以每次的加密结果都不一样!
指的注意的是并没有什么密码是绝对安全的,无论那种加密方式都可以被破解的,只是破解的成本和时间问题!如果你的数据并没有价值,那么破解你的密码就毫无意义,也就很安全!
加密登入实现
因为我们 matches 通过前端传输过来的密码对比数据库中密码即可判断密码是否正确!我们知道每次 encode 后的密码都不一样,所以我们不能通过查询数据库中 username+password 验证!我们先通过 username 查询到数据库中的密码,然后通过 mathes 匹配判断是否登入成功!
UserMapper 添加查询用户方法!
//加密后的登入方法!
public ResponseBodyMessage<User> login(@RequestParam String username, @RequestParam String password, HttpServletRequest request){
User user = userMapper.selectUserByUserName(username);
//返回响应
if(user!=null){//查询到username用户!
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
//匹配验证密码是否正确!
boolean flg = bCryptPasswordEncoder.matches(password,user.getPassword());
if(flg){//登入成功!
request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user);
return new ResponseBodyMessage<User> (0,"登入成功",user);
}else{//密码错误!
return new ResponseBodyMessage<User> (0,"用户名或密码错误",user);
}
}else{//用户不存在!
return new ResponseBodyMessage<User> (-1,"用户名或密码错误",user);
}
}
pastman 验证代码:
这里我们发现失败了,因为我们引入BCrypet
加密依赖时,导入的security
框架,我们只是用了其下的一个类用于加密,并没有用到该框架的功能!而导入security
框架后,该项目中的接口都需要身份验证和授权!所以这个我们就登入失败了!
我们在启动类上加上一行注解即可解决该问题!
@SpringBootApplication(exclude =
{org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration.class})
创建 config 类,添加 AppConfig 类
刚刚BCrypetPasswordEncoder
对象创建使用方式并不符合SpringIoC
思想,所以我们通过Bean
注解先将该对象注册到Spring
中,然后通过Spring
获取对象!
@Configuration
public class AppConfig {
//将BCrypetPasswordEncoder对象交给spring管理!
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
这样我们的登入功能就完善好了!
实现注册功能
约定注册请求和响应
请求:
{
post
url:user/register
data:{username,password}
}
响应:
{
status:"状态码"
message:"响应信息"
data:{username,password}
}
UserMapper 类中添加一个注册接口!
//注册功能!
int register(String username,String password);
Mapper.xml 下实现该接口
<!-- 注册添加用户-->
<insert id="register">
insert into user (username,password) values(#{username},#{password});
</insert>
Controller 的 User 类下实现注册功能
//注册功能
@RequestMapping("/register")
public ResponseBodyMessage<User>register(@RequestParam String username,@RequestParam String password,HttpServletRequest request){
//1.首先查询该用户是否存在!
User user = userMapper.selectUserByUserName(username);
if(user!=null){//查询到该用户,说明用户存在!
return new ResponseBodyMessage<User>(-1,"该用户已注册,请修改用户名重新注册",null);
}
//2.用户不存在,就注册该用户!
//对密码进行加密后保存在数据库中!
password = appConfig.getBCryptPasswordEncoder().encode(password);
//将用户注册到数据库中!
userMapper.register(username,password);
//将注册好的用户信息放回给客户端!
user = userMapper.selectUserByUserName(username);
request.getSession().setAttribute(Contant.USERINFO_SESSION_KEY,user);
return new ResponseBodyMessage<User>(0,"注册成功!",user);
}
postman 验证
用户存在
用户不存在注册成功!
通过注册的用户验证一下登入功能
上传音乐模块
上传音乐模块设计
上传音乐请求和响应
请求:
{
post,
url:music/upload
data:{singer,MultipartFile file} //上传音乐的歌手名和音乐文件
}
响应:
{
status:0,//0表示成功,-1失败!
message:"响应信息",
data:true //true表示成功
}
Music 类
package com.example.onlinemusic.model;
import lombok.Data;
/**
* Created with IntelliJ IDEA.
* Description:Music实体类
* User: hold on
* Date: 2022-07-27
* Time: 15:37
*/
@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 验证
未登录上传文件
登入后上传
可以看到我们设置的服务器目录下就含有了该上传的音乐文件!
如何保证上传的文件是音乐文件
可以通过后缀名.MP3
嘛?
虽然这是一种最简单的解决方案,但是显然不可以的,如果有人将不是
.MP3
文件改成后缀为.MP3
的音乐文件,不过这种情况比较罕见!
那么我们如何检测用户上传的是音乐文件呢?
其实每一种文件都有自己特点的文件结构!
我们拿
.MP3
文件举例:文件结构如下:
一个 MP3 文件结构分成 3 部分!
而确定是否是 MP3 文件可以通过
MPEG
音频标签例如我们通过
ID3V1
128 字节中的前 3 个字节中的标签标志包含了字符TAG
就可以判断该文件是MP3
文件了!
增加音乐文件校验功能
我们先创建一个文件校验的接口,测试一下:
//音乐文件验证
@RequestMapping("/ismp3")
public ResponseBodyMessage<String> ismp3(String path){
String str = null;
File file = null;
byte [] fileByte = null;
try {
file= new File(SAVE_PATH+File.separator+path);
//获取到这个文件的所有字节信息
fileByte = Files.readAllBytes(file.toPath());
} catch (IOException e) {
e.printStackTrace();
}
//将字节数组转成字符串
str = new String(fileByte);
//获取到最后128个字节的字符串信息!
str = str.substring(str.length()-128);
if(str.contains("TAG")){//最后128字节,含有音乐文件标志
return new ResponseBodyMessage<String>(0,"mp3文件",str);
}
//没有音乐文件标识
return new ResponseBodyMessage<String>(-1,"非mp3文件",str);
}
验证:
音乐文件:
非音乐文件:
测试该方法无误,我们就将其封装到上传音乐的模块中!
//1.先验证是否为音乐文件!
boolean flg = false;
try {
//获取到后128位含有标志字节的字符串
String str = new String(file.getBytes());
String flgTAG = str.substring(str.length()-128);
if(flgTAG.contains("TAG")){
//含有标志位,为音乐文件!
flg = true;
}
} catch (IOException e) {
e.printStackTrace();
}
if(!flg){//不是音乐文件
return new ResponseBodyMessage<>(-1,"文件有误,并非mp3音乐文件",false);
}
验证:
音乐文件上传成功:
非音乐文件:
上传到数据库
我们上传音乐信息到数据库,就是向数据库中的music
表中插入数据!
我们首先要明确我们需要到数据库那些信息!
我们看一下我们music
表结构!
id:自增主键不需要上传!title:歌曲名我们可以通过文件名去掉
.MP3
后缀获取! singer:歌手 我们请求信息中有!time:我们可以通过java
中的SimpleDateFormat
类获取到上传时间 url:音乐的url
,因为我们上传的音乐就是用来后面播放的嘛,而我们数据的传输是通过http
协议的,我们后面通过这个
url
就可以找到该音乐的位置!//先用这样的方式保存url /music/get?title(歌曲名称)
user_id:我们可以通过
session
中获取上传用户id
MusicMapper
接口
package com.example.onlinemusic.mapper;
import org.apache.ibatis.annotations.Mapper;
/**
* Created with IntelliJ IDEA.
* Description:
* User: hold on
* Date: 2022-07-28
* Time: 23:27
*/
@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);
}
}
验证:
查看数据库music
表
一个用户重复上传一首歌曲解决
显然我们的上传功能还有待优化,我们需要解决一个用户多次上传一首歌曲的行为!
在验证用户登入的行为后,再进行该用户上传的音乐文件是否已经上传过的验证
我们通过查询数据库信息,从而验证
MusicMapper 接口
/**
* 通过用户id和音乐信息验证是否重复上传
* @param title
* @param singer
* @param user_id
* @return
*/
Music getMusicByUidAndMusicInfo(String title, String singer, int user_id);
}
xml 实现
<select id="getMusicByUidAndMusicInfo" resultType="com.example.onlinemusic.model.Music">
select * from music where
title=#{title} and singer=#{singer} and user_id=#{user_id}
</select>
Controller 重复上传验证
//上传歌曲前验证歌曲是否已经存在!
String title = musicname.substring(musicname.lastIndexOf(".mp3"));
//获取到当前用户id
User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);
int user_id = user.getId();
Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id);
if(music!=null){ //说明该歌曲重复上传
System.out.println("重复上传");
return new ResponseBodyMessage<>(-1,"上传失败,歌曲已存在",false);
}
postman 验证
批量上传歌曲
我们可以将参数file
改成MulitspartFile
的数组,就可以一次性批量上传多首歌曲
但是我们这里的歌手信息就无法全部上传咯!
//实现支持多个文件上传就将MultipartFile改成数组即可!
@RequestMapping("/uploads")
public ResponseBodyMessage<Boolean> UploadMusics(@RequestParam(defaultValue = "未知歌手") String singer,
@RequestParam(value = "filename") MultipartFile[] files,
HttpServletRequest request){
//1.上传音乐前验证登入状态,如果未登入就不创建会话
//如果用户已登入,则允许上传音乐!
HttpSession session = request.getSession(false);
if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){
//未登入状态!
System.out.println("未登入");
return new ResponseBodyMessage<>(-1,"未登录,请登录后上传!",false);
}
//2.登入状态,可以将歌曲上传到服务器!
//保存上传失败的歌曲信息
StringBuilder uploadfailinfo = new StringBuilder();
for (MultipartFile file:files) {
//获取到音乐文件名 xxx.mp4
String musicname = file.getOriginalFilename();
System.out.println("musicfileAndtype:"+musicname);
//上传歌曲前验证歌曲是否已经存在!
String title = musicname.substring(0,musicname.lastIndexOf(".mp3"));
//获取到当前用户id
User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);
int user_id = user.getId();
Music music = musicMapper.getMusicByUidAndMusicInfo(title,singer,user_id);
if(music!=null){ //说明该歌曲重复上传
System.out.println(title+"已存在");
//保存歌曲信息,用于返回前端用户!
uploadfailinfo.append(title+",");
//进行下一首歌曲上传
continue;
}
//歌曲未上传
//上传文件在服务器下保存的路径!
String path = SAVE_PATH + musicname;
//创建文件对象
File dest = new File(path);
//该文件目录在磁盘中不存在,就创建该目录
if(!dest.exists()){
dest.mkdir();
System.out.println("mkdir"+dest);
}
//将音乐文件上传到该目录下!
try {
file.transferTo(dest);
System.out.println(dest);
} catch (IOException e) {
e.printStackTrace();
//上传失败
return new ResponseBodyMessage<>(-1,"服务器上传失败",false);
}
//服务器上传成功,我们就需要对数据库进行上传信息!
//1.数据准备
//1).title 通过文件名截取到歌名,验证上传重复上传时已获取title
//String title = musicname.substring(0,musicname.lastIndexOf(".mp3"));
//2).singer 直接获取用户上传的 singer
//3).time
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String time = simpleDateFormat.format(new Date());
//4).url 可以通过拼接! eg: music/get?path=隆里电丝
String url = URL_PRE+title;
//5).user_id 通过当前session获取,验证重复上传时已获取
// User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);
// int user_id = user.getId();
int ret = musicMapper.upload(title,singer,time,url,user_id);
if(ret!=1){//上传失败!
//我们数据库上传失败,那么就需要将服务器下的该音乐文件删除
System.out.println("服务器上传成功,数据库上传失败,删除服务器下文件");
dest.delete();
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
}
if(uploadfailinfo.length()==0) {
//说明全部歌曲上传成功!
return new ResponseBodyMessage<>(0, "上传成功", true);
}
//部分歌曲上传失败
return new ResponseBodyMessage<>(0,"歌曲:"+uploadfailinfo+"已存在,上传失败,"+"其他歌曲上传成功",true);
}
验证:
上传音乐模块总结
上传包括服务器和数据库上传
这里上传文件用到了
Spring
框架中处理文件上传的主要类MulitspartFile
类,这个类主要实现的是前端用表单的方式进行提交!上传服务器时要验证音乐是否重复上传,这里通过查询数据库中的信息进行验证
在上传音乐时验证是否为
MP3
文件,我们通过MP3
文件结构中的最后 128 个字节下有一个TAG
标签即可验证
播放音乐模块设计
请求响应设计
请求:
{
get
/music/get?path=xxx.mp3
}
响应:
{
data:音乐数据本身的字节信息
}
可以看到我们这里请求的设计采用的是
get
方法,通过/music/get?path=xxx.mp3
获取到响应的音乐信息,这也就和我们之前设计的保存音乐的url
匹配上了!而我们响应只要将对应的音乐字节信息返回给浏览器即可!
代码实现
//播放音乐
@RequestMapping("/get")
public ResponseEntity<byte[]> get(String path){
//我们要先获取到该音乐保存在服务器下的路径信息!
try {
byte[] fileByte = null;
//获取到文件对象
File file = new File(SAVE_PATH +File.separator+ path);
//获取到该文件对象的路径信息
Path filepath = file.toPath();
//读取文件中的所有字节
fileByte = Files.readAllBytes(filepath);
return ResponseEntity.ok(fileByte);
}catch (IOException e){
e.printStackTrace();
}
return ResponseEntity.badRequest().build();
}
方法讲解
Files.readAllBytes(Path path);
读取文件中的所有字节,参数是
Path
路径值!File.separator
与系统相关的默认名称 - 分隔符字符,以方便的方式表示为字符串!
就是在
Windows
系统下是\
,而在Linux
下是/
ReponseEntity
这是
Spring
对请求响应的分装,继承了HttpEntity
对象,包含Http
响应码(HttpStatus
),响应头(header
),响应体(body
)3 部分!ResponseEntity
类继承自HttpEntity
类,被用于 Controller 层方法 !我们可以通过这个类下面提供的静态方法,封装一下响应返回给浏览器!
ok
静态方法:我们代码里的
ResponseEntity.ok(fileByte);
就是将ok
状态和fileByte
音乐文件信息以body
的形式返回给前端!
验证结果
音乐文件有TAG
标志
![image-20220801131113688](https://bugmd.oss-cn-guangzhou.aliyuncs.com/image-20220801131113688.png)
假如我们拿到的并不是mp3
文件!
这里的隆里电丝.mp3
我是通过png
文件改成了这个,显然找不到这个音乐标志!
删除音乐模块
删除单个音乐
请求响应设计
请求:
{
post
/music/delete
id
}
响应:
{
status:0,
message:"删除成功"
data:true
}
代码实现
这里的删除操作需要分成 2 步
将服务器下的文件进行删除
将数据库中的文件信息删除
所以我们需要先查询到该 id 的音乐信息,再进行删除!
Mapper 接口
Mapper 实现
Controller 层代码实现
//删除单个音乐
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusic(@RequestParam Integer id){
//1.首先找到该音乐信息
Music music = musicMapper.getMusicById(id);
if(music==null){//音乐不存在
//未找到该音乐
return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);
}
//2.进行音乐删除
//2.1 删除服务器下的音乐文件
//找到服务器下该音乐文件路径
File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");
if(file==null){//服务器下不存在该文件
return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);
}
//删除
file.delete();
//2.2 删除数据库下的音乐信息
int ret = musicMapper.deleteById(id);
if(ret!=1){//数据库删除失败
return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);
}
return new ResponseBodyMessage<>(0,"删除成功!",true);
}
验证结果
先查看数据库下的音乐信息
删除的音乐不存在
删除音乐存在
删除成功!
批量删除音乐
请求响应设计
请求:
{
post
/music/deleteAll
id[]
}
响应:
{
status:0
message:"删除成功"
data:true
}
代码实现
我们只需要在删除单个音乐的基础上进行代码的修改即可!
直接增加Controller
层代码即可!
//批量删除音乐
@RequestMapping("/deleteAll")
public ResponseBodyMessage<Boolean> deleteMusicAll(@RequestParam(value = "id[]") List<Integer> ids){
String message = null;
for (Integer id:ids) {
//1.首先找到该音乐信息
Music music = musicMapper.getMusicById(id);
if(music==null){//音乐不存在
//未找到该音乐
//保存这个音乐id信息
message += id+" ";
continue;
//return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);
}
//2.进行音乐删除
//2.1 删除服务器下的音乐文件
//找到服务器下该音乐文件路径
File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");
System.out.println("musicPath:"+file.getPath());
if(file==null){//服务器下不存在该文件
//保存这个id信息
message += id + "";
continue;
//return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);
}
//删除
if(!file.delete()){
//删除失败
message += id + "";
continue;
//return new ResponseBodyMessage<>(-1,"服务器删除失败",false);
}
//2.2 删除数据库下的音乐信息
int ret = musicMapper.deleteById(id);
if(ret!=1){//数据库删除失败
message += id+" ";
continue;
//return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);
}
}
if(message==null){
return new ResponseBodyMessage<>(0,"删除成功!",true);
}
//部分删除失败
return new ResponseBodyMessage<>(0,"id:" + message+" 删除失败!",true);
}
验证结果
删除成功
删除失败
查询音乐模块
我们的查询需要支持一下功能
查询给定名称的歌曲
给定歌曲名称全
查询到单个歌曲
给定名称字段(支持模糊匹配查询)
查询到多个歌曲
未给定歌曲名
就查询所有歌曲
请求响应设计
请求:
{
get
/music/findMusic
musicName
}
响应:
{
status:0
message:"查询成功"
data:
{
{
id:2
title:"隆里电丝"
singer:"大傻"
time:"2022年8月1日"
url:/music/get?path="隆里电丝"
},
....
}
}
代码实现
MusicMapper
接口新增方法查询所有音乐
模糊匹配查询某些音乐
/**
*查询所有歌曲
* @return
*/
List<Music> findMusic();
/**
* 通过名称查询到歌曲信息,支持模糊查询
* @return
*/
List<Music> findMusicByName(String musicName);
MusicMapper.xml
实现
<!--查询所有歌曲-->
<select id="findMusic" resultType="com.example.onlinemusic.model.Music">
select * from music;
</select>
<!--模糊查询指定歌曲-->
<select id="findMusicByName" resultType="com.example.onlinemusic.model.Music">
select * from music where title like concat('%',#{musicName},'%')
</select>
MusicController
实现
//模糊查询
@RequestMapping("/findMusic")
public ResponseBodyMessage<List<Music>> findMusic(@RequestParam(required =false) String musicName){
List<Music> musicList = new LinkedList<>();
if(musicName==null){
//查询名称为空,查询所有音乐返回
musicList = musicMapper.findMusic();
return new ResponseBodyMessage<>(0,"查询成功!",musicList);
}
//进行模糊查询!
musicList = musicMapper.findMusicByName(musicName);
return new ResponseBodyMessage<>(0,"查询成功!",musicList);
}
验证结果
查询名称为空
模糊查询
收藏音乐模块
添加音乐到收藏列表
请求响应设计
请求:
{
post,
/lovemusic/likeMusic
data:user_id,music_id
}
响应:
{
status:0,
message:"收藏音乐成功",
data:true
}
代码实现
我们要将一首音乐收藏分为 2 步
找到该音乐信息,判断是否收藏过(查询
lovemusic
表)收藏该音乐(添加到
lovemusic
表)
新增
LoveMusicMapper
接口
package com.example.onlinemusic.mapper;
import com.example.onlinemusic.model.LoveMusic;
import org.apache.ibatis.annotations.Mapper;
/**
* Created with IntelliJ IDEA.
* Description:
* User: hold on
* Date: 2022-08-01
* Time: 19:54
*/
@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);
}
}
验证结果
歌曲已收藏,收藏失败
歌曲未收藏,收藏成功
查询喜欢的音乐列表
这里的查询喜欢列表和查询音乐模块类似!
未给定歌曲名称
查询该用户所有收藏歌曲
给定歌曲名称参数
查询歌曲名称含有该参数的歌曲
请求和响应设计
请求:
{
get
/lovemusic/findloveMusic
data:{musicName:musicName}
}
响应:
{
status:0,
message:"查询到收藏的音乐",
data:
{
{
id:1,
title:"隆里电丝",
singer:"大傻",
time:"2022年8月2日",
url:"/music/get?path=隆里电丝",
user_id:2
}
...
}
}
代码实现
LoveMusicMapper
接口新增方法
/**
* 查询该用户所有的收藏歌曲
* @param user_id
* @return
*/
List<Music> findLoveMusic(int user_id);
/**
* 通过用户id和歌曲名称查询收藏歌曲支持模糊匹配
* @param user_id
* @param musicName
* @return
*/
List<Music> findLoveMusicByUidAndMusicName(int user_id,String musicName);
xml
实现接口方法
<!--在该用户id下通过歌曲名称查询歌曲(支持模糊查询)-->
<select id="findLoveMusicByUidAndMusicName" resultType="com.example.onlinemusic.model.Music">
select m.* from music as m, lovemusic as lm where m.id = lm.music_id
and lm.user_id = #{user_id} and m.title like concat('%',#{musicName},'%')
</select>
<!--通过id查询收藏列表-->
<select id="findLoveMusicById" resultType="com.example.onlinemusic.model.LoveMusic">
select * from lovemusic where id = #{id}
</select>
LoveMusicController
代码实现
//通过音乐名称查询收藏列表
@RequestMapping("/findloveMusic")
public ResponseBodyMessage<List<Music>>
findLoveMusicByUidAndMusicName(@RequestParam(required = false) String musicName,
HttpServletRequest request){
//1.检查登入状态,未登入不创建回话
HttpSession session = request.getSession(false);
if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){
//未登入状态
return new ResponseBodyMessage<>(-1,"请登入用户",null);
}
//登入状态,进行音乐查询
//1.获取到用户id
User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);
int user_id = user.getId();
System.out.println("user_id:"+user_id+" musicName:"+musicName);
//2.查询收藏列表
List<Music> musicList = null;
if(musicName==null){
//2.1歌曲名称为空,查询该用户所有收藏歌曲!
musicList = loveMusicMapper.findLoveMusic(user_id);
return new ResponseBodyMessage<>(0,"查询到收藏列表",musicList);
}
//2.2 歌曲名称不为空,模糊查询
musicList = loveMusicMapper.findLoveMusicByUidAndMusicName(user_id,musicName);
return new ResponseBodyMessage<>(0,"查询收藏列表成功",musicList);
}
验证结果
数据库信息:
登入user_di=10
的用户
查询该用户所有收藏歌曲
模糊匹配查询指定歌曲
从喜欢列表移除音乐
请求和响应设计
请求:
{
get,
/lovemusic/deleteloveMusic,
data:{id}
}
响应:
{
status:0,
message:"取消收藏成功",
data:true
}
代码实现
LoveMusicMapper
接口新增方法
/**
* 通过用户id和音乐id取消音乐收藏
* @param user_id
* @param music_id
* @return
*/
int deleteLoveMusicByUidAndMusicId(int user_id, int music_id);
/**
* 通过音乐id删除lovemusic表中的信息
* @param music_id
* @return
*/
int deleteLoveMusicByMusicId(int music_id);
xml
实现接口
<!--通过用户id和音乐id移除收藏歌曲-->
<delete id="deleteLoveMusicByUidAndMusicId">
delete from lovemusic where user_id = #{user_id} and music_id = #{music_id}
</delete>
<!--通过musicId移除收藏列表-->
<delete id="deleteLoveMusicByMusicId">
delete from lovemusic where music_id = #{music_id}
</delete>
LoveMusicMapperController
代码实现
//通过收藏表中的id取消收藏歌曲信息
@RequestMapping("/deleteloveMusic")
public ResponseBodyMessage<Boolean> deleteLoveMusic(@RequestParam Integer id,HttpServletRequest request){
//1.检查登入状态,未登入不创建回话
HttpSession session = request.getSession(false);
if(session==null||null==request.getSession().getAttribute(Contant.USERINFO_SESSION_KEY)){
//未登入状态
return new ResponseBodyMessage<>(-1,"请登入用户",false);
}
//登入状态,可进行取消音乐收藏功能
//1.获取到用户id
User user = (User)session.getAttribute(Contant.USERINFO_SESSION_KEY);
int user_id = user.getId();
//取消收藏!
int ret = loveMusicMapper.deleteLoveMusicByUidAndMusicId(user_id,id);
if(ret!=1){
return new ResponseBodyMessage<>(-1,"取消收藏失败",false);
}
return new ResponseBodyMessage<>(0,"取消收藏成功",true);
}
验证结果
数据库信息:
歌曲不存在,移除失败
歌曲存在,取消收藏成功
完善删除音乐模块代码
我们对项目增添了收藏列表后,发现一个问题!我们上传的音乐删除后,收藏的音乐就不存在了,那么收藏列表中关于这首歌曲的信息也要删除,所以我们对我们的删除音乐代码进行完善!
删除音乐时,要先检查该音乐是否在收藏列表中,如果在就将歌曲移除收藏列表
我们只需要添加一个通过music_id
取消收藏音乐的方法即可
LoveMusicMapper
新增方法
/**
* 通过音乐id删除lovemusic表中的信息
* @param music_id
* @return
*/
int deleteLoveMusicByMusicId(int music_id);
xml
实现
<!--通过musicId移除收藏列表-->
<delete id="deleteLoveMusicByMusicId">
delete from lovemusic where music_id = #{music_id}
</delete>
//删除单个音乐
@RequestMapping("/delete")
public ResponseBodyMessage<Boolean> deleteMusic(@RequestParam Integer id){
//1.首先找到该音乐信息
Music music = musicMapper.getMusicById(id);
if(music==null){//音乐不存在
//未找到该音乐
return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);
}
//2.进行音乐删除
//2.1 删除服务器下的音乐文件
//找到服务器下该音乐文件路径
File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");
System.out.println("musicPath:"+file.getPath());
if(file==null){//服务器下不存在该文件
return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);
}
//删除
if(!file.delete()){
//删除失败
return new ResponseBodyMessage<>(-1,"服务器删除失败",false);
}
//2.2 删除数据库下的音乐信息
//2.2.1删除music表中的音乐信息
int ret = musicMapper.deleteById(id);
if(ret!=1){//数据库删除失败
return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);
}
//2.2.2删除lovemusic表中的音乐信息
loveMusicMapper.deleteLoveMusicByMusicId(id);
return new ResponseBodyMessage<>(0,"删除成功!",true);
}
//批量删除音乐
@RequestMapping("/deleteAll")
public ResponseBodyMessage<Boolean> deleteMusicAll(@RequestParam(value = "id[]") List<Integer> ids){
String message = "";
for (Integer id:ids) {
//1.首先找到该音乐信息
Music music = musicMapper.getMusicById(id);
if(music==null){//音乐不存在
//未找到该音乐
//保存这个音乐id信息
message += id+" ";
continue;
//return new ResponseBodyMessage<>(-1,"需要删除的音乐不存在",false);
}
//2.进行音乐删除
//2.1 删除服务器下的音乐文件
//找到服务器下该音乐文件路径
File file = new File(SAVE_PATH+File.separator+music.getTitle()+".mp3");
System.out.println("musicPath:"+file.getPath());
if(file==null){//服务器下不存在该文件
//保存这个id信息
message += id + "";
continue;
//return new ResponseBodyMessage<>(-1,"删除失败,服务器下未找到该文件信息",false);
}
//删除
if(!file.delete()){
//删除失败
message += id + "";
continue;
//return new ResponseBodyMessage<>(-1,"服务器删除失败",false);
}
//2.2 删除数据库下的音乐信息
int ret = musicMapper.deleteById(id);
if(ret!=1){//数据库删除失败
message += id+" ";
continue;
//return new ResponseBodyMessage<>(-1,"数据库信息删除失败",false);
}
//取消该歌曲的所有收藏
loveMusicMapper.deleteLoveMusicByMusicId(id);
}
if(message==""){
return new ResponseBodyMessage<>(0,"删除成功!",true);
}
//部分删除失败
return new ResponseBodyMessage<>(0,"id:" + message+" 删除失败!",true);
}
验证结果:
数据库信息:
我们将music_id
为 51 音乐删除!
前端设计
我们前端页面是在网上下载了一个静态页面的模板!我们需要在此基础上进行js
代码的编写,从而实现前后端用户的接口
登入逻辑
核心逻辑:
<script>
//核心业务逻辑
///$(function () {} 当DOM加载完毕后执行
$(function(){
//当点击这个按钮后就会执行function函数
$("#submit").click(function(){
//获取到用户名和密码
var username = $("#user").val();
var password = $("#password").val();
//判断用户名密码是否为空!
if(username.trim()==""||password.trim()==""){
alert("请输入用户名和密码");
return;
}
//这里就需要将请求前端数据返回给服务器!
$.ajax({
url:"/user/login",//指定路径
type:"post",
data:{"username":username,"password":password},
dataType:"json", //设置服务器返回json数据
success:function(body){//回调函数
console.log(body);
if(body.status==0){//通过后端发送回来的status判断登入状态!
alert("登入成功!");
//登入成功后跳转到指定页面
window.location.href("list.html");
}else{
alert("登入失败,账号或密码错误,请重试!");
//将输入框清空!
$("#message").text("");
$("#user").val("");
$("#password").val("");
}
}
});
});
});
</script>
验证结果:
注册逻辑
<script>
//核心业务逻辑
///$(function () {} 当DOM加载完毕后执行
$(function(){
//当点击这个按钮后就会执行function函数
$("#submit").click(function(){
//获取到用户名和密码
var username = $("#user").val();
var password = $("#password").val();
//判断用户名密码是否为空!
if(username.trim()==""||password.trim()==""){
alert("请输入用户名和密码");
return;
}
//这里就需要将请求前端数据返回给服务器!
$.ajax({
url:"/user/login",//指定路径
type:"post",
data:{"username":username,"password":password},
dataType:"json", //设置服务器返回json数据
success:function(body){//回调函数
console.log(body);
if(body.status==0){//通过后端发送回来的status判断登入状态!
alert("登入成功!");
//登入成功后跳转到指定页面
window.location.href = "list.html";
}else{
alert("登入失败,账号或密码错误,请重试!");
//将输入框清空!
$("#message").text("");
$("#user").val("");
$("#password").val("");
}
}
});
});
});
</script>
验证结果:
'
查询音乐逻辑
核心代码逻辑:
<script type="text/javascript">
$(function(){
load();
});
//musicName可以传参,也可以不传
function load(musicName){
$.ajax({
url:"/music/findMusic",//指定路径
type:"get",
data:{"musicName":musicName},
dataType:"json", //设置服务器返回json数据
success:function(body){//回调函数
console.log(body);
var s = '';
var data = body.data;//数组!
for(var i = 0;i<data.length;i++){
var musicUrl = data[i].url+'.mp3';
s += '<tr>';
s += '<th> <input id="'+data[i].id+'"type="checkbox"> </th>';
s += '<td>' + data[i].title + '</td>';
s += '<td>' + data[i].singer + '</td>';
s +='<td > <button class="btn btn-primary" onclick="playerSong(\''+musicUrl+'\')" >播放歌曲</button>' +
'</td>';
s +='<td > <button class="btn btn-primary" onclick="deleteInfo('+ data[i].id + ')" >删除</button>' +
' '+'<button class="btn btn-primary" onclick="loveInfo('+ data[i].id + ')" > 喜欢</button>'+
'</td>';
s += '</tr>';
}
$("#info").html(s);//把拼接好的页面放在info的id下
}
});
}
</script>
验证结果:
上传音乐逻辑
核心代码逻辑
<form method="post" enctype="multipart/form-data" action="/music/upload">
文件上传:<input type="file" name="filename"/>
歌手名: <label>
<input type="text" name="singer" placeholder="请输入歌手名"/>
</label>
<input type="submit" value="上传"/>
</form>
验证结果:
播放音乐
<!--嵌入播放器!-->
<div style="width: 180px; height: 140px; position:absolute; bottom:10px; right:10px">
<script type="text/javascript" src="player/sewise.player.min.js"></script>
<script type="text/javascript">
SewisePlayer.setup({
server: "vod",
type: "mp3",
//播放的地址
videourl:"http://jackzhang1204.github.io/materials/where_did_time_go.mp3",
//皮肤!
skin: "vodWhite",
//这里自动播放需要设置false
autostart:"false",
});
</script>
</div>
//播放音乐
function playerSong(obj) {
console.log(obj)
var name = obj.substring(obj.lastIndexOf('=')+1);
//obj:播放地址 name:歌曲或者视频名称 0:播放开始时间 false:点击后自动播放
SewisePlayer.toPlay(obj,name,0,true);
}
验证结果:
删除音乐逻辑
//删除音乐
function deleteInfo(obj){
console.log(obj);
$.ajax({
url:"/music/delete",
type:'post',
dataType:"json",
data:{"id":obj},
success:function(body){
console.log(body);
if(body.data==true){
//删除成功!
alert("删除成功!");
window.location.href = "list.html";
}else{
alert("删除失败!");
}
}
});
}
验证结果:
查询歌曲逻辑
$(function(){
$('#submit1').click(function(){
var name = $("#exampleInputName2").val();
load(name);
});
});
验证结果:
删除选中的歌曲逻辑
//删除选中逻辑在查询逻辑里
$(function(){
$('#submit1').click(function(){
var name = $("#exampleInputName2").val();
load(name);
});
//当查询结束才可进行选中删除(有了音乐列表)
$.when(load).done(function(){
//选中删除逻辑!
$("#delete").click(function(){
var id = new Array();
var i = 0;//数组下标!
//遍历input标签下的checkbox
$("input:checkbox").each(function(){
//判断是否选中!
if($(this).is(":checked")){
//获取input里面的id值!
id[i] = $(this).attr('id');
i++;
}
});
console.log(id);
$.ajax({//将获取到的id发送给服务器!
url:"/music/deleteAll",
data:{"id":id},
type:'post',
success:function(body){
if(body.status==0){
alert("删除成功!");
window.location.href="list.html";
}else{
alert("删除失败!");
}
}
});
});
});
});
验证结果:
收藏音乐逻辑
$(function(){
load();
});
//musicName可以传参,也可以不传
function load(musicName){
$.ajax({
url:"/lovemusic/findLoveMusic",//指定路径
type:"get",
data:{"musicName":musicName},
dataType:"json", //设置服务器返回json数据
success:function(body){//回调函数
console.log(body);
var s = '';
var data = body.data;//数组!
for(var i = 0;i<data.length;i++){
var musicUrl = data[i].url+'.mp3';
s += '<tr>';
s += '<td>' + data[i].title + '</td>';
s += '<td>' + data[i].singer + '</td>';
s +='<td > <button class="btn btn-primary" onclick="playerSong(\''+musicUrl+'\')" >播放歌曲</button>' +
'</td>';
s +='<td > <button class="btn btn-primary" onclick="deleteInfo('+ data[i].id + ')" >移除</button>' +
'</td>';
s += '</tr>';
}
$("#info").html(s);//把拼接好的页面放在info的id下
}
});
}
//播放音乐
function playerSong(obj) {
console.log(obj)
var name = obj.substring(obj.lastIndexOf('=')+1);
//obj:播放地址 name:歌曲或者视频名称 0:播放开始时间 false:点击后自动播放
SewisePlayer.toPlay(obj,name,0,true);
}
//删除音乐
function deleteInfo(obj){
console.log(obj);
$.ajax({
url:"/lovemusic/deleteloveMusic",
type:'post',
dataType:"json",
data:{"id":obj},
success:function(body){
console.log(body);
if(body.data==true){
//删除成功!
alert("移除成功!");
window.location.href = "loveMusic.html";
}else{
alert("移除失败!");
}
}
});
}
验证结果:
实现收藏功能逻辑
//在list.html文件中添加一个收藏歌曲函数即可!
//收藏歌曲
function loveInfo(obj){
$.ajax({
url:"/lovemusic/likeMusic",
type:"post",
data:{"id":obj},
dataType:"json",
success:function(body){
if(body.data==true){
alert("收藏成功!");
window.location.href="list.html";
}else{
alert("收藏失败!");
}
}
})
}
验证结果:
配置拦截器
有些页面我们没有登入也可以进行访问,通过输入
url
到地址栏即可!
我们可以在项目中进行登入状态的检查,如果登入了就可以访问,否则不能,这就配置了拦截器,保证程序安全!
我们首先在
config
包下自定义一个拦截器
核心代码
package com.example.onlinemusic.config;
import com.example.onlinemusic.model.Contant;
import com.example.onlinemusic.tools.ResponseBodyMessage;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Created with IntelliJ IDEA.
* Description:配置拦截器
* User: hold on
* Date: 2022-08-06
* Time: 0:43
*/
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//检查是否登入!
HttpSession session = request.getSession(false);
if(session==null||session.getAttribute(Contant.USERINFO_SESSION_KEY)==null){
//未登入状态!
System.out.println("未登入");
return false;
}
return true;
}
}
然后将配置好的拦截器添加到
AppConfig
类中
package com.example.onlinemusic.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* Created with IntelliJ IDEA.
* Description:将自定义拦截器添加到系统配置!
* User: hold on
* Date: 2022-07-27
* Time: 12:29
*/
@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");
}
}
验证结果:
部署到服务器
准备
更改数据库配置
因为我的云服务器下的数据库用户密码信息和本地的一样,所以不用更改数据库配置!
我们在云服务器下创建好数据库即可!
更改上传音乐路径
# 云服务器下的地址路径!
music.path.save = /root/javaweb部署环境/music
打包
项目打包
打包报错,我们需要统一编码 UTF-8
添加依赖
<!--添加maven.plugins依赖 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!--添加maven-surefire-plugin依赖-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
打包成功!
部署
将打包好的项目上传到服务器!
启动项目:
java -jar onlinemusic.jar
这里显示端口号8080
被占用,那么我们就将端口号给kill
//查看端口
netstat -anp | grep 8080
//杀进程
kill -9 pcb值
验证结果:
这样虽然部署好了项目,但是这个只能支持前台运行,也就是说这个项目关闭了,项目就访问不了了!
后台运行 SpringBoot 项目
运行指令:
nohup java -jar onlinemusic.jar>>log.log&
nohup
:后台运行项目的指令
>>log.log
:把控制台上的日志保存到 log.log 文件中!(未设置可默认生成)
&
:后台一直运行
这样运行就支持后台运行了!
后期项目维护更新
如果后面我们觉得项目需要完善该如何进行服务器项目更新呢?
将该项目的进制终止
netstat -anp |grep 8080
kill -9 17303
也可以使用ps -ef | grep java
kill 【进程ID】
命令说明:
ps : Linux 当中查看进程的命令
-e 代表显示所有的进程
-f 代表全格式【显示全部的信息】
grep : 全局正则表达式
重新上传jar包
重新进行后台的启动
更新项目,重新运行
nohup java -jar onlinemusic.jar>>log.log&
更新了一下注册登入的前端页面!
遇到的面试题总结
上传其他文件,然后将后缀改成
.mp3
,如何识别?是否可以正常播放?
因为每种类型的文件都有自己的文件结构,都有自己特有的格式,我们根据
mp3
特有的文件格式,在倒数第128
字节处,有有个TAG
音乐文件标志,从而在上传时就检测一下是否是音频文件,如果不是音频文件无法上传!
可以上传大文件嘛?
不能,因为一首歌曲的大小不会很大,所以我已经在配置文件配置了每个文件的最大上传大小,以及单次请求的文件总数大小!
#配置springboot上传文件的大小,默认每个文件的配置最大为15Mb,单次请求的文件的总数不能大于100Mb spring.servlet.multipart.max-file-size = 15MB spring.servlet.multipart.max-request-size=100MB
为啥不用
HTML
的原生audio
标签?
因为我想通过使用开源的播放器,提升一下自己的学习能力,毕竟我们经常会在自己的项目中使用到其他的优秀开源项目,我们也需要具备这样的能力,学习使用大佬的优秀项目!
只要将开源播放代码换成原生
audio
即可!s += "<td <a href=\"\"> <audio src= \""+ musicUrl+"\" + controls=\"controls\" preload=\"none\" loop=\"loop\"> >" + "</audio> </a> </td>";
原生的
audio
标签和开源播放器的一首歌曲的下载时间如下:开源播放器:
原生
audio
播放器:可以看到同样一首歌曲在线播放后下载的时间不同,虽然 2 个都是边下载边播放,但是这里的开源播放器下载时间更短!
版权声明: 本文为 InfoQ 作者【bug郭】的原创文章。
原文链接:【http://xie.infoq.cn/article/2c576c5ef890e9e9c47b3b089】。文章转载请联系作者。
bug郭
还未添加个人签名 2022.06.15 加入
还未添加个人简介
评论