JAVA实现大文件分片上传断点续传
直接上代码
import org.springframework.web.multipart.MultipartFile; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; import lombok.extern.slf4j.Slf4j; import java.text.DecimalFormat; import java.io.*; @Slf4j public class FileSliceUpload { // 文件上传地址 private final String uploadPath = "/data/upload/"; /** * @description: 文件分片上传 * @date: 2024/3/12 15:25 * @param fileSliceDTO * @return boolean */ public boolean fileUpload(FileSliceDTO fileSliceDTO) { // 当前分片序号 Integer chunkNumber = fileSliceDTO.getChunkNumber(); // 当前分片大小 Long currentChunkSize = fileSliceDTO.getCurrentChunkSize(); // 总分片数 Integer totalChunks = fileSliceDTO.getTotalChunks(); // 原文件md5 String fileMd5 = fileSliceDTO.getFileMd5(); // 文件名称 String fileName = fileSliceDTO.getFileName(); // 用户账号 String userAccount = fileSliceDTO.getUserAccount(); // 文件总大小 Long totalSize = fileSliceDTO.getTotalSize(); // 当前分片文件流 MultipartFile mFile = fileSliceDTO.getFile(); log.info("接收到文件:{},总大小:{} 总分片:{} 当前分片:{}", fileName, this.readableFileSize(totalSize), totalChunks, chunkNumber); String path = uploadPath + fileMd5 + "_" + userAccount + File.separator; File dirfile = new File(path); if (!dirfile.exists()) { dirfile.mkdirs(); } String currentChunkFileName = path + fileMd5 + "_" + chunkNumber + ".tmp"; File file = new File(currentChunkFileName); boolean uploadFlag = this.fileUpload(mFile, file); if (uploadFlag) { if (chunkNumber < totalChunks) { return true; } else { // 如果是最后一个分片,上传成功后就进行文件合并 String mergeFileName = null; try { mergeFileName = this.fileMerge(fileMd5, totalChunks, fileName, totalSize, userAccount); } catch (IOException e) { log.info("文件合并发生异常:{}, {}", fileName, e.toString()); } if (StringUtils.isBlank(mergeFileName)) { // 合并失败后异步删除文件夹 CompletableFuture.runAsync(() - > { delFile(new File(path)); }); return false; } log.info("文件合并成功:{}, 总大小: {}", fileName, this.readableFileSize(totalSize)); // 合并成功后异步删除文件夹 CompletableFuture.runAsync(() - > {delFile(new File(path)); }); return true; } } return false; } /** * @description: 文件上传私有方法 * @date: 2024/3/12 15:25 * @param multipartFile 分片文件 * @param file 目标文件 * @return boolean */ private boolean fileUpload(MultipartFile multipartFile, File file) { boolean flag = false; try { if (!file.exists()) { file.createNewFile(); multipartFile.transferTo(file); } else { log.info("当前分片文件已存在:{}", file.getName()); } flag = true; } catch (Exception e) { log.info("文件上传失败:{}, {}", multipartFile.getOriginalFilename(), e.toString()); } return flag; } /** * @description: 合并文件 * @date: 2024/3/12 15:21 * @param fileMd5 * @param chunks * @param fileName * @param totalSize * @param userAccount * @return java.lang.String */ private String fileMerge(String fileMd5, Integer chunks, String fileName, long totalSize, String userAccount) throws IOException { String fileType = this.getFileSuffix(fileName); String mergePath = uploadPath + UUIDUtil.uuid32() + "." + fileType; FileOutputStream fileOutputStream = new FileOutputStream(mergePath); String mergeFileName = null; long fileSize = 0 L; try { byte[] buf = new byte[1024 * 4]; for (int i = 1; i <= chunks; i++) { String chunkFile = i + ".tmp"; File file = new File(uploadPath + fileMd5 + "_" + userAccount + File.separator + fileMd5 + "_" + chunkFile); fileSize = fileSize + file.length(); InputStream inputStream = new FileInputStream(file); int len; while ((len = inputStream.read(buf)) != -1) { fileOutputStream.write(buf, 0, len); } inputStream.close(); } mergeFileName = mergePath; } catch (Exception e) { log.info("合并文件失败:{},{}", fileName, e.toString()); } finally { fileOutputStream.close(); } if (fileSize != totalSize) { log.info("文件总大小不一致:{}", fileName); return null; } return mergeFileName; } /** * @description: 删除文件/文件夹 * @date: 2024/3/12 15:19 * @param file * @return boolean */ private boolean delFile(File file) { if (file == null || !file.exists()) { return true; } if (!file.isDirectory()) { file.delete(); return true; } else { File[] files = file.listFiles(); for (File f: files) { if (f.isDirectory()) { delFile(f); } else { f.delete(); } } file.delete(); return true; } } /** * @description: 文件大小可读化转换 * @date: 2024/3/12 15:18 * @param size * @return java.lang.String */ private String readableFileSize(long size) { if (size <= 0) { return "0"; } final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; int digitGroups = (int)(Math.log10(size) / Math.log10(1024)); return new DecimalFormat("#,###.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; } /** * @description: 获取文件的拓展名 支持形式 abc.jpg or D:/data/abc.txt; * @date: 2024/3/12 15:16 * @param fileName * @return java.lang.String */ private String getFileSuffix(String fileName) { String newFileName = fileName;
if(fileName.contains("/") || fileName.contains("\\")){
String replaceFileName = fileName.replaceAll("\\\\", "/");
int lastIndexOf = replaceFileName.lastIndexOf("/");
if(lastIndexOf > 0) {
newFileName = replaceFileName.substring(lastIndexOf);
}
}
int lastIndexOf = newFileName.lastIndexOf("."); String fileSuffix = lastIndexOf >= 0 ? newFileName.substring(lastIndexOf + 1).toLowerCase() : ""; fileSuffix = newFileName.toLowerCase().endsWith(".tar.gz") ? "tar.gz" : fileSuffix; return fileSuffix; } }