thinkphp6自己实现文件分块上传

一般文件比较大时,可以采用第三方图片存储方案(如OSS),或者分块传输,本文将讲解使用php进行分块上传。

分块上传指的是将要上传的文件切成一片片小的区块进行上传,然后服务端接收这些前端上传过来的小区块,存储到

服务器存储文件的目录下,待上传完毕时将这些小区块合并成一个文件,并将已上传的这些区块删除。至此,上传过

程结束。

这里有几个细节需要注意,在上传时前端需要传给服务端一个“标志”,以表明这是一个分块上传的类型而不是文件

直接整个上传,用以区分这2种文件的上传类型。对于小文件可以直接上传,不需要分片。

(可选)其次前端需要再发送原文件的相关信息如文件名,便于服务端的存储。也可以不传,具体的存储逻辑开发者

可以自己实现。

技术栈:thinkphp、前端

上传结果:

image.png

一个个文件块:

2.png

以下是前端需要执行的一些代码逻辑

由于是个demo,因此开发者对于其它方面的需求可以自己实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>分块上传</title>
    <script src="/static/js/jquery.min.js"></script>
     <!-- md5相关的库文件 -->
    <script src="/static/js/core.js"></script>
    <script src="/static/js/md5.js"></script>
</head>
<body>
    <div>
	    <!-- 文件上传的元素 -->
        <input type="file" id="file">
    </div>
    <script>
        $(document).ready(function () {
        // 监听文件上传
            $('#file').change(function (e) {
                // 定义每个区块的大小,以字节为单位
                var chunk = 18*1024;
                var uploadFile = $(this)[0].files[0]
                var fileReader = new FileReader()
                // 初始化记录的进度
                var loaded = 0
                console.log(uploadFile,e)
                readPartFile(loaded,chunk)

                var fileId = CryptoJS.MD5(uploadFile).toString()
				// 监听加载完毕的区块
                fileReader.onload = function (event) {
	                // 更新进度
                    loaded += chunk
                    var targetResult = event.target.result
                    // 转换成Blob对象
                    var targetBlob = new Blob([targetResult])
                    console.log('event',event,'mime',targetBlob)
   
                    var isEnd = loaded >= uploadFile.size ? 1 : 0
                    //每3秒上传一次,防止请求频率过快,时间可自定义
                    setTimeout(() => {
                        pieceUpload(targetBlob,isEnd,fileId,uploadFile.name,function (e) {
                            console.log(e)
                            if (isEnd) {
                                return alert('上传完成!')
                            }
                            //当前区块上传完毕后再上传下一个区块
                            readPartFile(loaded,chunk)
                        })
                    },3000)

                }
                // 将文件分成指定大小的区块,start是起始位置
                function readPartFile(start) {
                   var piece = uploadFile.slice(start,start + chunk)
                    fileReader.readAsArrayBuffer(piece)
                }
                // 分块上传 file:上传的区块 isEnd:是否已整体上传结束 fileId:源文件的唯一标识 filename:文件名 callback:上传之后的回调
                function pieceUpload(file,isEnd,fileId,filename,callback) {
                    try {
                        var formData = new FormData()
   
                        formData.append('file',file,'file')
                        formData.append('is_end',isEnd)
                        formData.append('file_id',fileId)
                        formData.append('filename',filename)
                        //向服务端表明该请求是一个分块上传
                        formData.append('type','chunk')
                        console.log(formData)
                        $.ajax({
                        //分块上传的接口url
                            url: '/index/index/chunkUpload',
                            type: 'POST',
                            data: formData,
                            processData: false,
                            // dataType: 'json',
                            // contentType: 'application/json',
                            contentType: false,
                            success: callback,
                            error: function (err) {
                                console.log(err)
                            }
                        })
                    } catch (e) {
                        console.log(e)
                    }
                }
            })
        })
    </script>
</body>
</html>
复制代码

​后端的代码逻辑

//执行分块上传的控制器方法
public function chunkUpload() {
        if ($this->request->isPost()) {
        //执行分块上传流程
            $data = $this->request->post();
            //判断是否是分块上传
            if ($data['type'] === 'chunk') {
                $file = request()->file('file');
                //获取对应的上传配置
                $fs = Filesystem::disk('local');
                $ext = $file->extension();
                $chunkPath = $data['file_id'].'/'.$file->md5().($ext ? '.'.$ext : '');
                //存储分片文件到指定路径
                $savename = $fs->putFileAs( 'chunk', $file,$chunkPath);
                if (!$savename) {
                    return json([
                        'code' => 1,
                        'msg' => '上传失败',
                        'data' => [],
                    ]);
                }
                if (!$data['is_end']) {
                    $extra['url'] = '';
                } else {
                    //合并块文件
                    $fileUrl = '';
                    $chunkSaveDir = Filesystem::getDiskConfig('local');
                    $smallChunkDir = $chunkSaveDir['root'].'/chunk/'.$data['file_id'];
                    //获取已存储的属于源文件的所有分块文件 进行合并
                    if ($handle = opendir($smallChunkDir)) {
                        $chunkList = [];
                        $modifyTime = [];
                        while (false !== ($file = readdir($handle))) {
                            if ($file != "." && $file != "..") {
                                $temp['path'] = $smallChunkDir.'/'.$file;
                                $temp['modify'] = filemtime($smallChunkDir.'/'.$file);
                                $chunkList[] = $temp;
                                $modifyTime[] = $temp['modify'];
                            }
                        }
                        //对分块文件进行排序
                        array_multisort($modifyTime,SORT_ASC,$chunkList);
                        $saveDir = Filesystem::getDiskConfig('public');
//                        $newPath = $saveDir['root'].'/'.date('Ymd').'/'.$data['file_id'].($newExt ? '.'.$newExt : '');
                        $newPath = $saveDir['root'].'/'.date('Ymd').'/'.$data['filename'];
                        if (!file_exists($saveDir['root'].'/'.date('Ymd'))) {
                            mkdir($saveDir['root'].'/'.date('Ymd'),0777,true);
                        }
                        $newFileHandle = fopen($newPath,'a+b');
                        foreach ($chunkList as $item) {
                            fwrite($newFileHandle,file_get_contents($item['path']));
                            unlink($item['path']);
                        }
                        rmdir($smallChunkDir);
                        //将合并后的文件存储到指定路径
                        $fileUrl = $saveDir['url'].'/'.date('Ymd').'/'.$data['filename'];
                        fclose($newFileHandle);
                        closedir($handle);
                    } else {
                        return json([
                            'code' => 1,
                            'msg' => '目录:'.$chunkSaveDir['root'].'/chunk/'.$data['file_id'].'不存在',
                            'data' => [],
                        ]);
                    }
                    $extra['url'] = $fileUrl;
                }
                //合并流程结束
                return json([
                    'code' => 0,
                    'msg' => '上传成功',
                    'data' => $extra,
                ]);
            }
        }
        return view();
    }
复制代码

引入的js依赖:

<script src="/static/js/core.js"></script>
    <script src="/static/js/md5.js"></script>
复制代码

github.com/brix/crypto…

<script src="/static/js/jquery.min.js"></script>
复制代码

jquery.com/download/

授权许可:本文相关代码遵循MIT协议,用户可在遵循MIT协议所允许和禁止的范围下使用。若转载,请署名作者及来源。

MIT协议:baike.baidu.com/item/MIT/10…

额外说明:若您在使用代码的过程中遇到了问题,欢迎在评论下方留言

猜你喜欢

转载自juejin.im/post/7019535430647283726