TP6 文件分片上传、断点续传

PHP代码

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
    public function upload()
    {   
        $filename = 'file';
        $path = 'common';
        try {
            $file = request()->file($filename);
            if (empty($file)) {
                throw new \Exception('没有文件上传');
            }

            if ($this->request->request('chunkid')) 
            {
                $chunkid = $this->request->request('chunkid');
                $chunkindex = $this->request->request('chunkindex');
                $chunkcount = $this->request->request('chunkcount');

                $fileName = $chunkid . "-" . $chunkindex . '.tmp';
                $path = $path . DIRECTORY_SEPARATOR . date('Ymd');
                $filepath = \think\facade\Filesystem::disk('public')->putFileAs($path, $file, $fileName);

                // 合并
                if ($chunkindex + 1 == $chunkcount) {
                    $filePath = config('filesystem.disks.public.root') . DIRECTORY_SEPARATOR . $path . DIRECTORY_SEPARATOR;
                    $completed = true;
                    //检查所有分片是否都存在
                    for ($i = 0; $i < $chunkcount; $i++) {
                        if (!file_exists("{$filePath}{$chunkid}-{$i}.tmp")) {
                            $completed = false;
                            break;
                        }
                    }
                    // 删除
                    if(!$completed) {
                        for ($i = 0; $i < $chunkcount; $i++) {
                            if (file_exists("{$filePath}{$chunkid}-{$i}.tmp")) {
                                @unlink("{$filePath}{$chunkid}-{$i}.tmp"); //删除分片
                            }
                        }       
                        throw new \Exception("分片上传文件错误");
                    }

                    // uniapp 上传会拿不到文件扩展名
                    $ext = $file->getOriginalExtension() ? $file->getOriginalExtension() : substr(strrchr($this->request->post('filename'), '.'), 1);
                    if (!$destFile = @fopen($filePath . $chunkid . '.' . $ext, "wb")) {
                        throw new \Exception("分片上传文件错误.");
                    }

                    if (flock($destFile, LOCK_EX)) { // 进行排他型锁定
                        for ($i = 0; $i < $chunkcount; $i++) {
                            $partFile = "{$filePath}{$chunkid}-{$i}.tmp";
                            if (!$handle = @fopen($partFile, "rb")) {
                                break;
                            }
                            while ($buff = fread($handle, filesize($partFile))) {
                                fwrite($destFile, $buff);
                            }
                            @fclose($handle);
                            @unlink($partFile); //删除分片
                        }

                        flock($destFile, LOCK_UN);
                    }
                    @fclose($destFile);

                    $filepath = request()->domain() . '/storage/' . str_replace(DIRECTORY_SEPARATOR, '/', $path) . '/' . $chunkid . '.' . $ext;
                }

            } else {
                $upload = config('filesystem.config');
                validate(['file'=> [
                    'fileSize' => $upload['fileSize'],
                    'fileExt' => $upload['fileExt'],
                    'fileMime' => $upload['fileMime'],
                ]], [
                    'file.fileSize' => '上传文件不能大于'. $upload['fileSize'] / 1024 / 1024 .'M',
                    'file.fileExt' => '上传文件类型不允许',
                    'file.fileMime' => '上传文件类型不允许.',
                ])->check(['file' => $file]);

                if (is_array($file)) {
                    $url = [];
                    $filepath = [];
                    foreach ($file as $value) {
                        $upload_path = \think\facade\Filesystem::disk('public')->putFile($path, $value);
                        $url[] = request()->domain() . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $upload_path;            
                        $filepath[] = $upload_path;
                        $source[] = app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $upload_path;
                        event('upload_success', ['source' => app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $upload_path, 'file' => $file]);
                    }
                } else {
                    $upload_path = \think\facade\Filesystem::disk('public')->putFile($path, $file);
                    $url = request()->domain() . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $upload_path;
                    $filepath = $upload_path;
                    $source = app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $upload_path;
                    event('upload_success', ['source' => app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $upload_path, 'file' => $file]);
                }
            }
        } catch (\think\exception\ValidateException $e) {
            throw new \Exception($e->getMessage());
            
        } catch(\Exception $e) {
            throw new \Exception($e->getMessage());
            
        }
        return json(['filepath' => $filepath, 'url' => $url ?? '', 'source' => $source ?? '']);
    }

}

