700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 完美解决php无法切片上传大文件方法

完美解决php无法切片上传大文件方法

时间:2022-01-25 22:26:21

相关推荐

完美解决php无法切片上传大文件方法

前段时间做视频上传业务,通过网页上传视频到服务器。

视频大小 小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题:1,文件过大,超出服务端的请求大小限制;2,请求时间过长,请求超时;3,传输中断,必须重新上传导致前功尽弃;

解决方案:

1,修改服务端上传的限制配置;Nginx 以及 PHP 的上传文件限制 不宜过大,一般5M 左右为好;

2,大文件分片,一片一片的传到服务端,再由服务端合并。这么做的好处在于一旦上传失败只是损失一个分片而已,不用整个文件重传,而且每个分片的大小可以控制在4MB以内,服务端限制在4M即可。

前端使用UP6大文件上传控件

<divclass="section section6 section5">

<divclass="part1"><ahref="javascript:;"target="_blank"class="part1__btn">批量删除</a><spanclass="part1__txt"><emclass="part1__num"id="upload_num">0</em>个视频,共<emclass="part1__num"id="upload_size">0M</em></span></div>

<tableclass="section5__table">

<tbodyid="thelist">

<trclass="thead">

<thclass="col1 allCkeck"><inputtype="checkbox"name=""class="col1__checkBox"/>视频名称</th><thclass="col2">视频大小</th><thclass="col3">视频分类</th><thclass="col4">状态</th><thclass="col5">进度</th><th>操作</th>

</tr>

</tbody>

</table>

<divclass="selFile"id="selFile">

<divid="drag_tips">

<divid="btns__add2"></div>

<h2class="txt1">选择视频文件</h2>

<spanclass="txt2">或直接将文件拖拽至此窗口</span>

</div>

</div>

<divclass="btns"><spanclass="btns__add"id="btns__add">+添加视频文件</span><spanclass="btns__upload btns__upload-start"id="uploadBtn"><iclass="btns__upload_icon"></i>开始上传视频</span></div>

</div>

//引入插件

<scripttype="text/javascript"src="media/js/lib/webuploader/js/webuploader.min.js"></script>

upload.js

1//文件上传

