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代码:

<?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<files.length; 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('-----当文件上传错误的时候打印 结束----');
    });

</script>

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

本文代码github 地址: roverliang 的github

plupload 学习的相关地址:

  1. pluplod github 地址
  2. pluplod 官网(可能要FQ)
  3. pluplod 中文文档

原文地址:https://www.cnblogs.com/roverliang/p/8419858.html

时间: 2024-11-05 22:38:18

plupload 大文件分片上传与PHP分片合并探索的相关文章

android下大文件分割上传

由于android自身的原因,对大文件(如影视频文件)的操作很容易造成OOM,即:Dalvik堆内存溢出,利用文件分割将大文件分割为小文件可以解决问题. 文件分割后分多次请求服务. 1 //文件分割上传 2 public void cutFileUpload(String fileType,String filePath) 3 { 4 try 5 { 6 FileAccessI fileAccessI = new FileAccessI(filePath, 0); 7 Long nStartPo

PHP实现大文件的上传设置

打开php.ini,首先找到 ;;;;;;;;;;;;;;;; ; File Uploads ; ;;;;;;;;;;;;;;;; 区域,有影响文件上传的以下几个参数: file_uploads = on ;是否允许通过HTTP上传文件的开关.默认为ON即是开 upload_tmp_dir ;文件上传至服务器上存储临时文件的地方,如果没指 打开php.ini,首先找到;;;;;;;;;;;;;;;;; File Uploads ;;;;;;;;;;;;;;;;;区域,有影响文件上传的以下几个参数

Ajax实现大文件切割上传

Ajax大文件切割上传 2015-02-07 前面我们已经实现了Ajax的文件上传,不过会受限于服务器的允许的大小, 如果服务器并非自己的,我们就要使用Ajax大文件切割来实现上传. 首先解决Ajax跨域上传问题: 在HTML5中,ajax的跨域有了新的规则-----能否跨域取决于对应的应答. 对方服务器如果愿意接受远程过来的ajax,或某几个域名过来的ajax请求,可以在头信息header中,加入Access-Control-Allow-Origin * 在PHP中加入此信息,就可以实现跨域请

Ajax大文件切割上传

1问:在不更改php.ini中post_max_size的情况下怎么实现大文件的上? 答把大文件切割成许多小块,分块上传,传完后,重新合并成大文件即可. 2问:怎么切割? 答:用html5中的file API,这个API继承自Blob对象,Blob中有slice方法,可以截取二进制对象的一部分,实现切割大文件的效果. 3问:具体思路呢? 答:截取10M上传,判断是否截取完毕 while 还有数据{ 截取, Ajax上传 } 1 <script type="text/javascript&q

nginx由于权限导致大文件不能上传的问题

1.用了nginx代理后,应用上传文件时,发现小文件可上传,大文件上传不了,查nginx日志,发现有这么一句:client intended to send too large body 在nginx.conf配置文件的server加上 client_max_body_size 1024m; 重启nginx,上传大文件,还是上传不了,但后台没有client intended to send too large body日志了. 此时后台有日志如:2019/08/02 18:37:35 [crit

Android应用开发之使用Socket进行大文件断点上传续传

http://www.linuxidc.com/Linux/2012-03/55567.htm http://blog.csdn.net/shimiso/article/details/8529633/ 在Android中上传文件可以采用HTTP方式,也可以采用Socket方式,但是HTTP方式不能上传大文件,这里介绍一种通过Socket方式来进行断点续传的方式,服务端会记录下文件的上传进度,当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是J2SE里的知识. 这个上传程序的原理

支持IE低版本的上传 大文件切割上传 断点续传 秒传

1. http://files.cnblogs.com/files/blackice/UploadDemo.rar 此demo是使用的 swfupload 2.http://download.csdn.net/detail/rememberme001/9873136 支持大文件传输,先把大文件分割成每个2M的小文件分批上传,再组合成一个大文件. 支持断点续传,MD5校验实现妙传功能,支持IE低版本.

Web大文件(夹)上传(断点续传)控件-Xproer.HttpUploader6

版权所有 2009-2017荆门泽优软件有限公司 保留所有权利 官方网站:http://www.ncmem.com/ 产品首页:http://www.ncmem.com/webapp/up6.2/index.asp 在线演示:http://www.ncmem.com/products/up6.3/index.htm 产品介绍:http://www.cnblogs.com/xproer/archive/2012/10/26/2741264.html 升级日志:http://www.cnblogs.

Android中Socket大文件断点上传

什么是Socket? 所谓Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信连的句柄,应用程序通常通过“套接字”向网络发送请求或者应答网络请求,它就是网络通信过程中端点的抽象表示.它主要包括以下两个协议: TCP (Transmission Control Protocol 传输控制协议):传输控制协议,提供的是面向连接.可靠的字节流服务.当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据.TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功