首页 > 编程语言 >php结合webuploader断点续传的实现

php结合webuploader断点续传的实现

时间:2023-10-27 11:55:23浏览次数:40  
标签:断点续传 name 上传 webuploader file 分片 php data md5

最近公司项目需要用到断点续传,所以记录一下其中的坑
使用到的主要技术

webuploader
thinkphp5
断点续传的思路:

客户端:

     1.获取文件md5(MD5是文件唯一标识,用来判断是否存在此文件,并且用作分片的文件夹名)

     2.将文件分片

     3.验证分片是否上传过,上传过直接跳过当前分片

     3.上传分片到md5的文件夹(保存文件名建议按分片序号来,因为分片的顺序很重要)

     4.最后一个分片上传完成后发送合并分片请求并由服务器返回文件信息

服务端

     1.获取md5文件夹下的文件数量并返回用作分片验证

     2.接收文件分片并保存到文件md5的文件夹,文件名称使用分片序号:如0.mp4,1.mp4

     3.合并分片时将md5文件夹下的所有文件按文件名顺序提取并写入到最终的文件内

     4.写入完成获取最终文件hash并判断是否存在,存在则返回已存在文件,删除当前文件,不存在则写入数据库并返回文件信息

大体思路是这样,实际还要加入许多验证,比客户端获取到md5后马上要验证文件是否存在,存在就不上传,直接使用文件信息,不存在则上传
分片上传前也要验证,不过分片的跳过规则需要注意,服务器只需要返回已有的分片数量,客户端根据已有的分片和当前分片索引即可判断是否应该跳过,因为分片是按顺序上传的,如:

,服务端需要注意合并的时候顺序不能乱,顺序乱了就无法还原源文件,所以建议用分片索引作为文件名,获取的时候直接按0,1,2这样获取就行了.

并且保存时要注意保存在自定义的目录下需要确保该目录存在,不存在的目录需要新建后才可以保存文件

完整代码如下:

客户端:

