webuploader实现大文件断点续传

发布时间 2023-11-17 10:05:42作者: Xproer-松鼠

前端代码(基于Yii框架,逻辑可供参考)

 

    <script>

        var fileMd5;  //文件MD5

        var fileObj;  //文件对象

        var state = 'pending';  //状态

        WebUploader.Uploader.register({

            "before-send": "beforeSend",

            "after-send-file": "afterSendFile"

        }, {

            beforeSend: function (block) {

                //每个分片开始上传前检查分片是否存在

                var deferred = WebUploader.Deferred();

                $.post('<?=Url::to(['video-upload/check-chunk'])?>', {

                    fileMd5: fileMd5,

                    chunk: block.chunk

                }, function (res) {

                    //不存在继续下一步

                    if (res.code == 1) {

                        deferred.resolve();

                    } else {

                        //已存在直接跳过

                        deferred.reject();

                    }

                }, 'json');

                //把fileMd5作为参数传入upload接口,配置项里无法直接配置所以放在这里

                this.owner.options.formData.fileMd5 = fileMd5;

                return deferred.promise();

            },

            afterSendFile: function () {

                $.post('<?=Url::to(['video-upload/merge-chunks'])?>', {

                    fileMd5: fileMd5,

                }, function (res) {

                    $('#videoupload-url').val(res.path);

                    $('#message').text(res.message);

                }, 'json');

            }

        });

 

        var uploader = new WebUploader.Uploader({

            // 自动上传。

            auto: false,

            // swf文件路径

            swf: '/webuploader/Uploader.swf',

            // 文件接收服务端。

            server: '<?=Url::to(['video-upload/upload'])?>',

            sendAsBinary: false,

            method: 'POST',

            pick: '#upload',

            // 只允许选择MP4文件

            accept: {

                extensions: 'mp4',

                mimeTypes: 'video/mp4'

            },

            resize: false,

            chunked: true,   //开启分片上传

            chunkSize: 2 * 1024 * 1024,   //分片大小2M

            threads: 1,

            //单个文件的大小 50M

            fileSingleSizeLimit: 50 * 1024 * 1024

        });

 

        uploader.on('fileQueued', function (file) {

            //计算文件的唯一标记fileMd5,用于断点续传  如果.md5File(file)方法里只写一个file参数则计算MD5值会很慢 所以加了后面的参数:50*1024*1024,此处的大小取决于配置中的fileSingleSizeLimit

            (new WebUploader.Uploader()).md5File(file, 0, 50 * 1024 * 1024).progress(function (percentage) {

            }).then(function (val) {

                $('#message').text("成功获取文件信息!");

                fileMd5 = val;

                //检查文件上传进度

                $.post('<?=Url::to(['video-upload/check-file'])?>', {

                    fileMd5: fileMd5,

                }, function (res) {

                    if (res.code === 200) {

                        $(".progress-bar").css('width', res.percent);

                        $(".progress-bar").text(res.percent);

                        if (res.percent == '0%') {

                            $("#btn").text('开始上传');

                        } else if (res.percent == '100%') {

                            $("#btn").text('上传完毕');

                        } else {

                            $("#btn").text('开始上传');

                        }

                    }

                }, 'json');

            });

            uploader.stop(true);

        });

 

        uploader.on('uploadProgress', function (file, percentage) {

            //动态更改上传进度

            var percent = Math.round(percentage * 100) + '%';

            $(".progress-bar").css('width', percent);

            $(".progress-bar").text(percent);

            $("#message").val('上传中...');

        });

 

        uploader.on('error', function (error) {

            //错误处理

            console.log(error);

            $("#btn").val('开始上传');

            uploader.stop(true);

            if (error == 'F_EXCEED_SIZE') {

                $('#message').text('文件最大限制为50M');

            } else {

                $('#message').text(error);

            }

        });

 

        uploader.on('all', function (type) {

            //实时监听上传状态

            if (type === 'startUpload') {

                state = 'uploading';

            } else if (type === 'stopUpload') {

                state = 'paused';

            } else if (type === 'uploadFinished') {

                state = 'done';

            } else if (type === 'fileQueued') {

                state = 'queued';

            }

 

            if (state === 'uploading') {

                $("#btn").text('暂停上传');

            } else if (state === 'paused') {

                $("#btn").text('继续上传');

            } else if (state === 'queued') {

                $("#btn").removeAttr('disabled');

            } else if (state === 'done') {

                $("#btn").text('上传完毕');

                $("#btn").attr('disabled', 'disabled');

            }

        });

 

        uploader.on('uploadSuccess', function (file, response) {

            $(".progress-bar").css('width', '100%');

            $(".progress-bar").text('100%');

            $("#btn").text('上传完毕');

            $("#btn").attr('disabled', 'disabled');

        });

 

        //手动操作视频上传/暂停/继续

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

            if (state === 'uploading') {

                uploader.stop(true);

                return false;

            }

            uploader.upload(fileObj);

        });

    </script>

 