前端代码

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <!-- import CSS -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <style lang="less" scoped>
        .fileupload {
    	    display: flex;
    	    flex-direction: column;
        }
	    .fileupload-title {
	        display: flex;
	        justify-content: flex-end;
	        padding: 5px 0px;
	    }

	    .fileupload-content {
	        flex: 1;
	    }
	</style>
</head>

<body>
    <div id="app">
        <div class="fileupload">
            <div class="fileupload-title">
                <el-button type="primary" for="file" @click="$refs.file.click()">文件上传(分片上传)<i class="el-icon-upload el-icon--right"></i></el-button>
                <input style="display:none" type="file" id="file" name="file" ref="file" multiple="multiple" @change="fileChange($event)" />
            </div>
            <div class="fileupload-content">
                <el-table :data="fileUploadList" border stripe>
                    <el-table-column label="文件名称" prop="name"></el-table-column>
                    <el-table-column label="操作">
                        <template slot-scope="scope">
                            <el-button type="primary" plain v-if="scope.row.status==1" @click="stop(scope.$index)">停止上传</el-button>
                            <el-button type="danger" plain v-if="scope.row.status==-1" @click="continueUp(scope.$index)">断点续传</el-button>
                            <el-link :underline="false" v-if="scope.row.filepath" v-text="scope.row.filepath"></el-link>
                        </template>
                    </el-table-column>
                    <el-table-column label="上传进度">
                        <template slot-scope="scope">
                            <div>
                                <el-progress :status="scope.row.isSuccess==1?'success':scope.row.isSuccess==2?'exception':null" :percentage="scope.row.percent"></el-progress>
                            </div>
                        </template>
                    </el-table-column>
                </el-table>
            </div>
        </div>
    </div>
</body>
<!-- import Vue before Element -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
<!-- import JavaScript -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script>

let config = {
    timeout: 120000, //设置超时时长
    onUploadProgress: function(progress) {
        this.fileUploadList[i].percent = Math.round(
            (progress.loaded * 100) / progress.total
        );
    }.bind(this)
};

function AddFiles(filelist, conf) {
    return axios.post('/index/upload', filelist, conf)
}

