php七牛分片上传_plupload 大文件分片上传与PHP分片合并探索

最近老大分给我了做一个电影cms系统,其中涉及到一个功能,使用七牛云的文件上传功能。七牛javascript skd,使用起来很方便,屏蔽了许多的技术细节。如果只满足与调用sdk,那么可能工作中也就没有什么收获了。其中对七牛云的服务很佩服的一点是,无论我上传多大的文件,当我文件最后一片上传完成的时候,就立刻返回到文件链接,这个问题我想了好久,也不知道七牛是如何做到的。

七牛云的sdk分为 JavaScript 与 PHP端。 JavaScript端的作用是提供文件上传功能,客户端(浏览器)无需关注当前的环境,七牛云的sdk会自动检测浏览器的版本,并提供统一的接口调用。PHP端主要作用是提供鉴权的,每次七牛云上传图片到七牛云的空间,需要到自己的应用服务器,拿到一个授权的key,你可以理解为密钥。

关于七牛云 JavaScript 与 PHP端的 SDK 使用,本篇就不赘言了,官网的文档描述的很清楚,可以参考 七牛云javascript sdk 文档

七牛云的JavaScript SDK 继承了 plupload 的所有方法。本篇的主要目的是展示plupload的使用场景,以及文件分片上传后,后端服务器如何处理,提供一个思路。

如果你有以下的业务场景,可以尝试使用plupload 插件

1、用户上传图片,需要实时预览,并且兼容主流浏览器

2、上传的图片需要在本地进行质量压缩,或者文件类型校验

3、上传图片的时候,需要提示上传的百分比

4、客户端可能需要上传较大的文件,但是服务端的配置并不允许开启大文件上传

5、需要断点续传功能,即文件上传了一半,下次上传时可以接着上传。

前端的代码和后端PHP的代码放在文后,代码并不重要,思路最重要。

前端将文件分成片后,浏览器开启多线程上传服务。例如将一个文件分成100片,由于异步的原因,可能第1片 和 第10片 先到,因而分片的上传你可以理解为乱序的。另外php上传文件是上传到临时文件夹,当脚本执行结束后,就会自动删除文件。所以如果不对分片上传的数据进行保存,那么就会竹篮打水一场空,等所有分片都上传完了,结果却无法合并。因此我们后端需要解决几个问题:

1、将分片保存起来

2、自己维护分片的顺序

3、需要将分片合并到最终的目标文件中

4、当分片合并后,需要删除无用的分片

维护分片的这部分解决,我使用了Reids 的Zset 数据结构,该结构能帮我解决分片的顺序。

分片上传的时候,会标注共有多少分片, 这是第几个分片.

后端PHP代码:

/**

* 这是个上传测试文件,作为研究分片上传原理使用

*

*/

if (!is_array($_FILES) || empty($_FILES)) {

die(); //输出报错的话术

}

$destication = "/usr/local/var/www/uploads/"; //上传文件的最终文件夹

$destication_frag_path = "/usr/local/var/www/uploads_tmp/"; //分片上传的临时文件夹

if (!is_dir($destication) || !is_dir($destication_frag_path)) {

@mkdir($destication, 0755);

@mkdir($destication_frag_path, 0755);

}

if ($_REQUEST['chunks'] == 1) {

//文件很小,无需分片上传。

$tmp_file_path = $_FILES['file']['tmp_name']; //上传的临时文件

$save_file_name = $destication.$_REQUEST['name'];

move_uploaded_file($tmp_file_path, $save_file_name);

} else {

$redis = new Redis();

$redis->connect('127.0.0.1', 6379);

$redis_key = $_REQUEST['name'];

$file_name = explode('.', $_REQUEST['name']);

$save_tmp_name = $destication_frag_path.$file_name[0]."_".$_REQUEST['chunk']; //文件名拼接成第几块

$tmp_file_path = $_FILES['file']['tmp_name']; //上传的临时文件

move_uploaded_file($tmp_file_path, $save_tmp_name);

$redis->setTimeout($redis_key, 3600); //一个小时后过期

$redis->zAdd($redis_key, $_REQUEST['chunk'], $save_tmp_name);

$uploaded_count = $redis->zCard($redis_key);

//分片资源上传完毕后,开始分片合并工作

if ($uploaded_count == $_REQUEST['chunks']) {

//获取经过排序后的分片资源

$all_files_fen_pian = $redis->zRange($redis_key, 0, -1);

if ($all_files_fen_pian && is_array($all_files_fen_pian)) {

//创建要合并的最终文件资源

$final_file = $destication.$_REQUEST['name'];

$final_file_handler = fopen($final_file, 'wb');

//开始合并文件分片

foreach ($all_files_fen_pian as $fragmentation_file) {

$frag_file_handler = fopen($fragmentation_file, 'rb');

$frag_file_content = fread($frag_file_handler, filesize($fragmentation_file));

fwrite($final_file_handler, $frag_file_content);

unset($frag_file_content);

fclose($frag_file_handler); //销毁分片文件资源

unlink($fragmentation_file); //删除已经合并的分片文件

usleep(10000);

}

}

}

}

