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