- 斷點上傳能夠防止意外情況導致上傳一半的文件下次上傳時還要從頭下載,網上有很多關於斷點的實現,這篇文章只是從前到后完整的記錄下一個可用的實例,由於生產環境要求不高,而且就是提供給一兩個人用,所以我簡化了諸多過程,不用flash,也不用applet,只是通過html5的新特性進行瀏覽器端的處理。
- 簡單說下關鍵點
- 如果上次傳到n字節,那么瀏覽器下次續傳直接就是從文件的n字節開始向服務器傳送數據,而不是都傳過去,服務器從n字節開始接收。
- html5能給文件分片,所以每次上傳完一塊文件后,應該返回當前已經上傳的文件大小,好讓h5能從此斷點繼續讀取。
- 前端的js是網上別人的底子,我進行了可用性修改。
- 代碼完全可用,而且都是用的最簡單的東西實現
- 可以看到我用了本地文件的最后修改時間這個屬性,因為這樣可以脫離數據庫只通過文件名+文件最后修改時間來確定文件的唯一性,如果生產中有數據庫的接入,建議先生成續傳文件並返回對應的唯一id。
- 服務器端方法
- 獲取當前已經上傳文件的大小
/** * 獲取已上傳的文件大小 * @param request * @param response */ public void getChunkedFileSize(HttpServletRequest request,HttpServletResponse response){ //存儲文件的路徑,根據自己實際確定 String currentFilePath = "c:\\uploadFile\\Image\\"; PrintWriter print = null; try { request.setCharacterEncoding("utf-8"); print = response.getWriter(); String fileName = new String(request.getParameter("fileName").getBytes("ISO-8859-1"),"UTF-8"); String lastModifyTime = request.getParameter("lastModifyTime"); File file = new File(currentFilePath+fileName+"."+lastModifyTime); if(file.exists()){ print.print(file.length()); }else{ print.print(-1); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } }
2.文件上傳
/** * * 斷點文件上傳 1.先判斷斷點文件是否存在 2.存在直接流上傳 3.不存在直接新創建一個文件 4.上傳完成以后設置文件名稱 * */ public static void appendUpload2Server(HttpServletRequest request,HttpServletResponse response) { PrintWriter print = null; try { request.setCharacterEncoding("utf-8"); print = response.getWriter(); String fileSize = request.getParameter("fileSize"); long totalSize = StringUtil.toLong(fileSize); RandomAccessFile randomAccessfile = null; long currentFileLength = 0;// 記錄當前文件大小,用於判斷文件是否上傳完成 String currentFilePath = "c:\\uploadFile\\Image\\";// 記錄當前文件的絕對路徑 String fileName = new String(request.getParameter("fileName").getBytes("ISO-8859-1"),"UTF-8"); String lastModifyTime = request.getParameter("lastModifyTime"); File file = new File(currentFilePath+fileName+"."+lastModifyTime); // 存在文件 if(file.exists()){ randomAccessfile = new RandomAccessFile(file, "rw"); } else { // 不存在文件,根據文件標識創建文件 randomAccessfile = new RandomAccessFile(currentFilePath+fileName+"."+lastModifyTime, "rw"); } // 開始文件傳輸 InputStream in = request.getInputStream(); randomAccessfile.seek(randomAccessfile.length()); byte b[] = new byte[1024]; int n; while ((n = in.read(b)) != -1) { randomAccessfile.write(b, 0, n); } currentFileLength = randomAccessfile.length(); // 關閉文件 closeRandomAccessFile(randomAccessfile); randomAccessfile = null; // 整個文件上傳完成,修改文件后綴 if (currentFileLength == totalSize) { File oldFile = new File(currentFilePath+fileName+"."+lastModifyTime); File newFile = new File(currentFilePath+fileName); if(!oldFile.exists()){ return;//重命名文件不存在 } if(newFile.exists()){// 如果存在形如test.txt的文件,則新的文件存儲為test+當前時間戳.txt, 沒處理不帶擴展名的文件 String newName = fileName.substring(0,fileName.lastIndexOf(".")) +System.currentTimeMillis()+"." +fileName.substring(fileName.lastIndexOf(".")+1); newFile = new File(currentFilePath+newName); } if(!oldFile.renameTo(newFile)){ oldFile.delete(); } } print.print(currentFileLength); } catch (Exception e) { e.printStackTrace(); } } /** * 關閉隨機訪問文件 * * @param randomAccessfile */ public static void closeRandomAccessFile(RandomAccessFile rfile) { if (null != rfile) { try { rfile.close(); } catch (Exception e) { e.printStackTrace(); } } }
- jsp頁面
<html> <head> <title>斷點續傳文件</title> <meta charset="utf-8"> </head> <body onload="init();"> <div class="row"> <label for="fileToUpload">請選擇需要上傳的文件</label> <input type="file" name="fileToUpload" id="fileToUpload" onchange="fileSelected();" multiple/> </div> </div> <div class="row"> <button onclick="uploadFiles()">上傳</button> <button onclick="pauseUpload()">暫停</button> <label id="progressNumber"></label> </div> <div id="msg" style="max-height: 400px; overflow:auto;min-height: 100px;"> </div> <br> <div><h6>支持批量,支持斷點續傳</h6></div> </body> </html>
- js代碼
var msg = null; var paragraph = 1024*1024*2; //每次分片傳輸文件的大小 2M var blob = null;// 分片數據的載體Blob對象 var fileList = null; //傳輸的文件 var uploadState = 0; // 0: 無上傳/取消, 1: 上傳中, 2: 暫停 //初始化消息框 function init(){ msg = document.getElementById("msg"); } function uploadFiles(){ //將上傳狀態設置成1 uploadState = 1; if(fileList.files.length>0){ for(var i = 0; i< fileList.files.length; i++){ var file = fileList.files[i]; uploadFileInit(file,i); } }else{ msg.innerHTML = "請選擇上傳文件!"; } } /** * 獲取服務器文件大小,開始續傳 * @param file * @param i */ function uploadFileInit(file,i){ if(file){ var startSize = 0; var endSize = 0; var date = file.lastModifiedDate; var lastModifyTime = date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate()+"-" +date.getHours()+"-"+date.getMinutes()+"-"+date.getSeconds() //獲取當前文件已經上傳大小 jQuery.post("xxx/getChunkedFileSize.do", {"fileName":encodeURIComponent(file.name),"fileSize":file.size,"lastModifyTime":lastModifyTime,"chunkedFileSize":"chunkedFileSize"}, function(data){ if(data != -1){ endSize = Number(data); } uploadFile(file,startSize,endSize,i); }); } } /** * 分片上傳文件 */ function uploadFile(file,startSize,endSize,i) { var date = file.lastModifiedDate; var lastModifyTime = date.getFullYear()+"-"+(date.getMonth()+1)+"-"+date.getDate()+"-" +date.getHours()+"-"+date.getMinutes()+"-"+date.getSeconds() var reader = new FileReader(); reader.onload = function loaded(evt) { // 構造 XMLHttpRequest 對象,發送文件 Binary 數據 var xhr = new XMLHttpRequest(); xhr.sendAsBinary = function(text){ var data = new ArrayBuffer(text.length); var ui8a = new Uint8Array(data, 0); for (var i = 0; i < text.length; i++) ui8a[i] = (text.charCodeAt(i) & 0xff); this.send(ui8a); } xhr.onreadystatechange = function(){ if(xhr.readyState==4){ //表示服務器的相應代碼是200;正確返回了數據 if(xhr.status==200){ //純文本數據的接受方法 var message=xhr.responseText; message = Number(message); uploadProgress(file,startSize,message,i); } else{ msg.innerHTML = "上傳出錯,服務器相應錯誤!"; } } };//創建回調方法 xhr.open("POST", "xxx/appendUpload2Server.do?fileName=" + encodeURIComponent(file.name)+"&fileSize="+file.size+"&lastModifyTime="+lastModifyTime, false); xhr.overrideMimeType("application/octet-stream;charset=utf-8"); xhr.sendAsBinary(evt.target.result); }; if(endSize < file.size){ //處理文件發送(字節) startSize = endSize; if(paragraph > (file.size - endSize)){ endSize = file.size; }else{ endSize += paragraph ; } if (file.webkitSlice) { //webkit瀏覽器 blob = file.webkitSlice(startSize, endSize); }else blob = file.slice(startSize, endSize); reader.readAsBinaryString(blob); }else{ document.getElementById('progressNumber'+i).innerHTML = '100%'; } } //顯示處理進程 function uploadProgress(file,startSize,uploadLen,i) { var percentComplete = Math.round(uploadLen * 100 / file.size); document.getElementById('progressNumber'+i).innerHTML = percentComplete.toString() + '%'; //續傳 if(uploadState == 1){ uploadFile(file,startSize,uploadLen,i); } } /* 暫停上傳 */ function pauseUpload(){ uploadState = 2; } /** * 選擇文件之后觸發事件 */ function fileSelected() { fileList = document.getElementById('fileToUpload'); var length = fileList.files.length; var frame = document.getElementById('fileFrame'); for(var i=0; i<length; i++){ file = fileList.files[i]; if(file){ var fileSize = 0; if (file.size > 1024 * 1024) fileSize = (Math.round(file.size * 100 / (1024 * 1024)) / 100).toString() + 'MB'; else fileSize = (Math.round(file.size * 100 / 1024) / 100).toString() + 'KB'; var nameDiv = document.createElement("div"); nameDiv.setAttribute("id","fileName"+i); nameDiv.innerHTML='Name: ' + file.name; var sizeDiv = document.createElement("div"); sizeDiv.setAttribute("id","fileSize"+i); sizeDiv.innerHTML='fileSize: ' + fileSize; var typeDiv = document.createElement("div"); typeDiv.setAttribute("id","progressNumber"+i); typeDiv.innerHTML=''; } frame.appendChild(nameDiv); frame.appendChild(sizeDiv); frame.appendChild(typeDiv); } }