webuploader 大文件分片,断点续传,以及秒传功能

js代码

/**
 * Created by 西瓜哥 on 2017/8/18.
 * 分片,急速秒传和断点续传
 */

$(function () {

    var obj = $('#uploader-video');
    var attr = utils.toJson(obj.attr('data-options'));
    var $list = $("#videoList");
    var video_container = $(".video-container");


    var GUID = WebUploader.Base.guid();

    var chunkSize = 10 * 1024 * 1024;
    //注册功能一定要写在前头,否则不会生效
    WebUploader.Uploader.register({
        'before-send-file': 'beforeSendFile'
        , "before-send": "beforeSend"
        , "after-send-file": "afterSendFile"
    }, {
        beforeSendFile: function (file) {
            var owner = this.owner,
                server = this.options.server,
                deferred = WebUploader.Deferred(),
                obj =  $list.find(' #' + file.id);


            owner.md5File(file.source).fail(function () {
                deferred.reject();
            }).progress(function (percentage) {
                obj.find('.note').text('读取文件进度' + parseInt(percentage * 100) + "%");
            }).then(function (md5Value) {
                obj.find('.note').text('文件验证完毕...');
                file.wholeMd5 = md5Value;
                $.ajax(server, {
                    dataType: 'json',
                    type: 'post',
                    data: {
                        status: "md5Check",
                        unique: md5Value
                    },
                    cache: false,
                    timeout: 1000
                }).then(function (response, textStatus, jqXHR) {

                    if (response.exist) {
                        deferred.reject();

                        owner.skipFile(file);
                        obj.find('.note').remove();
                        obj.find('p.state').attr('title', '正在上传').html('100%');
                        UploadComlate(file, response);
                        file.uniqueFileName = md5Value;
                    } else {
                        deferred.resolve();
                        file.uniqueFileName = md5Value;
                    }

                }, function (jqXHR, textStatus, errorThrown) {
                    deferred.resolve();
                });

            });

            return deferred.promise();
        }, beforeSend: function (block) {
            //分片验证是否已传过,用于断点续传
            var deferred = WebUploader.Deferred();
            var server = this.options.server;
            $.ajax({
                type: "POST"
                , url: server
                , data: {
                    status: "chunkCheck"
                    , name: block.file.uniqueFileName
                    , chunkIndex: block.chunk
                    , ext: block.file.ext
                    , size: block.end - block.start
                }
                , cache: false
                , timeout: 1000
                , dataType: "json"
            }).then(function (response, textStatus, jqXHR) {
                if (response.exist) {
                    deferred.reject();
                } else {
                    deferred.resolve();
                }
            }, function (jqXHR, textStatus, errorThrown) {    //任何形式的验证失败,都触发重新上传
                deferred.resolve();
            });

            return deferred.promise();
        }, afterSendFile: function (file) {
            //合并文件
            var chunksTotal = 0;
            if ((chunksTotal = Math.ceil(file.size / chunkSize)) >= 1) {
                //合并请求
                var deferred = WebUploader.Deferred();
                var server = this.options.server;
                $.ajax({
                    type: "POST"
                    , url: server
                    , data: {
                        status: "chunksMerge"
                        , name: file.uniqueFileName
                        , chunks: chunksTotal
                        , original_name: file.source.name
                        , ext: file.ext
                        , md5: file.md5value
                    }
                    , cache: false
                    , dataType: "json"
                }).then(function (response, textStatus, jqXHR) {
                    deferred.resolve();
                    UploadComlate(file, response);
                }, function (jqXHR, textStatus, errorThrown) {
                    deferred.reject();
                });

                return deferred.promise();
            } else {
                UploadComlate(file);
            }
        }
    });


    var uploader = WebUploader.create({
        // 选完文件后,是否自动上传。
        auto: true,
        // swf文件路径
        swf: '/static/js/plugins/webuploader/Uploader.swf',
        runtimeOrder: 'html5,flash',
        // 文件接收服务端。
        server: attr.url,
        // 选择文件的按钮。可选。
        // 内部根据当前运行是创建,可能是input元素,也可能是flash.
        pick: {
            id: '#videoPicker',
            multiple: false,
        },
        threads: 1,
        resize: false,
        compress: false,
        duplicate: true,
        chunked: true,
        chunkSize: chunkSize,
        formData: {guid: GUID},
        fileNumLimit: 10,
        // fileSingleSizeLimit:2*1024*1024*1024,
        accept: {
            title: 'Videos',
            extensions: 'mp4,mkv,flv,avi,vob,mov,mpg',
            mimeTypes: 'video/*'
        }
    });


    uploader.on('fileQueued', function (file) {
        video_container.hide();
        var $li = $(
            '<div id="' + file.id + '" class="item">' +
            '<div class="note">正在计算文件特征...</div>' +
            '<div class="pic-box"><img src="/dist/home/images/video.jpg"></div>' +
            '<p class="state" ><i class="fa fa-arrow-up"></i></p>' +
            '</div>'
        );
        $list.append($li);

    });


    //发送前填充数据
    uploader.on('uploadBeforeSend', function (block, data) {
        // block为分块数据。
        // file为分块对应的file对象。
        // var file = block.file;
        // var fileMd5 = file.wholeMd5;
        // 修改data可以控制发送哪些携带数据。
        // console.info("fileName= " + file.name + " fileMd5= " + fileMd5 + " fileId= " + file.id);
        // 将存在file对象中的md5数据携带发送过去。
        data.md5value = block.file.wholeMd5;//md5
        //唯一标识符,用作断点续传
        data.uniqueFileName = block.file.uniqueFileName;

        if (block.chunks > 1) {
            data.isChunked = true;
        } else {
            data.isChunked = false;
        }

    });

    //前一个文件未传完,不能再添加文件
    uploader.on('beforeFileQueued', function () {
        if (obj.hasClass('disabled')) {
            utils.fail('请等待上一个文件传完!');
            return false;
        }
    });

    //开始上传做一个标记
    uploader.on('startUpload', function () {
        obj.addClass('disabled');
    });

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

        var $li = $list.find(' #' + file.id),
            $percent = $li.find('.progress .progress-bar');
        // 避免重复创建
        if (!$percent.length) {
            $percent = $('<div class="progress progress-striped active">' +
                '<div class="progress-bar" role="progressbar" style="width: 0%">' +
                '</div>' +
                '</div>').appendTo($li).find('.progress-bar');
        }
        $li.find(".note").remove();
        $li.find('p.state').attr('title', '正在上传').html(parseInt(percentage * 100) + '%');
        $percent.css('width', percentage * 100 + '%');
    });


    uploader.on('uploadError', function () {
        utils.fail('文件上传失败');
    });

    //删除标记和进度条
    uploader.on('uploadComplete', function (file) {
        $('#' + file.id).find('.progress').fadeOut();
        obj.removeClass('disabled');
        $list.html('');
    });

    uploader.on("error", function (type, handler) {
        if (type === "Q_TYPE_DENIED") {
            utils.fail('上传文件格式不符合要求');
        } else if (type === "F_EXCEED_SIZE") {
            utils.fail('上传文件超过限制');
        }
    });

    function UploadComlate(file, response) {
        utils.success('上传成功');
        uploader.reset();
        $list.find('.item').remove();
        video_container.show();
        if (response.state) {
            video_container.find('span').html(file.source.name);
            video_container.find('input').val(response.url);
        } else {
            video_container.find('span').html(file.source.name);
            video_container.find('input').val( response.data.file_path);
        }
    }


});

  

