PHP+JS實(shí)現(xiàn)大文件切片上傳功能實(shí)現(xiàn)實(shí)例源碼
近期公司的項(xiàng)目中,涉及到上傳大文件的問題,大文件上傳用普通表單上傳時(shí)出現(xiàn)的問題是,無法斷點(diǎn)續(xù)存,一但中途中斷上傳,就要重頭開始,這很明顯不是我們想要的,所以經(jīng)過一番查詢,學(xué)習(xí)了一下大文件分割上傳的方法。并且使用簡(jiǎn)單的php
做服務(wù)端處理程序?qū)崿F(xiàn)一個(gè)功能demo,供以后回顧使用。本人也是初出茅廬的前端小白,記錄下各種功能的實(shí)現(xiàn)總結(jié),代碼有錯(cuò)誤的地方,請(qǐng)多多指正。
1.簡(jiǎn)單文件上傳
普通表單上傳
表單上傳是我們經(jīng)常使用的功能,而且使用起來也是非常簡(jiǎn)單,我們只需要聲明表單內(nèi)容類型為enctype="multipart/form-data"
,表明表單上傳文件的二進(jìn)制數(shù)據(jù)。
點(diǎn)擊上傳按鈕,就可以將表單發(fā)送到服務(wù)器,并使用index.php
接受到對(duì)應(yīng)的表單數(shù)據(jù),存入$_GET/$_POST
超級(jí)全局變量中,我們只需要使用move_uploaded_file
方法,將接收到的文件數(shù)據(jù),存儲(chǔ)起來,就實(shí)現(xiàn)了文件上傳功能了。
$myfile = $_FILES['myfile']; ?//上傳路徑 ?$path = "upload/" . $myfile['name']; ?if(move_uploaded_file($myfile['tmp_name'], $path)){ ? echo "上傳成功"; ?} else{ ? echo "上傳失敗"; ?};
ajax模擬表單上傳文件
當(dāng)我們有需求,需要異步提交表單或者需要對(duì)上傳文件做一定修改(例如:裁剪尺寸)時(shí),普通的表單上傳就不能滿足我們的需求,因?yàn)槲覀儫o法修改表單的file值,這時(shí)候就需要ajax出場(chǎng)了。這里我們使用jQuery使用ajax更方便快捷。
我們需要做如下修改:
HTML
我們不需要配置form,只需要配置相應(yīng)的ID,用于獲取DOM元素對(duì)象。
JQuery
注意,jQuery
的ajax方法,會(huì)默認(rèn)配置一些請(qǐng)求信息,所以我們需要重新配置放置jQuery
的默認(rèn)行為導(dǎo)致數(shù)據(jù)格式或請(qǐng)求頭信息出現(xiàn)問題。
這里的contentType
和processData
為必須項(xiàng)。
$('#submitForm').on('click', function(e){ ? ?// 阻止默認(rèn)表單提交 ? ?e.preventDefault(); ?? ? ?// 創(chuàng)建表單 ? ?// 默認(rèn)配置了enctype="multipart/form-data" ? ?var formData = new FormData(); ? ?formData.append('myfile',$('#myFile')[0].files[0]) ?? ? ?// 提交表單 ? ?$.ajax({ ? ? ?type: "POST", ? ? ?url: 'post.php', ? ? ?data: formData, ? ? ?// 阻止jquery賦予默認(rèn)屬性,使用FormData默認(rèn)配置enctype="multipart/form-data" ? ? ?contentType: false, ? ? ?// 阻止jquery自動(dòng)序列化數(shù)據(jù) ? ? ?processData: false, ? ? ?success: function(data){ ? ? ? ?console.log('請(qǐng)求正常',data); ? ? } ? }) ?})
2.大文件分割上傳
簡(jiǎn)單上傳痛點(diǎn)
簡(jiǎn)單上傳,使用表單提交文件到服務(wù)器時(shí),如果網(wǎng)絡(luò)不好或者中途中斷,會(huì)使文件上傳失敗,試想一下如果要上傳文件很大,當(dāng)你上傳到99%時(shí),突然間中斷,又要重新上傳,那該有多崩潰,那時(shí)你可能電腦的想砸了。
實(shí)現(xiàn)思路
大文件上傳,實(shí)現(xiàn)的方法,就是將上傳文件的二進(jìn)制文件通過分割的形式,逐個(gè)上傳到服務(wù)器,在上傳完成后,服務(wù)器再對(duì)文件進(jìn)行拼接操作。
為了能識(shí)別上傳的數(shù)據(jù),是哪個(gè)文件,我們必須要擁有一個(gè)文件標(biāo)識(shí)符
,用于識(shí)別接收到的文件數(shù)據(jù)是屬于哪個(gè)文件的,以及可以實(shí)現(xiàn)避免重復(fù)上傳,實(shí)現(xiàn)秒傳功能等。
不要忘記由于是異步操作,而且操作的數(shù)據(jù)段大小不一,會(huì)導(dǎo)致整合時(shí)無法確認(rèn)拼接熟悉怒,所以我們需要一個(gè)index標(biāo)識(shí)數(shù)據(jù)段的位置。
通過初步整理,我們就需要以下的參數(shù)
文件唯一標(biāo)識(shí)符
分割后數(shù)據(jù)段
分割數(shù)據(jù)段的順序索引值
經(jīng)過思考,我們可以建立兩個(gè)處理程序,來分別處理接受chunk數(shù)據(jù)段和合并chunk數(shù)據(jù)段。
file_getchunk.php
功能:將分割chunk數(shù)據(jù),整理并保存,此處我們用文件形式實(shí)現(xiàn)。
file_integration.php
功能:接收到整合通知,將數(shù)據(jù)段拼接,并生成文件。
PHP.ini配置
由于PHP默認(rèn)配合中,限制了POST與上傳的大小,所以我們?yōu)榱藴y(cè)試,需要修改php.ini
中的默認(rèn)配置。
post_max_size = 50M upload_max_filesize = 50M
HTML
<script src="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="上傳" id="submitForm"/> ?</form>
JQuery
獲取文件對(duì)象,文件標(biāo)識(shí)符,分割文件,通過ajax發(fā)送切割好的blob數(shù)據(jù)段。
$('#submitForm').on('click', function(e){ ? ?// 阻止默認(rèn)表單提交 ? ?e.preventDefault(); ? ?var myfile = $('#myFile')[0].files[0]; ? ?// 定義文件標(biāo)識(shí)符 ? ? ?var fileId = getFileIdentifier(myfile); ? ?// 數(shù)據(jù)切片 ? ?var chunks = fileSlice(myfile); ? ?// 發(fā)送分割數(shù)據(jù)段 ?sendChunk(fileId, chunks); ?})
生成文件唯一標(biāo)識(shí)getFileIdentifier()
此處可以使用md5,生成文件唯一的md5(相同文件md5相同),作為標(biāo)識(shí)符。這里只初略的處理了一下文件標(biāo)識(shí)。
function getFileIdentifier(file){ ? ? ?// 獲取文件標(biāo)識(shí)符 ? ? ?return file.size + file.name; ? }
分割方法fileSlice()
先將文件使用blob文件繼承的方法slice進(jìn)行切割,生成blob字串。
function fileSlice(file, chunkSize = 1024*1024*0.2){ ? ? // 1.初始化數(shù)據(jù) ? ? 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; ? }
發(fā)送chunk方法sendChunk()
使用ajax依次發(fā)送已經(jīng)分割好的chunk,并提供對(duì)應(yīng)的數(shù)據(jù),請(qǐng)求file_getchunk.php
進(jìn)行處理。此處task列表,用于保證文件分隔符全部已經(jīng)完成上傳。
function sendChunk(id, chunks){ ? ?// 逐個(gè)提交 ? ?// 用于保證ajax發(fā)送完畢 ? ?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){ ? ? ? ? ?// 移除已完成任務(wù) ? ? ? ? ?task.pop(); ? ? ? ? ?console.log(done,' 已完成'); ? ? ? ? ?if (task.length === 0) { ? ? ? ? ? ?// 發(fā)送完畢,整合文件 ? ? ? ? ? ?console.log('通知整合'); ? ? ? ? ? ?makeFileIntegration(id, chunks.length); ? ? ? ? } ? ? ? } ? ? }) ? ? ?task.push('file Working'); ? }) ?}
通知整合方法makeFileIntegration()
接收到整合通知,請(qǐng)求file_integration.php
進(jìn)行文件的整合處理。
function makeFileIntegration(id, size){ ? ?// 通知已傳輸完成 ? ?$.post( ? ? ?"file_integration.php", ? ? { ? ? ? ?id: id, ? ? ? ?size: size ? ? }, ? ? ?function(data){ ? ? ? ?console.log(data); ? ? } ? ); ?}
PHP- file_getchunk.php
當(dāng)PHP監(jiān)聽到請(qǐng)求時(shí),獲取對(duì)應(yīng)的數(shù)據(jù),生成文件夾,按照chunkIndex
存儲(chǔ)數(shù)據(jù)段。
if(!is_dir('upload')){ ? ?mkdir('upload', 0777); ?} ?? ?$chunk = $_FILES['myFileChunk']; ?// 文件唯一標(biāo)識(shí) ?$fileId = $_POST['fileId']; ?// 臨時(shí)文件夾名稱 ?$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
監(jiān)聽到整合請(qǐng)求,對(duì)文件夾下面的所有文件,進(jìn)行依次拼接,并生成最終還原出來的文件。
$fileId = $_POST['id']; ?// 臨時(shí)文件夾名稱 ?$length = strlen($fileId) - (strlen($fileId) - strpos($fileId, '.')); ?$filedir = substr($fileId, 0, $length); ?? ?$size = $_POST['size']; ?$file = './upload/' . $fileId; ?? ?// 創(chuàng)建最終文件 ?if(!file_exists($file)){ ? ?// 最終文件不存在,創(chuàng)建文件 ? ?$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 文件,請(qǐng)重新上傳"; ? ? ?break; ? } ?} ?? ?fclose($myfile); ?echo "整合完成"; ??
3.更進(jìn)一步
大文件分割上傳功能已經(jīng)基本實(shí)現(xiàn),但是我們還可以擁有很多優(yōu)化的地方
1.斷點(diǎn)續(xù)存。
我們需要的文件已經(jīng)可以正常的分割上傳,服務(wù)端也可以正常接收切片,完成數(shù)據(jù)段切片的合并了。此時(shí)我們就可以進(jìn)一步實(shí)現(xiàn)斷點(diǎn)續(xù)存了。
斷點(diǎn)續(xù)存,實(shí)現(xiàn)方法很簡(jiǎn)單,我們只需要獲取到上傳完成的數(shù)據(jù)段切片信息,就可以判斷我們應(yīng)該從哪個(gè)數(shù)據(jù)段開始繼續(xù)傳輸數(shù)據(jù)。
獲取已經(jīng)完成數(shù)據(jù)段切片的信息,我們可以使用前端保存或者服務(wù)端獲取。此處我們使用服務(wù)端接口檢測(cè),返回?cái)?shù)據(jù)缺失位置來實(shí)現(xiàn)斷點(diǎn)續(xù)存。
思路整理
我們要在上傳前,請(qǐng)求服務(wù)端查詢出中斷時(shí)的位置,利用位置信息,篩選上傳的數(shù)據(jù)段切片。
那么我們要增加的邏輯就是:
offset
中斷位置信息
查詢中斷位置接口:file_get_breakpoint.php
實(shí)現(xiàn)
getFileBreakpoint()
獲取文件斷點(diǎn)函數(shù)
此處要保證ajax執(zhí)行順序,才能正確獲取offset偏移量,實(shí)現(xiàn)思路有很多。此處只使用jquery
提供的將ajax請(qǐng)求變?yōu)橥?,進(jìn)行處理。
注:同步請(qǐng)求時(shí),success函數(shù)返回值不可以直接return,要保存在一個(gè)變量中,在ajax請(qǐng)求外return才能生效。
// 獲取文件斷點(diǎn) ?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()
發(fā)送數(shù)據(jù)前獲取offset
// 上傳前,請(qǐng)求file_integration.php接口獲取數(shù)據(jù)段開始傳輸?shù)奈恢? ?var offset = getFileBreakpoint(id, chunks.length);
遍歷chunks發(fā)送數(shù)據(jù)段時(shí),增加篩選邏輯
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
這里使用的獲取中斷位置的邏輯很簡(jiǎn)單(不是最優(yōu)),只需要檢測(cè)文件夾是否存在,再依次檢測(cè)數(shù)據(jù)段是否缺失。缺失時(shí)返回缺失段的index
,已存在返回chunks長(zhǎng)度size
,不存在時(shí)返回0
// 1.檢測(cè)數(shù)據(jù)文件是否存在(文件標(biāo)識(shí),數(shù)據(jù)段總數(shù)) ?$fileId = $_POST['id']; ?$size = $_POST['size']; ?// 臨時(shí)文件夾名稱 ?$length = strlen($fileId) - (strlen($fileId) - strpos($fileId, '.')); ?$filedir = substr($fileId, 0, $length); ?? ?// 2.按順序檢測(cè)缺失的數(shù)據(jù)段的位置 ?// 檢測(cè)是否存在文件夾 ?if (is_dir("upload/$filedir")) { ? ?$offset = $size; ? ?// 檢測(cè)數(shù)據(jù)段缺失下標(biāo) ? ?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.文件秒傳
文件秒傳的概念,按照我的理解,就是在上傳文件請(qǐng)求后,服務(wù)器端檢測(cè)數(shù)據(jù)庫中是否存在相同的文件,如果存在相同的文件,就可以告訴用戶上傳完成了。
此處在獲取offset后,增加一個(gè)判斷就可以實(shí)現(xiàn)
var offset = getFileBreakpoint(id, chunks.length); ?// 增加判斷 ?if(chunks.length === offset) { ? ?console.log('文件已經(jīng)上傳完成'); ? ?return; ?}
當(dāng)然,這里僅僅是非常簡(jiǎn)單的處理,我們還可以使用MD5來作為文件標(biāo)識(shí)符,在在服務(wù)器端使用這個(gè)標(biāo)識(shí)符是否存在相同文件。
3.MD5檢測(cè)文件完整性。
通過md5對(duì)文件加密,傳輸?shù)椒?wù)器端,服務(wù)器端實(shí)現(xiàn)合并后對(duì)文件再進(jìn)行一次md5加密,比對(duì)兩串md5字串是否相同,就可以知道文件傳輸過程中是否完整。
3.上傳完成后,存儲(chǔ)數(shù)據(jù)段文件夾進(jìn)行刪除操作。
我們最后做一步就是將臨時(shí)文件移除操作,在整合完成后,我們只需要在file_integration.php
接口中,整合完成后,移除文件夾及其下面的所有文件。
function deldir($path){ ? ? //如果是目錄則繼續(xù) ? ?if(is_dir($path)){ ? ? ? ?//掃描一個(gè)文件夾內(nèi)的所有文件夾和文件并返回?cái)?shù)組 ? ? ?$p = scandir($path); ? ? ?foreach($p as $val){ ? ? ? ?//排除目錄中的.和.. ? ? ? ?if($val !="." && $val !=".."){ ? ? ? ? ?//如果是目錄則遞歸子目錄,繼續(xù)操作 ? ? ? ? ?if(is_dir($path.$val)){ ? ? ? ? ? ?//子目錄中操作刪除文件夾和文件 ? ? ? ? ? ?deldir($path.$val.'/'); ? ? ? ? ? ?//目錄清空后刪除空文件夾 ? ? ? ? ? ?@rmdir($path.$val.'/'); ? ? ? ? }else{ ? ? ? ? ? ?//如果是文件直接刪除 ? ? ? ? ? ?unlink($path.$val); ? ? ? ? } ? ? ? } ? ? } ? ? ?// 刪除文件夾 ? ? ?rmdir($path); ? } ?} ?//刪除臨時(shí)文件夾 ?deldir("upload/$filedir/");
4.總結(jié)
按照上述步驟,可以跟著實(shí)現(xiàn)簡(jiǎn)單上傳、大文件分割上傳、斷點(diǎn)續(xù)存等知識(shí),起碼下次遇到上傳文件,心里也有了點(diǎn)底氣。由于本人是前端小白,所以寫的代碼比較簡(jiǎn)陋,只是實(shí)現(xiàn)了功能,還有許多可以優(yōu)化的地方,如果代碼有誤,還望指正。
以上就是PHP+JS實(shí)現(xiàn)大文件上傳功能實(shí)現(xiàn)(切片上傳)全過程的詳細(xì)內(nèi)容,更多關(guān)于PHP+JS實(shí)現(xiàn)大文件上傳功能實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
PHP連接數(shù)據(jù)庫實(shí)現(xiàn)注冊(cè)頁面的增刪改查操作
這篇文章主要介紹了PHP連接數(shù)據(jù)庫實(shí)現(xiàn)注冊(cè)頁面的增刪改查操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03paypal即時(shí)到賬php實(shí)現(xiàn)代碼
paypal即時(shí)到賬php實(shí)現(xiàn)代碼,需要的朋友可以參考下。2010-11-11PHP輸出圖像imagegif、imagejpeg與imagepng函數(shù)用法分析
這篇文章主要介紹了PHP輸出圖像imagegif、imagejpeg與imagepng函數(shù)用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了imagegif()、imagejpeg()、imagepng()和imagewbmp()函數(shù)的功能、參數(shù)含義及使用技巧,需要的朋友可以參考下2016-11-11對(duì)PHP新手的一些建議(PHP學(xué)習(xí)經(jīng)驗(yàn)總結(jié))
這篇文章主要介紹了對(duì)PHP新手的一些建議,這是本人學(xué)習(xí)PHP過程中的經(jīng)驗(yàn)總結(jié),一切都源自切身體會(huì),需要的朋友可以參考下2014-08-08利用瀏覽器的Javascript控制臺(tái)調(diào)試PHP程序
現(xiàn)在,越來越多的瀏覽器都有了開發(fā)這工具或者Javascript控制臺(tái),通過這些工具,我們可以很方便的顯示PHP代碼中的變量或數(shù)組值2014-01-01Highcharts?圖表中圖例顯示狀態(tài)存儲(chǔ)的功能設(shè)計(jì)詳解
這篇文章主要介紹了Highcharts?圖表中圖例顯示狀態(tài)存儲(chǔ)的功能設(shè)計(jì)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03