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>
<!--Start row-->
<div id="myDropzone" class="dropzone form-control"></div>
<!-- end row -->
<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, //关闭自动上传功能,默认会true会自动上传,也就是添加一张图片向服务器发送一次请求
uploadMultiple: false,
parallelUploads: 5,
maxFiles: 5,
maxFilesize: 1,
addRemoveLinks: true,
dictFallbackMessage: '不好意思,您的浏览器不支持!', //如果浏览器不支持,默认消息将被替换为这个文本。默认为 “Your browser does not support drag'n'drop file uploads.”。
dictInvalidFileType: '该文件不允许上传', //如果文件类型不匹配时显示的错误消息。
dictResponseError: '上传失败,请稍后重试', //如果服务器响应是无效的时候显示的错误消息。 {{statusCode}} ` 将被 替换为服务器端返回的状态码。
init: function () {
var submitButton = $("#submit")
myDropzone = this; // closure
//为上传按钮添加点击事件
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, //并行上传文件数量,最大为8
maxFiles: 5, //最大提交文件数量,为null时,则全部提交
maxFilesize: 3, //文件大小,以MB为单位
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) {
//by xinlei
//将name用webkitRelativePath替换
let name = files[i].name
files[i].webkitRelativePath ? name = files[i].webkitRelativePath: true
formData.append(this._getParamName(i), files[i], name);
// formData.append(this._getParamName(i), files[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 最后
贴一下公众号吧!欢迎关注!