后端接口代码

 

/**

 * 分片上传

 * @return array

 */

public function actionUpload()

{

    if (Yii::$app->request->isPost) {

        Yii::$app->response->format = Response::FORMAT_JSON;

        $file = UploadedFile::getInstanceByName('file');

        $chunk = Yii::$app->request->post('chunk', 0);

        $fileMd5 = Yii::$app->request->post('fileMd5');

        $chunks = Yii::$app->request->post('chunks', 1);

        $percent = ((round(($chunk + 1) / $chunks, 2)) * 100) . '%';

        //记录该文件的上传进度

        Yii::$app->cache->set($fileMd5, $percent);

        //把分片内容按照一定规律存储

        rename($file->tempName, $this->tempFile($fileMd5, $chunk));

        return [

            'code' => 200,

            'message' => 'success',

        ];

    }

}

 

/**

 * 合并所有分片

 * @return array

 */

public function actionMergeChunks()

{

    $fileMd5 = Yii::$app->request->post('fileMd5');

    $full = $this->tempFile($fileMd5);

    $fp = fopen($full, 'wb');

    $i = 0;

    while (file_exists($this->tempFile($fileMd5, $i))) {

        $content = file_get_contents($this->tempFile($fileMd5, $i));

        fwrite($fp, $content);

        //删除分片

        unlink($this->tempFile($fileMd5, $i));

        $i++;

    }

    fclose($fp);

    Yii::$app->cache->delete($fileMd5);

    Yii::$app->response->format = Response::FORMAT_JSON;

    $data = [

        'code' => 200,

        'message' => 'success',

        'path' => '../upload/' . $fileMd5 . '.mp4',

    ];

    return $data;

}

 

/**

 * 分片上传前检查分片是否已存在

 */

public function actionCheckChunk()

{

    if (Yii::$app->request->isPost) {

        Yii::$app->response->format = Response::FORMAT_JSON;

        $fileMd5 = Yii::$app->request->post('fileMd5');

        $chunk = Yii::$app->request->post('chunk', 0);

        if (file_exists($this->tempFile($fileMd5, $chunk))) {

            return [

                'code' => 0,

                'message' => 'chunk_exists',

            ];

        } else {

            return [

                'code' => 1,

                'message' => 'chunk_not_exists',

            ];

        }

    }

}

 

/**

 * 开始上传前检查文件上传进度

 * @return array

 */

public function actionCheckFile()

{

    Yii::$app->response->format = Response::FORMAT_JSON;

    $fileMd5 = Yii::$app->request->post('fileMd5');

    $percent = Yii::$app->cache->get($fileMd5) ? Yii::$app->cache->get($fileMd5) : '0%';

    return [

        'code' => 200,

        'percent' => $percent,

    ];

}

 

/**

 * 获取格式化的文件名

 * @param $fileMd5

 * @param null $chunk

 * @return bool|string

 */

private function tempFile($fileMd5, $chunk = null)

{

    $tempFile = Yii::getAlias('@webroot/upload/' . $fileMd5);

    if (is_null($chunk)) {

        $tempFile .= '.mp4';

    } else {

        $tempFile .= '.chunk' . $chunk;

    }

    return $tempFile;

}

 

参考文章:http://blog.ncmem.com/wordpress/2023/11/17/webuploader%e5%ae%9e%e7%8e%b0%e5%a4%a7%e6%96%87%e4%bb%b6%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/

欢迎入群一起讨论