一、课程概述
在Web应用系统开发中,文件上传功能是非常常用的功能,今天来主要讲讲JavaWeb中的文件上传功能的相关技术实现,并且随着互联网技术的飞速发展,用户对网站的体验要求越来越高,在文件上传功能的技术上也出现许多创新点,例如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,大文件断点续传,大文件秒传等等。
本课程需要的基础知识:
了解基本的Http协议内容
基本IO流操作技术
Servlet基础知识
javascript/jQuery技术基础知识
二、文件上传的基础
对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,并且所有流数据都会随着Http请求携带到服务器端。所以,文件上传时的请求内容格式要能够基本看懂。
文件上传页面:
1
2
3
4
|
<form action= "/itheimaUpload/UploadServlet" method= "post" enctype= "multipart/form-data" >
请选择上传的文件:<input type= "file" name= "attach" /><br/>
<input type= "submit" value= "提交" />
</form>
|
Http请求内容:
三、Java后台使用Servlet接收文件
如果使用Servlet获取上传文件的输入流然后再解析里面的请求参数是比较麻烦,所以一般后台选择采用Apache的开源工具common-fileupload这个文件上传组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
//Java后台代码:Commons-fileUpload组件上传文件
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//1.配置缓存
DiskFileItemFactory factory = new DiskFileItemFactory( 1 * 1024 * 1024 , new File( "c:/tempFiles/" ));
//2.创建ServleFileUpload对象
ServletFileUpload sfu = new ServletFileUpload(factory);
//解决文件名称中文问题
sfu.setHeaderEncoding( "utf-8" );
//3.解析
try {
List<FileItem> list = sfu.parseRequest(request);
//解析所有内容
if (list!= null ){
for (FileItem item:list){
//判断是否为普通表单参数
if (item.isFormField()){
//普通表单参数
//获取表单的name属性名称
String fieldName = item.getFieldName();
//获取表单参数值
String value = item.getString( "utf-8" );
} else {
//文件
if (item.getName()!= null && !item.getName().equals( "" )) {
//保存到服务器硬盘
FileUtils.copyInputStreamToFile(item.getInputStream(), new File( "c:/targetFiles/" +item.getName()));
item.delete();
}
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
|
四、使用WebUploader上传组件
文件上传页面的前端我们可以选择使用一些比较好用的上传组件,例如百度的开源组件WebUploader,这个组件基本能满足文件上传的一些日常所需功能,如异步上传文件,拖拽式上传,黏贴上传,上传进度监控,文件缩略图,甚至是大文件断点续传,大文件秒传。
下载WebUpload组件
http://fex.baidu.com/webuploader/ 到WebUpload官网下载WebUpload包
WebUpload目录结构:
基本文件上传Demo(包含上传进度)
前端
1.1 在页面导入所需css,js
1
2
3
4
5
6
|
<link rel= "stylesheet" type= "text/css"
href= "${pageContext.request.contextPath}/css/webuploader.css" >
<script type= "text/javascript"
src= "${pageContext.request.contextPath }/js/jquery-1.10.2.min.js" ></script>
<script type= "text/javascript"
src= "${pageContext.request.contextPath }/js/webuploader.js" ></script>
|
1.2 编写上传页面标签
1
2
3
4
5
6
7
|
<!-- 上传div -->
<div id= "uploader" >
<!-- 显示文件列表信息 -->
<ul id= "fileList" ></ul>
<!-- 选择文件区域 -->
<div id= "filePicker" >点击选择文件</div>
</div>
|
1.3 编写webupload代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
<script type= "text/javascript" >
//1.初始化WebUpload,以及配置全局的参数
var uploader = WebUploader.create(
{
//flashk控件的地址
swf: "${pageContext.request.contextPath}/js/Uploader.swf" ,
//后台提交地址
server: "${pageContext.request.contextPath}/UploadServlet" ,
//选择文件控件的标签
pick: "#filePicker" ,
//自动上传文件
auto: true ,
}
);
//2.选择文件后,文件信息队列展示
// 注册fileQueued事件:当文件加入队列后触发
// file: 代表当前选择的文件
uploader.on( "fileQueued" ,function(file){
//追加文件信息div
$( "#fileList" ).append( "<div id='" +file.id+ "' class='fileInfo'><span>" +file.name+ "</span><div class='state'>等待上传...</div><span class='text'></span></div>" );
});
//3.注册上传进度监听
//file: 正在上传的文件
//percentage: 当前进度的比例。最大为1.例如:0.2
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.注册上传完毕监听
//file:上传完毕的文件
//response:后台回送的数据,以json格式返回
uploader.on( "uploadSuccess" ,function(file,response){
//更新状态信息
$( "#" +file.id).find( "div.state" ).text( "上传完毕" );
});
|
2)后端Servlet代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload sfu = new ServletFileUpload(factory);
sfu.setHeaderEncoding( "utf-8" );
try {
List<FileItem> items = sfu.parseRequest(request);
for (FileItem item:items){
if (item.isFormField()){
//普通信息
} else {
//文件信息
//判断只有文件才需要进行保存处理
System.out.println( "接收的文件名称:" +item.getName());
//拷贝文件到后台的硬盘
FileUtils.copyInputStreamToFile(item.getInputStream(), new File(serverPath+ "/" +item.getName()));
System.out.println( "文件保存成功" );
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
|
生成图片缩略图
关键点:调用uploader.makeThumb()方法生成缩略图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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);
});
});
|
拖拽,黏贴上传
1)页面添加拖拽区域的div
1
2
3
4
5
6
7
8
9
10
11
|
<!-- 上传div -->
<div id= "uploader" >
<!-- 文件拖拽区域 -->
<div id= "dndArea" >
<p>将文件直接拖拽到这里即可自动上传</p>
</div>
<!-- 显示文件列表信息 -->
<ul id= "fileList" ></ul>
<!-- 选择文件区域 -->
<div id= "filePicker" >点击选择文件</div>
</div>
|
2)在webuploader的全局配置参数添加拖拽功能的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
//1.初始化WebUpload,以及配置全局的参数
var uploader = WebUploader.create(
{
//flashk控件的地址
swf: "${pageContext.request.contextPath}/js/Uploader.swf" ,
//后台提交地址
server: "${pageContext.request.contextPath}/UploadServlet" ,
//选择文件控件的标签
pick: "#filePicker" ,
//自动上传文件
auto: true ,
//开启拖拽功能,指定拖拽区域
dnd: "#dndArea" ,
//禁用页面其他地方的拖拽功能,防止页面直接打开文件
disableGlobalDnd: true
//开启黏贴功能
paste: "#uploader"
}
);
|
大文件分块上传
1)在webuploader全局参数中添加分块上传参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
//1.初始化WebUpload,以及配置全局的参数
var uploader = WebUploader.create(
{
//flashk控件的地址
swf: "${pageContext.request.contextPath}/js/Uploader.swf" ,
//后台提交地址
server: "${pageContext.request.contextPath}/UploadServlet" ,
//选择文件控件的标签
pick: "#filePicker" ,
//自动上传文件
auto: true ,
//开启拖拽功能,指定拖拽区域
dnd: "#dndArea" ,
//禁用页面其他地方的拖拽功能,防止页面直接打开文件
disableGlobalDnd: true ,
//开启黏贴功能
paste: "#uploader" ,
//分块上传设置
//是否分块上传
chunked: true ,
//每块文件大小(默认5M)
chunkSize: 5 * 1024 * 1024 ,
//开启几个并发线程(默认3个)
threads: 3 ,
//在上传当前文件时,准备好下一个文件
prepareNextFile: true
}
);
|
2)监控上传文件的三个时间点
添加以上三个配置后,会发现当文件超过5M时,webuploader自动把文件会分几个请求发送给后台
每个分块请求,包含的信息:
可以监听文件分块上传的三个重要的时间点。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
before-send-file : 在所有分块发送之前调用
before-send: 如果有分块,在每个分块发送之前调用
after-send-file: 在所有分块发送完成之后调用
//5.监控文件上传的三个时间点(注意:该段代码必须放在WebUploader.create之前)
//时间点1::所有分块进行上传之前(1.可以计算文件的唯一标记;2.可以判断是否秒传)
//时间点2: 如果分块上传,每个分块上传之前(1.询问后台该分块是否已经保存成功,用于断点续传)
//时间点3:所有分块上传成功之后(1.通知后台进行分块文件的合并工作)
WebUploader.Uploader.register({
"before-send-file" : "beforeSendFile" ,
"before-send" : "beforeSend" ,
"after-send-file" : "afterSendFile"
},{
//时间点1::所有分块进行上传之前调用此函数
beforeSendFile:function(){
//1.计算文件的唯一标记,用于断点续传和秒传
//2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
},
//时间点2:如果有分块上传,则 每个分块上传之前调用此函数
beforeSend:function(){
//1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
},
//时间点3:所有分块上传成功之后调用此函数
afterSendFile:function(){
//1.如果分块上传,则通过后台合并所有分块文件
}
});
|
before-send-file逻辑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
//利用md5File()方法计算文件的唯一标记符
//该函数接收一个deferred
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){
uniqueFileTag = val;
$( "#" +file.id).find( "div.state" ).text( "成功获取文件信息" );
//只有文件信息获取成功,才进行下一步操作
deferred.resolve();
});
//alert(uniqueFileTag);
//2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
//返回deffered
return deferred.promise();
}
|
before-send逻辑:
1
2
3
4
5
|
//向后台发送当前文件的唯一标记,用于后台创建保存分块文件的目录
beforeSend:function(){
//携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
this .owner.options.formData.fileMd5 = fileMd5;
}
|
3)后台需要保存所有分块文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//为每个文件创建一个目录,并保存这个文件的所有分块文件
//判断是否已经分块上传
if (chunks!= null ){
System.out.println( "分块处理..." );
//进行分块上传了
//建立一个临时目录,用于保存所有分块文件
File chunksDir = new File(serverPath+ "/" +fileMd5);
if (!chunksDir.exists()){
chunksDir.mkdir();
}
if (chunk!= null ){
//保存分块文件
File chunkFile = new File(chunksDir.getPath()+ "/" +chunk);
FileUtils.copyInputStreamToFile(item.getInputStream(), chunkFile);
}
|
4)前台通知后台合并所有分块文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
//前台通知后台合并文件
after-send-file逻辑:
afterSendFile:function(file){
//1.如果分块上传,则通过后台合并所有分块文件
//请求后台合并文件
$.ajax(
{
type: "POST" ,
url: "${pageContext.request.contextPath}/UploadCheckServlet?action=mergeChunks" ,
data:{
//文件唯一标记
fileMd5:fileMd5,
//文件名称
fileName:file.name
},
dataType: "json" ,
success:function(response){
alert(response.msg);
}
}
);
}
//后台合并所有分块文件
if ( "mergeChunks" .equals(action)){
System.out.println( "开始合并文件..." );
//合并文件
String fileMd5 = request.getParameter( "fileMd5" );
String fileName = request.getParameter( "fileName" );
//读取目录里面的所有文件
File f = new File(serverPath+ "/" +fileMd5);
File[] fileArray = f.listFiles( new FileFilter(){
//排除目录,只要文件
public boolean accept(File pathname) {
if (pathname.isDirectory()){
return false ;
}
return true ;
}
});
//转成集合,便于排序
List<File> fileList = new ArrayList<File>(Arrays.asList(fileArray));
//从小到大排序
Collections.sort(fileList, new Comparator<File>() {
public int compare(File o1, File o2) {
if (Integer.parseInt(o1.getName()) < Integer.parseInt(o2.getName())){
return - 1 ;
}
return 1 ;
}
});
File outputFile = new File(serverPath+ "/" +fileName);
//创建文件
outputFile.createNewFile();
//输出流
FileChannel outChannel = new FileOutputStream(outputFile).getChannel();
//合并
FileChannel inChannel;
for (File file : fileList){
inChannel = new FileInputStream(file).getChannel();
inChannel.transferTo( 0 , inChannel.size(), outChannel);
inChannel.close();
//删除分片
file.delete();
}
//清除文件夹
File tempFile = new File(serverPath+ "/" +fileMd5);
if (tempFile.isDirectory() && tempFile.exists()){
tempFile.delete();
}
//关闭流
outChannel.close();
response.setContentType( "text/html;charset=utf-8" );
response.getWriter().write( "{\"msg\":\"合并成功\"}" );
}
|
大文件断点续传
在实现了分块上传的基础上,实现断点续传就非常简单了!!!
前端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
//时间点2:如果有分块上传,则 每个分块上传之前调用此函数
//block:代表当前分块对象
beforeSend:function(block){
//1.请求后台是否保存过当前分块,如果存在,则跳过该分块文件,实现断点续传功能
var deferred = WebUploader.Deferred();
//请求后台是否保存完成该文件信息,如果保存过,则跳过,如果没有,则发送该分块内容
$.ajax(
{
type: "POST" ,
url: "${pageContext.request.contextPath}/UploadCheckServlet?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();
}
}
}
);
//携带当前文件的唯一标记到后台,用于让后台创建保存该文件分块的目录
this .owner.options.formData.fileMd5 = fileMd5;
return deferred.promise();
},
|
后台:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
//检查该分块是否存在或者完整保存
private void checkChunk(HttpServletRequest request,
HttpServletResponse response) throws IOException,
FileNotFoundException {
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}" );
}
}
|
文件秒传
在所有分块请求之前,就已经可以进行实现秒传功能!!!
前端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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( "成功获取文件信息" );
//2.请求后台是否保存过该文件,如果存在,则跳过该文件,实现秒传功能
$.ajax(
{
type: "POST" ,
url: "${pageContext.request.contextPath}/UploadCheckServlet?action=fileCheck" ,
data:{
//文件唯一标记
fileMd5:fileMd5
},
dataType: "json" ,
success:function(response){
if (response.ifExist){
$( "#" +file.id).find( "div.state" ).text( "秒传成功" );
//如果存在,则跳过该文件,秒传成功
deferred.reject();
} else {
//继续上传
deferred.resolve();
}
}
}
);
});
//返回deffered
return deferred.promise();
},
|
后台:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
//检查文件的md5数据是否跟在数据库存在
private void fileCheck(HttpServletRequest request,
HttpServletResponse response) throws IOException,
FileNotFoundException {
String fileMd5 = request.getParameter( "fileMd5" );
//模拟数据库
Map<String,String> database = new HashMap<String,String>();
database.put( "576018603f4091782b68b78af85704a1" , "01.课程回顾.itcast" );
response.setContentType( "text/html;charset=utf-8" );
if (database.containsKey(fileMd5)){
response.getWriter().write( "{\"ifExist\":1}" );
} else {
response.getWriter().write( "{\"ifExist\":0}" );
}
}
|
以上所述是小编给大家介绍的JavaWeb文件上传下载实例讲解(酷炫的文件上传技术),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对服务器之家网站的支持!
原文链接:http://blog.csdn.net/axi295309066/article/details/53190065