WebUploader 實現大檔案的斷點續傳功能
阿新 • • 發佈:2018-11-23
斷點續傳指的是在下載或上傳時,將下載或上傳任務(一個檔案或一個壓縮包)人為的劃分為幾個部分,每一個部分採用一個片段進行上傳或下載,如果碰到網路故障,可以從已經上傳或下載的部分開始繼續上傳下載未完成的部分,而沒有必要從頭開始上傳下載。使用者可以節省時間,提高速度。
本文采用WebUploader外掛實現對大檔案進行唯一標識,並分塊進行上傳。
1、CheckChumServlet 進行檔案唯一標識的判斷,是否已經上傳過;
2、UploadVideoServlet 檔案的分塊上傳;
3、UploadSuccessServlet 對分塊的檔案進行合併。
index.jsp
<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>視訊檔案上傳</title> <base href="${pageContext.request.scheme}://${pageContext.request.serverName}:${pageContext.request.serverPort}${pageContext.request.contextPath}/"> <script type="text/javascript" src="webuploader/jquery-1.7.2.js"></script> <script type="text/javascript" src="webuploader/webuploader.min.js"></script> <link href="webuploader/webuploader.css" type="css/text" /> </head> <body> <h2 >視訊檔案上傳</h2> <div style="margin: 20px 20px 20px 0;"> <div id="picker" class="form-control-focus" >選擇檔案</div> </div> <div id="thelist" class="uploader-list"></div> <button id="btnSync" type="button" class="btn btn-warning">開始上傳</button> <div class="progress"> <div id="progress" class="progress-bar" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 0%;"> <span class="sr-only"></span> </div> </div> <script> _extensions ='3gp,mp4,rmvb,mov,avi,m4v,qlv,wmv'; _mimeTypes ='video/*,audio/*,application/*'; var fileMd5; //檔案唯一標識 /******************下面的引數是自定義的*************************/ var fileId;//檔案ID var fileName;//檔名稱 var oldJindu;//如果該檔案之前上傳過 已經上傳的進度是多少 var count=0;//當前正在上傳的檔案在陣列中的下標,一次上傳多個檔案時使用 var filesArr=new Array();//檔案陣列:每當有檔案被新增進佇列的時候 就push到陣列中 var map={};//key儲存檔案id,value儲存該檔案上傳過的進度 //監聽分塊上傳的三種狀態 WebUploader.Uploader.register({ "before-send-file":"beforeSendFile",//整個檔案上傳前 "before-send":"beforeSend", //每個分片上傳前 "after-send-file":"afterSendFile", //分片上傳完畢 }, { //所有分塊進行上傳之前呼叫此函式 beforeSendFile:function(file){ //alert('分塊上傳前呼叫的函式'); var deferred = WebUploader.Deferred(); //1、計算檔案的唯一標記fileMd5,用於斷點續傳 如果.md5File(file)方法裡只寫一個file引數則計算MD5值會很慢 所以加了後面的引數:5*1024*1024 (new WebUploader.Uploader()).md5File(file,0,5*1024*1024).progress(function(percentage){ $('#'+file.id ).find('p.state').text('正在讀取檔案資訊...'); }) .then(function(val){ $('#'+file.id ).find("p.state").text("正在上傳..."); fileMd5=val; fileId=file.id; uploader.options.formData.guid = fileMd5; //獲取檔案資訊後進入下一步 deferred.resolve(); }); fileName=file.name; //為自定義引數檔名賦值 return deferred.promise(); }, //如果有分塊上傳,則每個分塊上傳之前呼叫此函式 ,檢驗該分片是否上傳過 beforeSend:function(block){ //alert('-檢驗分塊是否上傳-'); var deferred = WebUploader.Deferred(); $.ajax({ type:"POST", url:"CheckChumServlet", //ajax驗證每一個分片 data:{ fileName : fileName, fileMd5:fileMd5, //檔案唯一標記 chunk:block.chunk, //當前分塊下標 chunkSize:block.end-block.start,//當前分塊大小 guid: uploader.options.formData.guid }, cache: false, async: false, // 與js同步 timeout: 1000, //todo 超時的話,只能認為該分片未上傳過 dataType:"json", success:function(response){ if(response.ifExist){ $('#'+fileId).find("p.state").text("正在續傳..."); //分塊存在,跳過 deferred.reject(); }else{ //alert("分塊檔案不完整或沒有上傳,重新上傳") //分塊不存在或不完整,重新發送該分塊內容 deferred.resolve(); } } }); this.owner.options.formData.fileMd5 = fileMd5; deferred.resolve(); //繼續執行分片上傳 return deferred.promise(); }, //所有分塊上傳成功後呼叫此函式,通知後臺合併所用分塊 afterSendFile:function(){ //alert('-所有分塊上傳完成後呼叫該函式-'); //如果分塊上傳成功,則通知後臺合併分塊 $.ajax({ type:"POST", url:"UploadSuccessServlet", //ajax將所有片段合併成整體 data:{ fileName : fileName, fileMd5:fileMd5, }, success:function(){ count++; //每上傳完成一個檔案 count+1 if(count<=filesArr.length-1){ uploader.upload(filesArr[count].id);//上傳檔案列表中的下一個檔案 } //合併成功之後的操作 } }); } }); var uploader = WebUploader.create({ // swf檔案路徑 swf : 'webuploader/Uploader.swf', // 檔案接收服務端。 server : 'UploadVideoServlet', // 選擇檔案的按鈕。可選。 // 內部根據當前執行是建立,可能是input元素,也可能是flash. pick : { id:'#picker', multiple: false }, accept: { title: 'Videos', extensions: _extensions, mimeTypes: _mimeTypes }, chunked: true, //分片處理 chunkSize: 5 * 1024 * 1024, //每片5M threads:1,//上傳併發數。允許同時最大上傳程序數。 // 不壓縮image, 預設如果是jpeg,檔案上傳前會壓縮一把再上傳! resize : false }); uploader.on("error", function (type) { if (type == "Q_TYPE_DENIED") { alert("請上傳mp4、rmvb、mov、avi、m4v、wmv格式檔案"); } }); // 當有檔案被新增進佇列的時候 uploader.on('fileQueued', function(file) { //alert(123); $("#thelist").append( '<div id="' + file.id + '" class="item">' + '<h4 class="info">' + file.name + '</h4>' + '<p class="file-pro"></p>' + '<p class="state">等待上傳...</p>' + '</div>'); }); uploader.on('uploadSuccess', function(file) { $('#' + file.id).find('p.state').text('已上傳'); }); uploader.on('uploadError', function(file) { $('#' + file.id).find('p.state').text('上傳出錯'); }); uploader.on('uploadComplete', function(file) { $('#' + file.id).find('.progress').fadeOut(); }); uploader.on( 'beforeFileQueued', function( file ) { // alert(file.size); }); $("#btnSync").on('click', function() { if ($(this).hasClass('disabled')) { return false; } //顯示遮罩層操作 //視訊上傳 uploader.upload(); }); // 檔案上傳過程中建立進度條實時顯示 uploader.on( 'uploadProgress', function( file, percentage ) { $("td.file-pro").text(""); var $li = $( '#'+file.id ).find('.file-pro'), $percent = $li.find('.file-progress .progress-bar'); // 避免重複建立 if ( !$percent.length ) { $percent = $('<div class="file-progress progress-striped active">' + '<div class="progress-bar" role="progressbar" style="width: 0%">' + '</div>' + '</div>' + '<br/><div class="per">0%</div>').appendTo( $li ).find('.progress-bar'); } $li.siblings('.file-status').text('上傳中'); $li.find('.per').text((percentage * 100).toFixed(2) + '%'); $percent.css( 'width', percentage * 100 + '%' ); }); // 所有檔案上傳成功後呼叫 uploader.on('uploadFinished', function () { //隱藏遮罩層操作 }); /*關閉上傳框視窗後恢復上傳框初始狀態*/ $('#picker').on('click',function(){ // 移除所有並將上傳檔案移出上傳序列 for (var i = 0; i < uploader.getFiles().length; i++) { // 將檔案從上傳序列移除 uploader.removeFile(uploader.getFiles()[i]); $("#"+uploader.getFiles()[i].id).remove(); } // 重置uploader uploader.reset(); }) </script> </body> </html>
CheckChumServlet
package com.uploader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileItem; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.FileUtils; /** * 分片上傳檔案處理類 */ public class CheckChumServlet extends HttpServlet { private static final long serialVersionUID = 1L; public CheckChumServlet() { super(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.getWriter().append("Served at: ").append(request.getContextPath()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 獲取分塊傳檔案的相關資訊 String chunk = request.getParameter("chunk"); String chunkSize = request.getParameter("chunkSize"); String guid = request.getParameter("guid"); //String fileName = request.getParameter("fileName"); //String fileMd5 = request.getParameter("fileMd5"); // 獲取分塊檔案臨時儲存的路徑並新建該分塊檔案 String path = request.getSession().getServletContext().getRealPath("/upload"); File checkFile = new File(path + "/" + guid + "/" + chunk); // 設定響應編碼為utf-8 response.setContentType("text/html;charset=utf-8"); // 檢查檔案是否存在,且大小是否一致 if (checkFile.exists() && checkFile.length() == Integer.parseInt(chunkSize)) { // 上傳過 將結果返回給前段處理 try { response.getWriter().write("{\"ifExist\":1}"); // System.out.println("分塊檔案已經存在"); } catch (IOException e) { e.printStackTrace(); } } else { // 沒有上傳過 返回給前段做處理 try { response.getWriter().write("{\"ifExist\":0}"); } catch (IOException e) { e.printStackTrace(); } } } }
UploadVideoServlet
package com.uploader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
/**
* 接受上傳檔案的servlet
*/
public class UploadVideoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 該類的無參構造方法
*/
public UploadVideoServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 臨時檔案的儲存路
String path = request.getSession().getServletContext().getRealPath("/upload");
// System.out.println(path);
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2、建立一個檔案上傳解析器
ServletFileUpload upload = new ServletFileUpload(factory);
// 設定單個檔案的最大上傳
upload.setFileSizeMax(15 * 1024 * 1024L);
// 設定整個request的最大記憶體
upload.setSizeMax(15 * 1024 * 1024L);
// 解決上傳檔名的中文亂碼
upload.setHeaderEncoding("UTF-8");
// 3、判斷提交上來的資料是否是上傳表單的資料
if (!ServletFileUpload.isMultipartContent(request)) {
return;
}
// 4、使用ServletFileUpload解析器解析上傳資料,解析結果返回的是一個List<FileItem>集合,每一個FileItem對應一個Form表單的輸入項
List<FileItem> list = null;
try {
list = upload.parseRequest(request);
} catch (FileUploadException e) {
e.printStackTrace();
}
HashMap<String, String> map = new HashMap<String, String>();
for (FileItem item : list) {
if (item.isFormField()) {
String name = item.getFieldName();
// 解決輸入項的資料的中文亂碼問題
String value = item.getString("UTF-8");
map.put(name, value);// 放入map集合
} else {
File fileParent = new File(path + "/" + map.get("guid"));// 以guid建立臨時資料夾
if (!fileParent.exists()) {
fileParent.mkdirs();
}
String filename = item.getName();
if (filename == null || filename.trim().equals("")) {
continue;
}
// 處理獲取到的上傳檔案的檔名的路徑部分,只保留檔名部分
filename = filename.substring(filename.lastIndexOf("\\") + 1);
// 建立檔案
File file;
// 以chunks分塊檔案的下標作為上傳檔案的名字
if (map.get("chunks") != null) {
file = new File(fileParent, map.get("chunk"));
} else {
file = new File(fileParent, "0");
}
// 將分塊檔案寫入到該檔案中
InputStream in = null;
FileOutputStream out = null;
byte[] byt = new byte[5 * 1024 * 1024];
try {
in = item.getInputStream();
out = new FileOutputStream(file);
int len;
while ((len = in.read(byt)) != -1) {
out.write(byt, 0, len);
}
} catch (Exception e) {
throw new RuntimeException("寫入失敗");
} finally {
try {
in.close();
} catch (Exception e2) {
throw new RuntimeException("關閉流失敗");
}
try {
out.close();
} catch (Exception e2) {
throw new RuntimeException("關閉流失敗");
}
}
}
}
}
}
UploadSuccessServlet
package com.uploader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//檔案合併類
public class UploadSuccessServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* 該類的無參構造方法
*/
public UploadSuccessServlet() {
super();
}
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getWriter().append("Served at: ").append(request.getContextPath());
}
/**
* 上傳成功後的業務處理
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 獲取上傳路徑
String path = request.getSession().getServletContext().getRealPath("/upload");
// 獲取guid引數
String guid = request.getParameter("fileMd5");
// 獲取檔名字
String fileName = request.getParameter("fileName");
// System.out.println("開始合併。。。檔案唯一表示符="+guid+";檔名字="+fileName);
// 建立儲存檔案的資料夾
new File("D:/XALC/DP/LianYunGangDaShuJuDP20181031-1/App_Uploads" + "/" + guid).mkdirs();
// 進行檔案合併
File newFile = new File("D:/XALC/DP/LianYunGangDaShuJuDP20181031-1/App_Uploads" + "/" + guid + "/" + fileName);
FileInputStream temp = null;
FileOutputStream outputStream = null;
List<File> files = null;
File dir = null;
dir = new File(path + "/" + guid);
File[] childs = dir.listFiles();
files = Arrays.asList(childs);
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File o1, File o2) {
int o1Num = Integer.parseInt(o1.getName());
int o2Num = Integer.parseInt(o2.getName());
return o1Num - o2Num;
}
});
try {
int len;
byte[] byt = new byte[5 * 1024 * 1024];
outputStream = new FileOutputStream(newFile, true);// 檔案追加寫入
for (int i = 0; i < files.size(); i++) {
temp = new FileInputStream(files.get(i));
while ((len = temp.read(byt)) != -1) {
outputStream.write(byt, 0, len);
}
temp.close();
temp = null;
}
} catch (IOException e) {
throw new RuntimeException("合併檔案失敗");
} finally {
if (temp != null) {
temp.close();
}
outputStream.close();
}
for (int i = 0; i < files.size(); i++) {
//刪除臨時檔案
files.get(i).delete();
}
//刪除臨時資料夾
dir.delete();
}
}
web.xml中註冊servlet:
<servlet>
<description></description>
<display-name>UploadVideoServlet</display-name>
<servlet-name>UploadVideoServlet</servlet-name>
<servlet-class>com.uploader.UploadVideoServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadVideoServlet</servlet-name>
<url-pattern>/UploadVideoServlet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>UploadSuccessServlet</display-name>
<servlet-name>UploadSuccessServlet</servlet-name>
<servlet-class>com.uploader.UploadSuccessServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UploadSuccessServlet</servlet-name>
<url-pattern>/UploadSuccessServlet</url-pattern>
</servlet-mapping>
<servlet>
<description></description>
<display-name>CheckChumServlet</display-name>
<servlet-name>CheckChumServlet</servlet-name>
<servlet-class>com.uploader.CheckChumServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>CheckChumServlet</servlet-name>
<url-pattern>/CheckChumServlet</url-pattern>
</servlet-mapping>
WebUploader前端資源以及lib下載