• spring boot 实现Minio分片上传


    应用场景

    分片上传,就是将所要上传的文件,按照一定的大小,将整个文件分隔成多个数据块(我们称之为Part)来进行分别上传,上传完之后再由服务端对所有上传的文件进行汇总整合成原始的文件。

    分片上传的场景

    • 大文件上传

    • 网络环境环境不好,存在需要重传风险的场景

    分片上传的步骤

    没有
    前端上传文件的MD5值
    判断是否上传过文件
    创建文件存储用于存储分块
    前端上传文件的MD5值和分块文件的序号
    通过序号判断是否包含该分块
    前端上传分块
    存储分块
    是否是最后一块
    合并分块
    返回文件的存储路径

    检查文件的代码

    在文件第一次上传时,上传文件的md5值,从而判断文件是否存在minio中

    public Result<Boolean> checkFile(String fileMd5) {
            //正常做业务时应该先从数据库中查询
    
                //如果数据库存在再查询 minio
                GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                        .bucket(bucketName)
                        //                    todo 这里固定了文件的后缀,实际情况下应该从数据库开始查询,得到文件的路径
                        .object(getFilePathByMd5(fileMd5,"png"))
                        .build();
                //查询远程服务获取到一个流对象
                try {
                    FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
                    if(inputStream!=null){
                        //文件已存在
                        return Result.success(true);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            //文件不存在
            return Result.success(false);
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    检查分块的代码

    检查分块是前端把需要上传的文件经过大小计算后,算出分块的数量,然后把循环发送文件的md5值和分块序号,然后在minio中检查对应文件夹下是否有对应的分块,如果检查到某一处没有对应的分块,便知道传输中断的位置。

     public Result<Boolean> checkChunk(String fileMd5, int chunkIndex) {
    
            //根据md5得到分块文件所在目录的路径
            String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
    
            //如果数据库存在再查询 minio
            GetObjectArgs getObjectArgs = GetObjectArgs.builder()
                    .bucket(bucketName)
                    .object(chunkFileFolderPath+chunkIndex)
                    .build();
            //查询远程服务获取到一个流对象
            try {
                FilterInputStream inputStream = minioClient.getObject(getObjectArgs);
                if(inputStream!=null){
                    //文件已存在
                    return Result.success(true);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            //文件不存在
            return Result.success(false);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    上传分块的代码

        public Result uploadChunk(String fileMd5, int chunk, String localChunkFilePath) {
            //分块文件的路径
            String chunkFilePath = getChunkFileFolderPath(fileMd5) + chunk;
            //获取mimeType
            String mimeType = localChunkFilePath.substring(localChunkFilePath.lastIndexOf("."));
            //将分块文件上传到minio
            boolean b = addMediaFilesToMinIO(localChunkFilePath, mimeType, bucketName, chunkFilePath);
            if(!b){
                return Result.error("上传分块文件失败");
            }
            //上传成功
            return Result.success(true);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    合并分块的代码

    合并分块文件之前,需要检查文件是否和源文件相同,我们通过把分块合并后取文件的md5值和传输过来的MD5值作比较,如果相同则证明传输正确,把合并后的文件存入minio中,并清除分块文件

    public Result mergechunks(String fileMd5, int chunkTotal) {
            //分块文件所在目录
            String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
            //找到所有的分块文件
            List<ComposeSource> sources = Stream.iterate(0, i -> ++i)
                    .limit(chunkTotal).map(i -> ComposeSource.builder()
                            .bucket(bucketName)
                            .object(chunkFileFolderPath + i).build()).collect(Collectors.toList());
    
    
            //合并后文件的objectname
            String objectName = getFilePathByMd5(fileMd5, "png");
            //指定合并后的objectName等信息
            ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)//合并后的文件的objectname
                    .sources(sources)//指定源文件
                    .build();
            //===========合并文件============
            //报错size 1048576 must be greater than 5242880,minio默认的分块文件大小为5M
            try {
                minioClient.composeObject(composeObjectArgs);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("合并文件出错,bucket:{},objectName:{},错误信息:{}",bucketName,objectName,e.getMessage());
                return Result.error("合并文件异常");
            }
    
            //===========校验合并后的和源文件是否一致,视频上传才成功===========
            //先下载合并后的文件
            File file = downloadFileFromMinIO(bucketName, objectName);
            try(FileInputStream fileInputStream = new FileInputStream(file)){
                //计算合并后文件的md5
                String mergeFile_md5 = DigestUtils.md5Hex(fileInputStream);
                //比较原始md5和合并后文件的md5
                if(!fileMd5.equals(mergeFile_md5)){
                    log.error("校验合并文件md5值不一致,原始文件:{},合并文件:{}",fileMd5,mergeFile_md5);
                    return Result.error("文件校验失败");
                }
    
            }catch (Exception e) {
                return Result.error("文件校验失败");
            }
    
            //==============将文件信息入库============
    //        在做业务时要将得到的路径存入数据库
            //==========清理分块文件=========
            clearChunkFiles(chunkFileFolderPath,chunkTotal);
    
    
            return Result.success(true);
        }
    
        /**
         * 清除分块文件
         * @param chunkFileFolderPath 分块文件路径
         * @param chunkTotal 分块文件总数
         */
        private void clearChunkFiles(String chunkFileFolderPath,int chunkTotal){
            Iterable<DeleteObject> objects =  Stream.iterate(0, i -> ++i).limit(chunkTotal).map(i -> new DeleteObject(chunkFileFolderPath+ i)).collect(Collectors.toList());;
            RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build();
            Iterable<io.minio.Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
            //要想真正删除
            results.forEach(f->{
                try {
                    DeleteError deleteError = f.get();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
  • 相关阅读:
    $refs不是响应式的
    入行数据分析要知道什么是数据&数据分析
    亚马逊平台不给力?来Starday,告诉你什么是真正的高阶玩法
    【K8S】Kubernetes
    (附源码)计算机毕业设计ssm《Java EE开发技术》课程学习网站
    结构体的理解
    面向对象05:创建对象内存分析
    【MindSpore易点通】Transformer的编码和解码
    一文让前端搞懂shell编程
    2022 年杭电多校第十场补题记录
  • 原文地址:https://blog.csdn.net/studycodeday/article/details/133765176