檔案/大檔案上傳功能實作(JS+PHP)
參考博文:掘金-橙紅年代 前端大檔案上傳
路漫漫 其修遠 PHP + JS 實作大檔案分割上傳
本文是學習檔案上傳后的學習總結文章,從無到有實作檔案上傳功能,前端小白寫的代碼不是最優,如果有錯誤的地方請多多指教,如果本文對你有所幫助,深感榮幸,
近期公司的專案中,涉及到上傳大檔案的問題,大檔案上傳用普通表單上傳時出現的問題是,無法斷點續存,一但中途中斷上傳,就要重頭開始,這很明顯不是我們想要的,所以經過一番查詢,學習了一下大檔案分割上傳的方法,并且使用簡單的php做服務端處理程式實作一個功能demo,供以后回顧使用,本人也是初出茅廬的前端小白,記錄下各種功能的實作總結,代碼有錯誤的地方,請多多指正,
1.簡單檔案上傳
普通表單上傳
表單上傳是我們經常使用的功能,而且使用起來也是非常簡單,我們只需要宣告表單內容型別為
enctype="multipart/form-data",表明表單上傳檔案的二進制資料,<form action="index.php" method="post" enctype="multipart/form-data">
<input type="file" name="myfile" />
<input type="submit" value=https://www.cnblogs.com/morningclock/p/"上傳" />
</form>點擊上傳按鈕,就可以將表單發送到服務器,并使用
index.php接受到對應的表單資料,存入$_GET/$_POST超級全域變數中,我們只需要使用move_uploaded_file方法,將接收到的檔案資料,存盤起來,就實作了檔案上傳功能了,$myfile = $_FILES['myfile'];
//上傳路徑
$path = "upload/" . $myfile['name'];
if(move_uploaded_file($myfile['tmp_name'], $path)){
echo "上傳成功";
} else{
echo "上傳失敗";
};ajax模擬表單上傳檔案
當我們有需求,需要異步提交表單或者需要對上傳檔案做一定修改(例如:裁剪尺寸)時,普通的表單上傳就不能滿足我們的需求,因為我們無法修改表單的file值,這時候就需要ajax出場了,這里我們使用jQuery使用ajax更方便快捷,
我們需要做如下修改:
HTML我們不需要配置form,只需要配置相應的ID,用于獲取DOM元素物件,
<form id="myForm">
<input type="file" name="myfile" id="myFile" />
<input type="submit" value=https://www.cnblogs.com/morningclock/p/"上傳" id="submitForm"/>
</form>
<script src=https://www.cnblogs.com/morningclock/p/"http://code.jquery.com/jquery-1.11.1.min.js"></script>JQuery注意,
jQuery的ajax方法,會默認配置一些請求資訊,所以我們需要重新配置放置jQuery的默認行為導致資料格式或請求頭資訊出現問題,這里的
contentType和processData為必須項,$('#submitForm').on('click', function(e){
// 阻止默認表單提交
e.preventDefault();
?
// 創建表單
// 默認配置了enctype="multipart/form-data"
var formData = new FormData();
formData.append('myfile',$('#myFile')[0].files[0])
?
// 提交表單
$.ajax({
type: "POST",
url: 'post.php',
data: formData,
// 阻止jquery賦予默認屬性,使用FormData默認配置enctype="multipart/form-data"
contentType: false,
// 阻止jquery自動序列化資料
processData: false,
success: function(data){
console.log('請求正常',data);
}
})
})
2.大檔案分割上傳
簡單上傳痛點
簡單上傳,使用表單提交檔案到服務器時,如果網路不好或者中途中斷,會使檔案上傳失敗,試想一下如果要上傳檔案很大,當你上傳到99%時,突然間中斷,又要重新上傳,那該有多崩潰,那時你可能電腦的想砸了,
實作思路
大檔案上傳,實作的方法,就是將上傳檔案的二進制檔案通過分割的形式,逐個上傳到服務器,在上傳完成后,服務器再對檔案進行拼接操作,
為了能識別上傳的資料,是哪個檔案,我們必須要擁有一個
檔案識別符號,用于識別接收到的檔案資料是屬于哪個檔案的,以及可以實作避免重復上傳,實作秒傳功能等,不要忘記由于是異步操作,而且操作的資料段大小不一,會導致整合時無法確認拼接熟悉怒,所以我們需要一個index標識資料段的位置,
通過初步整理,我們就需要以下的引數
檔案唯一識別符號
分割后資料段
分割資料段的順序索引值
經過思考,我們可以建立兩個處理程式,來分別處理接受chunk資料段和合并chunk資料段,
file_getchunk.php功能:將分割chunk資料,整理并保存,此處我們用檔案形式實作,
file_integration.php功能:接收到整合通知,將資料段拼接,并生成檔案,
整體流程大致如圖:

