本文对最近学习的Web文件上传方法做一些总结,主要用到了百度的Web Uploader。
本文目录:
- Web文件上传的过程分析
- Java基础的文件上传方法
- 图片上传生成缩略图
- 拖拽上传
- 文件分块上传
- 文件断点续传
1. 文件上传的过程分析
Web文件上传通过Http请求进行传输,可以通过Java工具进行接收。要注意包含文件上传内容的表单的类型必须为enctype=”multipart/form-data”
请求正文中的内容如下图:
2. Java基础的文件上传方法
最常见是使用Apache commons中的两个jar包实现Web文件上传
第一步是文件上传的jsp页面,上传文件和一个普通text输入框:
<form action="${pageContext.request.contextPath}/UploaderServlet" method="post" enctype="multipart/form-data">
选择要上传的文件:<input type="file" name="attach"/><br/>
普通数据:<input type="text" name="info"><br/>
<input type="submit" value="点击上传"/>
</form>
新建一个文件夹当做服务器保存上传的文件:F:uploader
接下来是新建一个servlet用于保存数据,接收到前台数据之后,对数据信息进行判断是普通数据还是文件(文件就是type=”file”),然后分别进行处理。文件就把它保存到服务器,就是前面新建的文件夹。
// 1. 创建DiskFileItemFactory对象,配置缓存信息
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2. 创建ServletFileUpload对象
ServletFileUpload sfu = new ServletFileUpload(factory);
// 3. 设置文件名称的编码
sfu.setHeaderEncoding("utf-8");
// 4. 开始解析文件
try {
List<FileItem> items = sfu.parseRequest(request);
// 服务器的目录
String serverPath = "F:/uploader";
// 5. 获取文件信息
for (FileItem item : items) {
// 6. 判断是文件还是普通的数据
if (item.isFormField()) {
// 普通数据
String fileName = item.getFieldName();
if (fileName.equals("info")) {
// 获取文件信息
String info = item.getString("utf-8");
System.out.println(info);
}
} else {
// 文件
// 获取文件的名称
String name = item.getName();
// 获取文件的实际内容
InputStream is = item.getInputStream();
// 保存文件
FileUtils.copyInputStreamToFile(is, new File(serverPath
+ "/" + name));
}
}
} catch (FileUploadException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
完成之后上传测试文件.txt以及输入文本信息弄浪的鱼文件,再看http请求头已经包含上传信息,然后文件夹会有文件,控制台输出弄浪的鱼:
3. 图片上传生成缩略图
从这里开始到后面的内容都用到了百度的Web Uploader,所以先下载Web Uploader,然后导入到工程中再将jsp页面进行修改
文件上传时会有三种状态:文件上传前,文件上传中和文件上传后。上传前可以进行一些初始操作,比如追加一些div用于显示上传信息;上传中显示上传的进度‘上传后显示上传完成。Web Uploader通过js对这三种状态进行监听,每个状态传递不同的参数就行。
所以先导入js和css并且在jsp中引用,注意有些地方用了jquery:
<link rel="stylesheet" type="text/css" href="${pageContext.request.contextPath}/css/webuploader.css">
<script type="text/javascript" src="${pageContext.request.contextPath}/js/jquery-1.7.2.js"> </script>
<script type="text/javascript" src="${pageContext.request.contextPath}/js/webuploader.js"> </script>
然后是网页代码
<div id="uploader">
<!-- 显示文件列表 -->
<ul id="fileList"></ul>
<!-- 选择文件区域 -->
<div id="filePicker">点击上传文件</div>
</div>
javascript初始化及注册三种状态监听
<script type="text/javascript">
//1.初始化WebUploader,以及配置全局参数
var uploader = WebUploader.create({
// swf文件路径
swf : "${pageContext.request.contextPath}/js/Uploader.swf",
// 文件接收服务端。
server : "${pageContext.request.contextPath}/UploaderServlet",
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick : '#filePicker',
// 自动上传
auto : true
});
//2. 选择文件后,文件信息队列展示
//注册fileQueued事件:当文件加入队列后触发
uploader.on("fileQueued",function(file){
//追加文件信息div
$("#fileList").append("<div id='" + file.id + "'class='fileInfo'><img/><span>" + file.name +
"</span><div class='state'>等待上传...</div><span class='text'><span></div>");
});
//3. 注册上传监听
//percentage:当前上传进度0-1
uploader.on("uploadProgress",function(file,percentage){
var id=$("#"+file.id);
//更新状态信息
id.find("div.state").text("上传中...");
//更新上传的百分比
id.find("span.text").text(Math.round(percentage*100)+"%");
});
//4. 注册上传完毕监听
//response:后台回送数据,json格式
uploader.on("uploadProgress",function(file,percentage){
//更新状态信息
$("#"+file.id).find("div.state").text("上传完毕");
});
</script>
~~~
生成缩略图,明显是在文件上传过程中生成的,所以在上传过程中的监听中添加生成缩略图的内容
~~~JavaScript
uploader.on("fileQueued",function(file){
//追加文件信息div
$("#fileList").append("<div id='" + file.id + "'class='fileInfo'><img/><span>" + file.name +
"</span><div class='state'>等待上传...</div><span class='text'><span></div>");
//生成缩略图:调用makeThumb()方法
//error:制造缩略图失败
//src:缩略图的路径
uploader.makeThumb(file,function(error,src){
var id = $("#" + file.id);
//如果失败,则显示不能预览
if(error){
id.find("img").replaceWith("不能预览");
}
//成功,则显示缩略图到指定位置
id.find("img").attr("src",src);
});
});
4. 拖拽、黏贴上传
很多网站支持拖拽上传,拖到指定区域就能上传感觉很酷很爽,其实不难。只需要在全局参数中开启拖拽功能:
var uploader = WebUploader.create({
// swf文件路径
swf : "${pageContext.request.contextPath}/js/Uploader.swf",
// 文件接收服务端。
server : "${pageContext.request.contextPath}/UploaderServlet",
// 选择文件的按钮。可选。
// 内部根据当前运行是创建,可能是input元素,也可能是flash.
pick : '#filePicker',
// 自动上传
auto : true,
//开启脱宅功能,指定拖拽区域
dnd:"#dndArea",
//禁止页面其他地方拖拽功能
disableGlobalDnd:true,
//开启黏贴功能
paste:"#uploader"
});
5. 大文件分块上传
分块上传:上传的文件可能比较大比如说有19M,如果单线程进行上传速度比较慢,但是如果把19M的文件分成多块采用多线程上传,最后将文件再次拼起来也能够完成文件上传的过程,而且速度更快了。
MD5值:将文件分块再拼接用到了一个概念MD5值。简单讲就是一个文件会有一个MD5就像人的身份证一样,是它在网上的身份标识,根据MD5值就能判断是哪一个文件。拿百度云举例子,有时候上传一步电影能够做到秒传,那是因为百度云的服务器存了一份相同的电影,正好和你上传电影的MD5值相同也就是说是同一部电影,所以只要给你一个指向这份文件的索引就相当于上传了电影,达到了秒传。
知道了这两个概念看一下分块上传图解:
首先很好理解,在初始化过程中需要开启分块上传功能:
var uploader = WebUploader.Uploader({
// 其他同上,略
// 开启分片上传。
chunked: true
});
接下来可以结合上面的图来看了:
第一步:肯定是先要获取MD5值,并且将值传递到后台。这样才能创建一个MD5命名的文件夹,用于保存分片。所以由前台js获取文件的MD5值,有一个后台程序用于创建文件夹。
第二步:本应该是将文件分片,但是分片的操作不需要我们进行,Web Uploader帮我们做好了,我们要做的就是将分片索引名传递到后台,并保存到相应文件夹下。
第三步:将分片根据索引排序,然后用I/O流合并分片,最后再给个文件名。所以这一步要有一个文件合并的后台程序,用Ajax传递数据到后台
后面有整个源码,就不贴所有代码了。在原来基础上添加如下js,注意要放在web uploader初始化之前:
//获取文件的标记
var fileMd5;
//5.监控文件的三个上传时间点
//时间点一:所有分块进行上传之前(1.计算文件的MD5 2.判断是否秒传)
//时间点二:如果分块上传,每个分块上传之前(选文后台该分块是否保存成功)
//时间点三:分块上传成功(通知后台合并)
WebUploader.Uploader.register({
"before-send-file":"beforeSendFile",
"before-send": "beforeSend",
"after-send-file": "afterSendFile"
},{
//时间点一
beforeSendFile:function(file){
//创建一个deffered
var deferred = WebUploader.Deferred();
//1.计算文件的唯一标记,用于断点续传和秒传
(new WebUploader.Uploader()).md5File(file,0,5*1024*1024)
.progress(function(percentage){
$("#"+file.id).find("div.state").text("正在获取文件信息...");
})
.then(function(val){
fileMd5 = val;
$("#"+file.id).find("div.state").text("成功获取文件信息");
//获取文件信息之后需要进入到下一步
deferred.resolve();
});
//返回deffered
return deferred.promise();
},
//时间点2:如果有分块上传,则 每个分块上传之前调用此函数
//block:代表当前分块对象
beforeSend:function(block){
//alert(fileMd5);
//1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
var deferred = WebUploader.Deferred();
//携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
this.owner.options.formData.fileMd5 = fileMd5;
//进入下一步
deferred.resolve();
return deferred.promise();
},
//时间点三
afterSendFile:function(file){
//1.如果分块上传,则通过后台合并所有分块文件
//请求后台合并文件
$.ajax(
{
type:"POST",
url:"${pageContext.request.contextPath}/UploaderCheckServlet?action=mergeChunks",
data:{
//文件唯一标记
fileMd5:fileMd5,
//文件名称
fileName:file.name
},
dataType:"json",
success:function(response){
alert(response.msg);
}
}
);
},
});
6.断点续传
断点续传:用百度云举例子,一个文件上传了一般有事把电脑关了,下次打开百度云上传会在原来的基础上继续上传,这个就是断点续传。上面已经知道了文件可以分块上传,所以有些块已经上传了而有些块没有。重新上传文件时,那些已经上传过的文件块就不需要上传了。
前台js
$.ajax(
{
type:"POST",
url:"${pageContext.request.contextPath}/UploaderCheckServlet?action=checkChunk",
data:{
//文件唯一标记
fileMd5:fileMd5,
//当前分块下标
chunk:block.chunk,
//当前分块大小
chunkSize:block.end-block.start
},
dataType:"json",
success:function(response){
if(response.ifExist){
//分块存在,跳过该分块
deferred.reject();
}else{
//分块不存在或者不完整,重新发送该分块内容
deferred.resolve();
}
}
}
);
后台代码:
if("checkChunk".equals(action)){
System.out.println("checkChunk...");
String fileMd5 = request.getParameter("fileMd5");
String chunk = request.getParameter("chunk");
String chunkSize = request.getParameter("chunkSize");
File checkFile = new File(serverPath+"/"+fileMd5+"/"+chunk);
response.setContentType("text/html;charset=utf-8");
//检查文件是否存在,且大小是否一致
if(checkFile.exists() && checkFile.length()==Integer.parseInt(chunkSize)){
response.getWriter().write("{"ifExist":1}");
}else{
response.getWriter().write("{"ifExist":0}");
}
}
源码地址:https://github.com/shuishui17/JavaUploader.git
代码都贴上来有点冗长就放git了,git我不怎么会用,也就会把代码传传上去,接下来会好好学下怎么使用