springboot断点上传视频至minio

167 阅读3分钟

controller

@Api(value = "大文件上传接口",tags="大文件上传接口")
@RestController
public class BigFilesController {
    @Autowired
    MediaFileService mediaFileService;

    @ApiOperation(value = "文件上传前检查文件")
    @PostMapping("/upload/checkfile")
    public RestResponse<Boolean> checkFile(@RequestParam("fileMd5")String fileMd5)throws Exception{

        return mediaFileService.checkFile(fileMd5);
    }

    @ApiOperation(value = "分块文件上传前的检测")
    @PostMapping("/upload/checkchunk")
    public RestResponse<Boolean> checkchunk(@RequestParam("fileMd5") String fileMd5,
                                            @RequestParam("chunk") int chunk) throws Exception{
        return mediaFileService.checkChunk(fileMd5,chunk);
    }

    @ApiOperation(value = "上传分块文件")
    @PostMapping("/upload/uploadchunk")
    public RestResponse uploadchunk(@RequestParam("file")MultipartFile file,
                                    @RequestParam("fileMd5") String fileMd5,
                                    @RequestParam("chunk") int chunk)throws Exception{

        return mediaFileService.uploadChunk(fileMd5,chunk,file.getBytes());
    }

    @ApiOperation(value = "合并文件")
    @PostMapping("/upload/mergechunks")
    public RestResponse mergechunks(@RequestParam("fileMd5")  String fileMd5,
                                    @RequestParam("fileMame") String fileName,
                                    @RequestParam("chunkTotal") int chunkTotal)throws Exception{
        //测试数据
        Long companyId = 1232141425L;
        UploadFileParamsDto uploadFileParamsDto = new UploadFileParamsDto();
        uploadFileParamsDto.setFileType("001002");
        uploadFileParamsDto.setTags("视频");
        uploadFileParamsDto.setRemark("");
        uploadFileParamsDto.setFilename(fileName);
        return mediaFileService.mergechunks(companyId,fileMd5,chunkTotal,uploadFileParamsDto);
    }
}

service

/**当前代理对象调用的入库接口*/
@Transactional
 public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket,String objectName);

 /**
  * @description 检查文件是否存在
  */
 public RestResponse<Boolean> checkFile(String fileMd5);

 /**
  * @description 检查分块是否存在
  */
 public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex);

 /**
  * @description 上传分块
  */
 public RestResponse uploadChunk(String fileMd5,int chunk,byte[] bytes);

 /**
  * @description 合并分块
  */
 public RestResponse mergechunks(Long companyId,String fileMd5,int chunkTotal,UploadFileParamsDto uploadFileParamsDto);
}

impl

@Slf4j
@Service
public class MediaFileServiceImpl implements MediaFileService {
    @Autowired
    MediaFilesMapper mediaFilesMapper;

    @Autowired
    MinioClient minioClient;

    //注入当前代理对象MediaFileService
    @Autowired
    MediaFileService currentProxy;

    //普通文件(照片)存储桶
    @Value("${minio.bucket.files}")
    private String bucket_Files;

    //视频文件存储桶
    @Value("${minio.bucket.videofiles}")
    private  String bucket_videofiles;
   

 


    /**检查文件是否存在*/
    @Override
    public RestResponse<Boolean> checkFile(String fileMd5) {
        //文件在数据库记录并且也在minio系统才算存在
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles==null){
            return RestResponse.success(false);
        }
        //检查是否在minio上是否存在
        try {
            GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(mediaFiles.getBucket()).object(mediaFiles.getFilePath()).build();
            InputStream stream = minioClient.getObject(getObjectArgs);
            if (stream==null){
                return RestResponse.success(false);
            }
        }catch (Exception e){
            XueChengPlusException.cast("系统异常");
        }