php部分代码:

public function batch(Request $request)
    {
        if ($request->isMethod('post')) {
            $action = request()->get('status');
            switch ($action) {
                case "md5Check":
                    $file = $this->checkFile();
                    if ($file) {
                        return ["exist" => 1, "data" => $file];
                    } else {
                        return ["exist" => 0];
                    }
                    break;
                case "chunkCheck":
                    $this->upload = new Upload();
                    $this->upload->chunkCheck();
                    return $this->upload->info();
                    break;
                case "chunksMerge":
                    $this->upload = new Upload();
                    $this->upload->type = 'batch';
                    $this->upload->public = false;
                    $this->upload->user = $this->service->setUser();
                    $this->upload->chunksMerge();
                    return $this->upload->info();
                    break;
                default:
                    $this->upload = new Upload();
                    $this->upload->chunkUpload();
                    return $this->upload->info();

            }
        }
    }


    public function checkFile()
    {
        if (request()->isMethod('post')) {
            $md5 = request()->get('unique');
            return $this->service->checkFile($md5);
        }

    }

  uploader 类

<?php

namespace App\Tools\Upload;


use Illuminate\Support\Facades\Event;
use Intervention\Image\Facades\Image;
use Storage;
use App\Events\UserUploadImage;
use App\Events\UserUploadAttach;
use App\Events\Logger;
use \App\Jobs\OSSQueue;

