前言:
前端如何將大文件上傳到後臺服務器,如何避免因爲特殊情況導致文件上傳失敗而不至於重新上傳。
序言
斷點續傳: 在下載或上傳時,將下載或上傳任務(一個文件或一個壓縮包)人爲的劃分爲幾個部分,每一個部分採用一個線程進行上傳或下載,如果碰到網絡故障,可以從已經上傳或下載的部分開始繼續上傳下載未完成的部分,而沒有必要從頭開始上傳下載。
一、webuploader.js實現斷點續傳
webuploader是由百度團隊開發的一個簡單的以h5爲主,flash爲輔的現代文件上傳組件,採用大文件分片併發上傳,極大的提高了文件上傳效率。
功能:1、分片、併發;2、預覽、壓縮;3、多途徑添加文件;4、h5 & flash 5、md5秒傳;6、易擴展、可拆分。
1、樣例
該樣例支持文件多選、單獨上傳、全部上傳、刪除、暫停以及進度管理。
2、代碼
html5代碼
<head>
<link rel="stylesheet" type="text/css"
href="https://cdnjs.cloudflare.com/ajax/libs/webuploader/0.1.1/webuploader.css">
<link rel="stylesheet" type="text/css"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<script src="/static/js/jquery-2.0.3.min.js"></script>
<script src="/static/js/webuploader.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</head>
<div id="uploader" class="container">
<!--用來存放文件信息-->
<div id="thelist" class="row">
<div class="panel panel-primary">
<div class="panel-heading">文件上傳</div>
<table class="table table-striped table-bordered" id="uploadTable">
<thead>
<tr>
<th>序號</th>
<th>文件名稱</th>
<th>文件大小</th>
<th>上傳狀態</th>
<th>上傳進度</th>
<th style="width:15%;">操作</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="panel-footer">
<div id="picker">選擇文件</div>
<button id="btn" class="btn btn-default">開始上傳</button>
</div>
</div>
</div>
</div>
script代碼
<script type="text/javascript">
var fileMd5;
var fileSuffix;
var $list = $("#thelist table>tbody");
var state = 'pending';//初始按鈕狀態
var $btn = $("#btn");
var count = 0;
//監聽分塊上傳過程中的三個時間點
WebUploader.Uploader.register({
"before-send-file": "beforeSendFile",
"before-send": "beforeSend",
"after-send-file": "afterSendFile",
}, {
//時間點1:所有分塊進行上傳之前調用此函數
beforeSendFile: function (file) {
var deferred = WebUploader.Deferred();
//1、計算文件的唯一標記,用於斷點續傳
(new WebUploader.Uploader()).md5File(file, 0, 1024)
.progress(function (percentage) {
$('#' + file.id).find("td.state").text("正在讀取文件信息...");
}).then(function (val) {
fileMd5 = val;
$('#' + file.id).find("td.state").text("成功獲取文件信息...");
//獲取文件信息後進入下一步
deferred.resolve();
});
return deferred.promise();
},
//時間點2:如果有分塊上傳,則每個分塊上傳之前調用此函數
beforeSend: function (block) {
var deferred = WebUploader.Deferred();
$.ajax({
type: "POST",
url: "{% url 'checkChunk'%}",
data: {
//文件唯一標記
fileMd5: fileMd5,
//當前分塊下標
chunk: block.chunk,
//當前分塊大小
chunkSize: block.end - block.start
},
dataType: "json",
success: function (response) {
if (response.ifExist) {
//分塊存在,跳過
deferred.reject();
} else {
//分塊不存在或不完整,重新發送該分塊內容
deferred.resolve();
}
}
});
this.owner.options.formData.fileMd5 = fileMd5;
deferred.resolve();
return deferred.promise();
},
//時間點3:所有分塊上傳成功後調用此函數
afterSendFile: function (file) {
//如果分塊上傳成功,則通知後臺合併分塊
$.ajax({
type: "POST",
url: "{% url 'mergeChunks'%}",
data: {
fileId: file.id,
fileMd5: fileMd5,
fileSuffix: fileSuffix,
fileName: file.name,
},
success: function (response) {
console.log(response.fileName + " 上傳成功")
$('#del' + file.id).hide();
}
});
}
});
var uploader = WebUploader
.create({
// swf文件路徑
swf: '/static/js/Uploader.swf',
// 文件接收服務端。
server: "/PV/projects/upload/",
// 選擇文件的按鈕。可選。
// 內部根據當前運行是創建,可能是input元素,也可能是flash.
pick: {
id: '#picker',
multiple: true
},
// 不壓縮image, 默認如果是jpeg,文件上傳前會壓縮一把再上傳!
resize: true,
auto: false,
//開啓分片上傳
chunked: true,
chunkSize: 10 * 1024 * 1024,
accept: {
extensions: "txt,jpg,jpeg,bmp,png,zip,rar,war,pdf,cebx,doc,docx,ppt,pptx,xls,xlsx,iso,flv,mp4",
mimeTypes: '.txt,.jpg,.jpeg,.bmp,.png,.zip,.rar,.war,.pdf,.cebx,.doc,.docx,.ppt,.pptx,.xls,.xlsx,.iso,.flv,.mp4'
}
});
// 當有文件被添加進隊列的時候
uploader.on('fileQueued', function (file) {
//保存文件擴展名
fileSuffix = file.ext;
fileName = file.source['name'];
var fileSize = file.size;
var fileSizeStr = "";
fileSizeStr = WebUploader.Base.formatSize(fileSize);
count++;
$list.append(
'<tr id="' + file.id + '" class="item" flag=0>' +
'<td class="index">' + count + '</td>' +
'<td class="info">' + file.name + '</td>' +
'<td class="size">' + fileSizeStr + '</td>' +
'<td class="state">等待上傳...</td>' +
'<td class="percentage"></td>' +
'<td class="operate"><button name="upload" id="del' + file.id + '" class="btn btn-warning">開始</button><button name="delete" class="btn btn-error">刪除</button></td></tr>');
});
// 文件上傳過程中創建進度條實時顯示。
uploader.on('uploadProgress', function (file, percentage) {
$('#' + file.id).find('td.percentage').text(
'上傳中 ' + Math.round(percentage * 100) + '%');
});
uploader.on('uploadSuccess', function (file) {
$('#' + file.id).find('td.state').text('已上傳');
});
uploader.on('uploadError', function (file) {
$('#' + file.id).find('td.state').text('上傳出錯');
});
uploader.on('uploadComplete', function (file) {
uploader.removeFile(file);
});
uploader.on('all', function (type) {
if (type === 'startUpload') {
state = 'uploading';
} else if (type === 'stopUpload') {
state = 'paused';
} else if (type === 'uploadFinished') {
state = 'done';
}
if (state === 'uploading') {
$btn.text('暫停上傳');
} else {
$btn.text('開始上傳');
}
});
$btn.on('click', function () {
if (state === 'uploading') {
uploader.stop(true);
} else {
uploader.upload();
}
});
$("body").on("click", "#uploadTable button[name='upload']", function () {
flag = $(this).parents("tr.item").attr("flag") ^ 1;
$(this).parents("tr.item").attr("flag", flag);
var id = $(this).parents("tr.item").attr("id");
if (flag == 1) {
$(this).text("暫停");
uploader.upload(uploader.getFile(id, true));
} else {
$(this).text("開始");
uploader.stop(uploader.getFile(id, true));
}
});
$("body").on("click", "#uploadTable button[name='delete']", function () {
var id = $(this).parents("tr.item").attr("id");
$(this).parents("tr.item").remove();
uploader.removeFile(uploader.getFile(id, true));
});
</script>
url路由代碼
"跳轉上傳頁面"
url('index/', views.index),
url('upload/', views.upload, name='upload'),
"分塊上傳"
url('checkChunk/', views.checkChunk, name='checkChunk'),
"上傳結束合併刪除分塊"
url('mergeChunks/', views.mergeChunks, name='mergeChunks'),
djagno 後臺代碼
def upload(request):
"""
前端上傳的分片 保存到 指定的目錄下
:param request:
:return:
"""
if request.method == 'POST':
md5 = request.POST.get("fileMd5")
chunk_id = request.POST.get("chunk", "0")
fileName = "%s-%s" % (md5, chunk_id)
file = request.FILES.get("file")
with open(FILE_PATH + fileName, 'wb') as f:
for i in file.chunks():
f.write(i)
return JsonResponse({'upload_part': True})
def checkChunk(request):
"""
檢查上傳分片是否重複,如果重複則不提交,否則提交
:param request:
:return:
"""
if request.method == 'POST':
chunkSize = request.POST.get("chunkSize")
if chunkSize == '0':
return JsonResponse({'ifExist': True})
file_name = request.POST.get('fileMd5') + request.POST.get('chunk')
if file_name not in get_deep_data():
return JsonResponse({'ifExist': False})
return JsonResponse({'ifExist': True})
def get_deep_data(path=FILE_PATH):
"""
判斷一個文件是否在一個目錄下
:param path:
:return:
"""
result = []
data = os.listdir(path)
for i in data:
if os.path.isdir(i):
get_deep_data(i)
else:
result.append(i)
return result
def mergeChunks(request):
"""
將每次上傳的分片合併成一個新文件,並刪除分片數據
:param request:
:return:
"""
if request.method == 'POST':
chunk = 0
id = request.POST.get("fileId")
md5 = request.POST.get("fileMd5")
fileName = request.POST.get("fileName")
path = os.path.join(FILE_PATH, fileName)
with open(path, 'wb') as fp:
while True:
try:
filename = FILE_PATH + '/{}-{}'.format(md5, chunk)
with open(filename, 'rb') as f:
fp.write(f.read())
os.remove(filename)
except:
break
chunk += 1
return JsonResponse({'upload': True, 'fileName': fileName, 'fileId': id})
3、原理
1、將大文件分片;
2、每一片校驗並上傳分片保存到指定目錄;
3、將分片數據合併成大文件,刪除分片數據。
注意:js代碼以及css格式可以從此處下載。
二、q.js實現斷點續傳
1、樣例
2、代碼
代碼以及測試樣例下載。
3、分析
Q.js可以實現多種類型的文件上傳,包括文件夾上傳。