PHP+JS實現大文件切片上傳功能實現實例源碼
近期公司的項目中,涉及到上傳大文件的問題,大文件上傳用普通表單上傳時出現的問題是,無法斷點續存,一但中途中斷上傳,就要重頭開始,這很明顯不是我們想要的,所以經過一番查詢,學習了一下大文件分割上傳的方法。并且使用簡單的php做服務端處理程序實現一個功能demo,供以后回顧使用。本人也是初出茅廬的前端小白,記錄下各種功能的實現總結,代碼有錯誤的地方,請多多指正。
1.簡單文件上傳普通表單上傳表單上傳是我們經常使用的功能,而且使用起來也是非常簡單,我們只需要聲明表單內容類型為enctype="multipart/form-data",表明表單上傳文件的二進制數據。
點擊上傳按鈕,就可以將表單發送到服務器,并使用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元素對象。
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 = 50Mupload_max_filesize = 50MHTML<script src='http://code.jquery.com/jquery-1.11.1.min.js'></script>?<form id='myForm'>? ?<input type='file' name='myfile' />? ?<input type='submit' value='上傳' />?</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.總結按照上述步驟,可以跟著實現簡單上傳、大文件分割上傳、斷點續存等知識,起碼下次遇到上傳文件,心里也有了點底氣。由于本人是前端小白,所以寫的代碼比較簡陋,只是實現了功能,還有許多可以優化的地方,如果代碼有誤,還望指正。
以上就是PHP+JS實現大文件上傳功能實現(切片上傳)全過程的詳細內容,更多關于PHP+JS實現大文件上傳功能實現的資料請關注好吧啦網其它相關文章!
