0 前言
1.项目需要上传文件和大量的文件夹,页面只有一个input file标签会很丑,偶然间得知dropzone类库,
决定使用。
2. 项目后端采用springmvc接收,调用minio代码上传至本地文件数据库。
3. 本以为只是美化一下界面,很快就能实现,没想到中间遇到了太多的问题。
4. 经过两天时间,网上查资料和自己摸索,终于实现了我想要的所有功能
5. 功能包括:单个文件上传、多个文件上传、大量文件夹(上千个)上传、文件和表单一起上传等。
6. 代码中用到jquery
1 dropzone介绍
DropzoneJS是一个提供文件拖拽上传并且提供图片预览的开源类库. 它是轻量级的,不依赖任何其他类库(如JQuery)并且高度可定制.
简而言之,有个强大的文件拖拽功能和好看的界面,网站链接如下: 中文网站 英文网站 GitHub
2 单文件上传
2.1 问题:文件与表单一同提交
刚开始就遇到了问题,dropzone默认情况下文件拖放到指定区域后就自动进行上传,而不需要点击提交,但是我后台需要将一些表单数据和文件一起提交,于是问题出现了。
2.2 解决:点击提交按钮,将文件和表单一起发送
- dropzone有两种穿件文件拖放区域的方式,一种是
<form> ,一种是<div> ,既然要和其他数据一起提交,表单中嵌套表单是不行的,于是采用<div> 的形式,下面是页面:
<form class="form-horizontal" action="" method="post" enctype="multipart/form-data">
<input class="form-control" value="default" type="text" name="folderName" id="folderName">
<textarea class="form-control" rows="5" name="siteDescription" id="siteDescription"></textarea>
<div id="myDropzone" class="dropzone form-control"></div>
<button type="submit" class="btn btn-primary" id="submit">Submit</button>
</form>
- 监听按钮的点击事件,点击后发送数据
2.1 首先需要禁用自动查找,采用Dropzone.autoDiscover = false 2.2 取消自动提交,autoProcessQueue : false, 2.3 文件的名称设置,相当于input 标签的name 属性,默认是file ,注意名称需要和后台一致 2.4 关闭多文件上传,uploadMultiple: false ,否则会拿不到文件,这个下面会解释 2.5 监听按钮点击事件,阻止默认事件,若区域内有文件,则调用myDropzone.processQueue(); 方法提交,如下图 2.6 监听sending 方法,当文件发送时,将其他表单数据一起发送,如下图
监听按钮点击事件
$("#submit").on("click", function (e) {
e.preventDefault();
e.stopPropagation();
if (myDropzone.getAcceptedFiles().length !== 0) {
myDropzone.processQueue();
}
});
将其他表单数据一起发送
this.on("sending", function (data, xhr, formData) {
formData.append("folderName", $("#folderName").val());
formData.append("siteDescription", $("#siteDescription").val());
});
下面是完整的js 代码
Dropzone.autoDiscover = false;
var myDropzone = new Dropzone("#myDropzone", {
url: "/file/upload",
method:"post",
dictDefaultMessage: '拖动文件至此或者点击上传',
paramName: "siteFile",
autoProcessQueue: false,
uploadMultiple: false,
parallelUploads: 5,
maxFiles: 5,
maxFilesize: 1,
addRemoveLinks: true,
dictFallbackMessage: '不好意思,您的浏览器不支持!',
dictInvalidFileType: '该文件不允许上传',
dictResponseError: '上传失败,请稍后重试',
init: function () {
var submitButton = $("#submit")
myDropzone = this;
submitButton.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
if (myDropzone.getAcceptedFiles().length !== 0) {
myDropzone.processQueue();
}
});
this.on("sending", function (data, xhr, formData) {
formData.append("folderName", $("#folderName").val());
formData.append("siteDescription", $("#siteDescription").val());
});
this.on("success", function (file, data) {
swal("上传成功!", file.name,"success")
});
}
});
这个问题在GitHub上有例子,这也是后来才知道的,具体链接见 文件和表单一起提交(GitHub)
3 多文件上传
问题:多文件上传时文件参数获取不到
3.1 多文件上传时,有几个参数需要提前了解下
paramName: "siteFile",
uploadMultiple: true,
parallelUploads: 5,
maxFiles: 5,
maxFilesize: 3,
3.2 springmvc接受参数配置:
@RequestParam(name = "siteFile") MultipartFile file,
3.3 提交后,springmvc无法接收到相同文件名的文件,导致文件上传失败,如下图
解决:详细过程如下
3.4 提示请求错误,然后去找请求的问题,后面发现请求头的Form Data 是这样的: 可以看到,多文件上上传时附带的是两个文件,而且是数组的形式,所以springmvc找不到到名为siteFile 的文件,于是报错了
3.5 网上找了一种解决办法,将siteFile 改成siteFile[] ,验证之后,发现还是无法接收到参数
3.6 后来通过修改源代码的形式进行解决,将siteFile[0] siteFile[1]... 全改为siteFile ,这样后台自然就能接收了,修改如下
return "" + this.options.paramName + (this.options.uploadMultiple ? "[" + n + "]" : "");
改为
return "" + this.options.paramName + (this.options.uploadMultiple ? "" : "");
文件成功上传!
这个问题困扰我很久,现在也算是圆满解决了!
4 文件夹上传
问题:项目要使用文件夹上传功能,dropzone默认是文件上传
解决:如下
4.1 要用到file 的webkitdirectory 功能(有局限,对部分浏览器如谷歌,兼容) 4.2 在dropzone的init() 函数中添加如下代码,开启文件夹选择
init: function () {
this.hiddenFileInput.setAttribute("webkitdirectory", true);
}
4.3 将文件夹中的所有文件上传,需要开启上述的多文件上传功能uploadMultiple: true ,同时修改源代码 4.4 后台接收
@RequestParam("siteFiles") List<MultipartFile> files
5 文件夹上传时路径问题
问题:需要保留原来的目录结构
采用dropzone上传时,获取的文件名不带相对路径,如file/a/b.txt ,只能获取到b.txt ,无法获取前面的路径。
但是我需要保留原有的文件结构
解决:如下
5.1 第一种方法:当文件发送时,将文件的相对路径一起通过参数的形式,通过formData.append() 方法绑定到表单上。 文件的相对路径在File 对象的webkitRelativePath 中,如下 将路径存储到数组上:
init: function () {
let webkitdirectorys = []
this.on("addedfile",function(file){
webkitdirectorys.push(file.webkitRelativePath)
});
this.on("sending", function (data, xhr, formData) {
formData.append("webkitdirectorys ", webkitdirectorys );
});
}
最后,文件路径也保留了,文件也上传成功了。
但是有个缺陷,就是webkitdirectorys 中的路径和文件无法一一对应,可能会出问题,同时,文件数量非常多时,表单参数会非常多,可能会降低传输效率?
有强迫症的我不想看到表单传一大堆数据到后台!!!
5.2 第二种方法,修改源代码
目前网上没有看到类似的处理方式,希望对大家有帮助!
- 通过debug可以看到,后台的文件只有两个属性,有一个是
name ,对应的是前端File 对象的name 属性,我需要将name 用webkitRelativePath 替代 - dropzone文件上传函数入口
processQueue() ,进去
myDropzone.processQueue();
- 找到处理多文件的函数,进去
return this.processFiles(queuedFiles.slice(0, parallelUploads - processingLength));
- 找到提交文件的函数,进去
return this.uploadFiles(files);
- 找到文件发送逻辑
for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) {
formData.append(this._getParamName(i), files[i], files[i].name);
}
return xhr.send(formData);
- 发现文件名是通过
formData.append() 方法进行追加的,修改它的文件名
for (i = _m = 0, _ref5 = files.length - 1; 0 <= _ref5 ? _m <= _ref5 : _m >= _ref5; i = 0 <= _ref5 ? ++_m : --_m) {
let name = files[i].name
files[i].webkitRelativePath ? name = files[i].webkitRelativePath: true
formData.append(this._getParamName(i), files[i], name);
}
return xhr.send(formData);
- 之后通过
getOriginalFilename() 方法获取的文件名,就是webkitRelativePath 了,如file/a/b.txt ,之后在进行处理即可!
6 上千个文件上传
问题:dropzone多文件上传最多同时能上传8个文件
注意:当maxFiles:null 时,可以上传该文件夹下的所有文件!
若文件超过8个,就无法一次性上传,而需要再次点击提交按钮,才会继续上传剩下的文件。 如果我需要上传大量的文件,不可能每次点击提交按钮!
解决:事件监听
监听success 事件,当文件上传成功后,就会调用该事件, 每当有文件上传成功后,就继续调用方法上传队列中的文件,直到队列中的文件为空,如下
this.on("success", function (file,data){
if (myDropzone.getQueuedFiles().length !== 0) {
myDropzone.processQueue();
}else {
swal("上传成功!", file.name, "success")
}
})
测试时成功的!
7 多文件删除
选择了文件夹下的所有文件,不可能一个个删除,现需要一个按钮来将区域内的文件全部清除,如下
removeButton.on("click", function(){
myDropzone.getAcceptedFiles().forEach(element => {
myDropzone.removeFile(element)
})
myDropzone.hiddenFileInput.setAttribute("webkitdirectory", true);
});
注意当文件清除完成后,控件中的webkitdirectory 属性会消失,需要重新添加!
8 总结
1. 本来以为能够轻松解决的问题,却花费了我两天的时间,还是值得纪念一下的
2. 当然也收获很多,最大的收获就是问题解决后的开心
3. 同时也明白了太多地方的不足,以及面对问题缺乏冷静的思考能力
4. 加油
9 最后
贴一下公众号吧!欢迎关注!
|