Java—大文件分片上传
Posted 宝**贝
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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; } }
以上是关于Java—大文件分片上传的主要内容,如果未能解决你的问题,请参考以下文章