備注:
由於源代碼[https://blog.csdn.net/qq43599939/article/details/79762042] 不支持超大文件上傳[上傳超大文件瀏覽器卡死,后台php內存不足],做了部分代碼修改,實測上傳超大文件瀏覽器不卡死,后台php代碼限制2M內存也能夠實現大文件的上傳和操作。
原文:
轉自:https://blog.csdn.net/qq43599939/article/details/79762042 php+html5實現無刷新上傳,大文件分片上傳,斷點續傳
理清思路:
引入了兩個概念:塊(block)和片(chunk)。每個塊由一到多個片組成,而一個資源則由一到多個塊組成
塊是服務端的永久數據存儲單位,片則只在分片上傳過程中作為臨時存儲的單位。服務端會以約一個月為單位周期性的清除上傳后未被合並為塊的數據片
實現過程:
將文件分割,分片上傳,然后合並
前端核心code:index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title></title> <link rel="stylesheet" href="layui-v2.5.4/css/layui.css"> </head> <body> <div class="layui-body"> <form class="layui-form" action=""> <!-- 內容主體區域 --> <div style="padding: 15px;">文件上傳 鏈接:https://blog.csdn.net/qq43599939/article/details/79762042</div> <!-- 進度條 --> <div class="layui-form-item"> <div class="layui-progress layui-progress-big" lay-filter="rate" lay-showPercent="true"> <div class="layui-progress-bar" lay-percent="0%"></div> </div> </div> <!-- 你的HTML代碼 --> <div class="layui-form-item"> <div> <input type="file" name="" id="file"> </div> </div> <div class="layui-form-item"> <div id="divlog"> MD5: </div> </div> <div class="layui-form-item"> <div class="layui-btn-group"> <button id="upstart" class="layui-btn layui-btn-normal">開始上傳</button> <button id="stop" class="layui-btn layui-btn-normal">停止</button> <button id="restart" class="layui-btn layui-btn-normal"><i class="layui-icon layui-icon-upload"></i>重新開始</button> </div> </div> </form> </div> <script src="layui-v2.5.4/layui.js"></script> <script> //注意進度條依賴 element 模塊,否則無法進行正常渲染和功能性操作 layui.use('element', function () { var element = layui.element, $=layui.jquery; // element.progress('rate', '50%'); }); </script> <script> var fileForm = document.getElementById("file"); var upstartBtn = document.getElementById('upstart'); var stopBtn = document.getElementById('stop'); var startBtn = document.getElementById('restart'); var rate = document.getElementById('rate'); // 進度 var divlog = document.getElementById('divlog'); //--------------------------- const LENGTH = 10 * 1024 * 1; var start = 0; var end = start + LENGTH; var blob; var blob_num = 1; var is_stop = 0 var file = null; var md5filename = ''; //----------------------------- var upload_instance = new Upload(); fileForm.onchange = function () { // browserMD5File(fileForm.files[0], function (err, md5) { //如果文件大,md5值生成較慢 md5值生成后才能上傳處理,自己優化下吧 // md5filename = md5; //如果需要刷新后也能斷點,可利用cookie記錄,自行完善 // divlog.innerHTML = '文件md5為:' + md5filename; // }); divlog.innerHTML = '文件md5為:不檢測'; } upstartBtn.onclick = function () { upload_instance.addFileAndSend(fileForm); return false; } stopBtn.onclick = function () { upload_instance.stop(); return false; } startBtn.onclick = function () { upload_instance.start(); return false; } function Upload() { // var xhr = new XMLHttpRequest(); if (window.XMLHttpRequest){ // code for IE7+, Firefox, Chrome, Opera, Safari var xhr=new XMLHttpRequest(); }else{ // code for IE6, IE5 var xhr=new ActiveXObject("Microsoft.XMLHTTP"); } //對外方法,傳入文件對象 this.addFileAndSend = function (that) { file = that.files[0]; blob = cutFile(file); sendFile(blob, file); blob_num += 1; } //停止文件上傳 this.stop = function () { xhr.abort(); is_stop = 1; } this.start = function () { sendFile(blob, file); is_stop = 0; } //切割文件 function cutFile(file) { var file_blob = file.slice(start, end); start = end; end = start + LENGTH; return file_blob; }; //發送文件 function sendFile(blob, file) { // 網上案例這里的 FormData 會因為上傳文件過大導致上傳瀏覽器卡死,寫在這里每次實例化減少內存消耗。 // 如果不修改,超過100M瀏覽器即卡死 // 修改后上傳速度比之前要快並且不會卡死 var form_data = new FormData(); var total_blob_num = Math.ceil(file.size / LENGTH); form_data.append('file', blob); form_data.append('blob_num', blob_num); form_data.append('total_blob_num', total_blob_num); form_data.append('md5_file_name', md5filename); form_data.append('file_name', file.name); xhr.open('POST', './test2.php', false); xhr.onreadystatechange = function () { // var progress; // var progressObj = document.getElementById('finish'); if (total_blob_num == 1) { progress = '100%'; } else { progress = (Math.min(100, (blob_num / total_blob_num) * 100)).toFixed(2) + '%'; } console.log('progress-----' + progress); // progressObj.style.width = progress; // rate.innerHTML = progress; layui.element.progress('rate', progress); var t = setTimeout(function () { if (start < file.size && is_stop === 0) { blob = cutFile(file); sendFile(blob, file); blob_num += 1; } else { setTimeout(t); } }, 1000); } xhr.send(form_data); //每次清空 form_data = ""; } } </script> </body> </html>
后端code test2.php
<?php ini_set('memory_limit',-1); //要有足夠大的內存來允許處理上傳的文件 // ini_set('memory_limit',"2M"); //實測2M可以實現大文件[600M]的文件上傳及拼接 set_time_limit(0); //防止超時 class Upload{ private $filepath = './upload'; //上傳目錄 private $tmpPath; //PHP文件臨時目錄 private $blobNum; //第幾個文件塊 private $totalBlobNum; //文件塊總數 private $fileName; //文件名 private $md5FileName; public function __construct($tmpPath,$blobNum,$totalBlobNum,$fileName, $md5FileName){ $this->tmpPath = $tmpPath; $this->blobNum = $blobNum; $this->totalBlobNum = $totalBlobNum; $this->fileName = $fileName; // $this->fileName = $this->createName($fileName, $md5FileName); $this->moveFile(); $this->fileMerge(); } private function fileMerge(){ if($this->blobNum == $this->totalBlobNum){ // 此處文件不應該拼接,應該是追加入內 // 實測修改后php占用很少的內存也可以實現大文件的上傳和拼接操作 for($i=1; $i<= $this->totalBlobNum; $i++){ $blob = ''; $blob = file_get_contents($this->filepath.'/'. $this->fileName.'__'.$i); file_put_contents($this->filepath.'/'. $this->fileName, $blob, FILE_APPEND ); unset($blob); } $this->deleteFileBlob(); } } //刪除文件塊 private function deleteFileBlob(){ for($i=1; $i<= $this->totalBlobNum; $i++){ @unlink($this->filepath.'/'. $this->fileName.'__'.$i); } } private function moveFile(){ $this->touchDir(); $filename = $this->filepath.'/'. $this->fileName.'__'.$this->blobNum; move_uploaded_file($this->tmpPath,$filename); } //API返回數據 public function apiReturn(){ if($this->blobNum == $this->totalBlobNum){ if(file_exists($this->filepath.'/'. $this->fileName)){ $data['code'] = 2; $data['msg'] = 'success'; $data['file_path'] = 'http://'.$_SERVER['HTTP_HOST'].str_replace('.','',$this->filepath).'/'. $this->fileName; } }else{ if(file_exists($this->filepath.'/'. $this->fileName.'__'.$this->blobNum)){ $data['code'] = 1; $data['msg'] = 'waiting'; $data['file_path'] = ''; } } header('Content-type: application/json'); echo json_encode($data); } private function touchDir(){ if(!file_exists($this->filepath)){ return mkdir($this->filepath); } } private function createName($fileName, $md5FileName){ return $md5FileName . '.' . pathinfo($fileName)['extension']; } } $upload = new Upload($_FILES['file']['tmp_name'],$_POST['blob_num'],$_POST['total_blob_num'],$_POST['file_name'],$_POST['md5_file_name']); $upload->apiReturn();