前言
最近在开发一个视频播放网站,用的是PHP技术。视频播放网站用户上传的都是视频资料,小的有几十M,大的都超过G了,对于这种超大文件的上传,我们不能用常规的上传方式,必须对大文件进行分块上传,否则一方面会造成服务器内存爆满,另一方如果上传的文件太大也很容造成上传超时的情况发生。
PHP上传超大文件代码
index.html
<div class="form-group"> <label class="col-sm-2 control-label no-padding-right" for="form-field-1"> 上传视频 </label> <div class="col-sm-6"> <div id="uploader" class="wu-example"> <!--堆代码 duidaima.com --> <!--用来存放文件信息--> <div class="filename"></div> <div class="state"></div> <div class="progress"> <div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 0%"> <span class="sr-only">40% Complete (success)</span> </div> </div> <input type="hidden" name="video"> <div class="btns"> <div id="picker">选择文件</div> <button id="ctlBtn" class="btn btn-default">开始上传</button> <div id="pause" class="btn btn-danger">暂停上传</div> </div> </div> </div> </div> <link rel="stylesheet" href="__PUBLIC__/webuploader/webuploader.css"> <script type="http://fex.baidu.com/webuploader/js/jquery-1.10.2.min.js"></script> <script src="__PUBLIC__/webuploader/webuploader.min.js"></script> <script> $(function(){ var fileMd5; //监听分块上传过程中的三个时间点 WebUploader.Uploader.register({ "before-send-file":"beforeSendFile", "before-send":"beforeSend", "after-send-file":"afterSendFile", },{ //时间点1:所有分块进行上传之前调用此函数 beforeSendFile:function(file){ var deferred = WebUploader.Deferred(); //1、计算文件的唯一标记,用于断点续传 (new WebUploader.Uploader()).md5File(file,0,10*1024*1024) .progress(function(percentage){ // $('#item1').find("p.state").text("正在读取文件信息..."); }) .then(function(val){ fileMd5=val; // $('#item1').find("p.state").text("成功获取文件信息..."); //获取文件信息后进入下一步 deferred.resolve(); }); return deferred.promise(); }, //时间点2:如果有分块上传,则每个分块上传之前调用此函数 beforeSend:function(block){ var deferred = WebUploader.Deferred(); // 同步校验,防止没校验完就上传了 $.ajaxSetup({async : false}); $.ajax({ type:"POST", url:"{:url('Ajax/check_breakpoint')}", data:{ //文件唯一标记 fileMd5:fileMd5, //当前分块下标 chunk:block.chunk, //当前分块大小 chunkSize:block.end-block.start, chunks:block.chunks }, dataType:"json", success:function(response){ if(response.ifExist==1){ //分块存在,跳过 deferred.reject(); }else{ //分块不存在或不完整,重新发送该分块内容 deferred.resolve(); } } }); $.ajaxSetup({async : true}); this.owner.options.formData.fileMd5 = fileMd5; deferred.resolve(); return deferred.promise(); }, //时间点3:所有分块上传成功后调用此函数 afterSendFile:function(file){ //如果分块上传成功,则通知后台合并分块 $.post("{:url('Ajax/vupload_merge')}", { fileMd5: fileMd5, fileName: file.name }, function (data) { if (data==0) { $("#uploader .state").html("上传完成"); } }); } }); var _extensions = '3gp,mp4,rmvb,mov,avi,m4v'; var _mimeTypes = 'video/*,audio/*,application/*'; var GUID = WebUploader.Base.guid();//一个GUID var uploader = WebUploader.create({ swf: '__PUBLIC/webUploader/Uploader.swf', server: "{:url('Ajax/vupload')}", pick: '#picker', resize: false, chunked: true,//开始分片上传 chunkSize: 5*1024*1024,//每一片的大小 accept: { title: '视频上传', extensions: _extensions, mimeTypes: _mimeTypes }, fileNumLimit: 1,//文件上传数量限制 threads: 1, formData: { guid: GUID //自定义参数,待会儿解释 } }); uploader.on('uploadProgress', function (file, percentage) { $("#uploader .progress-bar").width(percentage * 100 + '%'); $("#uploader .progress-bar").text(parseInt(percentage * 100) +"%"); }); uploader.on('uploadSuccess', function () { $("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('acti').removeClass('progress-bar-info').addClass('progress-bar-success'); $("#uploader .state").html("上传成功..."); }); uploader.on('uploadError', function () { $("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('acti').removeClass('progress-bar-info').addClass('progress-bar-danger'); $("#uploader .state").html("上传失败..."); }); $("#ctlBtn").click(function () { uploader.upload(); $("#ctlBtn").text("上传"); $('#ctlBtn').attr('disabled', 'disabled'); $("#uploader .progress-bar").addClass('progress-bar-striped').addClass('active'); $("#uploader .state").html("上传中..."); }); $('#pause').click(function () { uploader.stop(true); $('#ctlBtn').removeAttr('disabled'); $("#ctlBtn").text("继续上传"); $("#uploader .state").html("暂停中..."); $("#uploader .progress-bar").removeClass('progress-bar-striped').removeClass('acti'); }); }) </script>Ajax.php
<?php // +---------------------------------------------------------------------- // | thinkpphp [ WE CAN DO IT MORE SIMPLE ] // +---------------------------------------------------------------------- // | Copyright (c) 2018 rights reserved. // +---------------------------------------------------------------------- // | Author: luyunoob <595080590@qq.com> // +---------------------------------------------------------------------- namespace app\admin\controller; use think\Db; use think\Cache; use think\Session; class Ajax { //检测是否有断点 public function check_breakpoint(){ $post=request()->post(); // 找出分片目录 $dir=ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.$post['fileMd5']; if (file_exists($dir)) { // 扫描文件 $block_info=scandir($dir); // 去除无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } natsort($block_info); $end=end($block_info); if ($end>$post['chunk'] || $end==$post['chunk']) { echo json_encode(["ifExist"=>"1",'block_info'=>$end]); } }else{ // 无断点 echo json_encode(["ifExist"=>"0"]); } } // 视频上传 public function vupload(){ $post=request()->post(); $dir=ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.$post['fileMd5']; $file = request()->file('file'); if (file_exists($dir)) { $block_info=scandir($dir); natsort($block_info); // 去除无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } $end=end($block_info); if ($post['chunk']>$end) { $info = $file->move($dir.DS.$post['chunk'],''); } if (isset($info)) { die('{"status":1,"msg":"正在上传请稍等"}'); }else{ die('{"status":0,"msg":"跳过 "}'); } }else{ if (empty($post['chunk'])) { $info = $file->move($dir.DS.'0',''); }else{ $info = $file->move($dir.DS.$post['chunk'],''); } if ($info) { die('{"status":1,"msg":"正在上传请稍等"}'); } } } // 合并视频 public function vupload_merge() { $post=request()->post(); $dir=ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.$post['fileMd5']; $block_info = scandir($dir); // 除去无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } // 数组按照正常规则排序 natsort($block_info); // 定义保存文件 $save_file = ROOT_PATH . 'data' . DS . 'upload'.DS.'video'.DS.date('Ymd'); // 没有?建立 if (!file_exists($save_file)) { @mkdir ($save_file,0755,true); }; $count=count($block_info); // 开始写入 // 获取文件后缀 $name=explode('.',$post['fileName']); $ext=end($name); $out = @fopen($save_file.DS.date('Ymdhis').'.'.$ext, "wb"); $url='video'.DS.date('Ymd').DS.date('Ymdhis').'.'.$ext; // 增加文件锁 if (flock($out, LOCK_EX)) { foreach ($block_info as $b) { // 读取文件 if (!$in = @fopen($dir.DS.$b.DS.$post['fileName'], "rb")) { break; } // 写入文件 while ($buff = fread($in, 4096)) { fwrite($out, $buff); } @fclose($in); @unlink($dir.'/'.$b); } flock($out, LOCK_UN); } @fclose($out); delete_dir_file($dir); echo json_encode(["code"=>"0",'url'=>$url]);// } }common.php
<?php /** * 循环删除目录和文件 * @param string $dir_name * @return bool */ function delete_dir_file($dir_name) { $result = false; if (is_dir($dir_name)) { if ($handle = opendir($dir_name)) { while (false !== ($item = readdir($handle))) { if ($item != '.' && $item != '..') { if (is_dir($dir_name . DS . $item)) { delete_dir_file($dir_name . DS . $item); } else { unlink($dir_name . DS . $item); } } } closedir($handle); if (rmdir($dir_name)) { $result = true; } } } return $result; } ?>
总结:
以上就是PHP上传超大文件的实现方式,对于这种超大文件的上传主要思路就是化整为零,把大的文件拆分成小的文件进行上传就可以了,这里面主要还涉及到断点续传等问题,好了,如果你也有遇到上传超大文件超时等问题,可以参考一下这篇文章的实现思路(完)。