前端Javascript 代码:

var uploader = new plupload.Uploader({

browse_button: 'browse', //触发文件选择对话框的按钮,为那个元素id

url: 'upload.php', //服务器端的上传页面地址

flash_swf_url: './public/js/plupload/js//Moxie.swf', //flash文件地址

max_file_size: '1000mb',//限制为2MB

chunk_size:'1mb',

unique_names:true, //为每个文件生成一个临时名称

max_retries:3,

multipart_params:{

},//扩展参数

//filters: [{title: "image ",extensions: "jpg,gif,png"}], //图片限制

//filters: [{title: "movie ",extensions: "mp4"}], //电影限制

silverlight_xap_url: 'js/Moxie.xap' //silverlight文件,当需要使用silverlight方式进行上传时需要配置该参数

});

uploader.init(); //初始化uploader

uploader.start(); //开始上传

uploader.stop(); //暂停上传

$("#start_upload").click(function(){

uploader.start();

});

//文件添加的时候

uploader.bind('FilesAdded', function (uploader, files) {

/**

* 因此在这一步可以判断文件的上传个数,和文件的格式,以及上传文件的大小,以及进行图片预览的相关东东

*/

console.log('-----当文件添加的时候打印 开始----');

for (i=0; i

var tmp_file_size = files[i]['size'] / 1024;

var tmp_msg = '添加文件的索引:'+ files[i]['id']+ "\t\t原始文件名:"+files[i]['name'] + "\t文件后缀:"+files[i]['type']+"\t文件的大小:"+tmp_file_size+"kb";

console.log(tmp_msg);

}

// 如果是图片的时候可以启用这部分

// mOxie.each(files, function(files) {

// var image = new mOxie.Image();

// image.onload = function() {

// var dataUrl = image.getAsDataURL();

// $("#preview_img").attr('src', dataUrl);

// };

//

// image.load(files.getSource());

// });

console.log('-----当文件添加的时候打印 结束----');

});

//当上传队列中某一个文件开始上传后触发。

uploader.bind('BeforeUpload', function(uploader, file){

console.log('文件开始上传了');

//console.dir(file);

});

//当使用文件小片上传功能时,每一个小片上传完成后触发

uploader.bind('ChunkUploaded', function(uploader,file,responseObject){

console.log('-----当文件上传分片的时候打印 开始----');

//console.dir(JSON.parse(responseObject['response']));

console.log('-----当文件上传分片的时候打印 结束---');

});

//会在文件上传过程中不断触发,可以用此事件来显示上传进度

uploader.bind('UploadProgress', function(uploader,file) {

//console.log(uploader);

//console.log(file);

});

//当队列中的某一个文件上传完成后触发

uploader.bind('FileUploaded',function(uploader,files,data){

console.log('-----当文件上传完成的时候打印 开始----');

//console.dir(files);

console.dir(data);

console.log('-----当文件上传完成的时候打印 结束----');

});

//当上传队列中所有文件都上传完成后触发

uploader.bind('UploadComplete', function(uploader,files) {

console.log('所有的文件都已经上传完毕了');

});

//当上传发声错误时触发

uploader.bind('Error', function(uploader,errObj) {

console.log('-----当文件上传错误的时候打印 开始----');

console.dir(errObj);

console.log('-----当文件上传错误的时候打印 结束----');

});

注意事项: 本代码仅作为研究分片上传的原理使用,未应用于生产环境

plupload 学习的相关地址:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值