阿华代码,不是逆风,就是我疯
你们的点赞收藏是我前进最大的动力!!
希望本文内容能够帮助到你!!
目录
零:项目结果展示
一:上传音乐
1:接口设计
2:实体Music类
二:MusicController类
三:实现.MP3文件的检验
1: 文件格式解读
2:结构特点
(1)ID3V1标签
(2)ID3V2标签
(3)帧头Frame
3:上代码
零:项目结果展示
项目目前已经上线
音乐播放器登录页面铁子们可以后台私信获取管理员用户和密码
一:上传音乐
上文我们把用户登录模块设计实现完毕,本文将实现上传音乐的模块
1:接口设计
2:实体Music类
@Data
public class Music {private int id;private String title;private String singer;private String url;private String time;private int userid;
}
二:MusicController类
因为这个类比较复杂,我就单独拿出来给大家分析了
此类用于接待前端用户上传的音乐 ,总体实现框架如我注解分出的四步骤
①检查是否登录(这一步比较简单,就略过了哈,不清楚的可以看之前几篇文章讲解)
②校验是否是.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);//当前没有会话不创建会话,默认为trueif(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.mp3System.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)调用insertint 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默认就把这个文件当成音乐文件了。但是我们作为一名合格的程序猿,这么思考肯定是不行的,万一用户把一个图片文件后缀名改为.mp3,冒充上传,那不就g了。其次我们也要规定你上传的文件大小不能超过一定的范围(这里在配置中已经设置过了)一首歌也就十几二十兆
1: 文件格式解读
2:结构特点
我们的.mp3文件一定会包含Frame帧和ID3V1标签,那我们逐层分析
(1)ID3V1标签
ID3V1标签永远处于文件的末128个字节,这128个字节的前三位为TAG(有些ID3V1标签不讲武德,没有把这三个标志符放到前三位,少数,别问我怎么知道的!!)
有些歌曲,文件格式中TAG不在末尾,GG。
总结:判断.mp3文件后128字节中,前三个字节是否为TAG可以作为我们的判断依据之一
(2)ID3V2标签
这个标签一般是放在整个.mp3文件的头部(也可能在文件的中部),因为我们V1标签的长度有限,存储不了更多的信息,所以V2标签就是用来扩展信息内容的!
如果在头部:这个标签的特点就是前三个字节为ID3
总结:判断.mp3文件前三个字节是否为ID3,也可以作为我们的判断依据之一
(3)帧头Frame
前面说了,我们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,也可以作为我们的判断依据之一
3:上代码
注意点:这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字节,前三个字节为TAGtry {// 获取文件的字节数组,读取文件的最后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标签}}