- 通常视频文件都比较大,所以对于媒资系统上传文件的需求要满足大文件的上传要求。http协议本身对上传文件大
小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了,电断了没
有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传.
- 上传前先把文件分块
- 一块一块的上传,上传中断后重新上传,已上传的部分则不用再上传
- 全部上传成功之后,把上传的文件做文件合并.
- 获取源文件长度
- 根据设定的分块文件的大小计算出块数
- 从源文件读数据依次向每一个块文件写数据
public void testChunk() throws IOException {
//源文件
File sourceFile = new File("E:\\ffmpeg_test\\获取的文件名.avi");
//块文件目录
String chunkFileFolder = "E:\\ffmpeg_test\\chunks\\";
//先定义块文件大小
long chunkFileSize = 1 * 1024 * 1024;
//块数
long chunkFileNum = (long) Math.ceil(sourceFile.length() * 1.0 /chunkFileSize);
//创建读文件的对象
RandomAccessFile raf_read = new RandomAccessFile(sourceFile,"r");
//缓冲区
byte[] b = new byte[1024];
for(int i=0;i<chunkFileNum;i++){
//块文件
File chunkFile = new File(chunkFileFolder+i);
//创建向块文件的写对象
RandomAccessFile raf_write = new RandomAccessFile(chunkFile,"rw");
int len = -1;
while((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len);
//如果块文件的大小达到 1M开始写下一块儿
if(chunkFile.length()>=chunkFileSize){
break;
}
}
raf_write.close();
}
raf_read.close();
}
public void testMergeFile() throws IOException {
//块文件目录
String chunkFileFolderPath = "E:\\ffmpeg_test\\chunks\\";
//块文件目录对象
File chunkFileFolder = new File(chunkFileFolderPath);
//块文件列表
File[] files = chunkFileFolder.listFiles();
//将块文件排序,按名称升序
List<File> fileList = Arrays.asList(files);
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if(Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){
return 1;
}
return -1;
}
});
//合并文件
File mergeFile = new File("E:\\ffmpeg_test\\文件名.avi");
//创建新文件
boolean newFile = mergeFile.createNewFile();
//创建写对象
RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
byte[] b = new byte[1024];
for(File chunkFile:fileList){
//创建一个读块文件的对象
RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
int len = -1;
while((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len);
}
raf_read.close();
}
raf_write.close();
}
- 在开始对文件分块儿之前调用,可以做一些上传文件前的准备工作,比如检查文件目录是否创建完成等
- 在上传文件分块之前调用此方法,可以请求服务端检查分块是否存在,如果已存在则此分块儿不再上传
- 在所有分块上传完成后触发,可以请求服务端合并分块文件
WebUploader.Uploader.register({
"before‐send‐file":"beforeSendFile",
"before‐send":"beforeSend",
"after‐send‐file":"afterSendFile"
}
// 创建uploader对象,配置参数
this.uploader = WebUploader.create(
{
//上传文件的flash文件,浏览器不支持h5时启动flash
swf:"/static/plugins/webuploader/dist/Uploader.swf",
server:"/api/media/upload/uploadchunk",//上传分块的服务端地址,注意跨域问题
fileVal:"file",//文件上传域的name
pick:"#picker",//指定选择文件的按钮容器
auto:false,//手动触发上传
disableGlobalDnd:true,//禁掉整个页面的拖拽功能
chunked:true,// 是否分块上传
chunkSize:1*1024*1024, // 分块大小(默认5M)
threads:3, // 开启多个线程(默认3个)
prepareNextFile:true// 允许在文件传输时提前把下一个文件准备好
}
)
type:"POST",
url:"/api/media/upload/register",
data:{
// 文件唯一表示
fileMd5:this.fileMd5,
fileName: file.name,
fileSize:file.size,
mimetype:file.type,
fileExt:file.ext
}
//上传分块前前端请求服务端校验分块是否存在before-send方法
type:"POST",
url:"/api/media/upload/checkchunk",
data:{
// 文件唯一表示
fileMd5:this.fileMd5,
// 当前分块下标
chunk:block.chunk,
// 当前分块大小
chunkSize:block.end‐block.start
}
//after-send-file 在所有分块上传完成后触发,可以请求服务端合并分块文件
type:"POST",
url:"/api/media/upload/mergechunks",
data:{
fileMd5:this.fileMd5,
fileName: file.name,
fileSize:file.size,
mimetype:file.type,
fileExt:file.ext
}
//得到文件所属目录路径
private String getFileFolderPath(String fileMd5){
return upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/";
}
//得到文件的路径
private String getFilePath(String fileMd5,String fileExt){
return upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" + fileMd5 + "." +fileExt;
}
//得到块文件所属目录路径
private String getChunkFileFolderPath(String fileMd5){
return upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/chunk/";
}
/**
* 文件上传前的注册,检查文件是否存在
* 根据文件md5得到文件路径
* 规则:
* 一级目录:md5的第一个字符
* 二级目录:md5的第二个字符
* 三级目录:md5
* 文件名:md5+文件扩展名
* @param fileMd5 文件md5值
* @param fileExt 文件扩展名
* @return 文件路径
*/
public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
//1 检查文件在磁盘上是否存在
//文件所属目录的路径
String fileFolderPath = this.getFileFolderPath(fileMd5);
//文件的路径
String filePath =this.getFilePath(fileMd5,fileExt);
File file = new File(filePath);
//文件是否存在
boolean exists = file.exists();
//2 检查文件信息在数据库中是否存在
Optional<MediaFile> optional = mediaFileRepository.findById(fileMd5);
if(exists && optional.isPresent()){
//文件存在
ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_EXIST);
}
//文件不存在时作一些准备工作,检查文件所在目录是否存在,如果不存在则创建
File fileFolder = new File(fileFolderPath);
if(!fileFolder.exists()){
fileFolder.mkdirs();
}
return new ResponseResult(CommonCode.SUCCESS);
}
//分块检查
/**
*
* @param fileMd5 文件md5
* @param chunk 块的下标
* @param chunkSize 块的大小
* @return
*/
public CheckChunkResult checkchunk(String fileMd5, Integer chunk, Integer chunkSize) {
//检查分块文件是否存在
//得到分块文件的所在目录
String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
//块文件
File chunkFile = new File(chunkFileFolderPath + chunk);
if(chunkFile.exists()){
//块文件存在
return new CheckChunkResult(CommonCode.SUCCESS,true);
}else{
//块文件不存在
return new CheckChunkResult(CommonCode.SUCCESS,false);
}
}
//上传分块
public ResponseResult uploadchunk(MultipartFile file, String fileMd5, Integer chunk) {
//检查分块目录,如果不存在则要自动创建
//得到分块目录
String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
//得到分块文件路径
String chunkFilePath = chunkFileFolderPath + chunk;
File chunkFileFolder = new File(chunkFileFolderPath);
//如果不存在则要自动创建
if(!chunkFileFolder.exists()){
chunkFileFolder.mkdirs();
}
//得到上传文件的输入流
InputStream inputStream = null;
FileOutputStream outputStream =null;
try {
inputStream = file.getInputStream();
outputStream = new FileOutputStream(new File(chunkFilePath));
IOUtils.copy(inputStream,outputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new ResponseResult(CommonCode.SUCCESS);
}
//合并文件
public ResponseResult mergechunks(String fileMd5, String fileName, Long fileSize, String mimetype, String fileExt) {
//1、合并所有分块
//得到分块文件的属目录
String chunkFileFolderPath = this.getChunkFileFolderPath(fileMd5);
File chunkFileFolder = new File(chunkFileFolderPath);
//分块文件列表
File[] files = chunkFileFolder.listFiles();
List<File> fileList = Arrays.asList(files);
//创建一个合并文件
String filePath = this.getFilePath(fileMd5, fileExt);
File mergeFile = new File(filePath);
//执行合并
mergeFile = this.mergeFile(fileList, mergeFile);
if(mergeFile == null){
//合并文件失败
ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
}
//2、校验文件的md5值是否和前端传入的md5一到
boolean checkFileMd5 = this.checkFileMd5(mergeFile, fileMd5);
if(!checkFileMd5){
//校验文件失败
ExceptionCast.cast(MediaCode.MERGE_FILE_CHECKFAIL);
}
//3、将文件的信息写入mongodb
MediaFile mediaFile = new MediaFile();
mediaFile.setFileId(fileMd5);
mediaFile.setFileOriginalName(fileName);
mediaFile.setFileName(fileMd5 + "." +fileExt);
//文件路径保存相对路径
String filePath1 = fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" + fileMd5 + "." +fileExt;
mediaFile.setFilePath(filePath1);
mediaFile.setFileSize(fileSize);
mediaFile.setUploadTime(new Date());
mediaFile.setMimeType(mimetype);
mediaFile.setFileType(fileExt);
//状态为上传成功
mediaFile.setFileStatus("301002");
mediaFileRepository.save(mediaFile);
return new ResponseResult(CommonCode.SUCCESS);
}
//校验文件
private boolean checkFileMd5(File mergeFile,String md5){
try {
//创建文件输入流
FileInputStream inputStream = new FileInputStream(mergeFile);
//得到文件的md5
String md5Hex = DigestUtils.md5Hex(inputStream);
//和传入的md5比较
if(md5.equalsIgnoreCase(md5Hex)){
return true;
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return false;
}
//合并文件
private File mergeFile(List<File> chunkFileList, File mergeFile) {
try {
//如果合并文件已存在则删除,否则创建新文件
if (mergeFile.exists()) {
mergeFile.delete();
} else {
//创建一个新文件
mergeFile.createNewFile();
}
//对块文件进行排序
Collections.sort(chunkFileList, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
if(Integer.parseInt(o1.getName())>Integer.parseInt(o2.getName())){
return 1;
}
return -1;
}
});
//创建一个写对象
RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
byte[] b = new byte[1024];
for(File chunkFile:chunkFileList){
RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
int len = -1;
while ((len = raf_read.read(b))!=-1){
raf_write.write(b,0,len);
}
raf_read.close();
}
raf_write.close();
return mergeFile;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}