大文件上传, 在web应该用中是一个常规应用, 然因为服务器配置设置最大上传大小, 或者上传时间太久没有进度条等等, 都会给我们的使用带来较大的困扰, 这里基于layui进度条+thinkphp6.0+webuploader的实现, 分享给大家一个完整的案例
1, html代码:
<script type="text/javascript" src="__STATIC__/js/jquery.js"></script> <!--引入 上传大文件 用的插件 webuploade 的相关文件--> <link rel="stylesheet" type="text/css" href="__STATIC__/webuploader/webuploader.css"> <!--引入JS--> <script type="text/javascript" src="__STATIC__/webuploader/webuploader.js"></script> <div class="layui-form-item"> <label class="layui-form-label">上传大文件</label> <div class="layui-input-block"> <div id="uploader" class="wu-example"> <!--用来存放文件信息--> <div id="thelist" class="uploader-list"></div> <div class="btns"> <div id="pickerfile" style="float:left;">选择文件</div> <button id="startup" type="button" class="btn-default layui-btn" style="height: 44px; margin-left:10px; display: none;">开始上传</button> </div> <table class="layui-table"> <thead> <tr> <th>文件名</th> <th>文件大小</th> <th>文件验证</th> <th style="width: 300px;">进度</th> <th>操作</th> </tr> </thead> <tbody id="fileinfo"> </tbody> </table> <div id="infos" style="display:none;"> </div> </div> </div> </div>
2, JS代码
引入layui
<script src="__LAYUIADMIN__/layui/layui.js"></script>
function formatFileSize(size){ var fileSize =0; if(size/1024>1024){ var len = size/1024/1024; fileSize = len.toFixed(2) +"MB"; }else if(size/1024/1024>1024){ var len = size/1024/1024; fileSize = len.toFixeds(2)+"GB"; }else{ var len = size/1024; fileSize = len.toFixed(2)+"KB"; } return fileSize; } layui.config({ base: '__LAYUIADMIN__/' //静态资源所在路径 }).extend({ index: 'lib/index' //主入口模块 }).use(['index', 'form', 'laydate','upload','element'], function(){ var $ = layui.$ ,admin = layui.admin ,element = layui.element ,layer = layui.layer ,laydate = layui.laydate ,form = layui.form ,upload = layui.upload; var uploader = WebUploader.create({ // swf文件路径 swf: '__STATIC__/webuploader/Uploader.swf', // 文件接收服务端。 server: '{:url("@qile/upload/getBlockFile")}', // 选择文件的按钮。可选。 // 内部根据当前运行是创建,可能是input元素,也可能是flash. pick: { "id":'#pickerfile', "multiple":true //禁止多选。 }, chunked: true, //开启分片上传 chunkSize: 1 * 1024 * 1024, //每一片的大小 chunkRetry: 5, // 如果遇到网络错误,重新上传次数 threads: 3, //上传并发数。允许同时最大上传进程数。 // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传! resize: false, // 选完文件后,是否自动上传。 auto: false, // 只允许选择图片文件。 // accept: { // title: 'Images', // extensions: 'gif,jpg,jpeg,bmp,png', // mimeTypes: 'image/*' // } }); // 当有文件被添加进队列的时候 uploader.on('fileQueued', function( file ) { $("#startup").hide(); //隐藏开始上传按钮 , 待全部检验完再显示 let size = formatFileSize(file.size); //根据文件大小定义id名字 let progress = "<div class='layui-progress' lay-showPercent=\"yes\">" + "<div class='layui-progress-bar' lay-percent=''></div>" + "</div>"; let html = "<tr id='"+ file.id +"'>" + "<td>"+file.name+"</td>" + "<td>"+size+"</td>" + "<td class='md5'></td>" + "<td class='state'>" + progress+ "</td>" + "<td class='do'></td>" + "</tr>"; $("#fileinfo").append(html); element.render('progress'); uploader.md5File(file).progress(function (percentage) { // 及时显示进度 //console.log("测试进度"); let v = parseInt(percentage * 100); $("#"+file.id).find(".md5").text(v+"%"); }).then(function (fileMd5) { $("#"+file.id).find(".do").text('等待上传...'); file.md5 = fileMd5; var all_f = uploader.getFiles(); //队列中的所有的文件 var nums = uploader.getFiles().length; var count = 0; for(var i = 0; i < nums; i++){ if(all_f[i].hasOwnProperty('md5')) count++; } if(nums == count){ //当队列中的每一个文件都有了 md5 属性时, 开启文件上传按钮 $("#startup").show(); } //计算大的文件是需要一段时间的 }); //file : 代表队列中的各个文件 }); // 每个分块发送前检查,并附加MD5数据 uploader.on('uploadBeforeSend', function(block, data ) { data.md5 = block.file.md5; //data.status = block.file.status; }); // 上传提交 $("#startup").on('click', function() { uploader.upload(); }); // 文件上传过程中创建进度条实时显示。 uploader.on( 'uploadProgress', function( file, percentage ) { var id = "#" + file.id; var value = parseInt(percentage * 100)+"%"; $(id).find(".layui-progress-bar").attr({"lay-percent":value}); $(id).find('.do').text('上传中'); element.render('progress'); }); /*uploader.on( 'uploadSuccess', function( file ) { $( '#'+file.id ).find('.do').text('已上传'); }); uploader.on( 'uploadError', function( file ) { $( '#'+file.id ).find('.do').text('上传出错'); });*/ // 上传完成后触发 uploader.on('uploadSuccess', function (file,response) { $.post('{:url("@qile/upload/merge")}', { md5: file.md5, fileName: file.name }, function (obj) { if (obj.status) { //将上传的文件用 input做记录, 方便提交到数据库中 var data = {md5: file.md5, source_name: file.name, size:file.size,save_name:obj.save_name}; var str_data = JSON.stringify(data); var html = "<input type='text' class='info' name=\"info[]\" _id='"+file.id+"' value='"+str_data+"'>"; $("#infos").append(html); //更新设置 状态 var btn = '<button type="button" class="layui-btn layui-btn-sm layui-btn-danger big_del" _id="'+file.id+'" _save_name="'+obj.save_name+'">删除</button>'; $("#"+file.id).find(".do").html(btn); } }); }); //上传完成 //大文件上传结束 //删除 $(document).on("click",".big_del",function(){ let _this = $(this); layer.confirm('确定要删除吗?', {icon: 3, title:'提示', title:""}, function(index_alert){ var _id = _this.attr("_id"); var data = {files:_this.attr("_save_name")} $.post('{:url("@qile/upload/del")}',data); _this.parent().parent("tr").remove(); //删除本行 $("input[_id='"+_id+"']").remove(); //删除 layer.close(index_alert); }) }) //删除结束 });
3, 控制器接收文件
class UploadController { //接收大文件分开块状文件 public function getBlockFile(){ set_time_limit(0); // 建立临时目录存放文件-以MD5为唯一标识 $post = request()->post(); $dir = str_replace("\\","/",public_path()) . "static/upload/" . $post["md5"]; if (!file_exists($dir)) { mkdirs($dir,0777); } // 移动每一块文件到 唯一 标识文件夹下 if($post["size"] >= 1 * 1024 * 1024){ move_uploaded_file($_FILES["file"]["tmp_name"], $dir.'/'.$post["chunk"]); } else{ //文件 小于 设置的 chunk 大小 时,没有chunk $file = $_FILES["file"]; move_uploaded_file($_FILES["file"]["tmp_name"], $dir.'/'.$file["name"]); } } //合成分块上传的文件 public function merge(){ set_time_limit(0); // 接收相关数据 $post = $_POST; $path = str_replace("\\","/",public_path()) . "static/upload/"; // 找出分片文件 $dir = $path . $post["md5"]; // 获取分片文件内容 $block_info = scandir($dir); // 除去无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } // 数组按照正常规则排序 natsort($block_info); // 定义保存文件 $save_path = date("Ymd").'/'; $target_path = $path . $save_path; if(!is_dir($target_path)) mkdirs($target_path); $new_file_name = date('Ymdgis').rand().".".getSuffix($post['fileName']); $save_file = $target_path . $new_file_name; $save_name = $save_path . $new_file_name; // 没有?建立 if (!file_exists($save_file)) fopen($save_file, "w"); // 开始写入 $out = @fopen($save_file, "wb"); // 增加文件锁 if (flock($out, LOCK_EX)) { foreach ($block_info as $b) { // 读取文件 if (!$in = @fopen($dir.'/'.$b, "rb")) { break; } // 写入文件 while ($buff = fread($in, 4096)) { fwrite($out, $buff); } @fclose($in); @unlink($dir.'/'.$b); } flock($out, LOCK_UN); } @fclose($out); @rmdir($dir); return json(["save_name" => $save_name, "status" => 1]); } //删除提交的文件 public function del(){ $files = request()->post("files"); if($files){ $path = str_replace("\\","/",public_path()) . "static/upload/"; if(file_exists($path.$files)){ unlink($path.$files); } } } }
4. 实例效果图