Java—大文件分片上传
http协议本身对上传文件大 小没有限制,但是客户的网络环境质量、电脑硬件环境等参差不齐,如果一个大文件快上传完了网断了,电断了没 有上传完成,需要客户重新上传,这是致命的,所以对于大文件上传的要求最基本的是断点续传。
什么是断点续传:断点续传指的是在下载或上传时,将下载或上传任务(一个文件或一个压缩包)人为的划分为几个 部分,每一个部分采用一个线程进行上传或下载,如果碰到网络故障,可以从已经上传或下载的部分开始继续上传 下载未完成的部分,而没有必要从头开始上传下载,断点续传可以提高节省操作时间,提高用户体验性。
上传流程如下:
1、上传前先把文件分成块
2、一块一块的上传,上传中断后重新上传,已上传的分块则不用再上传
3、各分块上传完成最后合并文件 文件下载则同理。
实体类
package com.xuecheng.framework.domain.media;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import java.util.Date;
/**
* @Author: mrt.
* @Description:
* @Date:Created in 2018/1/24 10:04.
* @Modified By:
*/
@Data
@ToString
@Document(collection = "media_file")
public class MediaFile {
/*
文件id、名称、大小、文件类型、文件状态(未上传、上传完成、上传失败)、上传时间、视频处理方式、视频处理状态、hls_m3u8,hls_ts_list、课程视频信息(课程id、章节id)
*/
@Id
//文件id
private String fileId;
//文件名称
private String fileName;
//文件原始名称
private String fileOriginalName;
//文件路径
private String filePath;
//文件url
private String fileUrl;
//文件类型
private String fileType;
//mimetype
private String mimeType;
//文件大小
private Long fileSize;
//文件状态
private String fileStatus;
//上传时间
private Date uploadTime;
//处理状态
private String processStatus;
//hls处理
private MediaFileProcess_m3u8 mediaFileProcess_m3u8;
//tag标签用于查询
private String tag;
}
controller类
package com.xuecheng.manage_media.controller;
import com.xuecheng.api.media.MediaUploadControllerApi;
import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_media.service.MediaUploadService;
import jdk.management.resource.ResourceRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
/**
* Created by Administrator on 2020/6/6 0006.
*/
@RestController
@RequestMapping("media/upload")
public class MediaUploadController implements MediaUploadControllerApi {
@Autowired
private MediaUploadService uploadService;
@Override
@PostMapping("/register")
public ResponseResult register(String fileMd5, String fileName, Long fileSize, String mimeType, String fileExt) {
return uploadService.register( fileMd5, fileName, fileSize, mimeType, fileExt);
}
@Override
@PostMapping("/checkChunk")
public CheckChunkResult checkChunk(String fileMd5, Integer chunk, Integer chunkSize) {
return uploadService.checkChunk( fileMd5, chunk, chunkSize);
}
@Override
public ResponseResult uploadChunk(MultipartFile file, String fileMd5, Integer chunk) {
return uploadService.uploadChunk( file, fileMd5, chunk);
}
@Override
public ResponseResult mergeChunks(String fileMd5, String fileName, Long fileSize, String mimeType, String fileExt) {
return uploadService.mergeChunks( fileMd5, fileName, fileSize, mimeType, fileExt);
}
}
service类
package com.xuecheng.manage_media.service;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.domain.media.response.MediaCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_media.dao.MediaFileMapper;
import jdk.management.resource.ResourceRequest;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.security.MessageDigest;
import java.util.*;
/**
* Created by Administrator on 2020/6/6 0006.
*/
@Service
public class MediaUploadService {
@Autowired
private MediaFileMapper mediaFileMapper;
@Value("${xc-service-manage-media.upload-location}")
String uploadLocation;
/**
* 文件上传前的注册,检查文件是否存在
* 根据文件MD5获取文件路径
* 规则:
* 一级目录:md5的第一个字符
* 二级目录:md5的第二个字符
* 三级目录:md5
* 文件名:md5 + 文件扩展名
* @param fileMd5
* @param fileName
* @param fileSize
* @param mimeType
* @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、检查文件在数据库中是否有 上传记录
MediaFile mediaFile = mediaFileMapper.findByFileId(fileMd5);
if(exists && null != mediaFile){
ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_EXIST);
}
//文件不存在时作一些准备工作,检查文件所在目录是否存在,如果不存在创建
File fileFolder = new File(fileFolderPath);
if (!fileFolder.exists()) {
fileFolder.mkdirs();
}
return new ResponseResult(CommonCode.SUCCESS);
}
/**
* 检查分块文件是否存在
* @param fileMd5
* @param chunk
* @param chunkSize
* @return
*/
public CheckChunkResult checkChunk(String fileMd5, Integer chunk, Integer chunkSize) {
//得到分块文件的所在目录
String chunkFileFolderPath = this.getFileFolderPath(fileMd5) + "chunk" + File.separator;
//得到块文件
File chunkFile = new File(chunkFileFolderPath + chunk);
if(chunkFile.exists()){
return new CheckChunkResult(CommonCode.SUCCESS,true);
}
return new CheckChunkResult(CommonCode.SUCCESS,false);
}
/**
* 上传分块
* @param file
* @param fileMd5
* @param chunk
* @return
*/
public ResponseResult uploadChunk(MultipartFile file, String fileMd5, Integer chunk) {
//检查分块目录,如果不存在则要自动创建
String chunkFileFolderPath = this.getFileFolderPath(fileMd5) + "chunk" + File.separator;
//得到块目录
File chunkFile = new File(chunkFileFolderPath);
if(!chunkFile.exists()){
chunkFile.mkdirs();
}
//得到上传文件的输入流
InputStream inputStream = null;
FileOutputStream outputStream = null;
try {
inputStream = file.getInputStream();
outputStream = new FileOutputStream(new File(chunkFileFolderPath + chunk));
IOUtils.copy(inputStream,outputStream);
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
inputStream.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new ResponseResult(CommonCode.SUCCESS);
}
/**
* 合并块文件
* @param fileMd5
* @param fileName
* @param fileSize
* @param mimeType
* @param fileExt
* @return
*/
public ResponseResult mergeChunks(String fileMd5, String fileName, Long fileSize, String mimeType, String fileExt) {
//合并所有文件
//得到分块文件的目录
String chunkFileFolderPath = this.getFileFolderPath(fileMd5) + "chunk" + File.separator;
File chunkFile = new File(chunkFileFolderPath);
File[] files = chunkFile.listFiles();
//创建一个合并文件
String filePath = this.getFilePath(fileMd5, fileExt);
File mergeFile = new File(filePath);
//执行合并
mergeFile = this.mergeFile(Arrays.asList(files),mergeFile);
if(null == mergeFile){
ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
}
//检验文件的md5值是否与前端传入的md5一致
boolean checkResult = this.checkFileMd5(mergeFile,fileMd5);
if(!checkResult){
ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
}
//将文件的信息写入数据库
MediaFile mediaFile = new MediaFile();
mediaFile.setFileId(fileMd5);
mediaFile.setFileName(fileMd5+"."+fileExt);
mediaFile.setFileOriginalName(fileName);
//文件路径保存相对路径
mediaFile.setFilePath(getFileFolderRelativePath(fileMd5,fileExt));
mediaFile.setFileSize(fileSize);
mediaFile.setUploadTime(new Date());
mediaFile.setMimeType(mimeType);
mediaFile.setFileType(fileExt);
//状态为上传成功
mediaFile.setFileStatus("301002");
mediaFileMapper.insert(mediaFile);
return new ResponseResult(CommonCode.SUCCESS);
}
/**获取文件所述目录路径*/
private String getFileFolderPath(String fileMd5){
return uploadLocation + fileMd5.substring(0,1)+ File.separator+fileMd5.substring(1,2)+File.separator
+ fileMd5 +File.separator;
}
private String getFilePath(String fileMd5, String fileExt){
return uploadLocation + fileMd5.substring(0,1)+ File.separator+fileMd5.substring(1,2)+File.separator
+ fileMd5 +File.separator + fileMd5+"."+fileExt;
}
//得到文件目录相对路径,路径中去掉根目录
private String getFileFolderRelativePath(String fileMd5,String fileExt){
String filePath = fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" +
fileMd5 + "/";
return filePath;
}
//合并文件
private File mergeFile(List<File> chunkFiles,File mergeFile){
try {
if(mergeFile.exists()){
mergeFile.delete();
}
mergeFile.createNewFile();
//对块文件进行排序
Collections.sort(chunkFiles, 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_w = new RandomAccessFile(mergeFile,"rw");
byte[] bytes = new byte[1024];
for (File chunkFile:chunkFiles){
RandomAccessFile raf_r = new RandomAccessFile(chunkFile,"r");
int len = -1;
while ((len = raf_r.read(bytes)) != -1){
raf_w.write(bytes,0,len);
}
raf_r.close();
}
raf_w.close();
} catch (IOException e) {
e.printStackTrace();
return null;
}
return null;
}
//校验文件
private boolean checkFileMd5(File mergeFile,String md5){
try {
FileInputStream inputStream = new FileInputStream(mergeFile);
String md5Hex = DigestUtils.md5Hex(inputStream);
if(md5.equalsIgnoreCase(md5Hex)){
return true;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
浙公网安备 33010602011771号