背景:50G大文件的HTTP上传至服务器。
好了,根据这个命题,可以开始研究我们怎么做才能把这么大的文件上传成功。
分片上传是肯定的,断点续传也是要有的,进度可视化那就更好了,基于这些,我选择了Webuploader在前端进行分片上传。
为什么选择它呢,好吧,它简单,易上手,好排错,文档多......
实际是我懒......
网上的教程大部分是复制粘贴,借鉴起来也很无奈,推荐一个我觉得比较实在的
https://www.cnblogs.com/baiyunchen/p/5383507.html
本篇Demo地址,欢迎各位大佬指点
https://github.com/papapalh/Big_File
一:开始
新建立项目,这里用了php7.0版本后台处理。
没啥说的,下载WebUploader的包http://fex.baidu.com/webuploader/download.html
jQuery也是必须的,因为就是依赖jQ的。
好了,可以初始化我们的上传组件了,介绍一下这里Demo的配置
// 创建上传 var uploader = WebUploader.create({ swf: '/webuploader-0.1.5/Uploader.swf', server: 'index.php', // 服务端地址 pick: '#picker', // 指定选择文件的按钮容器 resize: false, chunked: true, //开启分片上传 chunkSize: 1024*1024*4, //每一片的大小 chunkRetry: 100, // 如果遇到网络错误,重新上传次数 threads: 3, //上传并发数。允许同时最大上传进程数。 });
// 上传提交 $("#ctlBtn").click(function () { console.log('准备上传...'); uploader.upload(); });
二:上传分片
好了,这样看吧,一个文件会被切分成为若干个小片段发送到服务器中。
但是,我们之后要做的断点续传,如何以唯一的标识来记录这个文件呢。
用MD5吧,简单粗暴,我觉得肯定有更好的办法,但是由于是DEMO,先整体跑下来在说。
下面这段代码做了这些事
- 添加文件进来时计算文件的MD5,用于文件的唯一标识
- 检查之前有没有上传一半出问题的,如果出问题了,那么,以MD5命名的文件夹肯定会有,那么我们传之后的就好了。
- 我给这个状态绑定了一个参数Status
// 当有文件被添加进队列的时候-md5序列化
uploader.on('fileQueued', function (file) {
console.log("正在计算MD5值..."); uploader.md5File(file) .then(function (fileMd5) { file.wholeMd5 = fileMd5; file_md5 = fileMd5; console.log("MD5计算完成。"); console.log("正在查找有无断点..."); $.post('check.php', {md5: file_md5}, function (data) { data = JSON.parse(data); switch (data.code) { // 断点 case '0': console.log('有断点.正在准备从断点处上传文件。'); for (var i in data.block_info) { block_info.push(data.block_info[i]); } file.status = 0; break; // 无断点 case '1': console.log('无断点.上传新文件。'); file.status = 1; break; } }) }); });
check.php
检查有没有遗留的文件夹,有的话说明你之前上传过,这些我就不要了,并返回上传成功的分片 JSON
<?php // 接收相关数据 $post = $_POST; // 找出分片文件 $dir = '/var/www/'.$post['md5']; // 有断点 if (file_exists($dir)) { // 找出上传成功的所有文件 $block_info=scandir($dir); // 除去无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } echo json_encode(["code"=>"0" , 'block_info' => $block_info]); } // 无断点 else { echo json_encode(["code"=>"1"]); }
index.php
接受传入文件,写入临时文件,这里其实也应该用个MD5来检查分片
<?php // 接收相关数据 $post = $_POST; $file = $_FILES; $status = $post['status']; // 建立临时目录存放文件-以MD5为唯一标识 $dir = "/var/www/" . $post['md5value']; // 断点上传 if ($status == '0') { // 获取分片文件内容 $block_info=scandir($dir); // 除去无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } } // 直接上传 elseif($status == '1') { if (!file_exists($dir)) { mkdir ($dir,0777,true); } // 移入缓存文件保存 move_uploaded_file($file["file"]["tmp_name"], $dir.'/'.$post["chunk"]); }
三:断点.跳过已有分片
这个地方是困扰了我很长时间的地方
官方API对于跳过分片的内容也找不到,所以单独把他拿出来,日后也方便查看
刚刚我们把如果有断点的,我们把上传成功的分片数组拿出来,比对一下,如果有,就不上传了
// 发送前检查分块,并附加MD5数据 uploader.on('uploadBeforeSend', function( block, data ) { var file = block.file; var deferred = WebUploader.Deferred(); data.md5value = file.wholeMd5; data.status = file.status; if ($.inArray(block.chunk.toString(), block_info) >= 0) { console.log("已有分片.正在跳过分片"+block.chunk.toString()); deferred.reject(); deferred.resolve(); return deferred.promise(); } });
这样就完成了我们对于断点和分片的处理
四:合并
首先你得告诉我,你上传完了,该合并了
// 上传完成后触发 uploader.on('uploadSuccess', function (file,response) { console.log("上传分片完成。"); console.log("正在整理分片..."); $.post('merge.php', { md5: file.wholeMd5, fileName: file.name }, function (data) { var object = JSON.parse(data); if (object.code) { console.log("上传成功"); } }); });
这是Webuploader它上传成功的一个回调
告诉了merge.php
让开吧,我要合并了,就这个意思吧
<?php // 接收相关数据 $post = $_POST; // 找出分片文件 $dir = '/var/www/'.$post['md5']; // 获取分片文件内容 $block_info = scandir($dir); // 除去无用文件 foreach ($block_info as $key => $block) { if ($block == '.' || $block == '..') unset($block_info[$key]); } // 数组按照正常规则排序 natsort($block_info); // 定义保存文件 $save_file = "/var/www/".$post['fileName']; // 没有?建立 if (!file_exists($save_file)) fopen($post['fileName'], "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); echo json_encode(["code"=>"0"]);//随便返回个值,实际中根据需要返回
看着挺长,实际就一个意思,按顺序写入。
五:其他
特殊效果也加了一点,可以试试
// 文件上传过程中创建进度条实时显示。 uploader.on('uploadProgress', function (file, percentage) { $("#percentage_a").css("width",parseInt(percentage * 100)+"%"); $("#percentage").html(parseInt(percentage * 100) +"%"); }); // 上传出错处理 uploader.on('uploadError', function (file) { uploader.retry(); }); // 暂停处理 $("#stop").click(function(e){ log("暂停上传..."); uploader.stop(true); }) // 从暂停文件继续 $("#start").click(function(e){ log("恢复上传..."); uploader.upload(); })
五:PS
- 其实需要做的还有很多,各种验证,一定要保证分片的正确上传和写入。
- 还有各种的错误处理,之后如果运用到项目中的话,也一定会回来补充需要注意的地方
- 作为这个知识领域的小白,感觉很奇妙,也很有意思。
六:展示效果
可以对页面进行下改动,也挺漂亮了,感谢。