class Upload
{

    private $public = true;
    private $type;
    private $water;
    private $info;
    private $img;
    private $original_name;
    private $original_path;
    private $thumb_image;
    private $ext;
    private $attachType = 'thumb';
    private $mimetype;
    private $user;
    private $size;
    private $image_exts = ["png", "jpg", "jpeg", "gif", "bmp"];
    private $video_exts = ["flv", "swf", "mkv", "avi", "rm", "rmvb", "mpeg", "mpg", "ogg", "ogv", "mov", "wmv", "mp4", "webm", "mp3", "wav", "mid"];
    private $fileField = 'file';


    public function __construct($type = 'images', $water = false)
    {
        $this->type = $type;
        $this->water = $water;
    }

    public function __set($name, $value)
    {
        $this->$name = $value;
    }

    public function __get($name)
    {

        return $this->$name;
    }

    public function info()
    {
        return $this->info;
    }

    public function upload()
    {
        $file = request()->file($this->fileField);
        if ($file->isValid()) {
            if ($this->type == 'images') {
                $allow_exts = explode(',', config('system.images_extensions'));
                $max_size = config('system.max_images_size') * 1024;
            } elseif ($this->type == 'video') {
                $allow_exts = explode(',', config('system.video_extensions'));
                $max_size = config('system.max_video_size') * 1024;
            } else {
                $allow_exts = explode(',', config('system.attach_extensions'));
                $max_size = config('system.max_attach_size') * 1024;
            }

            $this->ext = $file->getClientOriginalExtension();
            $this->size = $file->getClientSize();

            //批量上传
            if ($this->type == 'batch') {
                $flag = false;

                if (in_array(strtolower($this->ext), $this->image_exts)) {
                    $this->type = 'images';
                    $this->attachType = 'depot';
                    $flag = true;
                }
                if (in_array(strtolower($this->ext), $this->video_exts)) {
                    $this->type = 'video';
                    $this->attachType = 'video';
                    $flag = true;
                }
                if ($flag == false) {
                    $this->info = ['state' => '未知的上传类型'];
                    return;
                }
            } else {

                if (!in_array(strtolower($this->ext), $allow_exts)) {
                    $this->info = ['state' => '不允许上传的类型'];
                    return;
                }
                if ($this->size > $max_size) {
                    $this->info = ['state' => '上传文件大小超过限制'];
                    return;
                }
            }

            $this->original_name = $file->getClientOriginalName();
            $realPath = $file->getRealPath();
            $this->mimetype = $file->getClientMimeType();

            if ($this->attachType == 'agreement') {
                $path = upload_path() . '/agreements/' . date('Y-m-d');
            } else {
                $path = upload_path() . '/' . $this->type . '/' . date('Y-m-d');
            }


            if (!is_dir(public_path($path))) {
                @mkdir(public_path($path), 0777, true);
            }

            $small_thumb = '';
            $oss_file = '';
            if ($this->type == 'images') {
                if ($this->public == false) {
                    $filename = $file->store($path);
                    if (!is_file(storage_path('app/' . $filename))) {
                        $this->info = ['state' => '文件上传失败,请确保storage目录可写'];
                        return;
                    }
                } else {
                    $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext;
                    Storage::disk('uploads')->put($filename, file_get_contents($realPath));
                    if (!is_file(public_path($filename))) {
                        $this->info = ['state' => '文件上传失败,请确保uploads目录可写'];
                        return;
                    }
                }
                $this->thumb_image = $this->thumb($filename);
                $small_thumb = '/' . setSmallImg($filename);
                $oss_file = '/' . setOSSImg($filename);
                $this->original_path = $filename;

                $this->setImagesInfo();

                $images = Event::fire(new UserUploadImage($this->user))[0];
                $link = '/images/' . $images->name;
                $newName = $images->name;

                $this->user->message = '上传了图片:' . $filename;
            } else if ($this->type == 'video') {
                if ($this->public == false) {
                    $filename = $file->store($path);
                    if (!is_file(storage_path('app/' . $filename))) {
                        $this->info = ['state' => '文件上传失败,请确保storage目录可写'];
                        return;
                    }
                } else {
                    $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext;
                    Storage::disk('uploads')->put($filename, file_get_contents($realPath));
                    if (!is_file(public_path($filename))) {
                        $this->info = ['state' => '文件上传失败,请确保uploads目录可写'];
                        return;
                    }
                }
                $this->original_path = $filename;
                $this->setAttachInfo();
                $video = Event::fire(new UserUploadAttach($this->user))[0];
                $link = '/video/' . $video->name;
                $newName = $filename;
                $this->user->message = '上传了视频:' . $filename;
            } else if ($this->type == 'attach') {
                if ($this->public == false) {
                    $filename = $file->store($path);
                    if (!is_file(storage_path('app/' . $filename))) {
                        $this->info = ['state' => '文件上传失败,请确保storage目录可写'];
                        return;
                    }
                } else {
                    $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext;
                    Storage::disk('uploads')->put($filename, file_get_contents($realPath));
                    if (!is_file(public_path($filename))) {
                        $this->info = ['state' => '文件上传失败,请确保uploads目录可写'];
                        return;
                    }
                }
                $this->original_path = $filename;
                $this->setAttachInfo();
                $video = Event::fire(new UserUploadAttach($this->user))[0];
                $link = '/video/' . $video->name;
                $newName = $filename;
                $this->user->message = '上传了附件:' . $filename;
            } else {
                $filename = $path . '/' . md5(uniqid()) . '.' . $this->ext;
                Storage::disk('uploads')->put($filename, file_get_contents($realPath));
                if (!is_file(public_path($filename))) {
                    $this->info = ['state' => '文件上传失败,请确保uploads目录可写'];
                    return;
                }
                $this->original_path = $filename;
                $link = '/' . $filename;
                $newName = '';
                $this->user->message = '上传了附件:' . $filename;
            }
            Event::fire(new Logger($this->user));


            // 上传原图原附件到阿里云OSS
            if ($this->public == false) {
                dispatch((new OSSQueue($filename, false, $oss_file))->onQueue('high'));
            }

            $this->info = [
                'state' => 'SUCCESS',
                'originalName' => $this->original_name,
                'ext' => $this->ext,
                'small' => $small_thumb,
                'oss_file' => $oss_file,
                'type' => $this->type,
                'mime' => $this->mimetype,
                'size' => $this->size,
                'newName' => $newName,  //链接形式访问 文件名称
                'link' => $link,   //链接形式访问地址
                'url' => '/' . $filename,  //文件存放路径
            ];

            return;
        } else {
            $this->info = ['state' => '文件上传失败'];
            return;
        }
    }


