美文网首页
react+WebUploader+php大文件分片上传,断点续

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

作者: ABS_e056 | 来源:发表于2019-12-16 20:55 被阅读0次

    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

    相关文章

      网友评论

          本文标题:react+WebUploader+php大文件分片上传,断点续

          本文链接:https://www.haomeiwen.com/subject/ttfjnctx.html