zoukankan      html  css  js  c++  java
  • 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;
        }
    
    }
  • 相关阅读:
    数据结构与算法简记--拓扑排序
    数据结构与算法简记--redis有序集合数据结构-跳表
    数据结构与算法简记--动态规划实战
    数据结构与算法简记--动态规划理论
    数据结构与算法简记--动态规划初识
    数据结构与算法简记--回溯算法
    数据结构与算法简记--分治算法
    数据结构与算法简记--贪心算法
    数据结构与算法简记--搜索算法
    数据结构与算法简记--图
  • 原文地址:https://www.cnblogs.com/bao-bei/p/13058998.html
Copyright © 2011-2022 走看看