    public function chunkCheck()
    {

        $dir_name = request()->get('name');
        $chunkIndex = request()->get('chunkIndex');
        $size = request()->get('size');

        if (!is_dir(storage_path('chunk_temp_files/' . $dir_name))) {
            Storage::disk('storage')->makeDirectory('chunk_temp_files/' . $dir_name);
        }

        $chunk_file = storage_path('chunk_temp_files/' . $dir_name . '/' . $chunkIndex . '.tmp');


        if (file_exists($chunk_file)) {
            if (filesize($chunk_file) == $size) {
                $this->info = ['exist' => 1];
                return;
            }
        }

        $this->info = ['exist' => 0];
        return;
    }


    public function chunkUpload()
    {

        $file = request()->file('file');
        $isChunked = request()->get('isChunked');
        $chunk = request()->get('chunk');
        $chunks = request()->get('chunks');
        $uniqueFileName = request()->get('uniqueFileName');

        if ($file->isValid()) {
            $this->ext = $file->getClientOriginalExtension();
            $this->original_name = $file->getClientOriginalName();
            $this->mimetype = $file->getClientMimeType();
            $this->size = $file->getClientSize();
            $flag = false;

            $this->image_exts = explode(',', config('system.images_extensions'));
            $this->video_exts = explode(',', config('system.video_extensions'));

            if (in_array(strtolower($this->ext), $this->image_exts)) {
                $flag = true;
            }
            if (in_array(strtolower($this->ext), $this->video_exts)) {
                $flag = true;
            }
            if ($flag == false) {
                $this->info = ['chunked' => false, 'state' => '不允许的上传类型'];
                return;
            }
            $realPath = $file->getRealPath();

            if ($isChunked=='true') {
                Storage::disk('storage')->put('chunk_temp_files/' . $uniqueFileName . '/' . $chunk . '.tmp', file_get_contents($realPath));
                if ($chunks == ($chunk + 1)) {
                    $this->info = ['chunked' => true, 'state' => 'SUCCESS', 'ext' => $this->ext, 'original' => $this->original_name];
                } else {
                    $this->info = ['chunked' => true, 'state' => 'chunked'];
                }
            } else {
                Storage::disk('storage')->put('chunk_temp_files/' . $uniqueFileName . '/tmp.tmp', file_get_contents($realPath));

                $this->info = ['chunked' => true, 'state' => 'SUCCESS', 'ext' => $this->ext, 'original' => $this->original_name];

            }
            return;
        } else {
            $this->info = ['chunked' => false, 'state' => '文件上传失败'];
            return;
        }
    }

