项目目前已经上线
音乐播放器登录页面铁子们可以后台私信获取管理员用户和密码
上文我们把用户登录模块设计实现完毕,本文将实现上传音乐的模块
@Data
public class Music {
private int id;
private String title;
private String singer;
private String url;
private String time;
private int userid;
}
因为这个类比较复杂,我就单独拿出来给大家分析了
此类用于接待前端用户上传的音乐 ,总体实现框架如我注解分出的四步骤
①检查是否登录(这一步比较简单,就略过了哈,不清楚的可以看之前几篇文章讲解)
②校验是否是.mp3文件(这里需要实现FileVerify.isMP3File()方法,重点!!)
③把music上传到服务器(这里去业务逻辑层,也就是Service类中实现音乐的上传)
④把music的相关信息保存到数据库中
@RequestMapping("/upload")
public ResponseBodyMessage<Boolean> insertMusic(@RequestParam String singer
, @RequestParam("fileName")MultipartFile file , HttpServletRequest request
, HttpServletResponse response) throws IOException {
//1:检查是否登录了
HttpSession httpSession = request.getSession(false);//当前没有会话不创建会话,默认为true
if(httpSession == null ||
httpSession.getAttribute(Constants.USERINFO_SESSION_KEY) == null){
return new ResponseBodyMessage<>(-1,"没有登陆",false);
}
//2:检验是否是mp3文件格式
Boolean result = FileVerify.isMP3File(file);
if(result == false){
return new ResponseBodyMessage<>(-1,"上传的文件格式错误",false);
}
String fileNameAndType = file.getOriginalFilename();//获取文件的完整名称,包括文件名称和文件拓展名,xxx.mp3
System.out.println("fileNameAndType----->" + fileNameAndType);
String path = SAVE_PATH + "/" + fileNameAndType;//文件的存储路径
File dest = new File(path);//用这个存储路径new 一个file , dest现在就相当于 前端传进来的file可以这么理解
//3:music上传到服务器
Boolean result2 = musicService.uploadServer(file, path);
if(!result2){
return new ResponseBodyMessage<>(-1,"服务器上传失败",false);
}
//4:进行数据库的上传
// (1)准备数据 (2)调用insert
int ret = musicService.uploadDatebase(fileNameAndType,httpSession,singer);
if(ret == 1){
response.sendRedirect("/list.html");//前端实现完毕,上传成功进行跳转页面(重定向)
} else if (ret == -1) {//已经存在该音乐了
return new ResponseBodyMessage<>(-1,"数据库中已经存在该音乐",false);
} else {
dest.delete();//上传数据库失败
return new ResponseBodyMessage<>(-1,"数据库上传失败",false);
}
return new ResponseBodyMessage<>(0,"数据库上传成功",true);
}
思考:我们常规思维判定一个文件是否是.mp3音乐文件,是怎么样的?大部分人看到文件名后缀是.mp3默认就把这个文件当成音乐文件了。但是我们作为一名合格的程序猿,这么思考肯定是不行的,万一用户把一个图片文件后缀名改为.mp3,冒充上传,那不就g了。其次我们也要规定你上传的文件大小不能超过一定的范围(这里在配置中已经设置过了)一首歌也就十几二十兆
我们的.mp3文件一定会包含Frame帧和ID3V1标签,那我们逐层分析
ID3V1标签永远处于文件的末128个字节,这128个字节的前三位为TAG(有些ID3V1标签不讲武德,没有把这三个标志符放到前三位,少数,别问我怎么知道的!!)
有些歌曲,文件格式中TAG不在末尾,GG。
总结:判断.mp3文件后128字节中,前三个字节是否为TAG可以作为我们的判断依据之一
这个标签一般是放在整个.mp3文件的头部(也可能在文件的中部),因为我们V1标签的长度有限,存储不了更多的信息,所以V2标签就是用来扩展信息内容的!
如果在头部:这个标签的特点就是前三个字节为ID3
总结:判断.mp3文件前三个字节是否为ID3,也可以作为我们的判断依据之一
前面说了,我们mp3文件的头部不是v2标签就是Frame帧头,帧头有什么特点呢,先看看它的结构组成吧!
mp3文件的帧头前四个字节,也就是16位,其实是固定的
①看同步信息——11位固定为1——即红色字体
②版本——2位——这里我们要的是mp3音频格式——所以是MPEG——看蓝色圈圈——固定为11
③层——2位——看绿色圈圈——我们要的是Layer——那就是01
④CRC校验——这个没有强制要求——所以0和1都存在
综上所述:那我们的帧头4个字节,16位,只有两种可能
1111 1111 1111 1010 或者 1111 1111 1111 1011
换算为16进制表示就是 FFFA 或者 FFFB
总结:判断.mp3文件前四个字节是否为FFFA或者FFFB,也可以作为我们的判断依据之一
注意点:这1+3个判断依据是有先后顺序的
第一步:先判断文件名后缀是否是.mp3(这一步正常的音乐文件名后缀都有)
第二步:用ID3V1标签中TAG字节的特点判断(因为ID3V1一直在末尾,也是我们mp3文件中一定包含的)
第三步:用ID3V2标签中ID3字节(因为文件头部可能是ID3V2也可能是Frame,并且ID3V2还可能会在文件中部或者压根mp3文件中就没有v2标签)
第四步:用Frame帧头判断
package com.example.musicserver.tools;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class FileVerify {
/**
* 判断上传的文件是否为MP3文件
* @param file 上传的文件
* @return true 如果是MP3文件,false 如果不是
*/
public static boolean isMP3File(MultipartFile file) throws IOException {
// 第一种判断:判断文件名是否以.mp3结尾
String fileName = file.getOriginalFilename();
if (file == null || fileName == null || !fileName.toLowerCase().endsWith(".mp3")) {
return false;
}
// 第二种判断:尾部ID3v1标签128字节,前三个字节为TAG
try {
// 获取文件的字节数组,读取文件的最后128字节
byte[] fileBytes = file.getBytes();
int fileLength = fileBytes.length;
// 如果文件小于128字节,无法包含ID3v1标签
if (fileLength < 128) {
return false;
}
// 获取文件的最后128字节
byte[] last128Bytes = new byte[3];
/**
* 数组复制:源数组,起始位置,目标数组,目标数组起始位置,复制的元素个数
*/
System.arraycopy(fileBytes, fileLength - 128, last128Bytes, 0, 3);
// 检查ID3v1标签的前三个字节是否为 "TAG"
if (last128Bytes[0] == 'T' && last128Bytes[1] == 'A' && last128Bytes[2] == 'G') {
return true; // 包含ID3v1标签,认为是有效的MP3文件
}
} catch (IOException e) {
e.printStackTrace();
}
// 第三种判断:判断头部,mp3有一些特定的文件头(ID3v2标签的标识符,它可能在头部也可能在中间)或(MP3的帧头信息)
try (InputStream inputStream = file.getInputStream()) {
byte[] header = new byte[3]; // 用于读取文件头的前三个字节
inputStream.read(header);
// 检查文件头部是否包含 "ID3" 标识符(ID3v2标签)
if (header[0] == 'I' && header[1] == 'D' && header[2] == '3') {
// ID3v2标签
return true;
}
// 读取MP3帧头
byte[] frameHeader = new byte[4]; // 用来读取MP3帧头
inputStream.read(frameHeader);
// 第四种判断:检查文件是否是一个有效的MP3帧(音频数据通常以 "FF FB" 或者 "FF FA"开头)
if (frameHeader[0] == (byte) 0xFF && (frameHeader[1] == (byte) 0xFA || frameHeader[1] == (byte) 0xFB)) {
// MP3帧头的标志位,表示这是一个有效的MP3音频帧
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
return false; // 如果没有找到ID3v1标签
}
}