react+WebUploader+php大文件分片上传,断点续传

1,下载WebUploader并在react的index.html中引入,当然还有jquery


<script src="/statics/jquery.js"></script>

<script src="/statics/webuploader-0.1.5/webuploader.js"></script>

这样就可以在react中用window.WebUploader获取到WebUploader了

2,react部分

const $ = window.$;
const WebUploader = window.WebUploader;
constructor(props) {
        super(props);
        this.state = {
            uploadPercent: 0, //上传进度
            tips: ''//提示
        };
        this.uploader = '';//存储创建后的WebUploader对象
        this.blockInfo = [];//分片信息存储
}
componentDidMount() {
        this.createUploader();
}
createUploader() {
        WebUploader.Uploader.register(
            {
                "before-send": "beforeSend"
            },
            {
                beforeSend: (block) => {
                    console.log('检测分片是否上传');
                    let deferred = WebUploader.Deferred();
                    let chunk = block.chunk;
                    if ($.inArray(chunk.toString() + '.part', this.blockInfo) >= 0) {
                        console.log("已有分片.正在跳过分片" + block.chunk.toString() + '.part');
                        deferred.reject();
                    } else {
                        deferred.resolve();
                    }
                    return deferred.promise();
                }
            }
        );
        //配置可直接查看官网
        this.uploader = WebUploader.create({
            swf: '/webuploader-0.1.5/Uploader.swf',
            server: '/file/upload',
            auto: false,
            chunked: true,
            chunkSize: 1 * 1024 * 1024,
            fileSizeLimit: 2 * 1024 * 1024 * 1024,
            fileSingleSizeLimit: 2 * 1024 * 1024 * 1024,
            resize: false,
            accept: {
                title: 'mp4,jpg',
                extensions: 'mp4,jpg',
                mimeTypes: 'video/mp4,image/jpg'
            },
            chunkRetry: false,
            threads: 1,
            fileNumLimit: 1,
            //附加数据
            formData: {
                guid: WebUploader.Base.guid('hzk_'),
                id: '100'
            }
        });

        //这里我用的是单文件上传,所以每次在文件入列之前,重置uploader和分片信息
        this.uploader.on('beforeFileQueued', (file) => {
            this.uploader.reset();
            this.blockInfo = [];
        });

        //文件切面
        this.uploader.on('fileQueued', (file) => {
            this.uploader.md5File(file, 0, 4 * 1024 * 1024)
                .progress((percentage) => {
                    this.setState({
                        tips: '正在读取文件...' + percentage.toFixed(2) * 100 + '%'
                    });
                })
                .then((fileMd5) => {
                    let formData = this.uploader.option('formData');
                    formData.md5 = fileMd5;
                    this.uploader.option('formData', formData);
                    let fileInfo = {
                        name: file.name,
                        size: file.size,
                        type: file.type,
                        ext: file.ext
                    };
                    //下面这个方法是一个验证分片是否存在的请求,如果存在的话直接续传
                    _mm.request({
                        type: 'post',
                        url: '/file/uploadMd5check',
                        data: {
                            ...fileInfo,
                            ...formData
                        }
                    }).then(({data, msg}) => {
                        switch (data.code) {
                            // 断点
                            case '0':
                                this.setState({
                                    tips: '正在从上次断开的地方上传文件...'
                                });
                                for (let i in data.blockInfo) {
                                    this.blockInfo.push(data.blockInfo[i]);
                                }
                                file.status = 0;
                                break;
                            // 无断点
                            case '1':
                                this.setState({
                                    tips: '正在上传文件...'
                                });
                                file.status = 1;
                                break;
                        }
                        this.uploader.upload();
                    }, msg => {
                        this.setState({
                            tips: <span style={{color: 'red'}}>{msg}</span>
                        });
                    })
                });
        });

        //所有分片上传完成后
        this.uploader.on('uploadSuccess', (file, response) => {
            let formData = this.uploader.option('formData');
            let fileInfo = {
                name: file.name,
                size: file.size,
                type: file.type,
                ext: file.ext
            };
            this.setState({
                tips: '正在验证文件...'
            });
            //请求合并
            _mm.request({
                type: 'post',
                url: '/file/uploadMerge',
                data: {...formData, ...fileInfo}
            }).then(({data, msg}) => {
                this.setState({
                    tips: <span style={{color: 'green'}}>{msg}</span>
                });
            }, msg => {
                this.setState({
                    tips: <span style={{color: 'red'}}>{msg}</span>,
                    uploadPercent: 0
                });
            })
        });

        //进度处理
        this.uploader.on('uploadProgress', (file, percentage) => {
            this.setState({
                uploadPercent: percentage,
                uploading: true
            })
        });

        this.uploader.on('error', (type) => {
            switch (type) {
                case 'Q_EXCEED_NUM_LIMIT':
                    alert('一次只能上传一个文件');
                    break;
                case 'Q_EXCEED_SIZE_LIMIT':
                    alert('文件大小超过限制');
                    break;
                case 'Q_TYPE_DENIED':
                    alert('文件格式只能是video/mp4');
                    break;
            }
        });
        //创建上传按钮
        this.uploader.addButton({
            id: '#picker',
            multiple: false
        });
}

//render一个简单的进度条和上传提示信息
render() {
        return (<div>
            <div style={{height: "3px", background: "#EFEFEF", width: "400px"}}>
                <div style={{height: "3px", background: "#1890ff", width: this.state.uploadPercent * 400 + 'px'}}></div>
            </div>
            <span>
                {this.state.tips}
                {this.state.uploadPercent > 0 ? (this.state.uploadPercent * 100).toFixed(2) + '%' : ''}
           </span>
            <span id={"picker"}>上传视频</span>
        </div>)
}

