1. 程式人生 > 其它 >ajax+JAVA上傳大檔案,帶進度條(可以改造成斷點續傳)

ajax+JAVA上傳大檔案,帶進度條(可以改造成斷點續傳)

技術標籤:個人隨筆上傳超大檔案JAVA斷點續傳AJAX斷點續傳

上傳檔案是常用的功能,以前由於網速、技術、電腦硬體各方面的原因導致通過web方式上傳大檔案是一件比較困難的事情,隨著各方面的技術發展,大檔案上傳變的容易了。

上傳檔案如果是小檔案,以前都是把後臺的設定修改成專案中允許的最大容量,一次性做檔案上傳即可,但這種方法用於上傳大檔案肯定是不可取的,不管是網速還是客戶端瀏覽器都不支援這種情況。

大檔案上傳之所以比較麻煩,主要問題就在於檔案容量過大,那麼如果我們可以將要上傳的大檔案在前端分塊上傳其實就能解決這個問題。

上傳大檔案的流程: 前端將檔案分塊 -->不斷將小塊檔案依次上傳給後臺 --> 後臺接收檔案後,合併檔案

一、前端分割大檔案

檔案表單 :<input type="file" id="file" name="file" />

上傳按鈕 :<input type="button" id="breakPointUploadFile" name="breakPointUploadFile" />

分割大檔案

// 設定分塊的大小: 2 M
const chunkSize = 2*1024*1024;
const file = $('#file')[0].files[0];
// 分片總數
const totalChunk = Math.ceil(file.size / chunkSize); 

二、JAVA後端接收分塊的小檔案後合併檔案

java獲取到前端提交的上傳檔案,後臺通過常用的IO的處理類寫入檔案到伺服器的指定位置,這樣就完成了檔案的上傳,但常用的檔案處理類都是從頭開始寫入檔案,而分塊上傳大檔案時,如果每次都是從頭開始寫入,

那就會覆蓋掉前面上傳的檔案,這樣肯定是不對的。而如果我們可以做到在指定位置寫入檔案,其實就能解決小檔案合併成大檔案的功能,而JAVA 的API中的RandomAccessFile類就可以實現此功能。

RandomAccessFile是java Io體系中功能最豐富的檔案內容訪問類。即可以讀取檔案內容,也可以向檔案中寫入內容。

  • long getFilePointer(); 返回檔案記錄指標的當前位置
  • void seek(long pos); 將檔案記錄指標定位到pos位置

建立RandomAccessFile物件還需要指定一個mode引數。該引數指定RandomAccessFile的訪問模式,有以下4個值:

  • “r” 以只讀方式來開啟指定資料夾。如果試圖對該RandomAccessFile執行寫入方法,都將丟擲IOException異常。
  • “rw” 以讀,寫方式開啟指定檔案。如果該檔案尚不存在,則試圖建立該檔案。
  • “rws” 以讀,寫方式開啟指定檔案。相對於”rw” 模式,還要求對檔案內容或元資料的每個更新都同步寫入到底層裝置。
  • “rwd” 以讀,寫方式開啟指定檔案。相對於”rw” 模式,還要求對檔案內容每個更新都同步寫入到底層裝置。

三、前端AJAX迴圈上傳分塊的檔案

const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
$(function(){
	const chunkSize = 2 * 1024*1024;// 每個chunk的大小,2兆 	
	$("#breakPointUploadFile").click(function(){
		const file = $('#file')[0].files[0];
		// 分片總數
		const totalChunk = Math.ceil(file.size / chunkSize); 		
		breakPointUploadFile(0,totalChunk,chunkSize,file);
	});	
});
/**
 * 分片上傳
 * i - 第幾片
 * totalChunk - 分片總數
 * chunkSize  - 每片大小
 * file 要上傳的檔案
 */
