1,是基於webUploader的前端開源插件實現的大大文件 分片上傳功能:
javascript
四種文件上傳格式(之後要改進的地方)css
普通按鈕點擊上傳html
拖拽上傳前端
複製粘貼上傳java
拖拽+按鈕+複製粘貼上傳jquery
多線程上傳文件
web
2,簡單的文件分片合併上傳原理:ajax
借鑑的博客: 地址
json
前段頁面
後端
<input type="file" id="file6" multiple> <button type="button" class="btnFile6">分片上傳6</button> <div class="result"></div> //方式6 $(".btnFile6").click(function () { var upload = function (file, skip) { var formData = new FormData();//初始化一個FormData對象 var blockSize = 1000000;//每塊的大小 var nextSize = Math.min((skip + 1) * blockSize, file.size);//讀取到結束位置 var fileData = file.slice(skip * blockSize, nextSize);//截取 部分文件 塊 formData.append("file", fileData);//將 部分文件 塞入FormData formData.append("fileName", file.name);//保存文件名字 $.ajax({ url: "/Home/SaveFile6", type: "POST", data: formData, processData: false, // 告訴jQuery不要去處理髮送的數據 contentType: false, // 告訴jQuery不要去設置Content-Type請求頭 success: function (responseText) { $(".result").html("已經上傳了" + (skip + 1) + "塊文件"); if (file.size <= nextSize) {//若是上傳完成,則跳出繼續上傳 alert("上傳完成"); return; } upload(file, ++skip);//遞歸調用 } }); }; var file = $("#file6")[0].files[0]; upload(file, 0); });
後端代碼
public string SaveFile6() { //保存文件到根目錄 App_Data + 獲取文件名稱和格式 var filePath = Server.MapPath("~/App_Data/") + Request.Form["fileName"]; //建立一個追加(FileMode.Append)方式的文件流 using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { using (BinaryWriter bw = new BinaryWriter(fs)) { //讀取文件流 BinaryReader br = new BinaryReader(Request.Files[0].InputStream); //將文件留轉成字節數組 byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length); //將字節數組追加到文件 bw.Write(bytes); } } return "保存成功"; }
3,監控文件上傳的三個時間點:(上傳)本地項目實例
時間點1: 全部分塊進行上傳以前(1,算文件的惟一標識,2,判斷文件是否秒傳)
計算分片文件的MD5惟一標識,
請求後臺是否保存過該文件,存在跳過該文件,不存在則繼續上傳.
時間點2: 若是分片上傳, 每一個分片上傳以前(1, 通知詢問後臺是否已經保存成功, 用於斷點續傳)
每一個每一個分塊有每一個分塊的下標和大小.
請求後臺是否保存過當前分塊,存在跳過該分片文件,實現斷點續傳
請求後臺是否保存完成該文件信息,若是保存過,則跳過; 若是不存在或者不完整則發送該分塊內容
攜帶當前文件的惟一標識到後臺, 用於讓後臺建立保存該文件分塊的目錄
時間點3: 全部分塊上傳成功以後(1, 通知後臺進行分塊文件的合併工做)
前臺通知後臺合併文件
3,後臺文件的處理:
用於讀取,寫入,映射和操做文件的通道。
用到這個方法
將上傳的分片放到零時的文件夾中,合併以後刪除分片
// 輸出流
FileChannel outChannel = FileOutputStream(file).getChannel()FileChannel inChannel(File tmp : fileList) { inChannel = FileInputStream(tmp).getChannel()inChannel.transferTo(inChannel.size()outChannel)inChannel.close()file.delete()} (outChannel != ) { (f)outChannel.close()}
文件上傳的前端代碼:
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>webuploaderDemo</title> | |
<link rel="stylesheet" href="staticresource/layui/css/layui.css"> | |
<link rel="stylesheet" href="staticresource/webuploader/upload.media.css"> | |
<link rel="stylesheet" href="staticresource/css/style.css"> | |
<script src="staticresource/js/jquery-3.2.1.min.js"></script> | |
<script src="staticresource/layui/layui.all.js"></script> | |
<script src="staticresource/webuploader/webuploader.js"></script> | |
<style type="text/css"> | |
.upload-table{ | |
padding: 2px 12px; | |
} | |
.fileQueue{ | |
height: 300px; | |
overflow-x: hidden; | |
overflow-y: auto; | |
background-color: #f8f8f8; | |
width: 100%; | |
} | |
.hiden{ | |
display: none | |
} | |
</style> | |
<script type="text/javascript"> | |
var base = "/upload"; | |
var element; | |
var layer; | |
var index=0; | |
var batchWebUpload; | |
var fileAllNum=0; | |
var fileAllSize=0; | |
var successNum=0; | |
var successSize=0; | |
var percentages = {}; // 全部文件的進度信息,key爲file id | |
$(function(){ | |
layui.use(['layer','element','code'], function(){ | |
element = layui.element; | |
layer = layui.layer; | |
layui.code({ | |
elem: 'pre', //默認值爲.layui-code | |
encode: true, //是否轉義html標籤。默認不開啓 | |
about: false | |
}); | |
layer.ready(function(){ | |
showWindow() | |
}); | |
}); | |
var fileCheckUrl=base+"/file/checkFile";//檢測文件是否存在url | |
var checkChunkUrl=base+"/file/checkChunk";//檢測分片url | |
var mergeChunksUrl=base+"/file/mergeChunks";//合併文件請求地址 | |
//監控文件上傳的三個時間點(注意:該段代碼必須放在WebUploader.create以前) | |
//時間點1::全部分塊進行上傳以前(1.能夠計算文件的惟一標記;2.能夠判斷是否秒傳) | |
//時間點2: 若是分塊上傳,每一個分塊上傳以前(1.詢問後臺該分塊是否已經保存成功,用於斷點續傳) | |
//時間點3:全部分塊上傳成功以後(1.通知後臺進行分塊文件的合併工做) | |
WebUploader.Uploader.register({ | |
"before-send-file":"beforeSendFile", | |
"before-send":"beforeSend", | |
"after-send-file":"afterSendFile" | |
},{ | |
//時間點1::全部分塊進行上傳以前調用此函數 | |
//時間點1::全部分塊進行上傳以前調用此函數 | |
beforeSendFile:function(file){//利用md5File()方法計算文件的惟一標記符 | |
//建立一個deffered | |
var deferred = WebUploader.Deferred(); | |
//1.計算文件的惟一標記,用於斷點續傳和秒傳,獲取文件前2m的md5值,越小越快,防止碰撞,把文件名文件大小和md5拼接做爲文件惟一標識 | |
(new WebUploader.Uploader()).md5File(file,0,2*1024*1024).progress(function(percentage){ | |
}).then(function(val){ | |
fileMd5 = file.size+"-"+val+"-"+file.name;//防止碰撞,把文件名文件大小和md5拼接做爲文件惟一標識 | |
file.fileMd5=fileMd5; | |
//2.請求後臺是否保存過該文件,若是存在,則跳過該文件,實現秒傳功能 | |
$.ajax({ | |
type:"POST", | |
url:fileCheckUrl, | |
data:{ | |
fileMd5:fileMd5//文件惟一標記 | |
}, | |
dataType:"json", | |
success:function(response){ | |
console.log(response); | |
if(response.success){ | |
console.log($("#"+file.id).find('.percent').html()); | |
$("#"+file.id).find('.percent').html("100%"); | |
$("#"+file.id).find(".layui-progress-bar").removeClass('layui-bg-blue'); | |
element.progress('progress_'+file.id, '100%'); | |
batchWebUpload.skipFile(file); | |
successNum++; | |
successSize+=file.size; | |
//若是存在,則跳過該文件,秒傳成功 | |
deferred.reject(); | |
}else{ | |
//繼續上傳 | |
deferred.resolve(); | |
} | |
} | |
} | |
); | |
}); | |
//返回deffered | |
return deferred.promise(); | |
}, | |
//時間點2:若是有分塊上傳,則 每一個分塊上傳以前調用此函數 | |
//block:表明當前分塊對象 | |
beforeSend:function(block){//向後臺發送當前文件的惟一標記,用於後臺建立保存分塊文件的目錄 | |
//1.請求後臺是否保存過當前分塊,若是存在,則跳過該分塊文件,實現斷點續傳功能 | |
var deferred = WebUploader.Deferred(); | |
//請求後臺是否保存完成該文件信息,若是保存過,則跳過,若是沒有,則發送該分塊內容 | |
$.ajax({ | |
type:"POST", | |
url:checkChunkUrl, | |
data:{ | |
//文件惟一標記,敲黑板劃重點了,不要用file.fileMd5,否則服務器上的文件會出錯 | |
fileMd5: block.file.fileMd5, | |
//當前分塊下標 | |
chunk:block.chunk, | |
//當前分塊大小 | |
chunkSize:block.end-block.start | |
}, | |
dataType:"json", | |
success:function(response){ | |
if(response.success){ | |
//分塊存在,跳過該分塊 | |
deferred.reject(); | |
}else{ | |
//分塊不存在或者不完整,從新發送該分塊內容 | |
deferred.resolve(); | |
} | |
} | |
} | |
); | |
//攜帶當前文件的惟一標記到後臺,用於讓後臺建立保存該文件分塊的目錄 | |
// this.owner.options.formData.fileMd5 = block.file.fileMd5; | |
return deferred.promise(); | |
}, | |
//時間點3:全部分塊上傳成功以後調用此函數 | |
afterSendFile:function(file){//前臺通知後臺合併文件 | |
//1.若是分塊上傳,則經過後臺合併全部分塊文件 | |
//請求後臺合併文件 | |
$.ajax({ | |
type:"POST", | |
url:mergeChunksUrl, | |
data:{ | |
//文件惟一標記 | |
fileMd5:file.fileMd5, | |
//文件名稱 | |
fileName:file.name | |
}, | |
dataType:"json", | |
success:function(response){ | |
console.log("合併分片完成"); | |
console.log(response); | |
$("#"+file.id).find(".layui-progress-bar").removeClass('layui-bg-blue'); | |
element.progress('progress_'+file.id, '100%'); | |
$("#"+file.id).find('.percent').html("100%"); | |
successNum++; | |
successSize+=file.size; | |
} | |
}); | |
} | |
}); | |
var batchWebUpload_btn = $("#batchWebUpload"); | |
batchWebUpload = WebUploader.create({ | |
auto:false, | |
pick: { | |
id: batchWebUpload_btn,//指定選擇文件的按鈕容器,不指定則不建立按鈕。注意 這裏雖然寫的是 id, 不只支持 id, 還支持 class, 或者 dom 節點。 | |
//label : title, 官方建議採用 innerHTML 代替 | |
//innerHTML : title, | |
multiple :true //開啓文件多選 | |
}, | |
flash:base+'/staticresource/webuploader/Uploader.swf',//ie9一下會自動使用flash上傳 | |
/** accept:{//不驗證文件類型了 | |
title: '不驗證了',//字符串類型,文字描述 | |
extensions: '*',//容許的文件後綴,不帶點,多個用逗號分割。 | |
mimeTypes: 'application/*,'//多個用逗號分割,怎麼不知道咋寫的,參考w3c MIME 參考手冊,傳送門 http://www.w3school.com.cn/media/media_mimeref.asp | |
},**/ | |
server: base+"/file/uploadChunks", | |
//壓縮圖片,若是圖片尺寸超過設置的尺寸,會自動壓縮圖片,必要時會裁剪 | |
compress:{ | |
width: 600, | |
height: 600, | |
// 圖片質量,只有type爲`image/jpeg`的時候纔有效。 | |
quality: 90, | |
// 是否容許放大,若是想要生成小圖的時候不失真,此選項應該設置爲false. | |
allowMagnify: false, | |
// 是否容許裁剪 | |
crop: false, | |
// 是否保留頭部meta信息。 | |
preserveHeaders: true, | |
// 若是發現壓縮後文件大小比原來還大,則使用原來圖片 | |
// 此屬性可能會影響圖片自動糾正功能 | |
noCompressIfLarger: false | |
}, | |
// 單位字節,若是圖片大小小於此值,不會採用壓縮。512k 512*1024,若是設置爲0,原圖尺寸大於設置的尺寸就會壓縮;若是大於0,只有在原圖尺寸大於設置的尺寸,而且圖片大小大於此值,纔會壓縮 | |
compressSize: 0, | |
fileNumLimit : 10,//驗證文件總數量, 超出則不容許加入隊列,默認值:undefined,若是不配置,則不限制數量 | |
fileSizeLimit : 100*1024*1024*1024, //1kb=1024*1024,驗證文件總大小是否超出限制, 超出則不容許加入隊列。 | |
fileSingleSizeLimit :10*1024*1024*1024, //驗證單個文件大小是否超出限制, 超出則不容許加入隊列。 | |
chunked:true,//是否開啓分片上傳 | |
threads:3, | |
chunkSize:5*1024*1024,//若是要分片,每一片的文件大小 | |
prepareNextFile:false//在上傳當前文件時,準備好下一個文件,請設置成false,否則開啓文件多選你瀏覽器會卡死 | |
}); | |
//當文件上傳成功時觸發。file {File} File對象, response {Object}服務端返回的數據 | |
batchWebUpload.on('uploadSuccess',function(file,response){ | |
layer.msg("上傳完成,服務端返回信息請按F12,看控制檯:"); | |
console.log(file); | |
if(response.success){ | |
console.log(response); | |
} | |
}) | |
//錯誤類型。文件驗證不經過時觸發 | |
//錯誤類型說明:Q_EXCEED_NUM_LIMIT 上傳文件超過限制的數量 | |
//Q_EXCEED_SIZE_LIMIT文件總大小超出限制 | |
//Q_TYPE_DENIED 當文件類型不對 | |
batchWebUpload.on("error",function(type,file){ | |
console.log(type); | |
if (type=="Q_TYPE_DENIED"){ | |
layer.msg("只能上傳gif,jpg,jpeg,bmp,png格式文件"); | |
}else if(type=="Q_EXCEED_SIZE_LIMIT"){ | |
layer.msg("全部的文件大小總和不能超過10M"); | |
}else if(type=='F_EXCEED_SIZE'){ | |
layer.msg("單個文件大小不能超過1M"); | |
}else if(type=='Q_EXCEED_NUM_LIMIT'){ | |
layer.msg(typeName+"最多隻能上傳10個"); | |
}else if(type=='F_DUPLICATE'){ | |
layer.msg(file.name+"已經在上傳隊列,請勿重複上傳"); | |
}else{ | |
layer.msg("上傳出錯"); | |
} | |
}) | |
//文件加入隊列 | |
batchWebUpload.on("fileQueued",function(file){ | |
fileAllNum++; | |
fileAllSize+=file.size; | |
var fileSize = (file.size/1024/1024.0).toFixed(2)+"M"; | |
var progress = '<div class="layui-progress " lay-filter="progress_'+file.id | |
+'" lay-showPercent="yes" ><div class="layui-progress-bar layui-bg-blue" lay-percent="0"></div></div>'; | |
var buttons = '<a id="pause_Btn_'+file.id+'" class="layui-btn layui-btn-xs layui-btn-primary">暫停</a>'; | |
buttons+='<a id="continue_Btn_'+file.id+'" class="layui-btn layui-btn-xs layui-btn-primary hiden">繼續上傳</a>'; | |
buttons+='<a id="delete_Btn_'+file.id+'" class="layui-btn layui-btn-xs layui-btn-primary">刪除</a>'; | |
var htm = '<tr id="'+file.id+ | |
'"><td>'+file.name+ | |
'</td> <td>'+file.ext+ | |
'</td><td>'+fileSize+ | |
'</td><td>'+progress+ '</td><td class="percent">0%</td><td class="status">等待上傳</td><td>'+buttons+'</td></tr>'; | |
$("#fileList").append(htm); | |
//綁定事件 | |
$("#pause_Btn_"+file.id).bind('click',function(){ | |
batchWebUpload.stop(file); | |
}) | |
$("#continue_Btn_"+file.id).bind('click',function(){ | |
batchWebUpload.upload(file); | |
}) | |
$("#delete_Btn_"+file.id).bind('click',function(){ | |
batchWebUpload.removeFile( file, true );//從文件隊列移除 | |
}) | |
percentages[ file.id ] = [ file.size, 0 ]; | |
file.on('statuschange', function( cur, prev ) { | |
// 成功 | |
if ( cur === 'error' || cur === 'invalid' ) { | |
percentages[ file.id ][ 1 ] = 1; | |
} else if ( cur === 'queued' ) { | |
percentages[ file.id ][ 1 ] = 0; | |
} | |
}) | |
$("#"+file.id).find('.percent').html("0%"); | |
element.progress('progress_'+file.id, '0%'); | |
}); | |
/**從文件隊列移除**/ | |
batchWebUpload.on('fileDequeued', function( file ) { | |
fileAllNum--; | |
fileAllSize-=file.size; | |
delete percentages[ file.id ]; | |
}); | |
/**上傳以前**/ | |
batchWebUpload.on('uploadBeforeSend', function( block, data, headers ) { | |
data.fileMd5 = block.file.fileMd5; | |
//block.file.chunks = block.chunks;//當前文件總分片數量 | |
data.chunks = block.file.chunks; | |
}); | |
/**上傳過程當中觸發,攜帶上傳進度**/ | |
batchWebUpload.on('uploadProgress',function(file ,percentage){ | |
var percent=(percentage*100 ).toFixed(2)+"%"; | |
element.progress('progress_'+file.id, percent);//設置進度條百分比 | |
if(JSON.stringify(percentages) != "{}"){ | |
percentages[ file.id ][ 1 ] = percentage; | |
} | |
if(percentage==1){ | |
percentages[ file.id ][ 1 ] = 1; | |
} | |
$("#"+file.id).find(".status").text("正在上傳"); | |
$("#"+file.id).find('.percent').html(percent); | |
}) | |
batchWebUpload.on( 'all', function( type ,file) { | |
updateTotalProgress(); | |
switch( type ) { | |
case 'uploadComplete': | |
parentId = file.id; | |
percentages[ file.id ][ 1 ] = 1; | |
break; | |
case 'uploadFinished': | |
break; | |
case 'uploadProgress': | |
break; | |
case 'uploadStart': | |
break; | |
case 'stopUpload': | |
break; | |
} | |
}) | |
}) | |
/**左下角彈出框**/ | |
function showWindow(){ | |
if(index!=0){ | |
try{ | |
layer.restore(index); | |
}catch(err){ | |
//console.log("彈出層已經打開"); | |
return index; | |
} | |
return index; | |
} | |
//iframe窗 | |
index=layer.open({ | |
type: 1, | |
title: ['上傳隊列', 'font-size:14px;'], | |
closeBtn: 0, //不顯示關閉按鈕 | |
shade: false, | |
area: ['700px', '400px'], | |
offset: 'rb', //右下角彈出 | |
anim: 2, | |
maxmin :true, | |
content: $("#fileListDom"), //iframe的url,no表明不顯示滾動條 | |
}); | |
return index; | |
} | |
function startAll(file){ | |
batchWebUpload.upload(file); | |
} | |
function stopUpload(file){ | |
if(file==undefined){ | |
batchWebUpload.stop(); | |
}else{ | |
batchWebUpload.stop(file); | |
} | |
} | |
function cancelFile(file){ | |
batchWebUpload.cancelFile(file); | |
} | |
/**設置總百分比**/ | |
function updateTotalProgress() { | |
var fize = (fileAllSize/1024/1024.0).toFixed(2); | |
var sSize = (successSize/1024/1024.0).toFixed(2); | |
$("#fileQueueNum").text(fileAllNum) | |
$("#fileQueueSize").text(fize); | |
var loaded = 0; | |
var total = 0; | |
var percent = 0;; | |
$.each( percentages, function( k, v ) { | |
total += v[ 0 ]; | |
loaded += v[ 0 ] * v[ 1 ]; | |
} ); | |
percent = total ? loaded / total : 0; | |
$("#successNum").text(successNum); | |
$("#successSize").text(sSize); | |
var show_pe= Math.round( percent * 100 ) + '%'; | |
element.progress('sumProgress', show_pe); | |
$("#sumPercent").text(show_pe); | |
} | |
</script> | |
</head> | |
<body> | |
<a class="layui-btn layui-btn-danger layui-btn-sm" id="batchWebUpload"> | |
<i class="layui-icon"></i>劃重點,多選,分片批量上傳+斷點續傳 | |
</a> | |
</body> | |
<div id="fileListDom" class="upload-table" style="display: none"> | |
<a class="layui-btn layui-btn-danger layui-btn-sm" id="startAll" onclick="startAll()"> | |
所有開始 | |
</a> | |
<a class="layui-btn layui-btn-danger layui-btn-sm" id="stopAll" onclick="stopUpload()"> | |
所有暫停 | |
</a> | |
<table class="layui-table" lay-size="sm"> | |
<colgroup> | |
<col width="200"> | |
<col width="150"> | |
<col width="150"> | |
<col width="350"> | |
<col width="150"> | |
<col width="200"> | |
<col width="450"> | |
<col> | |
</colgroup> | |
<thead> | |
<tr> | |
<th>文件名</th> | |
<th>文件類型</th> | |
<th>文件大小</th> | |
<th>進度</th> | |
<th>百分比</th> | |
<th>狀態</th> | |
<th>操做</th> | |
</tr> | |
</thead> | |
<tbody id="fileList" class="fileQueue"> | |
</tbody> | |
<tfoot id="fileInfo"> | |
<tr> | |
<td colspan="4"> | |
<span>選中<span id="fileQueueNum">0</span>個文件,共<span id="fileQueueSize">0</span>M。</span><br> | |
<span>上傳成功<span id="successNum">0</span>個文件,共<span id="successSize">0</span>M。</span> | |
</td> | |
<td > | |
<span id="sumPercent"></span> | |
</td> | |
<td colspan="2"> | |
<div class="layui-progress" lay-showPercent="yes" lay-filter="sumProgress"> | |
<div class="layui-progress-bar" lay-percent="0%" id="sumProgress_bar" ></div> | |
</div> | |
</td> | |
</tr> | |
</tfoot> | |
</table> | |
</div> | |
</html> |
後端代碼