        return RestResponse.success(true);
    }
    

    /**检查分块是否存在*/
    @Override
    public RestResponse<Boolean> checkChunk(String fileMd5, int chunkIndex) {
        //获取分块文件所在目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件具体路径
        String chunkPath=chunkFileFolderPath+chunkIndex;
        //查询是否存在
        try {
            GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(bucket_videofiles).object(chunkPath).build();
            InputStream stream = minioClient.getObject(getObjectArgs);
            if (stream==null){
                return RestResponse.success(false);
            }
        }catch (Exception e){
            XueChengPlusException.cast("系统异常");
        }
        return RestResponse.success(true);
    }
    

    /**获取minio上分块文件目录*/
    public String getChunkFileFolderPath(String fileMd5){
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + "chunk" + "/";
        //return fileMd5.charAt(0)+"/"+fileMd5.charAt(1)+"/"+fileMd5+"/"+"chunk"+"/";
    }


   /**上传分块*/
    @Override
    public RestResponse uploadChunk(String fileMd5, int chunk, byte[] bytes) {
        //获取分块文件所在目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //得到分块文件具体路径
        String chunkPath=chunkFileFolderPath+chunk;
        try {
            //上传至minio
            addMediaFilesToMinIO(bytes,bucket_videofiles,chunkPath);
            return RestResponse.success(true);
        } catch (Exception e) {
            log.error(e.getMessage());
            XueChengPlusException.cast("上传过程失败");
        }
        return RestResponse.success(false);
    }


    /**合并分块*/
    @Override
    public RestResponse mergechunks(Long companyId, String fileMd5, int chunkTotal, UploadFileParamsDto uploadFileParamsDto) {

        //下载所有分块文件
        File[] chunkFiles = checkChunkStatus(fileMd5, chunkTotal);
        //扩展名
        String fileName = uploadFileParamsDto.getFilename();
        String extName = fileName.substring(fileName.lastIndexOf("."));
        //创建临时文件作为合并文件
        File mergeFile = null;
        try {
            mergeFile = File.createTempFile(fileMd5, extName);
        } catch (IOException e) {
            XueChengPlusException.cast("合并文件过程中创建临时文件出错");
        }

        try {
            //开始合并
            byte[] b = new byte[1024];
            try(RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");) {
                for (File chunkFile : chunkFiles) {
                    try (FileInputStream chunkFileStream = new FileInputStream(chunkFile);) {
                        int len = -1;
                        while ((len = chunkFileStream.read(b)) != -1) {
                            //向合并后的文件写
                            raf_write.write(b, 0, len);
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                XueChengPlusException.cast("合并文件过程中出错");
            }
            log.debug("合并文件完成{}",mergeFile.getAbsolutePath());
            uploadFileParamsDto.setFileSize(mergeFile.length());

            try (InputStream mergeFileInputStream = new FileInputStream(mergeFile);) {
                //对文件进行校验,通过比较md5值
                String newFileMd5 = DigestUtils.md5Hex(mergeFileInputStream);
                if (!fileMd5.equalsIgnoreCase(newFileMd5)) {
                    //校验失败
                    XueChengPlusException.cast("合并文件校验失败");
                }
                log.debug("合并文件校验通过{}",mergeFile.getAbsolutePath());
            } catch (Exception e) {
                e.printStackTrace();
                //校验失败
                XueChengPlusException.cast("合并文件校验异常");
            }

            //将临时文件上传至minio
            String mergeFilePath = getFilePathByMd5(fileMd5, extName);
            try {

                //上传文件到minIO
                addMediaFilesToMinIO(mergeFile.getAbsolutePath(), bucket_videofiles, mergeFilePath);
                log.debug("合并文件上传MinIO完成{}",mergeFile.getAbsolutePath());
            } catch (Exception e) {
                e.printStackTrace();
                XueChengPlusException.cast("合并文件时上传文件出错");
            }

            //入数据库
            MediaFiles mediaFiles = currentProxy.addMediaFilesToDb(companyId, fileMd5, uploadFileParamsDto, bucket_videofiles, mergeFilePath);
            if (mediaFiles == null) {
                XueChengPlusException.cast("媒资文件入库出错");
            }

            return RestResponse.success();
        } finally {
            //删除临时文件
            for (File file : chunkFiles) {
                try {
                    file.delete();
                } catch (Exception e) {

                }
            }
            try {
                mergeFile.delete();
            } catch (Exception e) {

            }
        }
    }



    /**遍历分块并下载*/
    private File[] checkChunkStatus(String fileMd5,int chunkTotal){
        //获取分块文件目录
        String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
        //分块文件数组
        File[] chunkFiles=new File[chunkTotal];
        //遍历分块
        for (int i = 0; i < chunkTotal; i++) {
            //拼接路径
            String chunkFilePath=chunkFileFolderPath + i;
            //分块文件
            File chunkFile=null;
            try {
                chunkFile = File.createTempFile("chunk", null);
            }catch (Exception e){
                log.error(e.getMessage());
                XueChengPlusException.cast("创建临时分块文件出错");
            }
            chunkFile = this.downloadFileFromMinIO(chunkFile, bucket_videofiles, chunkFilePath);
            chunkFiles[i]=chunkFile;
        }
        return chunkFiles;
    }



    /**下载分块方法*/
    public File downloadFileFromMinIO(File file,String bucket,String objectName){
        InputStream fileInputStream = null;
        OutputStream fileOutputStream = null;
        try {
            fileInputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectName)
                            .build());
            try {
                fileOutputStream = new FileOutputStream(file);
                IOUtils.copy(fileInputStream, fileOutputStream);

            } catch (IOException e) {
                XueChengPlusException.cast("下载文件"+objectName+"出错");
            }
        } catch (Exception e) {
            e.printStackTrace();
            XueChengPlusException.cast("文件不存在"+objectName);
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return file;
    }



    /**将文件上传到minIO,传入文件绝对路径*/
    public void addMediaFilesToMinIO(String filePath, String bucket, String objectName) {
        //扩展名
        String extension = null;
        if(objectName.indexOf(".")>=0){
            extension = objectName.substring(objectName.lastIndexOf("."));
        }
        //获取扩展名对应的媒体类型
        String contentType = getMimeTypeByExtension(extension);
        try {
            minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucket)
                            .object(objectName)
                            .filename(filePath)
                            .contentType(contentType)
                            .build());
        } catch (Exception e) {
            e.printStackTrace();
            XueChengPlusException.cast("上传文件到文件系统出错");
        }
    }

    private String getFilePathByMd5(String fileMd5,String fileExt){
        return   fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/" + fileMd5 + "/" +fileMd5 +fileExt;
    }
    
    
     /**入库方法
    事务加在入库方法,文件上传方法如文件过大上传时间就会变长事务持续时间也会变长*/
    @Transactional
    public MediaFiles addMediaFilesToDb(Long companyId,String fileMd5,UploadFileParamsDto uploadFileParamsDto,String bucket_Files,String objectName) {
        MediaFiles mediaFiles = mediaFilesMapper.selectById(fileMd5);
        if (mediaFiles==null){
            mediaFiles=new MediaFiles();
            //拷贝基本信息
            BeanUtils.copyProperties(uploadFileParamsDto, mediaFiles);
            mediaFiles.setId(fileMd5);
            mediaFiles.setFileId(fileMd5);
            mediaFiles.setCompanyId(companyId);
            mediaFiles.setUrl("/" + bucket_Files + "/" + objectName);
            mediaFiles.setBucket(bucket_Files);
            mediaFiles.setCreateDate(LocalDateTime.now());
            mediaFiles.setStatus("1");
            mediaFilesMapper.insert(mediaFiles);
        }
        return mediaFiles;
    }

}