function breakPointUploadFile(i,totalChunk,chunkSize,file){
	var per = (i*100/totalChunk).toFixed(1)+"%";
	$(".progressBar").css("width",per);
	$(".progressBar").html(per);
	const startLength = i * chunkSize; //當前上傳檔案塊的起始位置
    const endLength = Math.min(file.size, startLength + chunkSize);
	var formData = new FormData();
    formData.append("file", blobSlice.call(file, startLength, endLength));
    formData.append("startLength",startLength);
    formData.append("fileName",file.name);   
    $.ajax({
        url: '/upload/breakPointUploadFileDo.do',
        dataType:'json',
        type:'POST',
        async: false,
        data: formData,
        processData : false, // 使資料不做處理
        contentType : false, // 不要設定Content-Type請求頭
        success: function(data){
            console.log(data);
            if (data.succeed) {
            	i++;
            	if(i<totalChunk){
            		console.log("****>" + i);
    	        	setTimeout(function (){breakPointUploadFile(i,totalChunk,chunkSize,file);},200);
            	}else{
            		$(".progressBar").css("width","100%");
            		$(".progressBar").html("100%");
            		alert("檔案上傳成功");
            	}
            }
        },
        error:function(response){
            console.log(response);
            alert("異常")
        }
    });
}

上面就是分割檔案並通過AJAX迴圈上傳的程式碼。

上傳分塊檔案的方法:

functionbreakPointUploadFile(i,totalChunk,chunkSize,file)

進度條處理:

var per = (i*100/totalChunk).toFixed(1)+"%";
$(".progressBar").css("width",per);
$(".progressBar").html(per);

當前上傳檔案塊的起始位置:const startLength = i * chunkSize; //要將此值提交到後臺
當前上傳檔案塊的結束位置:const endLength = Math.min(file.size, startLength + chunkSize);

四、JAVA後臺合併檔案

    @ResponseBody
	@RequestMapping("breakPointUploadFileDo")
	public AjaxResponse breakPointUploadFileDo(
			@RequestParam final MultipartFile file,
			final long startLength,
			final String fileName) {
		final AjaxResponse ajaxResponse = new SucceedResponse("檔案上傳成功");
		System.out.println(file + " ** " + startLength);
		if (file.getSize() <= 0) {
			return new ErrorResponse("請選擇上傳的檔案!");
		}
		int len = 0;
		final byte[] arr = new byte[1024];
		final String writeFileName = "D:/test-" + fileName;
		try (RandomAccessFile writeFile = new RandomAccessFile(writeFileName, "rw")) {
			writeFile.seek(startLength);
			final InputStream iStream = file.getInputStream();
			while ((len = iStream.read(arr)) != -1) {
				writeFile.write(arr, 0, len);
			}
		} catch (final Exception e) {
			final String errorMessage = "斷點上傳檔案異常";
			this.logger.error(errorMessage, e);
			throw new NormStarRuntimeException(errorMessage, e);
		}
		return ajaxResponse;
	}

AjaxResponse.java

public abstract class AjaxResponse implements Serializable {

	private static final long serialVersionUID = -5858819825109609209L;

	/**
	 * 是否成功
	 */
	private boolean succeed;
	/**
	 * 提示資訊編碼 如:NS00001
	 */
	private String code;
}

SucceedResponse.java \ErrorRespons.java 繼承AjaxResponse ,分別預設succeed的值為true、false

五、效果

六、斷點續傳(簡單版)

具體的程式碼就不寫了,講一個簡單版本的思路。顧名思義,斷點續傳也即在上傳大檔案時中途因為各種原因導致檔案只上傳了一部分,需要再一次繼續上傳,並且跳過已經上傳的檔案部分,直接從未上傳的部分開始。

這裡就要解決幾個問題。1.如果保證上傳的檔案,以前已經上傳過了 2. 如何知道從那一塊開始上傳

1.如何判斷檔案已經上傳過了

可以通過檔案的MD5值來判斷此檔案以前是否已經上傳過,這裡就需要用到spark-md5.min.js 外掛 (文末有下載地址,及使用方法),後臺需要將檔案的md5值和當前檔案上傳成功的最後一次的檔案塊數記錄下來(必須保證2次檔案分塊的大小是一致的,及上面的chunkSize引數是一致的)

2.如何知道從那一塊開始上傳

在每次上傳的首次,先去資料庫裡獲取檔案的MD5是否存在,如果存在,則獲取上一次上傳成功時的檔案塊的位置數,然後直接調到下一個檔案塊上傳即可。

spark-md5.min.js 外掛及使用方法:https://download.csdn.net/download/zhuiyue82/13609404