    public function chunksMerge()
    {
        $store = request()->all();
        $this->ext = $store['ext'];
        $dir_name = $store['name'];
        $chunks = $store['chunks'];
        $this->original_name = $store['original_name'];

        if (in_array(strtolower($this->ext), $this->image_exts)) {
            $this->type = 'images';
            $this->attachType = 'depot';
        }elseif (in_array(strtolower($this->ext), $this->video_exts)) {
            $this->type = 'video';
            $this->attachType = 'video';
        }else{
            $this->type = 'attach';
            $this->attachType = 'attach';
        }

        $path = upload_path() . '/' . $this->type . '/' . date('Y-m-d');

        if (!is_dir(storage_path('app/' . $path))) {
            Storage::makeDirectory($path);
        }

        $filename = $path . '/' .$dir_name. '.' . $this->ext;

        $files = Storage::disk('storage')->files('chunk_temp_files/' . $dir_name);

        if (count($files) == $chunks) {

            $arr=array();
            foreach ($files as $value)
            {
                $arr[filemtime(storage_path($value))]=$value;
            }
            //        根据修改时间对文件排序
            ksort($arr);
            $fp = fopen(storage_path('app/') . $filename, "ab");

            foreach ($arr as $file) {
                $tempFile = storage_path($file);
                $handle = fopen($tempFile, "rb");
                fwrite($fp, fread($handle, filesize($tempFile)));
                fclose($handle);
                unset($handle);
            }
            fclose($fp);

            Storage::disk('storage')->deleteDirectory('chunk_temp_files/' . $dir_name);


            $this->mimetype = Storage::disk('local')->mimeType($filename);
            $this->size = Storage::disk('local')->size($filename);

            $small_thumb = '';
            $oss_file = '';
            if ($this->type == 'images') {

                if (!is_dir(public_path($path))) {
                    Storage::disk('uploads')->makeDirectory($path);
                }
                $this->thumb_image = $this->thumb($filename);
                $small_thumb = '/' . setSmallImg($filename);
                $oss_file = '/' . setOSSImg($filename);
                $this->original_path = $filename;
                $this->setImagesInfo();
                $images = Event::fire(new UserUploadImage($this->user))[0];
                $link = '/images/' . $images->name;
                $newName = $images->name;


                $this->user->message = '上传了图片:' . $filename;
                dispatch((new OSSQueue($filename, false, $oss_file))->onQueue('high'));
            }

            if ($this->type == 'video') {
                $this->original_path = $filename;
                $this->setAttachInfo();
                $video = Event::fire(new UserUploadAttach($this->user))[0];
                $link = '/videos/' . $video->name;
                $newName = $filename;

                $this->user->message = '上传了视频:' . $filename;
                dispatch((new OSSQueue($filename))->onQueue('low'));
            }

            //上传加入队列   控制台需要运行队列进程 php artisan queue:work
//        dispatch(new  OSSQueue($filename));

            Event::fire(new Logger($this->user));

            $this->info = [
                'state' => 'SUCCESS',
                'originalName' => $this->original_name,
                'ext' => $this->ext,
                'small' => $small_thumb,
                'oss_file' => $oss_file,
                'type' => $this->type,
                'mime' => $this->mimetype,
                'size' => $this->size,
                'newName' => $newName,  //链接形式访问 文件名称
                'link' => $link,   //链接形式访问地址
                'url' => '/' . $filename,  //文件存放路径
            ];
            return;
        }

    }