new Vue({
    el: '#app',
    data() {
        return {
            fileUploadList: [],
            bytesPerPiece: 1024 * 1024 * 2, // 每个文件切片大小定为4MB .
        };
    },
    created: function() {

    },
    methods: {
        async fileChange(event) {
            this.fileUploadList = [];
            let filelist = event.target.files;
            if (filelist.length > 8) {
                this.$message("上传文件个数不能超过8个");
                return;
            }
            if (filelist.length > 0) {
                for (let i = 0; i < filelist.length; i++) {
                    let filedata = {
                        file: filelist[i],
                        name: filelist[i].name.substring(0, filelist[i].name.length - 4),
                        size: filelist[i].size,
                        date: filelist[i].lastModified,
                        isSuccess: 0, //是否上传成功,0上传中,1成功,2失败
                        percent: 0,
                        speed_time: '',
                        lastTime: 0,//上一次计算时间
                        lastSize: 0,//上一次计算的文件大小
                        status: 1,
                        lastChunk: {},
                        filepath: '',
                    };
                    this.fileUploadList.push(filedata);
                }
                for (let i = 0; i < filelist.length; i++) {
                    let config = {
                        timeout: 120000,
                        onUploadProgress: function(progress) {
                            let percent = Math.round(
                                (progress.loaded * index * 100) / filesize
                            );
                            if (this.fileUploadList[i].percent < percent) {
                                this.fileUploadList[i].percent = percent > 100 ? 100 : percent;
                            }
                        }.bind(this)
                    };

                    let chunkid = Number(Math.random().toString().substr(5,10) + Date.now()).toString(36);
                    let bytesPerPiece = this.bytesPerPiece;
                    let totalPieces = 0;
                    let blob = filelist[i];
                    let start = 0;
                    let end;
                    let index = 0;
                    let filesize = blob.size;
                    let filename = blob.name;
                    console.log(blob)
                    console.log(typeof blob)
                    //计算文件切片总数
                    totalPieces = Math.ceil(filesize / bytesPerPiece);
                    while(start < filesize) {
                        // 停止退出
                        if (this.fileUploadList[i].status == -1) {
                            this.fileUploadList[i].lastChunk = {
                                chunkid: chunkid,
                                index: index,
                                start: start,
                                end: end,
                            };
                            break;
                        }
                        end = start + bytesPerPiece;
                        if(end > filesize) {
                            end = filesize;
                        }

                        let chunk = blob.slice(start,end);//切割文件    
                        let sliceIndex= blob.name + index;
                        let formData = new FormData();
                        formData.append("file", chunk, filename);
                        formData.append("chunkid", chunkid);
                        formData.append("chunkindex", index);
                        formData.append("chunkcount", totalPieces);
                        await AddFiles(formData, config).then(res => {
                            if (res.status == 200) {
                                if (index + 1 == totalPieces) {
                                    this.fileUploadList[i].isSuccess = 1;
                                    this.fileUploadList[i].percent = 100;
                                    this.fileUploadList[i].status = 0;
                                    this.fileUploadList[i].filepath = res.data.filepath;
                                    this.$message("上传成功");
                                }
                            } else {
                                this.fileUploadList[i].isSuccess = 2;
                                this.fileUploadList[i].status = 0;
                                this.$message("上传失败");
                            }
                        });                        
                        start = end;
                        index++;
                    }
                }
            }
        },
        stop(i) {
            this.fileUploadList[i].status = -1;
        },
        async continueUp(i) {
            let config = {
                timeout: 120000,
                onUploadProgress: function(progress) {
                    let percent = Math.round(
                        (progress.loaded * index * 100) / filesize
                    );
                    if (this.fileUploadList[i].percent < percent) {
                        this.fileUploadList[i].percent = percent > 100 ? 100 : percent;
                    }
                }.bind(this)
            };
            this.fileUploadList[i].status = 1;

            let row = this.fileUploadList[i];
            let blob = row.file;
            let chunkid = row.lastChunk.chunkid;
            let bytesPerPiece = this.bytesPerPiece;
            let totalPieces = 0;
            let start = row.lastChunk.start;
            let end = row.lastChunk.end;
            let index = row.lastChunk.index;
            let filesize = blob.size;
            let filename = blob.name;
            //计算文件切片总数
            totalPieces = Math.ceil(filesize / bytesPerPiece);
            while(start < filesize) {
                // 停止退出
                if (row.status == -1) {
                    row.lastChunk = {
                        chunkid: chunkid,
                        index: index,
                        start: start,
                        end: end,
                    };
                    break;
                }
                end = start + bytesPerPiece;
                if(end > filesize) {
                    end = filesize;
                }

                let chunk = blob.slice(start,end);//切割文件    
                let sliceIndex= blob.name + index;
                let formData = new FormData();
                formData.append("file", chunk, filename);
                formData.append("chunkid", chunkid);
                formData.append("chunkindex", index);
                formData.append("chunkcount", totalPieces);
                await AddFiles(formData, config).then(res => {
                    if (res.status == 200) {
                        if (index + 1 == totalPieces) {
                            row.isSuccess = 1;
                            row.percent = 100;
                            row.status = 0;
                            row.filepath = res.data.filepath;
                            this.$message("上传成功");
                        }
                    } else {
                        row.isSuccess = 2;
                        row.status = 0;
                        this.$message("上传失败");
                    }
                });                        
                start = end;
                index++;
            }
        }
    }
})
</script>

</html>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值