JAVA實現大檔案分片上傳斷點續傳

bug毁灭者發表於2024-03-17

直接上程式碼

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("\\")) {
            File file = new File(fileName);
            if (file.exists()) {
                newFileName = file.getName();
            }
        }
        int lastIndexOf = newFileName.lastIndexOf(".");
        String fileSuffix = lastIndexOf >= 0 ? newFileName.substring(lastIndexOf + 1).toLowerCase() : "";
        fileSuffix = newFileName.toLowerCase().endsWith(".tar.gz") ? "tar.gz" : fileSuffix;
        return fileSuffix;
    }
}

相關文章