SpringBoot文件分片上传,断点续传

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

SpringBoot⽂件分⽚上传,断点续传
前段时间做视频上传业务,通过⽹页上传视频到服务器。

视频⼤⼩⼩则⼏⼗M,⼤则 1G+,以⼀般的HTTP请求发送数据的⽅式的话,会遇到的问题:1,⽂件过⼤,超出服务端的请求⼤⼩限制;2,请求时间过长,请求超时;3,传输中断,必须重新上传导致前功尽弃;
解决⽅案:
1,修改服务端上传的限制配置;Nginx 以及 PHP 的上传⽂件限制不宜过⼤,⼀般5M 左右为好;
2,⼤⽂件分⽚,⼀⽚⼀⽚的传到服务端,再由服务端合并。

这么做的好处在于⼀旦上传失败只是损失⼀个分⽚⽽已,不⽤整个⽂件重传,⽽且每个分⽚的⼤⼩可以控制在4MB以内,服务端限制在4M即可。

前端
<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() {
3var $ = 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时,才弹出确认提⽰
25if(window.is_confirm !== false)
26return '正在上传视频,该操作将丢失视频,是否继续?';
27 })
28
29if ( !WebUploader.Uploader.support() ) {
30 alert( 'Web Uploader 不⽀持您的浏览器!如果你使⽤的是IE浏览器,请尝试升级浏览器');
31throw 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:'/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//添加⽂件具体函数
84function addFile( file ){
85var 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="'+ +'" 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
97if ( file.getStatus() === 'invalid' ) {
98switch( file.statusText ) {
99case 'exceed_size':
100 text = '⽂件⼤⼩超出';
101break;
102
103case 'interrupt':
104 text = '上传暂停';
105break;
106
107default:
108 text = '上传失败,请重试';
109break;
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 ) {
119if ( prev === 'progress' ) {
120//上传成功
121 } else if ( prev === 'queued' ) {
122// 开始上传
123 }
124
125// 成功
126if ( 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的销毁
152function removeFile( file ) {
153var $tr = $('#'+file.id);
154
155delete percentages[ file.id ];
156 $tr.off().find('.col6').off().end().remove();
157 }
158
159function setState( val ) {
160var file, stats;
161
162if ( val === state ) {
163return;
164 }
165
166 $upload.removeClass( 'state-' + state );
167 $upload.addClass( 'state-' + val );
168 state = val;
169
170switch ( state ) {
171case 'pedding':
172 uploader.refresh();
173break;
174
175case 'ready':
176 uploader.refresh();
177break;
178
179case 'uploading':
180 $upload.text( '暂停上传' );
181break;
182case 'paused':
183 $upload.text( '继续上传' );
184break;
185
186case 'confirm':
187//$progress.hide();
188 $upload.text( '开始上传' ).addClass( 'disabled' );
189
190 stats = uploader.getStats();
191if ( stats.successNum && !stats.uploadFailNum ) {
192 setState( 'finish' );
193return;
194 }
195break;
196case 'finish':
197 stats = uploader.getStats();
198if ( stats.successNum ) {
199 alert( '上传成功' );
200 } else {
201// 没有成功的图⽚,重设
202 state = 'done';
203 location.reload();
204 }
205break;
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));
239if ( !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 ) {
254var $tr = $('#'+file.id),
255 $percent = $tr.find('td.col5'),
256 $state = $tr.find('td.col4');
257 percentage = parseInt(percentage*100);
258if(! (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 ) {
267var type = 'POST';
268var url = '/service/upload/determine_video_exist';
269var request_data = {
270 'md5': md5Obj[ file.id ],
271 'type':1
272 };
273var success = function(r) {
274 uploader.upload( file );
275 console.log(r);
276if(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);
298var file_name = $tr.find(".name").val();
299var class_id = $tr.find("select.class_id").val();
300var 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) {
308if(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 ) {
331if ( type === 'startUpload' ) {
332 state = 'uploading';
333 } else if ( type === 'stopUpload' ) {
334 state = 'paused';
335 } else if ( type === 'uploadFinished' ) {
336 state = 'done';
337 }
338
339if ( state === 'uploading' ) {
340 window.is_confirm = true;
341 $('.toBeUploaded').addClass("uploaded").removeClass("toBeUploaded");
342 $('').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){
360var msg = ''
361switch (type){
362case "Q_TYPE_DENIED": msg = "请上传mp4格式⽂件";break;
363case "F_EXCEED_SIZE": msg = "⽂件⼤⼩不能超过1G";break;
364case "Q_EXCEED_NUM_LIMIT" : msg = "⼀次最多能上传10个⽂件";break;
365default: msg='';
366 }
367if(msg != ''){
368 showError(msg);
369 }
370 });
371
372 $part_btn.on('click',function(){
373 $('td .col1__checkBox').each(function(){
374if($(this).is(':checked')){
375var $tr = $(this).parents('tr');
376var id = $tr.attr('id');
377 uploader.removeFile( id );
378 }
379 });
380 });
381 $upload.on('click', function() {
382var isbreak = false;
383 $(".name").each(function(){
384if(!$(this).val()|| $(this).val() == ''){
385 isbreak = true;
386 }
387 })
388if(isbreak){
389 showError("⽂件名不能存在为空");
390return;
391 }
392 $("select.class_id").each(function(){
393if(!$(this).val()|| $(this).val() == ''){
394 isbreak = true;
395 }
396 })
397if(isbreak){
398 showError("分类不能为空,请先添加分类");
399return;
400 }
401if ( $(this).hasClass( 'btns__upload-refresh' ) ) {
402 location.reload();
403 }
404if ( $(this).hasClass( 'btns__upload-ing' ) ) {
405return false;
406 }
407var md5Ready = true;
408 $.each(md5Obj,function(index,item){
409if(!item || item==''){
410 md5Ready = false;
411 }
412 });
413if(!md5Ready){
414 showError('⽂件尚未读取完成,请耐⼼等待');
415return false;
416 }
417if ( 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)【仅分⽚上传相关代码】
1public 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
14if(empty($guid) || empty($file_name) || empty($md5)){
15$this->response_msg(-1, 'guid 或 file_name 或 md5 不能为空');
16return;
17 }
18
19if(empty($file['name'])){
20$this->response_msg(-1, '请上传⼀个⽂件');
21return;
22 }else{
23if($chunks){
24$res = $this->upload->saveFile_chunks($file,$chunks, $chunk, $guid); 25if(empty($res)){
26$this->response_msg(-2, '分⽚上传失败');
27return;
28 }
29
30 }else if($keepFileName){
31$res = $this->upload->saveFile_nochunks($file, '', '', $keepFileName);
32 }else{
33$res = $this->upload->saveFile_nochunks($file);
34 }
35if(empty($res)){
36$err = $this->upload->getError();
37$this->response_msg(-3, '上传⽂件出错!msg:'.print_r($err, true));
38return;
39 }
40if($unsize_change){
41$size = $res['size'];
42 }else{
43$size = $this->convert_size($res['size']);
44 }
45
46//视频上传完成
47if($chunks && $res['last_chunk']){
48$domain = Kohana::$config->load('domain');
49$video_domain = $domain[RUN_MOD]['VIDEO'];
50
51if(!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);
76return;
77 }
78//⾮分⽚上传
79if(!$chunks){
80$domain = Kohana::$config->load('domain');
81$video_domain = $domain[RUN_MOD]['VIDEO'];
82if(!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); 96if(empty($video)){
97$this->response_msg(-6, '视频信息保存失败');
98return;
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);
110return;
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*/
10public function saveFile_chunks($file,$chunks, $chunk, $guid) 11 {
12if(empty($guid) || empty($file) ){
13return false;
14 }
15$file_name = (string)$guid . $chunk;
16//保存分⽚⽂件
17$file_info = $this->saveFile($file, '', '', false, $file_name,true);
18if ($file_info) {
19$cache = Cache::instance('memcache');
20//记录已上传的分⽚编号,上传顺序并不是按编号顺序进⾏上传 21$chunks_list_pre = $cache->get($guid);
22if(empty($chunks_list_pre)){
23$strarr = array();
24for($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();
32for($i=0;$i<$chunks;$i++){
33if($cache->get($guid.$i)){
34$chunk_path_array[$i] = $cache->get($guid.$i);
35 }
36 }
37list($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;
40if (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;
49if(!is_dir($this->attachDIR.'upload/mp4/'.$month_file_path.'/original/')){
50mkdir($this->attachDIR.'upload/mp4/'.$month_file_path.'/original/',0755,true);
51 }
52if(! file_exists($join_file_name)){
53$fp = fopen($join_file_name, "ab");
54//合并过程中对⽂件加锁,防⽌同时操作⽽出错
55if (flock($fp,LOCK_EX)){
56for ($i = 0; $i < $chunks; $i++) {
57$tmp_file = $this->attachDIR . $chunk_path_array[$i];
58$handle = fopen($tmp_file, "rb");
59fwrite($fp, fread($handle, filesize($tmp_file)));
60fclose($handle);
61unset($handle);
62unlink($tmp_file);//合并完毕的⽂件就删除
63 }//组装分⽚
64$cache->delete($guid);
65for($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//⽗进程和⼦进程都会执⾏下⾯代码
78if ($pid == -1) {
79//错误处理:创建⼦进程失败时返回-1.
80die('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 {
88return$file_info;
89//⼦进程得到的$pid为0, 所以这⾥是⼦进程执⾏的逻辑。

90 }
91
92 }
93 }
94
95 }
96return$file_info;
97 } else {
98$this->error[] = '分⽚上传失败';
99return false;
100 }
101/*}}}*/
102 }
1,实现了分⽚上传;
2,同时在上传前检查视频md5 是否在库,如已存在可实现“秒传” 功能,即直接复制数据信息,指向同⼀个⽂件,不必再上传;3,可实现断点续传,上传过程中中断;之前上传的分⽚已保留在服务器,只需重新上传尚未上传的分⽚即可;。

相关文档
最新文档