2jQuery(function() {

3var$ = jQuery,

4$list = $('#thelist'),

5$btn = $('#upload-start'),

6$thead = $('.thead'),

7$part_btn = $('.part1__btn'),//批量上传按钮

8state = 'pending',

9fileCount = 0,//上传文件总数

10fileSize = 0,//上传文件的总大小

11//上传按钮

12$upload = $('#uploadBtn'),

13//所有文件的进度信息,key为file id

14percentages = {},

15//所有文件的md5,key为file id

16md5Obj = {},

17//可能有pedding, ready, uploading, confirm, done.

18state = 'pedding',

19uploader;

20

21//浏览器关闭提醒

22window.is_confirm =false;

23$(window).bind('beforeunload',function(){

24//只有在标识变量is_confirm不为false时,才弹出确认提示

25if(window.is_confirm !==false)

26return'正在上传视频,该操作将丢失视频,是否继续?';

27})

28

29if( !WebUploader.Uploader.support() ) {

30alert( 'Web Uploader不支持您的浏览器!如果你使用的是IE浏览器,请尝试升级浏览器');

31thrownewError( 'WebUploader does not support the browser you are using.' );

32}

33

34$(".pop2 .btns__sure").click(function(){

35$('.popup,.pop').hide();

36});

37

38uploader = WebUploader.create({

39//拖拽容器

40dnd:'#selFile',

41

42//不压缩image

43resize:false,

44

45// swf文件路径

46swf: '/media/js/lib/webuploader/js/Uploader.swf',

47

48//文件接收服务端。

49server: '/service/upload/upload_file',

50//server:'http://vod./service/upload/ssl_upload_file',

51formData: {

52file_id: 'file',

53guid:newDate().getTime() + Math.ceil(Math.random()*100),

54file_name:''

55},

56

57//选择文件的按钮。可选。

58//内部根据当前运行是创建,可能是input元素,也可能是flash.

59pick: {

60id:'#btns__add',

61innerHTML:"+添加视频文件"

62},

63

64//开起分片上传。

65chunked:true,

66

67//如果要分片,分多大一片2M

68chunkSize:2*1024*1024,

69

70//上传文件的类型

71accept:{

72title: 'Videos',

73extensions: 'mp4,avi,flv',

74mimeTypes: 'video/*'

75},

76//验证文件总数量, 超出则不允许加入队列。

77fileNumLimit: 10,

78//单个文件上传的大小限制 2G

79fileSingleSizeLimit:2*1024*1024*1024,

80

81});

82

83//添加文件具体函数

84functionaddFile( file ){

85vardata =newDate(),

86month = (data.getMonth()+1)<10 ? '0'+(data.getMonth()+1) : (data.getMonth()+1),

87day = data.getDate()<10 ? '0'+ data.getDate(): data.getDate(),

88time = 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

97if( file.getStatus() === 'invalid' ) {

98switch( file.statusText ) {

99case'exceed_size':

100text = '文件大小超出';

101break;

102

103case'interrupt':

104text = '上传暂停';

105break;

106

107default:

108text = '上传失败,请重试';

109break;

110}

111showError(text);

112}else{

113// @todo lazyload

114percentages[ file.id ] = [ file.size, 0 ];

115file.rotation = 0;

116}

117

118file.on('statuschange',function( cur, prev ) {

119if( prev === 'progress' ) {

120//上传成功

121}elseif( prev === 'queued' ) {

122//开始上传

123}

124

125//成功

126if( cur === 'error' || cur === 'invalid' ) {

127console.log( file.statusText );

128showError( file.statusText );

129percentages[ file.id ][ 1 ] = 1;

130}elseif( cur === 'interrupt' ) {

131showError( 'interrupt' );

132}elseif( cur === 'queued' ) {

133percentages[ file.id ][ 1 ] = 0;

134}elseif( cur === 'progress' ) {

135//正在上传

136

137}elseif( cur === 'complete' ) {

138//上传完成

139

140}

141

142$tr.removeClass( 'state-' + prev ).addClass( 'state-' + cur );

143});

144$delbtn.on('click',function(){

145uploader.removeFile( file );

146});

147$tr.appendTo($list);

148//$tr.insertAfter($thead);

149}

150

151//负责view的销毁

152functionremoveFile( file ) {

153var$tr = $('#'+file.id);

154

155deletepercentages[ file.id ];

156$tr.off().find('.col6').off().end().remove();

157}

158

159functionsetState( val ) {

160varfile, stats;

161

162if( val === state ) {

163return;

164}

165

166$upload.removeClass( 'state-' + state );

167$upload.addClass( 'state-' + val );

168state = val;

169

170switch( state ) {

171case'pedding':

172uploader.refresh();

173break;

174

175case'ready':

176uploader.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

190stats = uploader.getStats();

191if( stats.successNum && !stats.uploadFailNum ) {

192setState( 'finish' );

193return;

194}

195break;

196case'finish':

197stats = uploader.getStats();

198if( stats.successNum ) {

199alert( '上传成功' );

200}else{

201//没有成功的图片,重设

202state = 'done';

203location.reload();

204}

205break;

206}

207}

208

209

210//当有文件添加进来的时候

211uploader.on( 'fileQueued',function( file ) {

212fileCount++;

213fileSize += file.size;

214$("#upload_num").text(fileCount);

215$("#upload_size").text(convert_size(fileSize));

216md5Obj[ file.id ] = '';

217//获取文件MD5 值

218uploader.md5File( file )

219//及时显示进度

220.progress(function(percentage) {

221$( '#'+file.id ).find('.col4').text('读取文件'+parseInt(percentage*100)+"%");

222})

223//完成

224.then(function(val) {

225console.log('md5 result:', val);

226md5Obj[ file.id ] = val;

227$( '#'+file.id ).find('.col4').text('待上传');

228setState( 'ready' );

229});

230addFile( file );

231});

232

233//删除文件

234uploader.onFileDequeued =function( file ) {

235fileCount--;

236fileSize -= file.size;

237$("#upload_num").text(fileCount);

238$("#upload_size").text(convert_size(fileSize));

239if( !fileCount ) {

240setState( 'pedding' );

241}

242removeFile( file );

243

244};

245

246//添加“添加文件”的按钮,

247uploader.addButton({

248id: '#btns__add2',

249label: ''

250});

251

252//文件上传过程中创建进度实时显示。

253uploader.onUploadProgress =function( file, percentage ) {

254var$tr = $('#'+file.id),

255$percent = $tr.find('td.col5'),

256$state = $tr.find('td.col4');

257percentage = parseInt(percentage*100);

258if(! (percentage == 0 && percentage == 100)){

259$state.text("正在上传");

260}

261$percent.text( percentage + "%")

262percentages[ file.id ][ 1 ] = percentage;

263};

264

265//上传前,请求服务端 判断文件是否已经上传过

266uploader.on( 'uploadStart',function( file ) {

267vartype = 'POST';

268varurl = '/service/upload/determine_video_exist';

269varrequest_data = {

270'md5': md5Obj[ file.id ],

271'type':1

272};

273varsuccess =function(r) {

274uploader.upload( file );

275console.log(r);

276if(r.code == 1) {

277uploader.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}elseif(r.code <= 0) {

287showError(r.msg);

288}else{

289

290}

291};

292request(type, url, request_data, success);

293});

294

295//当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。

296uploader.on('uploadBeforeSend',function(obj, data, headers) {

297$tr = $("#"+data.id);

298varfile_name = $tr.find(".name").val();

299varclass_id = $tr.find("select.class_id").val();

300varreg = /[1-9][0-9]*/g;

301data.md5 = md5Obj[ obj.file.id ];

302data.file_name = file_name;

303data.class_id = class_id;

304data.guid = data.guid + data.id.replace(/[^0-9]+/g, '');

305});

306

307uploader.on( 'uploadSuccess',function( file ,res) {

308if(res.code == 1){

309$( '#'+file.id ).find('.col4').text('成功上传');

310console.log(res);

311$('#'+file.id).find('.view').find('a').attr('href',playmain +'/?video_id='+ res.data.video_id);

312}elseif(res.code == 2) {

313$( '#'+file.id ).find('.col4').text('视频已存在');

314console.log(res);

315$('#'+file.id).find('.view').find('a').attr('href',playmain +'/?video_id='+ res.data.video_id);

316}else{

317showError(res.msg);

318}

319});

320

321uploader.on( 'uploadError',function( file,reason ) {

322$( '#'+file.id ).find('.col4').text('上传失败');

323console.log(reason);

324});

325

326uploader.on( 'uploadComplete',function( file ) {

327$( '#'+file.id ).find('.progress').fadeOut();

328});

329

330uploader.on( 'all',function( type ) {

331if( type === 'startUpload' ) {

332state = 'uploading';

333}elseif( type === 'stopUpload' ) {

334state = 'paused';

335}elseif( type === 'uploadFinished' ) {

336state = 'done';

337}

338

339if( state === 'uploading' ) {

340window.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}elseif(state === 'done') {

350window.is_confirm =false;

351console.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*/

359uploader.on("error",function(type){

360varmsg = ''

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 != ''){

368showError(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');

376varid = $tr.attr('id');

377uploader.removeFile( id );

378}

379});

380});

381$upload.on('click',function() {

382varisbreak =false;

383$(".name").each(function(){

384if(!$(this).val()|| $(this).val() == ''){

385isbreak =true;

386}

387})

388if(isbreak){

389showError("文件名不能存在为空");

390return;

391}

392$("select.class_id").each(function(){

393if(!$(this).val()|| $(this).val() == ''){

394isbreak =true;

395}

396})

397if(isbreak){

398showError("分类不能为空,请先添加分类");

399return;

400}

401if( $(this).hasClass( 'btns__upload-refresh' ) ) {

402location.reload();

403}

404if( $(this).hasClass( 'btns__upload-ing' ) ) {

405returnfalse;

406}

407varmd5Ready =true;

408$.each(md5Obj,function(index,item){

409if(!item || item==''){

410md5Ready =false;

411}

412});

413if(!md5Ready){

414showError('文件尚未读取完成,请耐心等待');

415returnfalse;

416}

417if( state === 'ready' && md5Ready ) {

418uploader.upload();

419}elseif( state === 'paused' ) {

420uploader.upload();

421}elseif( state === 'uploading' ) {

422uploader.stop();

423}

424});

425$upload.addClass( 'state-' + state );

426

427});

后台(PHP)【仅分片上传相关代码】

1publicfunctionaction_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 =newCommon_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}elseif($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=newModel_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=newModel_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*/

10publicfunctionsaveFile_chunks($file,$chunks,$chunk,$guid)

11{

12if(empty($guid) ||empty($file) ){

13returnfalse;

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=newModel_Base();

75$model_mod->disconnect();

76$pid= pcntl_fork();

77//父进程和子进程都会执行下面代码

78if($pid== -1) {

79//错误处理:创建子进程失败时返回-1.

80die('could not fork');

81}elseif($pid) {

82$model_mod->connect();

83//对上传完成的视频进行排队转码

84$this->thread($join_file_name,$file_info['type'],$guid);

85//父进程会得到子进程号,所以这里是父进程执行的逻辑

86pcntl_wait($status);//等待子进程中断,防止子进程成为僵尸进程。

87}else{

88return$file_info;

89//子进程得到的$pid为0, 所以这里是子进程执行的逻辑。

90}

91

92}

93}

94

95}

96return$file_info;

97}else{

98$this->error[] = '分片上传失败';

99returnfalse;

100}

101/*}}}*/

102}

1,实现了分片上传;

2,同时在上传前检查视频md5 是否在库,如已存在可实现“秒传” 功能,即直接复制数据信息,指向同一个文件,不必再上传;

3,可实现断点续传,上传过程中中断;之前上传的分片已保留在服务器,只需重新上传尚未上传的分片即可;

参考文章:/wordpress//08/12/java-http%E5%A4%A7%E6%96%87%E4%BB%B6%E6%96%AD%E7%82%B9%E7%BB%AD%E4%BC%A0%E4%B8%8A%E4%BC%A0/

欢迎入群一起讨论:374992201

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。