基于php+webuploader的大文件分片上传,带进度条,支持断点续传(刷新、关闭页面、重新上传、网络中断等情况)。文件上传前先检测该文件是否已上传,如果已上传提示“文件已存在”,如果未上传则直接上传。视频上传时会根据设定的参数(分片大小、分片数量)进行上传,上传过程中会在目标文件夹中生成一个临时文件夹,用于存储临时分片,等所有分片上传完毕后,会根据序号重新组合成一个完整的视频,临时文件被删除。
如果文件上传至七牛云,可参看基于php大文件分片上传至七牛云,带进度条
首先下载webuploader
效果图:
临时文件,用于存储分片
html代码
<title>webuploader分片上传</title> <meta charset="utf8"> <!--引入CSS--> <link rel="stylesheet" type="text/css" href="/static/webupload/webuploader.css"> <script type="text/javascript" src="/static/index/js/jquery.js"></script> <script type="text/javascript" src="/static/index/js/jquery.md5.js"></script> <!--引入JS--> <script type="text/javascript" src="/static/webupload/webuploader.js"></script> <div id="uploader" class="wu-example"> <!--用来存放文件信息--> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="picker">选择文件</div> <button id="ctlBtn" class="btn btn-default">开始上传</button> </div> </div> <style> .progress{ height: 20px; width: 300px; background: #ccc; } .progress-bar{ height: 20px; background: #0a3536;} </style> <script> var uploadswf = '/static/webupload/Uploader.swf'; var chunkSize = 2*1024*1024; var server_url='uploadVedio'; var GUID = WebUploader.Base.guid();//一个GUID var chunkObj = {}; //用来记录文件的状态、上传中断的位置 var seq=1; var msg=''; $(function () { var $ = jQuery; var $list = $('#thelist'); WebUploader.Uploader.register({ "before-send-file":"beforeSendFile", "before-send": "beforeSend" }, { "beforeSendFile": function (file) { //上传前校验文件是否已经上传过 var deferred = WebUploader.Deferred(); $.ajax({ type:"POST", //上传前校验文件上传到第几片 url: "checkFile", data: { seq: seq, fileMd5: $.md5(file.name + file.size + file.ext), fileName:file.name }, dataType: "json", success: function (data) { console.log(data); chunkObj = data; chunkObj.type = data.type; chunkObj.chunk == data.chunk; msg = data.msg; if(data.type==404){ deferred.reject(); $("#" + file.id).find(".state").text(data.msg); }else if (data.type == 0) { deferred.reject(); $("#" + file.id).find(".state").text("文件已上传"); } else if (data.type == 1) { if (data.chunk) { deferred.resolve(); } } else { deferred.resolve(); } }, error: function () { deferred.resolve(); } }) //deferred.resolve(); return deferred.promise(); }, "beforeSend": function (block) { var deferred = WebUploader.Deferred(); var curChunk = block.chunk; var totalChunk = block.chunks; console.log(chunkObj) if (chunkObj.type == "1") { if (curChunk < chunkObj.chunk) { deferred.reject(); } else { deferred.resolve(); } } else { deferred.resolve(); } return deferred.promise(); } }); var uploader = WebUploader.create({ // 选完文件后,是否自动上传。 auto: false, // swf文件路径 swf: uploadswf, // 文件接收服务端。 server: server_url, // 内部根据当前运行是创建,可能是input元素,也可能是flash. pick: '#picker', chunked: true,//开始分片上传 chunkSize:1 * 1024 * 1024,//每一片的大小 threads: 1, formData: { }, // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传! resize: false }); // 当有文件被添加进队列的时候 uploader.on('fileQueued', function (file) { $list.append('<div id="' + file.id + '" class="item">' + '<div class="item-file"><div class="fileType-logo"></div>' + '<div class="fileMes"><h4 class="info">' + file.name + '</h4>' + '<p class="state">等待上传...</p>' + '</div></div></div>'); }); // 文件上传过程中创建进度条实时显示。 uploader.on('uploadProgress', function (file, percentage) { var $li = $('#' + file.id), $percent = $li.find('.progress .progress-bar'); // 避免重复创建 if (!$percent.length) { $percent = $('<div class="progress progress-striped active">' + '<div class="progress-bar" role="progressbar">' + '</div>' + '</div>').appendTo($li).find('.progress-bar'); } $li.find('p.state').text('上传中'); $percent.css('width', percentage * 100 + '%'); }); uploader.on("uploadBeforeSend", function (obj, data, headers) { var file = obj.cuted.file; data.test = 1; data.fileMd5 = $.md5(file.name + file.size + file.ext); }) // 文件上传成功,给item添加成功class, 用样式标记上传成功。 uploader.on('uploadSuccess', function (file, response) { if(response.status==299){ $('#' + file.id).find('p.state').text('文件已存在'); }else{ $('#' + file.id).find('p.state').text('已上传'); } }); // 文件上传失败,显示上传出错。 uploader.on('uploadError', function (file) { $('#' + file.id).find('p.state').text(msg); }); // 完成上传完了,成功或者失败,先删除进度条。 uploader.on('uploadComplete', function (file) { $('#' + file.id).find('.progress').fadeOut(); }); //所有文件上传完毕 uploader.on("uploadFinished", function () { //提交表单 }); //开始上传 $("#ctlBtn").click(function () { uploader.upload(); }); }); </script>
php请求后端
use app\index\controller\Upload;
public function uploadVedio() { $model =new Upload(); $res = $model->doUpload(); $model->ajaxReturn($res); }
封装上传类
<?php namespace app\index\controller; use think\Controller; /** * 大文件分片上传 */ class Upload extends Controller { private $filepath = 'uploads/'; //上传目录 private $blobNum; //第几个文件块 private $totalBlobNum; //文件块总数 private $fileName; //文件名 #允许上传的文件 private $allowExtension = ['mp4','avi','wmv']; #文件后缀 private $fileExtension =''; #当前块内容 private $nowFile = ''; #文件大小 private $totalSize = 0; #文件总大小只允许1G private $allowFileSize = 0; #文件md5 前端传过来的 用于创建临时文件夹 上传完后删除 private $fileMd5=''; public function __construct($savePath =''){ $postData = $_POST; #测试断点上传 if(isset($postData['test'])){ sleep(1); } if($savePath){ $this->filepath = $this->filepath.$savePath; } # #文件名称 # var_dump($postData); $postData['name'] =isset($postData['name'])?$postData['name']:''; $this->fileName =$postData['name']; if($this->isHaveFile()){ $this->ajaxReturn(['status'=>299,'msg'=>'文件已存在!']); } $this->fileMd5 =$postData['fileMd5']; #允许文件的大小 1G $this->allowFileSize =(1*1024*1024*1024); if((int)$postData['size']>$this->allowFileSize){ $this->ajaxReturn(['status'=>204,'msg'=>"文件大小超1G限制!"]); } #文件大小 $this->totalSize=$postData['size']; $postData['chunks']=isset($postData['chunks'])?(int)$postData['chunks']:1; $postData['chunk']=isset($postData['chunk'])?(int)$postData['chunk']:0; if(!(int)$postData['chunks']){ $this->ajaxReturn(['status'=>208,'msg'=>'chunks参数错误']); } #当前块 $this->blobNum =$postData['chunk']+1; #总共块 $this->totalBlobNum =$postData['chunks']; #获取后缀 $fileExtension =explode(".",basename( $this->fileName)); $this->fileExtension=array_pop($fileExtension); #检测后缀是否在允许范围 $this->checkFileExtension(); $this->nowFile = $_FILES['file']; if( $this->nowFile['error'] > 0) { $msg['status'] = 502; $msg['msg'] = "文件错误!"; $this->ajaxReturn($msg); } } public function doUpload(){ #临时文件移动到指定目录下 $res = $this->moveFile(); if($res['status']==999){ return $this->fileMerge(); }else{ return $res; } } #创建md5 文件名 public function createFileName(){ return $this->filepath.$this->fileName; } #检测文件是否重复 public function isHaveFile(){ if(file_exists($this->filepath.$this->fileName)){ return true; } return false; } #文件合并 public function fileMerge(){ if ($this->blobNum == $this->totalBlobNum) { $fileName = $this->createFileName(); @unlink($fileName); #删除旧文件 #文件合并 文件名以 $handle=fopen($fileName,"a+"); for($i=1; $i<= $this->totalBlobNum; $i++){ #当前分片数 $this->blobNum = $i; #吧每个块的文件追加到 上传的文件中 fwrite($handle,file_get_contents($this->createBlockFileName())); } fclose($handle); #删除分片 for($i=1; $i<= $this->totalBlobNum; $i++){ $this->blobNum = $i; @unlink($this->createBlockFileName()); } #删除临时目录 @rmdir($this->filepath.$this->fileMd5); if(filesize($fileName) == $this->totalSize){ $msg['status'] = 200; $msg['msg'] = '上传成功!'; $msg['size'] = $this->totalSize; $msg['filename'] = "http://".$_SERVER['HTTP_HOST']."/".$this->createFileName(); $msg['name'] = $this->fileName; }else{ $msg['status'] = 501; $msg['msg'] = '上传文件大小和总大小有误!'; @unlink($this->createFileName()); } return $msg; # $this->ajaxReturn($msg); } } #检测上传类型 public function checkFileExtension(){ if(!in_array(strtolower($this->fileExtension),$this->allowExtension)){ $this->ajaxReturn(['status'=>203,'msg'=>"文件类型不允许"]); } } #将临时文件移动到指定目录 public function moveFile(){ try{ #每个块的文件名 以文件名的MD5作为命名 $filename=$this->createBlockFileName(); #分片文件写入 $handle=fopen($filename,"w+"); fwrite($handle,file_get_contents($this->nowFile ['tmp_name'])); fclose($handle); #不是最后一块就返回当前信息 是最后一块往下执行合并操作 if($this->blobNum != $this->totalBlobNum) { $msg['status'] = 201; $msg['msg'] = "上传成功!"; $msg['blobNum'] = $this->blobNum; return $msg; #$this->ajaxReturn($msg); }else{ $msg['status'] = 999; $msg['msg'] = "上传成功!"; $msg['blobNum'] = $this->blobNum; return $msg; } }catch (Exception $e){ $msg['status'] = 501; $msg['error'] = $e->getMessage(); return $msg; #$this->ajaxReturn($msg); } } #创建分片文件名 public function createBlockFileName(){ $dirName = $this->filepath.$this->fileMd5."/"; if (!is_dir($dirName) ) { @mkdir($dirName, 0700); }; return $dirName.$this->blobNum.".part"; } #json格式放回处理 public function ajaxReturn($msg){ exit(json_encode($msg)); } }