php結(jié)合webuploader斷點(diǎn)續(xù)傳的實(shí)現(xiàn)
最近公司項(xiàng)目需要用到斷點(diǎn)續(xù)傳,所以記錄一下其中的坑
使用到的主要技術(shù)
webuploader
thinkphp5
斷點(diǎn)續(xù)傳的思路:
客戶(hù)端:
? ? ?1.獲取文件md5(MD5是文件唯一標(biāo)識(shí),用來(lái)判斷是否存在此文件,并且用作分片的文件夾名)
? ? ?2.將文件分片
? ? ?3.驗(yàn)證分片是否上傳過(guò),上傳過(guò)直接跳過(guò)當(dāng)前分片
? ? ?3.上傳分片到md5的文件夾(保存文件名建議按分片序號(hào)來(lái),因?yàn)榉制捻樞蚝苤匾?
? ? ?4.最后一個(gè)分片上傳完成后發(fā)送合并分片請(qǐng)求并由服務(wù)器返回文件信息
服務(wù)端
? ? ?1.獲取md5文件夾下的文件數(shù)量并返回用作分片驗(yàn)證
? ? ?2.接收文件分片并保存到文件md5的文件夾,文件名稱(chēng)使用分片序號(hào):如0.mp4,1.mp4
? ? ?3.合并分片時(shí)將md5文件夾下的所有文件按文件名順序提取并寫(xiě)入到最終的文件內(nèi)
? ? ?4.寫(xiě)入完成獲取最終文件hash并判斷是否存在,存在則返回已存在文件,刪除當(dāng)前文件,不存在則寫(xiě)入數(shù)據(jù)庫(kù)并返回文件信息
大體思路是這樣,實(shí)際還要加入許多驗(yàn)證,比客戶(hù)端獲取到md5后馬上要驗(yàn)證文件是否存在,存在就不上傳,直接使用文件信息,不存在則上傳
分片上傳前也要驗(yàn)證,不過(guò)分片的跳過(guò)規(guī)則需要注意,服務(wù)器只需要返回已有的分片數(shù)量,客戶(hù)端根據(jù)已有的分片和當(dāng)前分片索引即可判斷是否應(yīng)該跳過(guò),因?yàn)榉制前错樞蛏蟼鞯?如:
,服務(wù)端需要注意合并的時(shí)候順序不能亂,順序亂了就無(wú)法還原源文件,所以建議用分片索引作為文件名,獲取的時(shí)候直接按0,1,2這樣獲取就行了.
并且保存時(shí)要注意保存在自定義的目錄下需要確保該目錄存在,不存在的目錄需要新建后才可以保存文件
完整代碼如下:
客戶(hù)端:
var uploader;
var fileMd5;
var is_upload;
var totalFiles;
function initWebUploader() {
? ? /***************************************************** 監(jiān)聽(tīng)分塊上傳過(guò)程中的三個(gè)時(shí)間點(diǎn) start ***********************************************************/
? ? WebUploader.Uploader.register({
? ? ? ? "before-send":"beforeSend", ?//每個(gè)分片上傳前
? ? },
? ? {
? ? ? ? //時(shí)間點(diǎn)2:如果有分塊上傳,則每個(gè)分塊上傳之前調(diào)用此函數(shù)
? ? ? ? beforeSend:function(block){
? ? ? ? ? ? var deferred = WebUploader.Deferred();
? ? ? ? ? ? if(is_upload)//跳過(guò)到開(kāi)始上傳的哪一個(gè)分片時(shí)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? deferred.resolve();
? ? ? ? ? ? }else if(totalFiles)//已經(jīng)獲取過(guò)文件數(shù)量則直接判斷是否跳過(guò)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //當(dāng)前分片下標(biāo)小于等于目錄下的文件數(shù)量就認(rèn)為分塊存在,因?yàn)榉謮K上傳下標(biāo)是由小到大
? ? ? ? ? ? ? ? if (block.chunk > totalFiles - 1) {
? ? ? ? ? ? ? ? ? ? is_upload = true;
? ? ? ? ? ? ? ? ? ? deferred.resolve();
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? //分塊存在,跳過(guò)
? ? ? ? ? ? ? ? ? ? deferred.reject();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? $.post('/api.php/upload/checkUpload', {md5: fileMd5}, function (data) {
? ? ? ? ? ? ? ? ? ? if (data.code) {
? ? ? ? ? ? ? ? ? ? ? ? totalFiles = data.data;
? ? ? ? ? ? ? ? ? ? ? ? //當(dāng)前分片下標(biāo)小于等于目錄下的文件數(shù)量就認(rèn)為分塊存在,因?yàn)榉謮K上傳下標(biāo)是由小到大
? ? ? ? ? ? ? ? ? ? ? ? if (block.chunk > data.data - 1) {
? ? ? ? ? ? ? ? ? ? ? ? ? ? is_upload = true;
? ? ? ? ? ? ? ? ? ? ? ? ? ? deferred.resolve();
? ? ? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? ? ? //分塊存在,跳過(guò)
? ? ? ? ? ? ? ? ? ? ? ? ? ? deferred.reject();
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? is_upload = true;
? ? ? ? ? ? ? ? ? ? ? ? deferred.resolve();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? });
? ? ? ? ? ? }
? ? ? ? ? ? return deferred.promise();
? ? ? ? }
? ? });
? ? /***************************************************** 監(jiān)聽(tīng)分塊上傳過(guò)程中的三個(gè)時(shí)間點(diǎn) end **************************************************************/
? ? uploader = WebUploader.create({
? ? ? ? // swf文件路徑
? ? ? ? swf: ?'{$maccms.path}static/webupload/Uploader.swf',
? ? ? ? // 文件接收服務(wù)端。
? ? ? ? server: '/api.php/upload/chunkUpload',
? ? ? ? // 選擇文件的按鈕。可選。
? ? ? ? // 內(nèi)部根據(jù)當(dāng)前運(yùn)行是創(chuàng)建,可能是input元素,也可能是flash.
? ? ? ? pick: '#name',
? ? ? ? accept: {
? ? ? ? ? ? title: 'Images',
? ? ? ? ? ? extensions: 'mp4',
? ? ? ? },
? ? ? ? // 不壓縮image, 默認(rèn)如果是jpeg,文件上傳前會(huì)壓縮一把再上傳!
? ? ? ? resize: false,
? ? ? ? prepareNextFile:true,
? ? ? ? chunked : true, // 分片處理
? ? ? ? chunkSize : 5 * 1024 * 1024, // 每片50M,經(jīng)過(guò)測(cè)試,發(fā)現(xiàn)上傳1G左右的視頻大概每片50M速度比較快的,太大或者太小都對(duì)上傳效率有影響
? ? ? ? chunkRetry : false,// 如果失敗,則不重試
? ? ? ? threads:1,
? ? ? ? formData:{guid:WebUploader.Base.guid()}
? ? });
? ? uploader.on('fileQueued',function (file) {
? ? ? ? uploader.md5File( file,0,10*1024*1024 )
? ? ? ? // 及時(shí)顯示進(jìn)度
? ? ? ? .progress(function(percentage) {
? ? ? ? ? ? console.log('Percentage:', percentage);
? ? ? ? ? ? $(".readFile").text("正在讀取視頻信息..."+(percentage * 100).toFixed(2) + '%');
? ? ? ? })
? ? ? ? // 完成
? ? ? ? .then(function(val) {
? ? ? ? ? ? console.log('md5 result:', val);
? ? ? ? ? ? $(".readFile").text('');
? ? ? ? ? ? fileMd5 = val;
? ? ? ? ? ? var formData = uploader.option('formData');
? ? ? ? ? ? formData.md5 = val;
? ? ? ? ? ? uploader.option('formData',formData);
? ? ? ? ? ? //驗(yàn)證文件是否存在
? ? ? ? ? ? $.post('/api.php/upload/md5check',{md5:val},function (data) {
? ? ? ? ? ? ? ? if(data.code)
? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? //選擇后直接上傳
? ? ? ? ? ? ? ? ? ? uploader.upload();
? ? ? ? ? ? ? ? ? ? $("#name .webuploader-pick").text(file.name);
? ? ? ? ? ? ? ? ? ? $(".up-state .file_name").text(file.name);
? ? ? ? ? ? ? ? ? ? var size;
? ? ? ? ? ? ? ? ? ? size = Math.round(file.size/(1024*1024),2);
? ? ? ? ? ? ? ? ? ? if(size > 1024)
? ? ? ? ? ? ? ? ? ? {
? ? ? ? ? ? ? ? ? ? ? ? size = Math.round(size/1024,2) + 'G';
? ? ? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? ? ? size += 'M';
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? $(".up-state .file_size").text(size);
? ? ? ? ? ? ? ? ? ? $('.up-in').width('0');
? ? ? ? ? ? ? ? ? ? $('.bar').find('span').html('0%');
? ? ? ? ? ? ? ? ? ? $('.up-con').hide();
? ? ? ? ? ? ? ? ? ? $('.up-state').show();
? ? ? ? ? ? ? ? ? ? $('#go').click(function () {
? ? ? ? ? ? ? ? ? ? ? ? $('.up-end').hide();
? ? ? ? ? ? ? ? ? ? ? ? $('.up-con').show();
? ? ? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? ? ? var msg = '該視頻已存在!';
? ? ? ? ? ? ? ? ? ? alert(msg);
? ? ? ? ? ? ? ? ? ? uploader.reset();
? ? ? ? ? ? ? ? ? ? $("#name .webuploader-pick").text(file.name);
? ? ? ? ? ? ? ? ? ? $(".up-state .file_name").text(file.name);
? ? ? ? ? ? ? ? ? ? $(".file-address input
name=vodplayurl
").val(data.data.path);
? ? ? ? ? ? ? ? ? ? $(".file-address input
name=urlid
").val(data.data.id);
? ? ? ? ? ? ? ? ? ? //獲取視頻時(shí)長(zhǎng),配合video標(biāo)簽
? ? ? ? ? ? ? ? ? ? $("#duration").attr("src",data.data.path);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? });
? ? ? ? });
? ? });
? ? // 文件上傳過(guò)程中創(chuàng)建進(jìn)度條實(shí)時(shí)顯示。
? ? uploader.on( 'uploadProgress', function( file, percentage ) {
? ? ? ? $('.bar').find('span').html((percentage * 100).toFixed(2) + '%');
? ? ? ? $('.up-in').width(percentage * 100 + '%');
? ? });
? ? uploader.on( 'uploadSuccess', function( file,res ) {
? ? ? ? //最后一塊完成時(shí)間
? ? ? ? //全部上傳完成發(fā)送合并請(qǐng)求
? ? ? ? $.post('/api.php/upload/videoUpload',{md5:fileMd5},function (data) {
? ? ? ? ? ? if(data.code)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? $('.up-end').show();
? ? ? ? ? ? ? ? $('.up-state').hide();
? ? ? ? ? ? ? ? setTimeout(function () {
? ? ? ? ? ? ? ? ? ? $(".up-end").hide();
? ? ? ? ? ? ? ? ? ? $('.up-con').show();
? ? ? ? ? ? ? ? },2000);
? ? ? ? ? ? ? ? $(".file-address input
name=vodplayurl
").val(data.data.path);
? ? ? ? ? ? ? ? $(".file-address input
name=urlid
").val(data.data.id);
? ? ? ? ? ? ? ? $("#duration").attr("src",data.data.path);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? (".up-end h1").text('上傳出錯(cuò)');
? ? ? ? ? ? ? ? $('.up-end').show();
? ? ? ? ? ? ? ? $('.up-state').hide();
? ? ? ? ? ? ? ? setTimeout(function () {
? ? ? ? ? ? ? ? ? ? $(".up-end").hide();
? ? ? ? ? ? ? ? ? ? $('.up-con').show();
? ? ? ? ? ? ? ? },2000);
? ? ? ? ? ? }
? ? ? ? });
? ? });
? ? uploader.on( 'uploadError', function( file ) {
? ? ? ? $(".up-end h1").text('上傳出錯(cuò)');
? ? ? ? $('.up-end').show();
? ? ? ? $('.up-state').hide();
? ? ? ? setTimeout(function () {
? ? ? ? ? ? $(".up-end").hide();
? ? ? ? ? ? $('.up-con').show();
? ? ? ? },2000);
? ? });
}
服務(wù)器:
?
//分片上傳
function chunkUpload($name = 'file')
{
? ? $md5 = request()->param()
′md5′
;
? ? $object_info = request()->file($name);
? ? //保存文件的順序很重要!
? ? $object = $object_info->rule('uniqid')->move(PATH_FILE . $md5 . '/',request()->param()
′chunk′
);
? ? if($object)
? ? {
? ? ? ?return
′chunks′=>request()?>param()\[′chunks′
,'chunk'=>request()->param()
′chunk′
\];
? ? }else{
? ? ? ? return false;
? ? }
}
//最終合并文件
function videoUpload()
{
? ? $md5 = request()->param()
′md5′
;
? ? $dir = PATH_FILE . $md5;
? ? if(is_dir($dir)) {
? ? ? ? //獲取文件的順序很重要!!!
? ? ? ? $files =
?
;
? ? ? ? $chunk_id = 0;
? ? ? ? $chunk_file = PATH_FILE . $md5 . '/';
? ? ? ? while (file_exists($chunk_file . $chunk_id . '.mp4')){
? ? ? ? ? ? $files
?
= $chunk_file . $chunk_id . '.mp4';
? ? ? ? ? ? $chunk_id++;
? ? ? ? }
? ? ? ? $file_name = randomStr().'.mp4';
? ? ? ? $path_file = date('Ymd') . SYS_DS_PROS . $file_name;
? ? ? ? //日期目錄不存在則創(chuàng)建目錄
? ? ? ? if(!is_dir(PATH_FILE . date('Ymd')))
? ? ? ? {
? ? ? ? ? ? mkdir(PATH_FILE . date('Ymd'));
? ? ? ? }
? ? ? ? $count = 0;
? ? ? ? foreach ($files as $v)
? ? ? ? {
? ? ? ? ? ? $_file = file_get_contents($v);
? ? ? ? ? ? $_res = file_put_contents(PATH_FILE . $path_file,$_file,FILE_APPEND);
? ? ? ? ? ? if($_res)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? unlink($v);
? ? ? ? ? ? }else{
? ? ? ? ? ? ? ? $count++;
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? if($count == 0)
? ? ? ? {
? ? ? ? ? ? rmdir($dir);
? ? ? ? ? ? //檢查合并后的文件hash
? ? ? ? ? ? $_hash = hash_file('sha1', PATH_FILE . $path_file);
? ? ? ? ? ? //合并后的文件已存在則刪除已合并文件并返回已有文件信息
? ? ? ? ? ? $file_info = $this->modelFile->getInfo(
′sha1′=>$hash
,'id,name,path,sha1,guid,md5');
? ? ? ? ? ? if(!empty($file_info))
? ? ? ? ? ? {
? ? ? ? ? ? ? ? unlink(PATH_FILE . $path_file);
? ? ? ? ? ? ? ? return $file_info;
? ? ? ? ? ? }
? ? ? ? ? ? //合并后的文件入庫(kù)并返回
? ? ? ? ? ? $_data =
′name′=>$filename,′path′=>$pathfile,′sha1′=>$hash,′guid′=>′′,′md5′=>request()?>param()\[′md5′
\];
? ? ? ? ? ? $result = $this->modelFile->addInfo($_data);
? ? ? ? ? ? $_data
′id′
= $result;
? ? ? ? ? ? return $_data;
? ? ? ? }else{
? ? ? ? ? ? $this->error = '合并文件失敗';
? ? ? ? ? ? return false;
? ? ? ? }
? ? }else{
? ? ? ? $this->error = '分片目錄不存在!';
? ? ? ? return false;
? ? }
}
//分片驗(yàn)證
function checkChunk()
{
? ? $md5 = request()->param()
′md5′
;
? ? $dir = PATH_FILE . $md5;
? ? if(is_dir($dir)) {
? ? ? ? $files = $this->getFileByPath($dir);
? ? ? ? return count($files);
? ? }else{
? ? ? ? return false;
? ? }
}
?
=========================================================================
最新更新:
分片驗(yàn)證存在bug,當(dāng)上傳分片不小心刪除了前面的分片時(shí)就導(dǎo)致無(wú)法合成文件(文件數(shù)量導(dǎo)致跳過(guò)了),因此,更新分片驗(yàn)證
前端:
//時(shí)間點(diǎn)2:如果有分塊上傳,則每個(gè)分塊上傳之前調(diào)用此函數(shù)
beforeSend:function(block){
? ? var deferred = WebUploader.Deferred();
? ? if(is_upload)//跳過(guò)到開(kāi)始上傳的哪一個(gè)分片時(shí)
? ? {
? ? ? ? deferred.resolve();
? ? }else if(totalFiles)//已經(jīng)獲取過(guò)文件數(shù)量則直接判斷是否跳過(guò)
? ? {
? ? ? ? //當(dāng)前分片下標(biāo)小于等于目錄下的文件數(shù)量就認(rèn)為分塊存在,因?yàn)榉謮K上傳下標(biāo)是由小到大
? ? ? ? if (!totalFiles.in_array(block.chunk)) {
? ? ? ? ? ? deferred.resolve();
? ? ? ? } else {
? ? ? ? ? ? //分塊存在,跳過(guò)
? ? ? ? ? ? deferred.reject();
? ? ? ? }
? ? } else {
? ? ? ? $.post('/api.php/upload/checkUpload', {md5: fileMd5}, function (data) {
? ? ? ? ? ? if (data.code) {
? ? ? ? ? ? ? ? totalFiles = data.data;
? ? ? ? ? ? ? ? //當(dāng)前分片下標(biāo)小于等于目錄下的文件數(shù)量就認(rèn)為分塊存在,因?yàn)榉謮K上傳下標(biāo)是由小到大
? ? ? ? ? ? ? ? if (!data.data.in_array(block.chunk)) {
? ? ? ? ? ? ? ? ? ? deferred.resolve();
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? //分塊存在,跳過(guò)
? ? ? ? ? ? ? ? ? ? deferred.reject();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? is_upload = true;
? ? ? ? ? ? ? ? deferred.resolve();
? ? ? ? ? ? }
? ? ? ? });
? ? }
? ? return deferred.promise();
后端:
//分片驗(yàn)證
public function checkChunk()
{
? ? $md5 = request()->param()
′md5′
;
? ? $dir = PATH_FILE . $md5;
? ? if(is_dir($dir)) {
? ? ? ? $files = $this->getFileByPath($dir);
? ? ? ? foreach ($files as $k=>$v)
? ? ? ? {
? ? ? ? ? ? $file_name = explode('/',$v);
? ? ? ? ? ? $file_name = $file_name
count($filename)?1
;
? ? ? ? ? ? $file_index = explode('.',$file_name)
00
;
? ? ? ? ? ? $files
$k
= (int)$file_index;
? ? ? ? }
? ? ? ? return $files;
? ? }else{
? ? ? ? return false;
? ? }
}
利用文件名的有序性判斷當(dāng)前分片索引是否存在服務(wù)器
判斷是否存在數(shù)組中的函數(shù):
?
Array.prototype.in_array = function (element) {
? ? for (var i = 0; i < this.length; i++) {
? ? ? ? if (this
i
== element) {
? ? ? ? ? ? return true;
? ? ? ? }
? ? } return false;
};
轉(zhuǎn)自鏈接:www.npqdlp.com