spring boot实现大文件上传【分片上传】插件(x-file-storage),同时支持本地、FTP、SFTP、阿里云 OSS、腾讯云 COS、MinIO、 Amazon S3等

这篇文章 主要说一下 前端 分片后 传给后端 利用 插件(x-file-storage)实现 大文件上传 方案

插件(x-file-storage) 具体这个插件怎么使用 简单的文件上传 看下面的这篇博客 有详细说明
博客地址:

https://blog.csdn.net/Drug_/article/details/137794530

如果大家没用过 x-file-storage 这个插件 建议先看一下 上面得这个博客 再来用这个大文件上传
因为这个博客里没有讲解 引入依赖 配置 那些东西
下面我直接分享 大文件上传的代码 这个插件的配置 等等信息 参考上面博客地址

先给大家 看一下插件源码里 提供的分片上传的 demo
在这里插入图片描述
但是 他提供的例子 不太适合 前后端 分离开发 需要根据他的例子 改
下面 以我的理解 说一下例子中 哪里不合理

在这里插入图片描述
在这里插入图片描述

下面是我写的方案 只测试了 本地 大文件上传 没问题 代码应该还存在细节问题 大家请参考使用 如果有更好的方案 也可以留言分享给我 我再补充 博客
控制器

   //调用初始化 他会在他的file_detail 这个表里写一条数据 
    // 如果取消上传 或者异常 建议再写一个删除接口 把这条数据删除
    @ApiOperation(value = "初始化上传准备 filename 文件名 size 文件大小")
    @PostMapping("initUpload")
    public Map initUpload(@RequestParam("filename") String filename,@RequestParam("size") long size){
        return filesUtil.initUpload("dmt", filename,  size);
    }

    @ApiOperation(value = "大文件上传")
    @PostMapping("uploadBig")
    public R uploadBig(@RequestParam("file") MultipartFile file,  //分片文件
                       @RequestParam("chunkNumber") int chunkNumber,  //分片数
                       @RequestParam("totalChunks") int totalChunks,  //分片总数
                       @RequestParam("fileInfo") String fileInfo  //initUpload 初始化的返回值
    ){
        FileInfo fileInfo2 =  JSON.parseObject(fileInfo, FileInfo.class);
        Map<String, Objects> fileInfo1 = filesUtil.uploadFileBig(file, chunkNumber,totalChunks,fileInfo2);
        return R.data(fileInfo1);
    }

下面的代码 就是 为了让 上面截图提到的 代码 只初始化一次 也是写在 filesUtil 类里

  @Autowired
    private FileStorageService fileStorageService;//注入实列
    public R initUpload(String fileDir,String filename, long size) {
        if (StringUtils.isEmpty(fileDir)) {
            fileDir = "files";
        }
         this.init();
         //isSupportMultipartUpload
         // 这个判断是根据 fileStorageService 默认上传方式进行判断的 他的默认上传方式是本地上传
         // 他不会根据this.init(); 初始化了 然后修改默认上传方式 如果需要做"此存储类型不支持分片上传!" 这个判断 
         // 需要再FileStorageInit类里  init() 方法里 初始化的地方 增加一行代码 修改一下他的初始化值
         //  fileStorageService.getProperties().setDefaultPlatform(this.filesystemType);
        MultipartUploadSupportInfo supportInfo = fileStorageService.isSupportMultipartUpload();
        if (!supportInfo.getIsSupport()) {
            throw new RuntimeException("此存储类型不支持分片上传!");
        }
        FileInfo fileInfo = fileStorageService
                .initiateMultipartUpload()
                .setPath(generateFilePath(fileDir))
                .setOriginalFilename(filename)
                .setSaveFilename(getFileName() + "." + getFileExtensionWithDot(Objects.requireNonNull(filename)))
                .setSize(size)
                .init();
        log.info("手动分片上传文件初始化成功:{}", fileInfo);
        Map<String,FileInfo> map=new HashMap<>();
        map.put("fileInfo",fileInfo);
        return R.data(map);
    }

如果上面这段代码 和分片上传的代码 在前后端分离的情况 写在一起 不合适。 多次初始化 就会导致 每次上传上来的分片数据 都会
创建一个新得文件夹 存放起来 比如你上传了50次 分片数据 他就会创建50个文件夹
这样 他正执行合并 数据得时候 就会合并失败!

