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> |
后端代码