文章目录
创建时间: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使用介绍
- Dropzone文档网站
- 实现分块上传,要使用到配置属性:
- chunking
- chunkSize
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 注意事项
- 使用Dropzone进行文件上传,是进行多次post请求,如果遇到其中一次上传失败,该如何解决?