前段时间做视频上传业务,通过网页上传视频到服务器。
视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制;2,请求时间过长,请求超时;3,传输中断,必须重新上传导致前功尽弃;
解决方案:
1,修改服务端上传的限制配置;Nginx 以及 PHP 的上传文件限制 不宜过大,一般5M 左右为好;
2,大文件分片,一片一片的传到服务端,再由服务端合并。这么做的好处在于一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分片的大小可以控制在4MB以内,服务端限制在4M即可。
前端
Web前端可使用百度的 webuploader H5大文件分片上传插件;官网地址:http://fex.baidu.com/webuploader/
<div class="section section6 section5"> <div class="part1"><a href="javascript:;" target="_blank" class="part1__btn">批量删除</a><span class="part1__txt"><em class="part1__num" id="upload_num">0</em>个视频,共 <em class="part1__num" id="upload_size">0M</em></span></div> <table class="section5__table"> <tbody id="thelist"> <tr class="thead"> <th class="col1 allCkeck"><input type="checkbox" name="" class="col1__checkBox"/>视频名称</th><th class="col2">视频大小</th><th class="col3">视频分类</th><th class="col4">状态</th><th class="col5">进度</th><th>操作</th> </tr> </tbody> </table> <div class="selFile" id="selFile"> <div id="drag_tips"> <div id="btns__add2"></div> <h2 class="txt1">选择视频文件</h2> <span class="txt2">或直接将文件拖拽至此窗口</span> </div> </div> <div class="btns"><span class="btns__add" id="btns__add">+添加视频文件</span><span class="btns__upload btns__upload-start" id="uploadBtn"><i class="btns__upload_icon"></i>开始上传视频</span></div> </div>
//引入插件
<script type="text/javascript" src="media/js/lib/webuploader/js/webuploader.min.js"></script>
upload.js
1 // 文件上传 2 jQuery(function() { 3 var $ = jQuery, 4 $list = $(\'#thelist\'), 5 $btn = $(\'#upload-start\'), 6 $thead = $(\'.thead\'), 7 $part_btn = $(\'.part1__btn\'), //批量上传按钮 8 state = \'pending\', 9 fileCount = 0, //上传文件总数 10 fileSize = 0,//上传文件的总大小 11 // 上传按钮 12 $upload = $(\'#uploadBtn\'), 13 // 所有文件的进度信息,key为file id 14 percentages = {}, 15 // 所有文件的md5,key为file id 16 md5Obj = {}, 17 // 可能有pedding, ready, uploading, confirm, done. 18 state = \'pedding\', 19 uploader; 20 21 //浏览器关闭提醒 22 window.is_confirm = false; 23 $(window).bind(\'beforeunload\', function(){ 24 // 只有在标识变量is_confirm不为false时,才弹出确认提示 25 if(window.is_confirm !== false) 26 return \'正在上传视频,该操作将丢失视频,是否继续?\'; 27 }) 28 29 if ( !WebUploader.Uploader.support() ) { 30 alert( \'Web Uploader 不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级浏览器\'); 31 throw new Error( \'WebUploader does not support the browser you are using.\' ); 32 } 33 34 $(".pop2 .btns__sure").click(function(){ 35 $(\'.popup,.pop\').hide(); 36 }); 37 38 uploader = WebUploader.create({ 39 //拖拽容器 40 dnd:\'#selFile\', 41 42 // 不压缩image 43 resize: false, 44 45 // swf文件路径 46 swf: \'/media/js/lib/webuploader/js/Uploader.swf\', 47 48 // 文件接收服务端。 49 server: \'/service/upload/upload_file\', 50 //server:\'http://vod.test.4399sy.com/service/upload/ssl_upload_file\', 51 formData: { 52 file_id: \'file\', 53 guid:new Date().getTime() + Math.ceil(Math.random()*100), 54 file_name:\'\' 55 }, 56 57 // 选择文件的按钮。可选。 58 // 内部根据当前运行是创建,可能是input元素,也可能是flash. 59 pick: { 60 id:\'#btns__add\', 61 innerHTML:"+添加视频文件" 62 }, 63 64 // 开起分片上传。 65 chunked: true, 66 67 //如果要分片,分多大一片2M 68 chunkSize:2*1024*1024, 69 70 //上传文件的类型 71 accept:{ 72 title: \'Videos\', 73 extensions: \'mp4,avi,flv\', 74 mimeTypes: \'video/*\' 75 }, 76 //验证文件总数量, 超出则不允许加入队列。 77 fileNumLimit: 10, 78 //单个文件上传的大小限制 2G 79 fileSingleSizeLimit:2*1024*1024*1024, 80 81 }); 82 83 //添加文件具体函数 84 function addFile( file ){ 85 var data = new Date(), 86 month = (data.getMonth()+1)<10 ? \'0\'+(data.getMonth()+1) : (data.getMonth()+1), 87 day = data.getDate()<10 ? \'0\'+ data.getDate(): data.getDate(), 88 time = data.getFullYear() + "-" + month + "-" + day, 89 $tr = $(\'<tr class="toBeUploaded" id="\'+file.id+\'"></tr>\'), 90 $td = $(\'<td class="col1"><input type="checkbox" name="" class="col1__checkBox"/><input type="text" value="\'+ file.name +\'" name="" class="name"/></td><td class="col2">\'+convert_size(file.size)+\'</td><td class="col3"><select class="class_id">\'+ class_options +\'</select></td><td class="col4">读取视频中</td><td class="col5">0%</td><td class="col6"><ul><li class="view"><a target="_blank" href="javascript:;">查看</a></li><li class="delete">删除</li></ul></td>\').appendTo($tr), 91 $state = $tr.find(\'td.col4\'), 92 $prgress = $tr.find(\'td.col5\'), 93 $delbtn = $tr.find(\'li.delete\'); 94 95 $("#selFile").hide(); 96 97 if ( file.getStatus() === \'invalid\' ) { 98 switch( file.statusText ) { 99 case \'exceed_size\': 100 text = \'文件大小超出\'; 101 break; 102 103 case \'interrupt\': 104 text = \'上传暂停\'; 105 break; 106 107 default: 108 text = \'上传失败,请重试\'; 109 break; 110 } 111 showError(text); 112 } else { 113 // @todo lazyload 114 percentages[ file.id ] = [ file.size, 0 ]; 115 file.rotation = 0; 116 } 117 118 file.on(\'statuschange\', function( cur, prev ) { 119 if ( prev === \'progress\' ) { 120 //上传成功 121 } else if ( prev === \'queued\' ) { 122 // 开始上传 123 } 124 125 // 成功 126 if ( cur === \'error\' || cur === \'invalid\' ) { 127 console.log( file.statusText ); 128 showError( file.statusText ); 129 percentages[ file.id ][ 1 ] = 1; 130 } else if ( cur === \'interrupt\' ) { 131 showError( \'interrupt\' ); 132 } else if ( cur === \'queued\' ) { 133 percentages[ file.id ][ 1 ] = 0; 134 } else if ( cur === \'progress\' ) { 135 // 正在上传 136 137 } else if ( cur === \'complete\' ) { 138 // 上传完成 139 140 } 141 142 $tr.removeClass( \'state-\' + prev ).addClass( \'state-\' + cur ); 143 }); 144 $delbtn.on(\'click\',function(){ 145 uploader.removeFile( file ); 146 }); 147 $tr.appendTo($list); 148 //$tr.insertAfter($thead); 149 } 150 151 // 负责view的销毁 152 function removeFile( file ) { 153 var $tr = $(\'#\'+file.id); 154 155 delete percentages[ file.id ]; 156 $tr.off().find(\'.col6\').off().end().remove(); 157 } 158 159 function setState( val ) { 160 var file, stats; 161 162 if ( val === state ) { 163 return; 164 } 165 166 $upload.removeClass( \'state-\' + state ); 167 $upload.addClass( \'state-\' + val ); 168 state = val; 169 170 switch ( state ) { 171 case \'pedding\': 172 uploader.refresh(); 173 break; 174 175 case \'ready\': 176 uploader.refresh(); 177 break; 178 179 case \'uploading\': 180 $upload.text( \'暂停上传\' ); 181 break; 182 case \'paused\': 183 $upload.text( \'继续上传\' ); 184 break; 185 186 case \'confirm\': 187 //$progress.hide(); 188 $upload.text( \'开始上传\' ).addClass( \'disabled\' ); 189 190 stats = uploader.getStats(); 191 if ( stats.successNum && !stats.uploadFailNum ) { 192 setState( \'finish\' ); 193 return; 194 } 195 break; 196 case \'finish\': 197 stats = uploader.getStats(); 198 if ( stats.successNum ) { 199 alert( \'上传成功\' ); 200 } else { 201 // 没有成功的图片,重设 202 state = \'done\'; 203 location.reload(); 204 } 205 break; 206 } 207 } 208 209 210 // 当有文件添加进来的时候 211 uploader.on( \'fileQueued\', function( file ) { 212 fileCount++; 213 fileSize += file.size; 214 $("#upload_num").text(fileCount); 215 $("#upload_size").text(convert_size(fileSize)); 216 md5Obj[ file.id ] = \'\'; 217 //获取文件MD5 值 218 uploader.md5File( file ) 219 // 及时显示进度 220 .progress(function(percentage) { 221 $( \'#\'+file.id ).find(\'.col4\').text(\'读取文件\'+parseInt(percentage*100)+"%"); 222 }) 223 // 完成 224 .then(function(val) { 225 console.log(\'md5 result:\', val); 226 md5Obj[ file.id ] = val; 227 $( \'#\'+file.id ).find(\'.col4\').text(\'待上传\'); 228 setState( \'ready\' ); 229 }); 230 addFile( file ); 231 }); 232 233 // 删除文件 234 uploader.onFileDequeued = function( file ) { 235 fileCount--; 236 fileSize -= file.size; 237 $("#upload_num").text(fileCount); 238 $("#upload_size").text(convert_size(fileSize)); 239 if ( !fileCount ) { 240 setState( \'pedding\' ); 241 } 242 removeFile( file ); 243 244 }; 245 246 // 添加“添加文件”的按钮, 247 uploader.addButton({ 248 id: \'#btns__add2\', 249 label: \'\' 250 }); 251 252 // 文件上传过程中创建进度实时显示。 253 uploader.onUploadProgress = function( file, percentage ) { 254 var $tr = $(\'#\'+file.id), 255 $percent = $tr.find(\'td.col5\'), 256 $state = $tr.find(\'td.col4\'); 257 percentage = parseInt(percentage*100); 258 if(! (percentage == 0 && percentage == 100)){ 259 $state.text("正在上传"); 260 } 261 $percent.text( percentage + "%") 262 percentages[ file.id ][ 1 ] = percentage; 263 }; 264 265 //上传前,请求服务端 判断文件是否已经上传过 266 uploader.on( \'uploadStart\', function( file ) { 267 var type = \'POST\'; 268 var url = \'/service/upload/determine_video_exist\'; 269 var request_data = { 270 \'md5\': md5Obj[ file.id ], 271 \'type\':1 272 }; 273 var success = function(r) { 274 uploader.upload( file ); 275 console.log(r); 276 if(r.code == 1) { 277 uploader.skipFile( file ); 278 $( \'#\'+file.id ).find(\'.col4\').text(\'视频已存在\'); 279 $( \'#\'+file.id ).find(\'.col5\').text(\'100%\'); 280 $(\'#\'+file.id).find(\'.view\').find(\'a\').attr(\'href\',playmain +\'/?video_id=\'+ r.data.video_id); 281 $(\'.pop2 .video_game\').text("所在游戏:"+r.data.game_name); 282 $(\'.pop2 .create_time\').text("上传时间:"+r.data.create_time); 283 $(\'.pop\').hide(); 284 $(\'.pop2\').show(); 285 $(\'.popup\').show(); 286 }else if(r.code <= 0) { 287 showError(r.msg); 288 }else { 289 290 } 291 }; 292 request(type, url, request_data, success); 293 }); 294 295 //当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 296 uploader.on(\'uploadBeforeSend\', function (obj, data, headers) { 297 $tr = $("#"+data.id); 298 var file_name = $tr.find(".name").val(); 299 var class_id = $tr.find("select.class_id").val(); 300 var reg = /[1-9][0-9]*/g; 301 data.md5 = md5Obj[ obj.file.id ]; 302 data.file_name = file_name; 303 data.class_id = class_id; 304 data.guid = data.guid + data.id.replace(/[^0-9]+/g, \'\'); 305 }); 306 307 uploader.on( \'uploadSuccess\', function( file ,res) { 308 if(res.code == 1){ 309 $( \'#\'+file.id ).find(\'.col4\').text(\'成功上传\'); 310 console.log(res); 311 $(\'#\'+file.id).find(\'.view\').find(\'a\').attr(\'href\',playmain +\'/?video_id=\'+ res.data.video_id); 312 }else if(res.code == 2) { 313 $( \'#\'+file.id ).find(\'.col4\').text(\'视频已存在\'); 314 console.log(res); 315 $(\'#\'+file.id).find(\'.view\').find(\'a\').attr(\'href\',playmain +\'/?video_id=\'+ res.data.video_id); 316 }else { 317 showError(res.msg); 318 } 319 }); 320 321 uploader.on( \'uploadError\', function( file,reason ) { 322 $( \'#\'+file.id ).find(\'.col4\').text(\'上传失败\'); 323 console.log(reason); 324 }); 325 326 uploader.on( \'uploadComplete\', function( file ) { 327 $( \'#\'+file.id ).find(\'.progress\').fadeOut(); 328 }); 329 330 uploader.on( \'all\', function( type ) { 331 if ( type === \'startUpload\' ) { 332 state = \'uploading\'; 333 } else if ( type === \'stopUpload\' ) { 334 state = \'paused\'; 335 } else if ( type === \'uploadFinished\' ) { 336 state = \'done\'; 337 } 338 339 if ( state === \'uploading\' ) { 340 window.is_confirm = true; 341 $(\'.toBeUploaded\').addClass("uploaded").removeClass("toBeUploaded"); 342 $(\'input.name\').attr("disabled","disabled"); 343 $(\'input.col1__checkBox\').hide(); 344 $(\'input\').attr("disabled","disabled"); 345 $(\'select.class_id\').attr("disabled","disabled"); 346 $(\'.btns__add\').remove(); 347 $upload.addClass("btns__upload-ing").removeClass("btns__upload-start").html(\'<i class="btns__upload_icon"></i>正在上传视频\'); 348 349 } else if(state === \'done\') { 350 window.is_confirm = false; 351 console.log("上传完成"); 352 $upload.addClass("btns__upload-start btns__upload-refresh").removeClass("btns__upload-ing").html(\'<i class="btns__upload_icon"></i>开始上传视频\'); 353 } 354 }); 355 356 /** 357 * 验证文件格式以及文件大小 358 */ 359 uploader.on("error",function (type){ 360 var msg = \'\' 361 switch (type){ 362 case "Q_TYPE_DENIED": msg = "请上传mp4格式文件";break; 363 case "F_EXCEED_SIZE": msg = "文件大小不能超过1G";break; 364 case "Q_EXCEED_NUM_LIMIT" : msg = "一次最多能上传10个文件";break; 365 default: msg=\'\'; 366 } 367 if(msg != \'\'){ 368 showError(msg); 369 } 370 }); 371 372 $part_btn.on(\'click\',function(){ 373 $(\'td .col1__checkBox\').each(function(){ 374 if($(this).is(\':checked\')){ 375 var $tr = $(this).parents(\'tr\'); 376 var id = $tr.attr(\'id\'); 377 uploader.removeFile( id ); 378 } 379 }); 380 }); 381 $upload.on(\'click\', function() { 382 var isbreak = false; 383 $(".name").each(function(){ 384 if(!$(this).val()|| $(this).val() == \'\'){ 385 isbreak = true; 386 } 387 }) 388 if(isbreak){ 389 showError("文件名不能存在为空"); 390 return; 391 } 392 $("select.class_id").each(function(){ 393 if(!$(this).val()|| $(this).val() == \'\'){ 394 isbreak = true; 395 } 396 }) 397 if(isbreak){ 398 showError("分类不能为空,请先添加分类"); 399 return; 400 } 401 if ( $(this).hasClass( \'btns__upload-refresh\' ) ) { 402 location.reload(); 403 } 404 if ( $(this).hasClass( \'btns__upload-ing\' ) ) { 405 return false; 406 } 407 var md5Ready = true; 408 $.each(md5Obj,function(index,item){ 409 if(!item || item==\'\'){ 410 md5Ready = false; 411 } 412 }); 413 if(!md5Ready){ 414 showError(\'文件尚未读取完成,请耐心等待\'); 415 return false; 416 } 417 if ( state === \'ready\' && md5Ready ) { 418 uploader.upload(); 419 } else if ( state === \'paused\' ) { 420 uploader.upload(); 421 } else if ( state === \'uploading\' ) { 422 uploader.stop(); 423 } 424 }); 425 $upload.addClass( \'state-\' + state ); 426 427 });
后台(PHP)【仅分片上传相关代码】
1 public function action_upload_file(){ 2 $file_id = R::string(\'file_id\', \'file\'); 3 $keepFileName = R::string(\'keepFileName\', 0); 4 $unsize_change = R::numeric(\'unsize_change\',0); 5 $id = R::string(\'id\'); //插件每上传一个视频自带id 6 $guid = R::string(\'guid\'); //标识视频 7 $chunks = R::numeric(\'chunks\'); // 分片数 8 $chunk = R::numeric(\'chunk\'); //分片号 9 $file_name = R::string(\'file_name\'); 10 $file = isset($_FILES[$file_id])?$_FILES[$file_id]:\'\'; 11 $md5 = R::string(\'md5\'); 12 $this->upload = new Common_Upload(); 13 14 if(empty($guid) || empty($file_name) || empty($md5)){ 15 $this->response_msg(-1, \'guid 或 file_name 或 md5 不能为空\'); 16 return; 17 } 18 19 if(empty($file[\'name\'])){ 20 $this->response_msg(-1, \'请上传一个文件\'); 21 return; 22 }else{ 23 if($chunks){ 24 $res = $this->upload->saveFile_chunks($file,$chunks, $chunk, $guid); 25 if(empty($res)){ 26 $this->response_msg(-2, \'分片上传失败\'); 27 return; 28 } 29 30 }else if($keepFileName){ 31 $res = $this->upload->saveFile_nochunks($file, \'\', \'\', $keepFileName); 32 }else{ 33 $res = $this->upload->saveFile_nochunks($file); 34 } 35 if(empty($res)){ 36 $err = $this->upload->getError(); 37 $this->response_msg(-3, \'上传文件出错!msg:\'.print_r($err, true)); 38 return; 39 } 40 if($unsize_change){ 41 $size = $res[\'size\']; 42 }else{ 43 $size = $this->convert_size($res[\'size\']); 44 } 45 46 //视频上传完成 47 if($chunks && $res[\'last_chunk\']){ 48 $domain = Kohana::$config->load(\'domain\'); 49 $video_domain = $domain[RUN_MOD][\'VIDEO\']; 50 51 if(!empty($file_name)){ 52 $res[\'name\'] = $file_name; 53 } 54 $video_data = array( 55 \'video_name\'=> $file_name, 56 \'video_url\'=> $video_domain."/".$res[\'path\'], 57 \'size\'=>$res[\'size\'], 58 \'create_time\'=> date(\'y-m-d H:i:s\',time()), 59 \'update_time\'=> date(\'y-m-d H:i:s\',time()), 60 \'duration\'=> $res[\'time\'], 61 \'md5\'=>$md5 62 ); 63 $video_mod = new Model_Videoinfo(); 64 $video = $video_mod->save_video($video_data,$guid); 65 $res = array( 66 \'path\'=>$res[\'path\'], 67 \'chunks\'=>$chunks, 68 \'chunk\'=>$chunk, 69 \'size\'=>$size, 70 \'guid\'=> $guid, 71 \'video_id\'=>$video[0], 72 \'file\'=>$file, 73 \'id\'=>$id 74 ); 75 $this->response_msg(1,\'视频上传成功\',$res); 76 return; 77 } 78 //非分片上传 79 if(!$chunks){ 80 $domain = Kohana::$config->load(\'domain\'); 81 $video_domain = $domain[RUN_MOD][\'VIDEO\']; 82 if(!empty($file_name)){ 83 $res[\'name\'] = $file_name; 84 } 85 $video_data = array( 86 \'video_name\'=> $file_name, 87 \'video_url\'=> $video_domain."/".$res[\'path\'], 88 \'size\'=>$res[\'size\'], 89 \'create_time\'=> date(\'y-m-d H:i:s\',time()), 90 \'update_time\'=> date(\'y-m-d H:i:s\',time()), 91 \'duration\'=> $res[\'time\'], 92 \'md5\'=>$md5 93 ); 94 $video_mod = new Model_Videoinfo(); 95 $video = $video_mod->save_video($video_data,$guid); 96 if(empty($video)){ 97 $this->response_msg(-6, \'视频信息保存失败\'); 98 return; 99 } 100 $res = array( 101 \'path\'=>$res[\'path\'], 102 \'video_data\'=>$video_data, 103 \'size\'=>$size, 104 \'guid\'=> $guid, 105 \'video_id\'=>$video[0], 106 \'file\'=>$file, 107 \'id\'=>$id 108 ); 109 $this->response_msg(1,\'视频上传成功\',$res); 110 return; 111 } 112 //分片上传成功(未全部分片上传完成) 113 $res = array( 114 \'chunks\'=>$chunks, 115 \'chunk\'=>$chunk, 116 ); 117 $this->response_msg(2, \'分片上传成功\',$res); 118 } 119 }
1 /** 2 * 保存分片文件(注意先验证文件是否合法) 3 * 4 * @param array $file 单个文件 5 * @param string $attachdir 上传文件路径 6 * @param string $upload_type 上传文件类型 7 * @param bool $keepFileName 是否保持文件名,默认不不保持 8 * @return bool 9 */ 10 public function saveFile_chunks($file,$chunks, $chunk, $guid) 11 { 12 if(empty($guid) || empty($file) ){ 13 return false; 14 } 15 $file_name = (string)$guid . $chunk; 16 //保存分片文件 17 $file_info = $this->saveFile($file, \'\', \'\', false, $file_name,true); 18 if ($file_info) { 19 $cache = Cache::instance(\'memcache\'); 20 //记录已上传的分片编号,上传顺序并不是按编号顺序进行上传 21 $chunks_list_pre = $cache->get($guid); 22 if(empty($chunks_list_pre)){ 23 $strarr = array(); 24 for($i=0;$i<$chunks;$i++){ 25 $strarr[] = $guid.$i; 26 } 27 $cache->set($guid,$strarr,60 * 60 * 24); 28 } 29 $file_path = $cache->set($guid.$chunk,$file_info[\'path\'],60 * 60 * 24); 30 31 $chunk_path_array= array(); 32 for($i=0;$i<$chunks;$i++){ 33 if($cache->get($guid.$i)){ 34 $chunk_path_array[$i] = $cache->get($guid.$i); 35 } 36 } 37 list($Y,$M,$D,$H,$I,$S) = explode(\'-\',date("Y-m-d-H-i-s", time())); 38 $file_info[\'chunks_path_count\'] = count($chunk_path_array); 39 $file_info[\'last_chunk\'] = false; 40 if (count($chunk_path_array) == $chunks) { 41 //按目录类型存储 42 $dirType = substr($file_info[\'type\'], 1, strlen($file_info[\'type\']));; 43 //目录类型前面加上前缀url 44 $dirType = $this->pre_url.$dirType; 45 //按年月二级存储 46 $month_file_path = $Y.\'/\'.$M; 47 $saveName =\'upload/mp4/\'.$month_file_path.\'/original/\'.$guid.$file_info[\'type\']; 48 $join_file_name =$this->attachDIR.$saveName; 49 if(!is_dir($this->attachDIR.\'upload/mp4/\'.$month_file_path.\'/original/\')){ 50 mkdir($this->attachDIR.\'upload/mp4/\'.$month_file_path.\'/original/\',0755,true); 51 } 52 if(! file_exists($join_file_name)){ 53 $fp = fopen($join_file_name, "ab"); 54 //合并过程中对文件加锁,防止同时操作而出错 55 if (flock($fp,LOCK_EX)){ 56 for ($i = 0; $i < $chunks; $i++) { 57 $tmp_file = $this->attachDIR . $chunk_path_array[$i]; 58 $handle = fopen($tmp_file, "rb"); 59 fwrite($fp, fread($handle, filesize($tmp_file))); 60 fclose($handle); 61 unset($handle); 62 unlink($tmp_file);//合并完毕的文件就删除 63 }//组装分片 64 $cache->delete($guid); 65 for($i=0;$i<$chunks;$i++){ 66 $cache->delete($guid.$i); 67 } 68 $time = $this->getTime($join_file_name,$file_info[\'type\']); 69 $file_info[\'time\'] = $time; 70 $file_info[\'path\'] = $saveName; 71 $file_info[\'size\'] = filesize($join_file_name); 72 $file_info[\'last_chunk\'] = true; 73 74 $model_mod = new Model_Base(); 75 $model_mod->disconnect(); 76 $pid = pcntl_fork(); 77 //父进程和子进程都会执行下面代码 78 if ($pid == -1) { 79 //错误处理:创建子进程失败时返回-1. 80 die(\'could not fork\'); 81 } else if ($pid) { 82 $model_mod->connect(); 83 //对上传完成的视频进行排队转码 84 $this->thread($join_file_name,$file_info[\'type\'],$guid); 85 //父进程会得到子进程号,所以这里是父进程执行的逻辑 86 pcntl_wait($status); //等待子进程中断,防止子进程成为僵尸进程。 87 } else { 88 return $file_info; 89 //子进程得到的$pid为0, 所以这里是子进程执行的逻辑。 90 } 91 92 } 93 } 94 95 } 96 return $file_info; 97 } else { 98 $this->error[] = \'分片上传失败\'; 99 return false; 100 } 101 /*}}}*/ 102 }
1,实现了分片上传;
2,同时在上传前检查视频md5 是否在库,如已存在可实现“秒传” 功能,即直接复制数据信息,指向同一个文件,不必再上传;
3,可实现断点续传,上传过程中中断;之前上传的分片已保留在服务器,只需重新上传尚未上传的分片即可;