PHP.ini配置
由于PHP默認配合中,限制了POST與上傳的大小,所以我們為了測驗,需要修改
php.ini中的默認配置,post_max_size = 50M
upload_max_filesize = 50Mtalk is cheap,show me the code
HTML<script src=https://www.cnblogs.com/morningclock/p/"http://code.jquery.com/jquery-1.11.1.min.js"></script>
<form id="myForm">
<input type="file" name="myfile" id="myFile" />
<input type="submit" value=https://www.cnblogs.com/morningclock/p/"上傳" id="submitForm"/>
</form>JQuery獲取檔案物件,檔案識別符號,分割檔案,通過ajax發送切割好的blob資料段,
$('#submitForm').on('click', function(e){
// 阻止默認表單提交
e.preventDefault();
var myfile = $('#myFile')[0].files[0];
// 定義檔案識別符號
var fileId = getFileIdentifier(myfile);
// 資料切片
var chunks = fileSlice(myfile);
// 發送分割資料段
sendChunk(fileId, chunks);
})
?生成檔案唯一標識
getFileIdentifier()此處可以使用md5,生成檔案唯一的md5(相同檔案md5相同),作為識別符號,這里只初略的處理了一下檔案標識,
function getFileIdentifier(file){
// 獲取檔案識別符號
return file.size + file.name;
}分割方法
fileSlice()先將檔案使用blob檔案繼承的方法slice進行切割,生成blob字串,
function fileSlice(file, chunkSize = 1024*1024*0.2){
// 1.初始化資料
var totalSize = file.size;
var start = 0;
var end = start + chunkSize;
var chunks = [];
// 2.使用bolb提供的slice方法切片
while(start < totalSize){
var chunk = file.slice(start, end);
chunks.push(chunk);
start = end;
end += chunkSize;
}
// 3.回傳切片組chunk[]
return chunks;
}發送chunk方法
sendChunk()使用ajax依次發送已經分割好的chunk,并提供對應的資料,請求
file_getchunk.php進行處理,此處task串列,用于保證檔案分隔符全部已經完成上傳,function sendChunk(id, chunks){
// 逐個提交
// 用于保證ajax發送完畢
var task = [];
?
chunks.forEach(function(chunk, index){
var formData = new FormData();
formData.append('fileId', id);
formData.append('myFileChunk', chunk);
formData.append('chunkIndex', index);
$.ajax({
type: "POST",
url: 'file_getchunk.php',
data: formData,
contentType: false,
processData: false,
success: function(done){
// 移除已完成任務
task.pop();
console.log(done,' 已完成');
if (task.length === 0) {
// 發送完畢,整合檔案
console.log('通知整合');
makeFileIntegration(id, chunks.length);
}
}
})
task.push('file Working');
})
}通知整合方法
makeFileIntegration()接收到整合通知,請求
file_integration.php進行檔案的整合處理,function makeFileIntegration(id, size){
// 通知已傳輸完成
$.post(
"file_integration.php",
{
id: id,
size: size
},
function(data){
console.log(data);
}
);
}PHP-
file_getchunk.php當PHP監聽到請求時,獲取對應的資料,生成檔案夾,按照
chunkIndex存盤資料段,if(!is_dir('upload')){
mkdir('upload', 0777);
}
?
$chunk = $_FILES['myFileChunk'];
// 檔案唯一標識
$fileId = $_POST['fileId'];
// 臨時檔案夾名稱
$length = strlen($fileId) - (strlen($fileId) - strpos($fileId, '.'));
$filedir = substr($fileId, 0, $length);
?
$chunkIndex = $_POST['chunkIndex'];
?
$filepath = 'upload/' . $filedir;
?
$filename = $filepath . '/' . $chunkIndex;
?
if(!is_dir($filepath)){
mkdir($filepath, 0777);
}
move_uploaded_file($chunk['tmp_name'], $filename);
?
echo $chunkIndex;PHP-
file_integration.php監聽到整合請求,對檔案夾下面的所有檔案,進行依次拼接,并生成最侄訓原出來的檔案,
$fileId = $_POST['id'];
// 臨時檔案夾名稱
$length = strlen($fileId) - (strlen($fileId) - strpos($fileId, '.'));
$filedir = substr($fileId, 0, $length);
?
$size = $_POST['size'];
$file = './upload/' . $fileId;
?
// 創建最終檔案
if(!file_exists($file)){
// 最終檔案不存在,創建檔案
$myfile = fopen($file, 'w+');
fclose($myfile);
}
// 用增加方式打開最終檔案
$myfile = fopen($file, 'a');
?
for ($i = 0; $i < $size; $i++) {
// 單檔案路徑
$filePart = 'upload/' . $filedir . '/' . $i;
?
if(file_exists($filePart)){
$chunk = file_get_contents($filePart);
// 寫入chunk
fwrite($myfile, $chunk);
} else{
echo "缺少Part$i 檔案,請重新上傳";
break;
}
}
?
fclose($myfile);
echo "整合完成";
?
3.更進一步
大檔案分割上傳功能已經基本實作,但是我們還可以擁有很多優化的地方
1.斷點續存,
我們需要的檔案已經可以正常的分割上傳,服務端也可以正常接收切片,完成資料段切片的合并了,此時我們就可以進一步實作斷點續存了,
斷點續存,實作方法很簡單,我們只需要獲取到上傳完成的資料段切片資訊,就可以判斷我們應該從哪個資料段開始繼續傳輸資料,
獲取已經完成資料段切片的資訊,我們可以使用前端保存或者服務端獲取,此處我們使用服務端介面檢測,回傳資料缺失位置來實作斷點續存,
思路整理
我們要在上傳前,請求服務端查詢出中斷時的位置,利用位置資訊,篩選上傳的資料段切片,
那么我們要增加的邏輯就是:
offset中斷位置資訊查詢中斷位置介面:
file_get_breakpoint.php
實作
getFileBreakpoint()獲取檔案斷點函式此處要保證ajax執行順序,才能正確獲取offset偏移量,實作思路有很多,此處只使用
jquery提供的將ajax請求變為同步,進行處理,注:同步請求時,success函式回傳值不可以直接return,要保存在一個變數中,在ajax請求外return才能生效,
// 獲取檔案斷點
function getFileBreakpoint(id, size){
var offset = '';
$.ajax({
type:"post",
url:"file_get_breakpoint.php",
data: {
id: id,
size: size
},
async: false,
success:function(res){
offset = parseInt(res);
}
})
return offset;
}在
sendChunk()發送資料前獲取offset// 上傳前,請求file_integration.php介面獲取資料段開始傳輸的位置
var offset = getFileBreakpoint(id, chunks.length);遍歷chunks發送資料段時,增加篩選邏輯
chunks.forEach(function(chunk, index){
// ==============新增=================
// 從offset開始傳輸
if (index < offset) {
return;
}
// ==============新增=================
var formData = new FormData();
formData.append('fileId', id);
formData.append('myFileChunk', chunk);
formData.append('chunkIndex', index);
$.ajax({
type: "POST",
url: 'file_getchunk.php',
data: formData,
contentType: false,
processData: false,
success: function(done){
task.pop();
console.log(done,' 已完成');
if (task.length === 0) {
console.log('通知整合');
makeFileIntegration(id, chunks.length);
}
}
})
task.push(index+' is Working');
})獲取中斷位置介面
file_get_breakpoint.php這里使用的獲取中斷位置的邏輯很簡單(不是最優),只需要檢測檔案夾是否存在,再依次檢測資料段是否缺失,缺失時回傳缺失段的
index,已存在回傳chunks長度size,不存在時回傳0// 1.檢測資料檔案是否存在(檔案標識,資料段總數)
$fileId = $_POST['id'];
$size = $_POST['size'];
// 臨時檔案夾名稱
$length = strlen($fileId) - (strlen($fileId) - strpos($fileId, '.'));
$filedir = substr($fileId, 0, $length);
?
// 2.按順序檢測缺失的資料段的位置
// 檢測是否存在檔案夾
if (is_dir("upload/$filedir")) {
$offset = $size;
// 檢測資料段缺失下標
for ($i = 0; $i < $size; $i++) {
$filepath = "upload/$filedir/$i";
if(!file_exists($filepath)){
// 缺失i部分
$offset = $i;
break;
}
}
// 輸出偏移量
echo $offset;
}
else {
// 是否存在已合并檔案
if(file_exists("upload/$fileId")){
echo $size;
} else{
// 檔案尚未上傳
echo 0;
}
}
2.檔案秒傳
檔案秒傳的概念,按照我的理解,就是在上傳檔案請求后,服務器端檢測資料庫中是否存在相同的檔案,如果存在相同的檔案,就可以告訴用戶上傳完成了,
此處在獲取offset后,增加一個判斷就可以實作
var offset = getFileBreakpoint(id, chunks.length);
// 增加判斷
if(chunks.length === offset) {
console.log('檔案已經上傳完成');
return;
}當然,這里僅僅是非常簡單的處理,我們還可以使用MD5來作為檔案識別符號,在在服務器端使用這個識別符號是否存在相同檔案,
3.MD5檢測檔案完整性,
通過md5對檔案加密,傳輸到服務器端,服務器端實作合并后對檔案再進行一次md5加密,比對兩串md5字串是否相同,就可以知道檔案傳輸程序中是否完整,
3.上傳完成后,存盤資料段檔案夾進行洗掉操作,
我們最后做一步就是將臨時檔案移除操作,在整合完成后,我們只需要在
file_integration.php介面中,整合完成后,移除檔案夾及其下面的所有檔案,function deldir($path){
//如果是目錄則繼續
if(is_dir($path)){
//掃描一個檔案夾內的所有檔案夾和檔案并回傳陣列
$p = scandir($path);
foreach($p as $val){
//排除目錄中的.和..
if($val !="." && $val !=".."){
//如果是目錄則遞回子目錄,繼續操作
if(is_dir($path.$val)){
//子目錄中操作洗掉檔案夾和檔案
deldir($path.$val.'/');
//目錄清空后洗掉空檔案夾
@rmdir($path.$val.'/');
}else{
//如果是檔案直接洗掉
unlink($path.$val);
}
}
}
// 洗掉檔案夾
rmdir($path);
}
}
//洗掉臨時檔案夾
deldir("upload/$filedir/");
4.總結
按照上述步驟,可以跟著實作簡單上傳、大檔案分割上傳、斷點續存等知識,起碼下次遇到上傳檔案,心里也有了點底氣,由于本人是前端小白,所以寫的代碼比較簡陋,只是實作了功能,還有許多可以優化的地方,如果代碼有誤,還望指正,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/36830.html
標籤:jQuery
上一篇:vue-cli中使用jquery
下一篇:基礎表單驗證