3,服务端php

我这里使用的是phalcon框架,相信phper应该能看懂

    private function validUpload() {
        $ext = pathinfo($this->request->getPost('name'),PATHINFO_EXTENSION);
        $type = $this->request->getPost('type');
        $size = $this->request->getPost('size');
        if (! in_array(strtolower($ext), ['mp4']) || !in_array(strtolower($type), ['video/mp4'])) {
            $this->di->get('util')->R(Err::ERR_RESPONSE_VALID, '文件类型错误');
        }
        $maxSize = 2 * 1024 * 1024 * 1024;
        if ($size > $maxSize) {
            $this->di->get('util')->R(Err::ERR_RESPONSE_VALID, '文件太大');
        }
    }
    /**
     * 分片上传前验证
     * 返回上传临时文件夹内是否有已经上传的分片
     * @return void
     */
    public function uploadMd5checkAction()
    {
        $this->validUpload();
        $md5 = $this->request->getPost('md5');
        $dir = $this->di->get('config')->application->uploadTempDir;
        $dir = $dir . $md5;
        if (file_exists($dir)) {
            $blockInfo = $this->getUploadTempFile($dir);
            if (count($blockInfo) > 0) {
               //下面这个函数R的功能是response一个类似{"errCode":0,"msg":"","data":{"code":0,"blockInfo":"$blockInfo"}}的json
                $this->di->get('util')->R(0, '', ["code"=>"0" , 'blockInfo' => $blockInfo]);
            } else {
                $this->di->get('util')->R(0, '', ["code"=>"1"]);
            }
        } else {
            @mkdir ($dir,0777,true);
            $this->di->get('util')->R(0, '', ["code"=>"1"]);
        }
    }

   /**
     * 上传分片
     * 分片上传附带参数中必须要有验证所需的信息,否则不予上传
     * @return void
     */
    public function uploadAction()
    {
        $this->validUpload();
        $file = $_FILES;
        $md5 = $this->request->getPost('md5');
        $dir = $this->di->get('config')->application->uploadTempDir;
        $dir = $dir . $md5;
        //md5文件夹已经在上传前验证的时候新建了,如果不存在则不上传
        if (!file_exists($dir)) {
            die('error');
        }
        // 移入缓存文件保存
        $chunk = $this->request->getPost('chunk') . '.part';
        move_uploaded_file($file["file"]["tmp_name"], $dir.'/' . $chunk);

    }

   /**
     * 文件验证合并
     * 简单验证:分片必须连续
     * @return void
     */
    public function uploadMergeAction() {
        $md5 = $this->request->getPost('md5');
        //临时文件夹
        $dir = $this->di->get('config')->application->uploadTempDir;
        $dir = $dir . $md5;
        $blockInfo = $this->getUploadTempFile($dir);
        //排序
        natsort($blockInfo);
        //验证文件分片是否连续
        for($i = 0; $i < count($blockInfo); $i++) {
            $chunkName = $i . '.part';
            if ( ! in_array($chunkName, $blockInfo)) {
                $this->di->util->R(4, '文件验证错误,上传失败');
            }
        }
        //新文件名
        $ext = pathinfo($this->request->getPost('name'),PATHINFO_EXTENSION);
        $filename = uniqid(mt_rand(), true) . '.' . $ext;//生成唯一的字符串作为文件名
        $uploadDir = $this->di->get('config')->application->uploadDir . date('Y') . '/';
        if (! is_dir($uploadDir) && ! mkdir($uploadDir)) {
            $this->di->util->R(4, '文件夹创建失败');
        }
        $uploadFile = $uploadDir . $filename;
        //新建新文件
        if (!$out = @fopen($uploadFile, "wb")) {
            $this->di->get('util')->R(4, '系统错误,文件未打开');
        }
        //把所有的分片写入文件,写入前给这个文件加个锁
        if (flock($out, LOCK_EX)) {
            foreach ($blockInfo as $b) {
                if (!$in = @fopen($dir.'/'.$b, "rb")) {
                    break;
                }
                while ($buff = fread($in, 4096)) {
                    fwrite($out, $buff);
                }
                @fclose($in);
                @unlink($dir.'/'.$b);
            }
            flock($out, LOCK_UN);
        }
        @fclose($out);
        @rmdir($dir);
        //$uploadFile就是最终上传完成的文件
        $this->di->get('util')->R(0, '上传成功');
    }
9d134eba-0823-418e-b933-0c473a37f647.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容

  • 随着移动支付的普及,越来越多的人习惯用手机支付,而有些商家甚至也只接受微信、支付宝付账,不接受现金。那么,商家这种...
    旺米铺阅读 1,285评论 0 0
  • 过好年的天气一路奔着春暖花开而去,温暖和煦,春天已然在那了。这两天来了个意想不到的大转弯,剧烈降温。持续着寒风瑟瑟...
    劳拉的旅程阅读 629评论 2 9
  • 第二期罗伟思维导图早读会 时间:2017年5月2日 完成:02/28(第2天/共28天) 主题:宁静---宇宙间最...
    绿宝石_ce43阅读 278评论 0 1
  • 《惊奇队长》是由美国漫威影业公司出品的科幻电影,由安娜·波顿、瑞安·弗雷克联合执导,布丽·拉尔森、塞缪尔·杰克逊、...
    张漪纹阅读 1,047评论 0 6
  • 春节放假结束,从福州坐动车到宁德开始上班。一出动车站,迎接我的是绵绵细雨。穿着过年新买浅色长款大衣的我,心里一咯噔...
    漫步的小马驹阅读 264评论 4 7