1. 程式人生 > >WebUploader 實現大檔案的斷點續傳功能

WebUploader 實現大檔案的斷點續傳功能

       斷點續傳指的是在下載或上傳時,將下載或上傳任務(一個檔案或一個壓縮包)人為的劃分為幾個部分,每一個部分採用一個片段進行上傳或下載,如果碰到網路故障,可以從已經上傳或下載的部分開始繼續上傳下載未完成的部分,而沒有必要從頭開始上傳下載。使用者可以節省時間,提高速度。

       本文采用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下載

連結:https://pan.baidu.com/s/1QsKoX4Xndoa-9dS5GvPy9w     密碼:m96u