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;
    }

}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM