一、课程概述
在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