PHP在上傳超大的文件時,不能只讓后端php上傳,會容易上傳到一半內測益處失效。需要前端要和后端相互配合一起來處理,文件上傳要使用AJAX的方法,而不是form的submit的方式。
前端把file文件對象按一定的大小分割成一定大小的文件(如按2M或5M來分割),對分割后的文件,一個個的上傳到后端去,后端接收到分片文件后,把它們先放到一個臨時的目錄下,在收到前端完成的數據請求的時候,把臨時目錄中的文件組裝起來成一個新的文件,保存后,把臨時目錄下的文件刪除掉就可以了。
示例代碼
HTML代碼:
<div class="a"> 上傳<input id="myfile" type="file" name="myfile"/> </div>
這里要說明一下,沒有使用 submit 上傳,使用 ajax上傳。
JS代碼:
<script> $(function(){ let myfile = document.getElementById("myfile"); myfile.onchange = function(){ let file = myfile.files[0]; // 這里可以得到上傳的文件對象 let length = 1024 * 1024 * 5; // 這里是每一個分片的大小 let total_number = Math.ceil(file.size/length); // 使用進一法,來確定分片的個數 let start = 0; // 分片的初始位置 let end = length; // 分片的結束位置 let parr = []; // 這里為promise.all方法準備一個數組 for(let i = 1;i<=total_number;i++){ // 這里開始分片,并且把每一個分片上傳到服務器 let bolb = file.slice(start,end); // 得到一個分片 start = end; // 調整下一個分片的起始位置 end = start+length; // 調整下一個分片的結束位置 if(end > file.size){ end=file.size; // 這里對最后的一個分片結束位置進行調整 } let formdata = new FormData(); // 創建一個FormData對象,準備傳送數據 formdata.append("file",blob); // 將分片數據放入formdata formdata.append("tempfilename",i+"_"+file.name); // 同時為這個分片設置一個名稱,其中的i幫助后端進行排序處理 // formdata組裝好之后,調用pro()函數,返回一個promise對象,并把它放入parr數組中,方便后面的promise.all方法使用 parr.push(pro(formadata)); } // 以上for循環結束之后,parr數組中就全部是分片上傳的promise的對象了,此時我們使用promise.all方法,等待所有上傳都成功執行后,再向服務器發送一個請求,也就是上傳完成,讓服務器組裝分片的請求 Promise.all(parr).then(res=>{ if(res.length == parr.length){ // 如果返回成功的數組長度和parr的數組長度相等,說明分片全部上傳成功 // 此時對上傳接口再次發送請求,同時把上傳的文件名帶上,方便后臺查找要組裝的分片文件名,因為是請求同一個上傳接口,所以我們還要傳一個flag=1表示這是一個數據組裝的請求 $.ajax({ type:"post", url:"http://xxx.com/index/upload/getupload", data:{flag:1,filename:file.name}, // 這里flag=1表示上傳完成,請求組裝,filename表示要組成哪一組文件分片 success:function(res){ if(res.length == parr.length){ console.log(111); } }, fail: function () { reject() } }) } }) } }) // 這個函數用來上傳分片文件,返回的是一個promise對象,方便后面使用promise.all還判斷所有分片是否是上傳成功的 // 這里要說明一下,$.post()是不可以上傳文件的,只能用$.ajax()并且要把contentType:false和processData:false帶上 function pro(formData){ return new Promise((resolve,reject)=>{ $.ajax({ type:"post", url:"http://xxx.com/index/upload/getupload", // 后臺上傳文件的地址 data:formData, contentType: false, // 這個不能少,ajax上傳文件是不能少的 processData: false, // 這個不能少,ajax上傳文件必傳false success:function(res){ resolve(res) }, fail: function () { reject() } }) }) } </script>
以上就是前端的js核心部分,注釋基本就可以看懂了。
php代碼:
<?php // tp5框架 public function getUpload(){ $tempdir = APP_PATH."../public/tempdir"; // 這里分片的文件指定了一個臨時目錄,后面會用到 $flag = input("flag",0); // 接收參數flag如果沒有這個參數就默認為0,如果flag=1,表示要組裝分片 if($flag == 0){ // 這里是上傳分片 $file = request()->file("file"); // 接收到這個分片 $tempfilename = input("tempfilename"); // 接收到這個分片的名稱,注意:這個名稱中含有排序信息 if(!file_exists($tempdir)){ mkdir($tempdir,0755,true); // 如果臨時目錄不存在,則創建一個臨時目錄 } $fileinfo = $file->move($tempdir,$tmpfilename); if($fileinfo){ // 把分片的文件保存在了臨時目錄中,返回有點簡單,可以根據需求返回相應的數據 return josn(['error'=>0]); }else{ return json(['error'=>1]); } }elseif($flag == 1){ // 如果flag為1表示,分片已上傳完成了 $filename = input("filename"); // 通過文件名的字符串匹配,找上所有的分片,返回一個文件路徑的數組 $fileArr = glob($tempdir."/*".$filename); // 這里的*是一個通配符,將所有的文件名中包含的$filename的文件都找到 // 說明一下,$fileArr中的數組的順序不是我們想要的,所以我們新建一個數組來整理一下順序 $newfileArr = []; foreach($fileArr as $f){ // js前端把文件的名稱前加了'序號+_',以下劃線來分開并把序號寫在key中 $fileBaseName = basename($f); // $f是一個個的路徑,這里使用basename得到文件名 $fileBaseNameSplit = explode("_", $fileBaseName); // 通過下劃線分割文件名 $newfileArr[$fileBaseNameSplit[0]] = $f; // 構造了一個新的數組,其中數組的key就是順序號,數組的值就是分片文件的路徑 } // 分片的序號和路徑都準備好了,就可以組裝了 $num = count($newfileArr); // 得到的所有分片的個數,為后面使用for循環做準備 // 開始使用for循環來組裝 $newfilename = "new-".$filename; // 這里為組裝后的文件起一個名字 for($i=1; $i<=$num; $i++){ // 這里以追加的方式,把分片文件都寫入到了一個文件中 file_put_contents($newfilename,file_get_contents($newfileArr[$i]),FILE_APPEND); } // ...... // 刪除臨時文件中的分片文件,這里可以使用try catch來判斷是否有錯誤 foreach($newfileArr as $fi){ unlink($fi); } // 最后給前端返回保存的文件名就可以 } }
上面的方法,我本地測試上傳了一個 650M的文件,只用的 20秒的時間,沒有在服務器上測試過,大家可以按照這個方法試一下。