大文件分片上传,断点续传,秒传实现

AllenChou AllenChou     2022-10-01     120

关键词:

前段时间做视频上传业务,通过网页上传视频到服务器。
视频大小 小则几十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,可实现断点续传,上传过程中中断;之前上传的分片已保留在服务器,只需重新上传尚未上传的分片即可;

 

springboot实现分片上传断点续传大文件极速秒传-备忘(代码片段)

  文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会忍受,当文件上传... 查看详情

前端+后端实现分片上传(断点续传/极速秒传)(代码片段)

...下,后面有时间再去实现可参考链接:vue上传大文件/视频前后端(java)代码前端+后端实现分片上传(断点续传/极速秒传)前端slice分片上传,后端用表记录分片索引和分片大小和分片总数,当... 查看详情

10g大文件上传最全方案:秒传断点续传分片上传,包教会!

上一篇:麻了!Fastjson再曝反序列化漏洞。。前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进... 查看详情

如何实现大文件上传:秒传断点续传分片上传(代码片段)

前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有... 查看详情

如何实现大文件上传:秒传断点续传分片上传(代码片段)

前言文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有... 查看详情

支持ie低版本的上传大文件切割上传断点续传秒传

...swfupload2、http://download.csdn.net/detail/rememberme001/9873136支持大文件传输,先把大文件分割成每个2M的小文件分批上传,再组合成一个大文件。支持断点续传,MD5校验实现妙传功能,支 查看详情

springboot分片上传断点续传大文件极速秒传功能,这篇都帮你搞定!(典藏版)...(代码片段)

文件上传是一个老生常谈的话题了,在文件相对比较小的情况下,可以直接把文件转化为字节流上传到服务器,但在文件比较大的情况下,用普通的方式进行上传,这可不是一个好的办法,毕竟很少有人会... 查看详情

搭建fastdfs服务,及单机redis服务,springboot实现h5与fastdfs之间的断点续传,大文件上传,秒传文件和批量上传(代码片段)

前言搭建单机redis服务,结合fastdfs,springboot实现h5与fastdfs之间的断点续传,大文件上传,秒传。技术采用:webuploader+springboot+redis+fastdfs(服务端)+FastDFS_Client。本文所需实现工具,皆在此包中https://download. 查看详情

前端实现大文件上传分片上传断点续传

总结一下大文件分片上传和断点续传的问题。因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况。http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件切片(分块)... 查看详情

ios大文件分片上传和断点续传

总结一下大文件分片上传和断点续传的问题。因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况。http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件切片(分块)... 查看详情

jsp利用webuploader实现超大文件分片上传断点续传

​需求:项目要支持大文件上传功能,经过讨论,初步将文件上传大小控制在500M内,因此自己需要在项目中进行文件上传部分的调整和配置,自己将大小都以501M来进行限制。第一步:前端修改由于项目使用的是BJUI前端框架,并... 查看详情

实战篇:断点续传?文件秒传?手撸大文件上传(代码片段)

...味菜最近接到一个新的需求,需要上传2G左右的视频文件,用测试环境的OSS试了一下,上传需要十几分钟,再考虑到公司的资源问题,果断放弃该方案。一提到大文件上传,我最先想到的就是各种网盘了... 查看详情

项目难点——断点续传分片上传(代码片段)

项目难点——【2】断点续传、分片上传1文件分片在网络请求中,如果我们有时是上传大文件,可能由于网络原因,导致上传断断续续,很难一次性上传成功,那么这个时候我们就需要将大文件分块,分成... 查看详情

完整版断点续传秒传,支持超大大大文件_支持重定义文件名和路径

需求:支持大文件批量上传(20G)和下载,同时需要保证上传期间用户电脑不出现卡死等体验;内网百兆网络上传速度为12MB/S服务器内存占用低支持文件夹上传,文件夹中的文件数量达到1万个以上,且包含层级结构。支持PC端全... 查看详情

超大文件上传和断点续传的源代码

总结一下大文件分片上传和断点续传的问题。因为文件过大(比如1G以上),必须要考虑上传过程网络中断的情况。http的网络请求中本身就已经具备了分片上传功能,当传输的文件比较大时,http协议自动会将文件切片(分块)... 查看详情

如何使用断点续传上传大文件

概念大文件上传的需求介绍不管怎样简单的需求,在量级达到一定层次时,都会变得异常复杂。文件上传简单,文件变大就复杂上传大文件时,以下几个变量会影响我们的用户体验服务器处理数据的能力请求超时... 查看详情

teracloud传不了大文件

teracloud传不了超过5G大文件。传大文件用以下方法解决如果我们需要使用工具上传单个或者多个大文件,而不是使用API、SDK编写代码上传时,可以使用ossutil。1、可以使用ossutil命令行工具的cp命令上传大文件2、可通过–bigfile-thres... 查看详情

vue超大文件上传解决方案:分片断点上传

众所皆知,web上传大文件,一直是一个痛。上传文件大小限制,页面响应时间超时.这些都是web开发所必须直面的。本文给出的解决方案是:前端实现数据流分片长传,后面接收完毕后合并文件的思路。实现文件夹上传,要求:... 查看详情