var uploader;
var fileMd5;
var is_upload;
var totalFiles;
function initWebUploader() {
    /***************************************************** 监听分块上传过程中的三个时间点 start ***********************************************************/
    WebUploader.Uploader.register({
        "before-send":"beforeSend",  //每个分片上传前
    },
    {
        //时间点2:如果有分块上传,则每个分块上传之前调用此函数
        beforeSend:function(block){
            var deferred = WebUploader.Deferred();
            if(is_upload)//跳过到开始上传的哪一个分片时
            {
                deferred.resolve();
            }else if(totalFiles)//已经获取过文件数量则直接判断是否跳过
            {
                //当前分片下标小于等于目录下的文件数量就认为分块存在,因为分块上传下标是由小到大
                if (block.chunk > totalFiles - 1) {
                    is_upload = true;
                    deferred.resolve();
                } else {
                    //分块存在,跳过
                    deferred.reject();
                }
            } else {
                $.post('/api.php/upload/checkUpload', {md5: fileMd5}, function (data) {
                    if (data.code) {
                        totalFiles = data.data;
                        //当前分片下标小于等于目录下的文件数量就认为分块存在,因为分块上传下标是由小到大
                        if (block.chunk > data.data - 1) {
                            is_upload = true;
                            deferred.resolve();
                        } else {
                            //分块存在,跳过
                            deferred.reject();
                        }
                    } else {
                        is_upload = true;
                        deferred.resolve();
                    }
                });
            }
            return deferred.promise();
        }
    });
    /***************************************************** 监听分块上传过程中的三个时间点 end **************************************************************/
    uploader = WebUploader.create({
        // swf文件路径
        swf:  '{$maccms.path}static/webupload/Uploader.swf',
        // 文件接收服务端。
        server: '/api.php/upload/chunkUpload',
        // 选择文件的按钮。可选。
        // 内部根据当前运行是创建,可能是input元素,也可能是flash.
        pick: '#name',
        accept: {
            title: 'Images',
            extensions: 'mp4',
        },
        // 不压缩image, 默认如果是jpeg,文件上传前会压缩一把再上传!
        resize: false,
        prepareNextFile:true,
        chunked : true, // 分片处理
        chunkSize : 5 * 1024 * 1024, // 每片50M,经过测试,发现上传1G左右的视频大概每片50M速度比较快的,太大或者太小都对上传效率有影响
        chunkRetry : false,// 如果失败,则不重试
        threads:1,
        formData:{guid:WebUploader.Base.guid()}
    });
    uploader.on('fileQueued',function (file) {
        uploader.md5File( file,0,10*1024*1024 )
        // 及时显示进度
        .progress(function(percentage) {
            console.log('Percentage:', percentage);
            $(".readFile").text("正在读取视频信息..."+(percentage * 100).toFixed(2) + '%');
        })
        // 完成
        .then(function(val) {
            console.log('md5 result:', val);
            $(".readFile").text('');
            fileMd5 = val;
            var formData = uploader.option('formData');
            formData.md5 = val;
            uploader.option('formData',formData);
            //验证文件是否存在
            $.post('/api.php/upload/md5check',{md5:val},function (data) {
                if(data.code)
                {
                    //选择后直接上传
                    uploader.upload();
                    $("#name .webuploader-pick").text(file.name);
                    $(".up-state .file_name").text(file.name);
                    var size;
                    size = Math.round(file.size/(1024*1024),2);
                    if(size > 1024)
                    {
                        size = Math.round(size/1024,2) + 'G';
                    }else{
                        size += 'M';
                    }
                    $(".up-state .file_size").text(size);
                    $('.up-in').width('0');
                    $('.bar').find('span').html('0%');
                    $('.up-con').hide();
                    $('.up-state').show();
                    $('#go').click(function () {
                        $('.up-end').hide();
                        $('.up-con').show();
                    })
                }else{
                    var msg = '该视频已存在!';
                    alert(msg);
                    uploader.reset();
                    $("#name .webuploader-pick").text(file.name);
                    $(".up-state .file_name").text(file.name);
                    $(".file-address input

name=vodplayurl
").val(data.data.path);
                    $(".file-address input name=urlid
").val(data.data.id);
                    //获取视频时长,配合video标签
                    $("#duration").attr("src",data.data.path);
                }
            });
        });
    });
    // 文件上传过程中创建进度条实时显示。
    uploader.on( 'uploadProgress', function( file, percentage ) {
        $('.bar').find('span').html((percentage * 100).toFixed(2) + '%');
        $('.up-in').width(percentage * 100 + '%');
    });
    uploader.on( 'uploadSuccess', function( file,res ) {
        //最后一块完成时间
        //全部上传完成发送合并请求
        $.post('/api.php/upload/videoUpload',{md5:fileMd5},function (data) {
            if(data.code)
            {
                $('.up-end').show();
                $('.up-state').hide();
                setTimeout(function () {
                    $(".up-end").hide();
                    $('.up-con').show();
                },2000);
                $(".file-address input name=vodplayurl
").val(data.data.path);
                $(".file-address input name=urlid
").val(data.data.id);
                $("#duration").attr("src",data.data.path);
            }else{
                (".up-end h1").text('上传出错');
                $('.up-end').show();
                $('.up-state').hide();
                setTimeout(function () {
                    $(".up-end").hide();
                    $('.up-con').show();
                },2000);
            }
        });
    });
    uploader.on( 'uploadError', function( file ) {
        $(".up-end h1").text('上传出错');
        $('.up-end').show();
        $('.up-state').hide();
        setTimeout(function () {
            $(".up-end").hide();
            $('.up-con').show();
        },2000);
    });
}

服务器:

 

//分片上传
function chunkUpload($name = 'file')
{
    $md5 = request()->param()

′md5′
;
    $object_info = request()->file($name);
    //保存文件的顺序很重要!
    $object = $object_info->rule('uniqid')->move(PATH_FILE . $md5 . '/',request()->param() ′chunk′
);
    if($object)
    {
       return ′chunks′=>request()−>param()\[′chunks′
,'chunk'=>request()->param() ′chunk′
\];
    }else{
        return false;
    }
}
//最终合并文件
function videoUpload()
{
    $md5 = request()->param() ′md5′
;
    $dir = PATH_FILE . $md5;
    if(is_dir($dir)) {
        //获取文件的顺序很重要!!!
        $files =   ;
        $chunk_id = 0;
        $chunk_file = PATH_FILE . $md5 . '/';
        while (file_exists($chunk_file . $chunk_id . '.mp4')){
            $files   = $chunk_file . $chunk_id . '.mp4';
            $chunk_id++;
        }
        $file_name = randomStr().'.mp4';
        $path_file = date('Ymd') . SYS_DS_PROS . $file_name;
        //日期目录不存在则创建目录
        if(!is_dir(PATH_FILE . date('Ymd')))
        {
            mkdir(PATH_FILE . date('Ymd'));
        }
        $count = 0;
        foreach ($files as $v)
        {
            $_file = file_get_contents($v);
            $_res = file_put_contents(PATH_FILE . $path_file,$_file,FILE_APPEND);
            if($_res)
            {
                unlink($v);
            }else{
                $count++;
            }
        }
        if($count == 0)
        {
            rmdir($dir);
            //检查合并后的文件hash
            $_hash = hash_file('sha1', PATH_FILE . $path_file);
            //合并后的文件已存在则删除已合并文件并返回已有文件信息
            $file_info = $this->modelFile->getInfo( ′sha1′=>$hash
,'id,name,path,sha1,guid,md5');
            if(!empty($file_info))
            {
                unlink(PATH_FILE . $path_file);
                return $file_info;
            }
            //合并后的文件入库并返回
            $_data = ′name′=>$filename,′path′=>$pathfile,′sha1′=>$hash,′guid′=>′′,′md5′=>request()−>param()\[′md5′
\];
            $result = $this->modelFile->addInfo($_data);
            $_data ′id′
= $result;
            return $_data;
        }else{
            $this->error = '合并文件失败';
            return false;
        }
    }else{
        $this->error = '分片目录不存在!';
        return false;
    }
}
//分片验证
function checkChunk()
{
    $md5 = request()->param() ′md5′
;
    $dir = PATH_FILE . $md5;
    if(is_dir($dir)) {
        $files = $this->getFileByPath($dir);
        return count($files);
    }else{
        return false;
    }
}

 


=========================================================================
最新更新:
分片验证存在bug,当上传分片不小心删除了前面的分片时就导致无法合成文件(文件数量导致跳过了),因此,更新分片验证
前端:

//时间点2:如果有分块上传,则每个分块上传之前调用此函数
beforeSend:function(block){
    var deferred = WebUploader.Deferred();
    if(is_upload)//跳过到开始上传的哪一个分片时
    {
        deferred.resolve();
    }else if(totalFiles)//已经获取过文件数量则直接判断是否跳过
    {
        //当前分片下标小于等于目录下的文件数量就认为分块存在,因为分块上传下标是由小到大
        if (!totalFiles.in_array(block.chunk)) {
            deferred.resolve();
        } else {
            //分块存在,跳过
            deferred.reject();
        }
    } else {
        $.post('/api.php/upload/checkUpload', {md5: fileMd5}, function (data) {
            if (data.code) {
                totalFiles = data.data;
                //当前分片下标小于等于目录下的文件数量就认为分块存在,因为分块上传下标是由小到大
                if (!data.data.in_array(block.chunk)) {
                    deferred.resolve();
                } else {
                    //分块存在,跳过
                    deferred.reject();
                }
            } else {
                is_upload = true;
                deferred.resolve();
            }
        });
    }
    return deferred.promise();

后端:

//分片验证
public function checkChunk()
{
    $md5 = request()->param()

′md5′
;
    $dir = PATH_FILE . $md5;
    if(is_dir($dir)) {
        $files = $this->getFileByPath($dir);
        foreach ($files as $k=>$v)
        {
            $file_name = explode('/',$v);
            $file_name = $file_name count($filename)−1
;
            $file_index = explode('.',$file_name) 00 ;
            $files $k
= (int)$file_index;
        }
        return $files;
    }else{
        return false;
    }
}

利用文件名的有序性判断当前分片索引是否存在服务器
判断是否存在数组中的函数:

 

Array.prototype.in_array = function (element) {
    for (var i = 0; i < this.length; i++) {
        if (this

i
== element) {
            return true;
        }
    } return false;
};

 

 

 

参考文章:http://blog.ncmem.com/wordpress/2023/10/27/php%e7%bb%93%e5%90%88webuploader%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0%e7%9a%84%e5%ae%9e%e7%8e%b0/

欢迎入群一起讨论

 

 

标签:断点续传,name,上传,webuploader,file,分片,php,data,md5
From: https://www.cnblogs.com/songsu/p/17791951.html

相关文章

  • php 金额格式胡
    //转换不彻底functionExchangeMoney($N_money){$A_tmp=explode(".",$N_money);//将数字按小数点分成两部分,并存入数组$A_tmp$I_len=strlen($A_tmp[0]);//测出小数点前面位数的宽度if($I_len%3==0){$I_step=$I_len/3;//如前面位数的宽度mod3=0,可按,......
  • php结合web uploader插件实现分片上传文件
    这篇文章主要为大家详细介绍了php结合webuploader插件实现分片上传文件,采用大文件分片并发上传,极大的提高了文件上传效率,感兴趣的小伙伴们可以参考一下 最近研究了下大文件上传的方法,找到了webuploaderjs插件进行大文件上传,大家也可以参考这篇文章进行学习:《WebUploader......
  • php魔术方法
    PHP中把以两个下划线__开头的方法称为魔术方法(Magicmethods),这些方法在PHP中充当了举足轻重的作用。魔术方法包括:__construct(),类的构造函数__destruct(),类的析构函数__call(),在对象中调用一个不可访问方法时调用__callStatic(),用静态方式中调用一个不可访问方法时调用__get(),......
  • PHP RSA加密解密实例
    <?phpheader('Content-Type:text/html;charset=utf-8');//RSA加密解密实例$private_key=<<<EOF-----BEGINRSAPRIVATEKEY-----MIICXQIBAAKBgQC3//sR2tXw0wrC2DySx8vNGlqt3Y7ldU9+LBLI6e1KS5lfc5jlTGF7KBTSkCHBM3ouEHWqp1Z......
  • SpringBoot 实现大文件断点续传
    最近在工作中有涉及到文件上传功能,需求方要求文件最大上限为1G,如果直接将文件在前端做上传,会出现超长时间等待,如果服务端内存不够,会直接内存溢出,此时我们可以通过断点续传方式解决,前端我们通过WebUploader实现文件分割和上传,语言是React,后端我们通过SpringBoot实现文件接收和组装......
  • 【Springboot文件上传】前后端双开,大文件秒传、断点续传的解决方案和优雅实现
    思路和解决方案探讨秒传这里指的“秒传”,是指:当用户选择上传一个文件时,服务端检测该文件之前是否已经被上传过,如果服务器已经存有该文件(完全一样),就立马返回前端“文件已上传成功”。前端随即将进度条更新至100%。这样给用户的感觉就是“秒传”的感觉。对于每一个上传到服务......
  • springboot 整合 gridfs 、webUploader实现大文件分块上传、断点续传、秒传
    主要的pom.xml:<dependency>      <groupId>mysql</groupId>      <artifactId>mysql-connector-java</artifactId>    </dependency><!--mongodb-->    <dependency>      <groupId>org.spri......
  • SpringBoot实现对文件的断点续传和秒传
    本文断点续传文件思路:前端(客户端)需要根据固定大小对文件进行分片,请求后端(服务端)时要带上分片序号和大小;服务端创建conf文件用来记录分块位置,conf文件长度为总分片数,每上传一个分块即向conf文件中写入一个127,那么没上传的位置就是默认的0,已上传的就是Byte.MAX_VALUE127(这步是......
  • 查看Linux 、Apache 、 MySQL 、 PHP 版本的方法
    1.查看linux的内核版本,系统信息,常用的有三种办法:uname-a;more/etc/issue;cat/proc/version;2.查看apache的版本信息如果是通过yum,或者是rpm安装的,可以使用rpm-qa|gerphttpd来查看;还可以通过httpd-v来查询;当然,安装好apache后,可以直接elink回环查看apache的信息。3.查看ph......
  • GraphPrompt: Unifying Pre-Training and Downstream Tasks for Graph Neural Network
    目录概符号说明GraphPrompt代码LiuZ.,YuX.,FangY.andZhangX.GraphPrompt:Unifyingpre-traininganddownstreamtasksforgraphneuralnetworks.WWW,2023.概统一的图预训练模型+Prompt微调.符号说明\(G=(V,E)\),图;\(\mathbf{X}\in\mathbb{R}^{|......