初始化多次 会创建多个文件夹 就是setUploadId() 这个方法 在起作用 你可以给他设置一个值
比如 setUploadId(“tmp”) 临时目录 但是 文件存储表(file_detail) 这个setUploadId 目前发现是 是不能设置的
所以还是会出现多个文件夹
所以我认为最好得解决方案就是 把示例里的代码 拆分成两个接口 调用分片之前 先初始化返回给前端初始化数据,然后前端再调用分片的时候 一直带着这个初始化数据!

uploadFileBig() 上传方法 filesUtil 类里

  public Map<String, Objects> uploadFileBig(MultipartFile file2,  int partNumber2, int countNumber,FileInfo fileInfo) {

        
        FilePartInfo filePartInfo = fileStorageService
                .uploadPart(fileInfo, partNumber2, file2, (long) file2.getSize())
                .setProgressListener(new ProgressListener() {
                    @Override
                    public void start() {
                        System.out.println("分片 " + partNumber2 + " 上传开始");
                    }

                    @Override
                    public void progress(long progressSize, Long allSize) {
                        if (allSize == null) {
                            System.out.println("分片 " + partNumber2 + " 已上传 " + progressSize + " 总大小未知");
                        } else {
                            System.out.println("分片 " + partNumber2 + " 已上传 " + progressSize + " 总大小"
                                    + allSize + " " + (progressSize * 10000 / allSize * 0.01) + "%");
                        }
                    }

                    @Override
                    public void finish() {
                        System.out.println("分片 " + partNumber2 + " 上传结束");

                    }
                })
                .setHashCalculatorMd5()
                .setHashCalculatorSha256()
                .upload();

     
        // 这个 分片数 和 总数  一定要让前端确保 按顺序排队上传 1分片 成功 才能上传分片 2 然后分片3 等等 
        // 如果不按顺序  合并的时候 随便合并成功了 但是文件是不能用的
        if (partNumber2 == countNumber) {
            fileStorageService.completeMultipartUpload(fileInfo)
                    .setProgressListener(new ProgressListener() {
                        @Override
                        public void start() {
                            System.out.println("文件合并开始");
                        }

                        @Override
                        public void progress(long progressSize, Long allSize) {
                            if (allSize == null) {
                                System.out.println("文件已合并 " + progressSize + " 总大小未知");
                            } else {
                                System.out.println("文件已合并 " + progressSize + " 总大小" + allSize + " "
                                        + (progressSize * 10000 / allSize * 0.01) + "%");
                            }
                        }

                        @Override
                        public void finish() {
                            System.out.println("文件合并结束");
                        }
                    })
                    .complete();
            log.info("手动分片上传文件完成成功:{}", fileInfo);
            Map map = JSON.parseObject(JSON.toJSONString(fileInfo), Map.class);
            return map;
        } else {
            Map map = JSON.parseObject(JSON.toJSONString(filePartInfo), Map.class);
            return map;
        }


    }

思路2:
如果大家觉得 两个接口有点麻烦 我觉得合并成一个接口应该也行,就根据 文件名 加用户id 把初始化的数据 存到redis里
先判断redis 存在不 不存在就初始化 存在 就直接分片上传,上传完成后,再把redis里的数据删除!

个人建议 还是分开写两个接口好,如果后续需要做暂停 ,取消上传 、继续上传 应该都需要FileInfo 这个对象里的数据
目前我的需求里 还没涉及到 暂停 ,取消上传 、继续上传 但是我以我看到这个依赖的源码 我觉得是后续扩展这些功能
应该是需要 FileInfo 这个对象里的数据

前端调用方式
一个大文件上传 就初始化一次 两个大文件上传 就初始化2次
意思就是 一个文件 对应一个初始化 initUpload 一次后 就一直分片调用uploadBig 这个接口 一直到文件上传完成
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码里 像分片验证 就没写了 如果有需要 可以去看源码 例子里有代码。

 Assert.isTrue(SecureUtil.md5().digestHex(bytes).equals(hashInfo.getMd5()), "分片 MD5 对比不一致!");
            log.info("分片 MD5 对比通过");
            Assert.isTrue(SecureUtil.sha256().digestHex(bytes).equals(hashInfo.getSha256()), "分片 SHA256 对比不一致!");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Json____

您的鼓励是我创作的动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值