【工作笔记】Dropzone实现文件分块上传

创建时间:2021-08-13

一、问题与思路

1.1 开发过程中的附件问题

在维护SSH项目的过程中(其实也是从别人那里接手),遇到了附件上传的一个问题。需求:将上传最多2MB文件的限制,改为上传最多5MB。

2.2 原因与思路

为了实现需求(拿好饭碗),首先找一找附件上传限制的原因。根据前人的思路,大概有以下几点:

  • JS代码中,有判断文件大小限制的地方,这里改成5MB–没有限制提示了,但是上传大于2MB的文件报错

  • Tomcat7本身文件上传限制

    • 修改conf/server.xml, 增加maxPostSize=“10485760”

    •  <Connector port="8080" protocol="HTTP/1.1"
                     connectionTimeout="20000"
                     redirectPort="8443"
                     maxPostSize="10485760" />
      
    • 在本地测试的时候,依旧无法上传

  • 网络上,也有关于修改struts.xml的说法

    • <constant name="struts.multipart.maxSize" value="10000000"/>
      
    • 修改以后也没有效果

  • 在无法上传单的大文件的情况下,也查了一些关于分布式存储系统的知识

    • 考虑把文件上传方式改一下,不上传到项目里,而是上传到分布式存储系统中。但是到这样做工程量比较大,因为要专门部署一个文件系统,所以没有实现。
    • 最后,想到了在前端将文件分块(分段),分多次上传到后端,再在后端合并,而Dropzone***(页面附件上传插件)正好有分块上传的功能,比较容易实现。但是,需要使用比较高版本的Dropzone*,有没有兼容问题未知,我使用的是version 5.2
  • 还有一点,就是生产环境中,使用到了Nginx代理

    • 需要在配置文件中,增加client_max_body_size

二、解决过程

2.1 Dropzone使用介绍

2.2 前端代码参考

(1)网上的例子(项目是PHP)
// 网上的例子(项目是PHP)
function initDropzone(dropzone_id){
	Dropzone.autoDiscover = false;// 抑制Uncaught Error: Dropzone already attached.错误
	// 文件上传
	var dropzone = new Dropzone('#' + dropzone_id,{
	    previewTemplate: document.querySelector('#preview-template').innerHTML,
        url: "/contributes/fileUpload",
        paramName: "file",
        addRemoveLinks: true,
        dictRemoveFile: '<div class="glyphicon glyphicon-trash" aria-hidden="true" style="color:#00a881;"></div>',
        dictCancelUpload: "<div class=\"glyphicon glyphicon-trash\" aria-hidden=\"true\" style=\"color:#00a881;\"></div>",
        dictMaxFilesExceeded: '最大' + allowedMaxFiles[aspSelected] + '枚の画像をアップロードできます。',
        autoProcessQueue:true,
        uploadMultiple:false, //开启分段上传的话,这个配置项要设为false
        clickable:true,
        maxFiles: 9, //最多可以传多少个文件
        maxFilesize: 4096, // MB
        parallelUploads: 1, // 同时上传多少张
        chunking: true, // 开启分段上传
        chunkSize: 1048576000,//每次分段的size,单位byte,1024*1024*1000=104857600,
        timeout: 300000,
        acceptedFiles: "image/*,.mp4,.MOV,.wmv",
        params: function (files, xhr, chunk) { 
        // 此处拼装分段上传时参数,会伴随每个分段请求post过去
            if (chunk) {
                return {
                    dzUuid: chunk.file.upload.uuid, // 当前上传文件的uuid
                    dzChunkIndex: chunk.index,// 第几个片段
                    dzTotalFileSize: chunk.file.size,// 文件总size
                    dzCurrentChunkSize: chunk.dataBlock.data.size, // 当前片段的size
                    dzTotalChunkCount: chunk.file.upload.totalChunkCount, // 总共分几段上传
                    dzChunkByteOffset: chunk.index * this.options.chunkSize, // 
                    dzChunkSize: this.options.chunkSize,
                    dzFilename: chunk.file.name
                };
            }
        },
        init: function() {
        	this.on("addedfile", function(file) {    
                // 添加文件的时候想进行的操作
            });
        	this.on("removedfile", function(removedfile) {
                // 删除文件时的操作
            });
            this.on("success", function(data) {
                // 一个文件上传成功后的操作
            });
            this.on("error", function(file, message) {
            	alert(message);
                this.removeFile(file);
            });
            this.on("complete", function(file) {
                // 文件上传完成后的操作
            });
            this.on("processing", function(file) {});
            this.on("uploadprogress", function(progress,p2) {});
            this.on("totaluploadprogress", function(p1, p2, p3) {});
            this.on("sending", function(file) {});
            this.on("queuecomplete", function(progress) {});
        }
    });
}
(2)项目(后端为Java,Struts2项目)中的例子
<div id="myDropzone">
   ......
