美文网首页
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