    private function setImagesInfo()
    {
        $this->user->public = $this->public;
        $this->user->image_type = $this->attachType;
        $this->user->mime = $this->mimetype;
        $this->user->original_name = $this->original_name;
        $this->user->ext = $this->ext;
        $this->user->thumb_image = $this->thumb_image;
        $this->user->original_image = $this->original_path;
    }


    private function setAttachInfo()
    {
        $this->user->public = $this->public;
        $this->user->attach_type = $this->attachType;
        $this->user->mime = $this->mimetype;
        $this->user->original_name = $this->original_name;
        $this->user->ext = $this->ext;
//        $this->user->thumb_image = $this->thumb_image;
        $this->user->original_path = $this->original_path;
    }


    private function thumb($filename)
    {
        if ($this->type == 'images') {


            $temp = explode('.', $filename);


            $ext = $temp[count($temp) - 1] ?: 'jpg';


            $width = config('system.images_max_width') ?: 500;
            $height = config('system.images_max_height') ?: 500;

            $file = $this->public ? public_path($filename) : storage_path('app/' . $filename);

            $this->img = Image::make($file);
            $this->resizeByWidth(360);

            if (!is_dir(public_path(dirname(setSmallImg($filename))))) {
                @mkdir(public_path(dirname(setSmallImg($filename))), 0777, true);
            }
            $this->img->save(public_path(setSmallImg($filename)));

            $this->img = Image::make($file);

            if ($this->img->width() > $this->img->height()) {
                if ($this->img->width() > $width) {
                    $this->resizeByWidth($width);
                }
            } else {
                if ($this->img->height() > $height) {
                    $this->resizeByHeight($height);
                }
            }

            if ($this->water) {
                $this->water();
                $this->img->save(public_path($filename), 60);
            } else {
                $this->img->save(public_path($filename), 90);
            }
            return $filename;
        } else {
            return '';
        }
    }

    private function resizeByWidth($width)
    {
        $this->img = $this->img->resize($width, null, function ($constraint) {
            $constraint->aspectRatio();
        });

    }

    private function resizeByHeight($height)
    {
        $this->img = $this->img->resize(null, $height, function ($constraint) {
            $constraint->aspectRatio();
        });
    }


    private function water()
    {
        if ($this->water) {
            if (file_exists(public_path(config('system.images_water')))) {
                $this->img->insert(public_path(config('system.images_water')), 'center');
            } else {
                $this->img->insert(resource_path('logo.png'), 'center');
            }
        }
    }


}

 

posted @ 2018-04-19 14:58  alston-lee  阅读(2328)  评论(0编辑  收藏  举报