chunkupload簡介
chunkupload是一款基於java語言的斷點續傳組件,針對文件上傳,非文件下載,集成方便,使用簡單。
chunkupload實現如下功能:
· 實現斷點續傳
· 對於同一個文件,允許多用戶同時上傳,並且上傳的用戶越多,上傳越快
· 線程安全
· 同一物理機下進程安全
· 文件自動切片,支持合並
· 內存占用小
· 高效穩定,高可用
· 易集成,無第三方依賴
chunkupload只關注文件上傳,並沒有安全機制,開發者需要自行設計安全控制策略,防范用戶上傳非法文件,chunkupload默認上傳的文件是安全的。
chunkupload功能完備,服務端和客戶端無縫銜接,開發者只需關注自身業務和UI展現即可。
為了盡可能提升用戶體驗,chunkupload在客戶端的技術選型有些激進,采用了許多先進的技術,比如:web worker、XMLHttpRequest數據傳送進度、FileReader、file slice等,所以對浏覽器兼容性會有一定影響,在確定使用chunkupload前請務必仔細斟酌!
chunkupload集成
服務端
chunkupload服務端運行需要JRE7或更高版本,無任何第三方依賴。
1.引用chunkupload.1.0.jar。
2.在項目web.xml中配置chunkupload servlet。
1 <servlet> 2 <servlet-name>ChunkUpload</servlet-name> 3 <servlet-class>com.iyangyuan.chunkupload.servlet.DispatcherServlet</servlet-class> 4 </servlet> 5 <servlet-mapping> 6 <servlet-name>ChunkUpload</servlet-name> 7 <url-pattern>/chunkupload/*</url-pattern> 8 </servlet-mapping>
強烈建議將chunkupload servlet配置在安全框架之後(比如shiro),mvc框架之前(如springmvc)。
chunkupload servlet中的攔截路徑(url-pattern),如果無特殊需求,無需更改,假如一定要更改,還需要同步更改客戶端的配置。
客戶端
chunkupload客戶端對浏覽器的要求比較高,目前已知chrome、firefox浏覽器完整支持,360浏覽器如果啟用webkit內核,應該也沒有問題,IE浏覽器絕對不支持(想都不要想),其他浏覽器未知。
開發者可能會問,為什麼兼容性如此捉襟見肘?
chunkupload是新時代的產物,它代表了時代的發展方向,它的價值在於提供最先進的技術示范,而不是沉重的歷史包袱。
chunkupload在客戶端的實現,無任何第三方依賴,只關注與服務端的邏輯交互,並不干預UI展現,為開發者創造最大的發揮空間。
1.引用dawn.1.0.js,用於在客戶端計算文件MD5,dawn.js是chunkupload的一個附屬項目,相比未經優化的javascript計算MD5方法,dawn.js將計算效率提升50%左右。
2.引用chunkupload.1.0.js,此乃chunkupload客戶端核心庫,封裝了所有上傳所需的邏輯。
chunkupload使用
服務端
服務端需要創建chunkupload.properties配置文件,放置在項目classpath根目錄下,也就是大家熟悉的log4j.properties所在目錄,配置文件中有如下選項:
· root 文件存儲路徑,相當於根目錄,內部還會有chunkupload創建的目錄結構;假如同一台物理機配置多個文件上傳容器,此項配置應該設置成統一目錄,默認為[/data]。
· fileLockCapacity 文件鎖緩存容量,一般設置為2048即可,開發者可根據服務器性能自行調整,默認為2048。
· createFile 文件上傳完成後,是否立即合並切片,生成完整文件;強烈建議此配置項設置為false,一般情況下,切片無需合並,就算合並,也不需要立即合並;如果設為true,立即合並文件會占用大量服務器資源,並且會造成客戶端長時間等待;合並的速度大約100M/S,視服務器具體性能而定,默認為true。
至此,服務端已經可以正常運作了。
客戶端
默認情況下,客戶端無需任何配置。
假如開發者更改過chunkupload servlet攔截路徑,那麼chunkupload.1.0.js中的Block.config.api配置也需要做相應的改動,具體情況需要開發者自行斟酌。
ChunkUpload 類
文件上傳核心實現類。
實例化
上傳組件初始化需要提供目標文件。
1 /** 2 3 * 實例化ChunkUpload組件 4 5 * file 要上傳的目標文件對象 6 7 */ 8 9 var cu = new ChunkUpload(file);
upload 方法
upload 方法用來啟動文件上傳,通過四個異步回調完成上傳交互,無返回值。
1 cu.upload({ 2 "success": function(block){ 3 /** 4 * 上傳成功回調 5 * 6 * block 對象,塊對象 7 */ 8 }, 9 "error": function(e){ 10 /** 11 * 上傳異常回調 12 * 13 * e 字符串,異常信息 14 */ 15 }, 16 "md5Progress": function(n){ 17 /** 18 * 計算文件md5進度回調 19 * 20 * n 整型,進度數值 21 */ 22 }, 23 "uploadProgress": function(n){ 24 /** 25 * 上傳進度回調 26 * 27 * n 整型,進度數值 28 */ 29 } 30 });
abort方法
abort方法用來中斷上傳,可以在任意階段任意時刻中斷,無返回值。
1 cu.abort();
Block 類
文件控制類。
實例化
1 /** 2 3 * 初始化塊對象 4 5 * md5 文件md5,32位 6 7 * size 文件大小,字節 8 9 */ 10 11 var block = new Block(md5, size);
info方法
獲取塊(文件)信息,返回javascript對象。
1 block.info();
返回示例:
1 { 2 3 "status": 0, //業務狀態,0表示成功 4 5 "data": { //數據域 6 7 "chunks": [ //所有切片信息 8 9 { 10 11 "md5": "e114c21f7d9f8ad1a8551225c3d085be", //切片md5 12 13 "n": 1 //切片序號 14 15 }, 16 17 { 18 19 "md5": "48357caa7607a636e858315e1b0216d5", 20 21 "n": 2 22 23 }, 24 25 { 26 27 "md5": "a23c6ab7104d2ce4ae3c1624ea7eab55", 28 29 "n": 3 30 31 }, 32 33 { 34 35 "md5": "3eb29f6241d6fbb35cc715fff2b9ab91", 36 37 "n": 4 38 39 }, 40 41 { 42 43 "md5": "120ddc96b878a63adcd7835cbac0c95c", 44 45 "n": 5 46 47 } 48 49 ], 50 51 "chunkNum": 5, //切片數量 52 53 "md5": "f1154ca6fab7f3628927c1268f3570fd", //文件md5 54 55 "state": 1, //文件狀態,1為上傳完成 56 57 "size": 20879935 //文件長度 58 59 } 60 61 }
delete方法
刪除塊(文件) ,無返回值。
1 block.delete();
chunkupload服務端存儲珠玑
任何上傳的文件都會在服務端進行切片處理,每個切片4M大小。
通過文件MD5和文件大小,唯一確定一個文件。
目錄分散策略,基於開發者自定義的rootpath,文件MD5前6位,每兩位作為一級目錄,最後以文件MD5+文件長度作為最終目錄,所有文件信息均存儲在此目錄下。
假如文件MD5為[071287fffa974b878732a7a17858be36],長度為[20879935],開發者自定義的rootpath為[/data],那麼生成的目錄結構為:[/data/07/12/87/071287fffa974b878732a7a17858be3620879935]。
chunkupload存儲的關於文件的所有信息,均為二進制文件,並且文件名稱固定,具體組織如下圖:
chunkupload未來
展望chunkupload,未來無疑是開源的,只不過現在還不是時候,因為作者覺得它還不夠完美。
通過大家的寶貴意見、建議,作者會不斷完善、改進chunkupload,等到chunkupload成熟時,也就是開源之日!
希望大家多多與我交流~
chunkupload組件下載
你可以下載如下內容:
· chunkupload.jar
· chunkupload.js
· dawn.js
· 腳手架(集成了chunkupload的空白項目)
下載
附:客戶端使用示例
上傳示例
1 <html> 2 <head> 3 <title>ChunkUpload 文件上傳示例</title> 4 <meta charset="utf-8"> 5 <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"> 6 <style> 7 body{ 8 font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 } 10 body > div { 11 width: 200px; 12 margin: 20px auto 0 auto; 13 } 14 body > div.form { 15 text-align: center; 16 } 17 body > div.form > input { 18 margin-top: 12px; 19 border: 1px dashed #dcdcdc; 20 padding: 4px 8px; 21 cursor: pointer; 22 background-color: transparent; 23 color: #686868; 24 font-family: inherit; 25 outline: none; 26 width: 100%; 27 box-sizing: border-box; 28 } 29 body > div.form > input.button { 30 font-size: 15px; 31 line-height: 29px; 32 padding: 0 10px; 33 } 34 body > div.form > input:hover { 35 background-color: #f5f5f5; 36 } 37 div.info > div{ 38 margin: auto; 39 height: 31px; 40 width: 88px; 41 background: url('data:image/gif;base64,R0lGODlhWAAfAMMBAAAAAP////XcoPDLdfTboP+dzv+z2f+AwP9brfPZoAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFFAAKACwAAAAAWAAfAAAE/xDISau9OOvNd/hgKI5kaZ5oWkqB4L5wLM90bd+4PLB57//A2g7QChqPyNiwmGw6c8undKrj/STULC3qAxQAMKw0IUiYyy6yWf3j5rwGsMsrd5JfdzQelre5bxIFcROCdUBneWx6aHd9UFY1hAaTAJQTRo6LfYl8i0KQMoGCk6Slg0FsnGmdq3uZSpBiAnCmtaWGqK4yiq2eVURzwbS2pBK3hzabezOvLlxyYMPEg5aXWk1uWNLTxaOU10lR2oHckwcHlIWn4Ed/c97ECOfocJWy7EDus/C28udx8HAlaZYP1ItttRAgKEYBGS9NelL10LevnC2BR1RhM3iwAD9uGE5/NBvpg+IcLx5BhvTxiiDBGSYPivJI0xo+JzHfZfjyxNHDGC9/MaFhb0bRmzg5hoqE9EnOpk2XDJhKtarVq1izat3KtSvWDmDDih0LNgIAIfkEBRQACgAsAwAGADEAFgAABJ9QyEmrBMDqzbsFRTZh3pacZQUaogC2qZRQc4oVLLbCaS2XOpyBNdTxYjPf5iYcOp+kmCfqaj6vUClHlFlhr5is9nOpfsGAonGs6lrPUOvR1r3BnYdDEZemskdvWAh5ejs5fyqBV4N5LHKIiXcGCAhORnMmAkoUXpJgkEsFimeYPVsgond+oEtMoq86rJpTIZdBpbIjLBp9spu8W7nCshEAIfkEBRQACgAsAwAFADoAFgAABLdQyEmrAMDqzWXySdhZQJFN2KhS30oWxnmZrtp++IrB2B7XNmAPZogVezKgJidobXbEonSaUjaZE2erOpt6vz+gk5VFSTIlsBdDFTffnRM6qo4Bjkirbl4Hp6VJHTdxaD59BgcHRzx2ei5/dQiJiml3XI5xdGCSiTF0gZhPml8ICIB5oSqQh22pjwWjdaAuOWNjJCWwfZeuWoS5sME9rh4VtxolSMo0vXqWyGHESyOzZ9LHx9LaAhEAIfkEBRQACgAsAgAFAEQAFgAABMtQyEmrvTjrmmT63VYBgGieVIhSQFFO5CpPYWefrfEK7T5fto+g9iMVdKSc75dqOlWZ5NGgoyaXK6HqNvR4vxbjlEoux2ZQcHrL2uXK8DhWdGODo5LSO24GmJlqXSIvemN8Vn5ISVkaaRgxe4dwkXOAGpBGkmQHB1ZHfmeWRYZ8CJydSkiiTJGlpzqGlauDpHEICGRXsrMbrZq5THVPXcJhBbWSu2g0MmLIucona4BSx9YuoaKOzBbbMNi6RtHaRToXoLyO3nfneLw/EQAh+QQFFAAKACwCAAUASwAXAAAE3lDISau9OOtdE/9gKI4aAJDo5qUSUJyTyVJeYgtrfeesa8ACF5C1khSLuEkNZSr8TL4haKdMWpM8o/YDdRp+X6iUg9xer2VR0/ttu2Wh5bFKP5eAPrd+P55W00t2FjAneXtvAG9xGkhzHIRBbIdhiU9QM5gxhZKTb5J9mSAyXZ1tBwdhTolwoZiGkwinqFFPrZmvh7GnP5+haTSinHsICG1ioBdywFo8WRu4pcaZjslqBcKdyGQYv2ZcLtelrCS/3YKia9fql7btgy/HXdofZYHVTD8Xq+78FMjz/TBEAAA7') no-repeat center center; 42 padding: 2px; 43 box-sizing: border-box; 44 } 45 div.info > div > p{ 46 text-align: right; 47 font-size: 12px; 48 margin: 0; 49 } 50 </style> 51 </head> 52 <body> 53 <!-- 表單部分 --> 54 <div class="form"> 55 <input id="bigFile" type="file" placeholder="選擇一個文件" /> 56 <input id="execBtn" class="button" type="button" value="上傳" /> 57 <input id="cancelBtn" class="button" type="button" value="取消" /> 58 </div> 59 <!-- 上傳進度展示 --> 60 <div class="info"> 61 <div> 62 <!-- 提示文本 --> 63 <p id="text"></p> 64 <!-- 執行進度 --> 65 <p id="progress"></p> 66 </div> 67 </div> 68 </body> 69 <script src="/static/lib/dawn/dawn.js"></script> 70 <script src="/static/lib/chunkupload/chunkupload.js"></script> 71 <script> 72 var fileInput = document.getElementById("bigFile"), 73 execBtn = document.getElementById("execBtn"), 74 cancelBtn = document.getElementById("cancelBtn"), 75 text = document.getElementById("text"), 76 progress = document.getElementById("progress"), 77 cu; 78 79 execBtn.addEventListener("click", function(e) { 80 var file; 81 file = fileInput.files[0]; 82 cu = new ChunkUpload(file); 83 cu.upload({ 84 "success": function(block){ 85 /** 86 * 上傳成功後,會返回一個block對象 87 * 通過block對象,可以在一定程度上管理文件,目前支持: 88 * 獲取文件信息(參見info_example.html) 89 * 刪除文件(參見delete_example.html) 90 */ 91 text.innerText = "上傳完成"; 92 }, 93 "error": function(e){ 94 text.innerText = e; 95 progress.innerText = ""; 96 }, 97 "md5Progress": function(n){ 98 text.innerText = "計算MD5"; 99 progress.innerText = n + "%"; 100 }, 101 "uploadProgress": function(n){ 102 text.innerText = "正在上傳"; 103 progress.innerText = n + "%"; 104 } 105 }); 106 }); 107 108 cancelBtn.addEventListener("click", function(e) { 109 if(cu){ 110 cu.abort(); 111 alert("已經取消!"); 112 } 113 }); 114 </script> 115 </html> View Code
獲取文件信息示例
1 <html> 2 <head> 3 <title>ChunkUpload 獲取文件信息示例</title> 4 <meta charset="utf-8"> 5 <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"> 6 <style> 7 body { 8 font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 } 10 body > div { 11 width: 200px; 12 margin: 20px auto 0 auto; 13 } 14 body > div.form { 15 color: #686868; 16 font-size: 15px; 17 text-align: center; 18 } 19 body > div.form > p { 20 margin: 1em 0 .4em 0; 21 text-align: left; 22 } 23 body > div.form > input { 24 background-color: transparent; 25 outline: none; 26 font-family: inherit; 27 font-size: inherit; 28 color: inherit; 29 } 30 body > div.form > input:hover { 31 background-color: #f5f5f5; 32 } 33 body > div.form > input.text { 34 padding: 6px 8px; 35 border: 1px solid #dcdcdc; 36 } 37 body > div.form > input.button { 38 border: 1px dashed #dcdcdc; 39 cursor: pointer; 40 padding: 0 10px; 41 line-height: 29px; 42 } 43 div.info{ 44 width: 400px; 45 background-color: #f7faff; 46 border: 1px solid #b2d7ff; 47 padding: 10px 8px; 48 } 49 div.info > p{ 50 margin: 0; 51 text-align: left; 52 font-size: 14px; 53 word-break: break-all; 54 } 55 </style> 56 </head> 57 <body> 58 <!-- 表單部分 --> 59 <div class="form"> 60 <p>文件md5</p> 61 <input id="md5" class="text" type="text" placeholder="文件md5" /> 62 <p>文件大小(字節)</p> 63 <input id="size" class="text" type="text" placeholder="文件大小(字節)" /> 64 <p></p> 65 <input id="execBtn" class="button" type="button" value="獲取" /> 66 </div> 67 <!-- 結果展示 --> 68 <div class="info"> 69 <p id="text"> 70 </p> 71 </div> 72 </body> 73 <script src="/static/lib/dawn/dawn.js"></script> 74 <script src="/static/lib/chunkupload/chunkupload.js"></script> 75 <script> 76 var execBtn = document.getElementById("execBtn"), 77 md5 = document.getElementById("md5"), 78 size = document.getElementById("size"), 79 text = document.getElementById("text"); 80 81 execBtn.addEventListener("click", function(e) { 82 var block = new Block(md5.value, size.value); 83 84 block.info(function(info){ 85 text.innerText = JSON.stringify(info); 86 },function(status, text){ 87 text.innerText = "哎呀!出錯啦," + text; 88 }); 89 }); 90 </script> 91 </html> View Code
刪除文件示例
1 <html> 2 <head> 3 <title>ChunkUpload 刪除文件示例</title> 4 <meta charset="utf-8"> 5 <meta name="viewport" id="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0"> 6 <style> 7 body { 8 font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif; 9 } 10 body > div { 11 width: 200px; 12 margin: 20px auto 0 auto; 13 } 14 body > div.form { 15 color: #686868; 16 font-size: 15px; 17 text-align: center; 18 } 19 body > div.form > p { 20 margin: 1em 0 .4em 0; 21 text-align: left; 22 } 23 body > div.form > input { 24 background-color: transparent; 25 outline: none; 26 font-family: inherit; 27 font-size: inherit; 28 color: inherit; 29 } 30 body > div.form > input:hover { 31 background-color: #f5f5f5; 32 } 33 body > div.form > input.text { 34 padding: 6px 8px; 35 border: 1px solid #dcdcdc; 36 } 37 body > div.form > input.button { 38 border: 1px dashed #dcdcdc; 39 cursor: pointer; 40 padding: 0 10px; 41 line-height: 29px; 42 } 43 div.info{ 44 width: 400px; 45 background-color: #f7faff; 46 border: 1px solid #b2d7ff; 47 padding: 10px 8px; 48 } 49 div.info > p{ 50 margin: 0; 51 text-align: left; 52 font-size: 14px; 53 word-break: break-all; 54 } 55 </style> 56 </head> 57 <body> 58 <!-- 表單部分 --> 59 <div class="form"> 60 <p>文件md5</p> 61 <input id="md5" class="text" type="text" placeholder="文件md5" /> 62 <p>文件大小(字節)</p> 63 <input id="size" class="text" type="text" placeholder="文件大小(字節)" /> 64 <p></p> 65 <input id="execBtn" class="button" type="button" value="刪除" /> 66 </div> 67 <!-- 結果展示 --> 68 <div class="info"> 69 <p id="text"> 70 </p> 71 </div> 72 </body> 73 <script src="/static/lib/dawn/dawn.js"></script> 74 <script src="/static/lib/chunkupload/chunkupload.js"></script> 75 <script> 76 var execBtn = document.getElementById("execBtn"), 77 md5 = document.getElementById("md5"), 78 size = document.getElementById("size"), 79 text = document.getElementById("text"); 80 81 execBtn.addEventListener("click", function(e) { 82 var block = new Block(md5.value, size.value); 83 84 block.delete(function(info){ 85 text.innerText = JSON.stringify(info); 86 },function(status, text){ 87 text.innerText = "哎呀!出錯啦," + text; 88 }); 89 }); 90 </script> 91 </html> View Code