</div>
<script type="text/javascript">
    Dropzone.autoDiscover = false;
    // 项目(后端为Java,Struts2项目)中的例子 
    var myDropzone = new Dropzone("#myDropzone", {
                url: "",
                addRemoveLinks: true,
                paramName:"imgFile",
                method: 'post',
                dictRemoveFile:"删除",
                dictFileTooBig:"文件过大",
                maxFilesize: 5, // MB
                parallelUploads: 1, // 同时上传多少张
                //文件过大----修改
                dictMaxFilesExceeded:"不能上传更多附件",
                filesizeBase: 1000, // 这里是基本数吗?
                uploadMultiple: false, //开启分段上传的话,这个配置项要设为false
                chunking: true, // 开启分段上传
                chunkSize: 2000000, // 分块大小 1000*1000*2
                params: function (files, xhr, chunk) {
                    // 此处拼装分段上传时参数,会伴随每个分段请求post过去
                    if (chunk) {
                        // 为分块文件名添加"_xx",来区分顺序
                        let dzFilename = chunk.file.name + "_" + chunk.index;
                        // 这里让后端的能用一个DzChunk对象,接收参数
                        let dz = {
                            "dzChunk.dzUuid": chunk.file.upload.uuid, // 当前上传文件的uuid
                            "dzChunk.dzChunkIndex": chunk.index,// 第几个片段
                            "dzChunk.dzTotalFileSize": chunk.file.size,// 文件总size
                            "dzChunk.dzCurrentChunkSize": chunk.dataBlock.data.size, // 当前片段的size
                            "dzChunk.dzTotalChunkCount": chunk.file.upload.totalChunkCount, // 总共分几段上传
                            "dzChunk.dzChunkByteOffset": chunk.index * this.options.chunkSize, //
                            "dzChunk.dzChunkSize": this.options.chunkSize,
                            "dzChunk.chunkName": dzFilename,
                            "dzChunk.fileName": chunk.file.name
                        };
                        return dz;
                    }
                },
                init: function() {
                    this.on("removedfile", function(file) {});
                },
                sending: function(file, xhr, formData) {
                },
                success: function (file, response, e) {
                    setTimeout(function(){
                        // var index = parent.layer.getFrameIndex(window.name); //先得到当前iframe层的索引
                        // parent.layer.close(index);
                        layer.closeAll();
                        // parent.refreshPage("disabledAssistAuditLists.action");
                        // 上传完以后刷新一下
                        location.reload();
                    },1000);
                }
            });
</script>

  • HTML部分的代码,就请自己实现了
  • 使用谷歌开发工具(F12),调试,看一下请求的样子

2.3 后端代码

  • Java后端,实现文件的保存合并,网上的参考有很多,这里就写一下自己的实现,比较简陋

  • request请求处理,保存文件的方法:略

  • 合并文件的方法

    /**
     * 获取dirSrc目录下,包含prefix(前缀/文件名)的分段文件list集合。
     * 分段文件名,"a.jpg_0","a.jpg_1",命名规则由前端来定
     * @param dirSrc
     * @param prefix
     * @return
     * @throws IOException
     */
    public static List<File> getChunkFiles(String dirSrc, String prefix) throws IOException {
        File dir = new File(dirSrc);
        if (!dir.exists() || !dir.isDirectory()) {
            return null;
        }
        File[] files = dir.listFiles();
        if (files == null || files.length == 0) {
            return null;
        }
        List<File> fileList = new ArrayList<>(files.length);
        Map<Integer, File> map = new HashMap<>();
        for (File file : files) {
            String fileName = file.getName();
            if (fileName.contains(prefix) && fileName.contains("_")) {
                String[] s = fileName.split("_");
                map.put(Integer.valueOf(s[s.length - 1]), file);
            }
        }
        for (Map.Entry<Integer, File> m : map.entrySet()) {
            fileList.add(m.getKey(), m.getValue());
        }
        return fileList;
    }

    /**
     * 合并文件
     * 
     * @param list
     * @param fileOut
     */
    public static void mergeFileList(List<File> list, String fileOut) {
        OutputStream out = null;
        BufferedOutputStream bos = null;
        try {
            out = new FileOutputStream(new File(fileOut));
            bos = new BufferedOutputStream(out);
            if (list == null || list.size() == 0) {
                return;
            }
            for (File f : list) {
                if (f.isFile()) {
                    InputStream in = new FileInputStream(f);
                    BufferedInputStream bis = new BufferedInputStream(in);
                    byte[] bytes = new byte[1024 * 1024];
                    int len = -1;
                    while ((len = bis.read(bytes)) != -1) {
                        bos.write(bytes, 0, len);
                    }
                    bis.close();
                    in.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭IO流
            try {
                if (bos != null) {
                    bos.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

三、总结

3.1 注意事项

  1. 使用Dropzone进行文件上传,是进行多次post请求,如果遇到其中一次上传失败,该如何解决?

四、参考文档

  1. Dropzonejs + php 实现大文件分段上传
  2. Java下合并多个文件
  3. 通过java 来实现对多个文件的内容合并到一个文件中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

行走中思考

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值