当前位置:   article > 正文

vue断点续传一_springboot vue 断点续传

springboot vue 断点续传

vue+spring 断点续传(一)

本文主要利用vue和SpringBoot实现了断点续传,大文件分割传递。本章主要讲述前端实现,后端实现请查看 断点续传二


一、断点续传要点

断点续传的核心就是文件分割,对于大文件来说断点续传是十分有必要的,如若直接上传大文件则很有可能会使网络请求时间过长,并且不能保证在上传过程中网络没有波动,因此大文件上传需要使用分片上传,分片的大小需要根据实际情况确定,本文则粗略以5MB计算
在这里插入图片描述

二、前端实现

1.环境

vue2、element-ui2.3.6、 spark-md5 3.0.1

2.html

html主要是运用的element的upload组件和table表格,没有进行任何美化(只进行了一点点美化-_-)

<template>
    <div id="app">
        <el-upload
                class="upload-demo"
                drag
                ref="upload"
                action=""
                :file-list="fileList"
                :show-file-list="false"
                :on-change="fileChange"
                :auto-upload="false"
        >
            <i class="el-icon-upload"></i>
            <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
        </el-upload>

        <el-table :data="tableData" style="width: 100%">
            <el-table-column align="center" label="name" width="180">
                <template slot-scope="scope">
                    <span>{{ scope.row.fileName }}</span>
                </template>
            </el-table-column>
            <el-table-column align="center" label="进度">
                <template slot-scope="scope">
                    <el-progress :percentage="scope.row.progress"></el-progress>
                </template>
            </el-table-column>
            <el-table-column width="180" align="center" label="操作">
                <template slot-scope="scope" v-if="scope.row.isShow">
                    <el-button size="mini" @click="handleUpload(scope.$index, scope.row)"
                    >上传
                    </el-button
                    >
                    <el-button
                            size="mini"
                            type="danger"
                            @click="handlePause(scope.$index, scope.row)"
                    >暂停
                    </el-button
                    >
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
.upload-demo {
  display: flex;
  flex-direction: column;
  align-items: center;
}
  • 1
  • 2
  • 3
  • 4
  • 5

3.逻辑部分

代码如下(示例):以下代码并没有考虑实际的生产环境,还有很多未完善之处,比如缓存的清除逻辑、并发的控制是否合理等等问题,还请同学们自行完善

    import SparkMD5 from "spark-md5";

    //  分片大小
    const shardSize = 1024 * 1024 * 5;
    //  支持并发数
    const concurrency = 8;
    //  并发控制临界
    const concurrencyLimit = 50;

    export default {
        data() {
            return {
                fileList: [],
                list: [],
                uploadURL: "http://localhost:8888/file/upload/shard",
                statusURL: "http://localhost:8888/file/status",
                memoryFileList: "http://localhost:8888/file/list",
                Headers: {
                    "Content-Type": "application/json; charset=utf-8",
                },
                tableData: [],
            };
        },
        created() {
            this.getMemoryFileInfo();
        },
        methods: {
            //  请求文件上传信息
            async queryFileInfo(data) {
                return this.$axios
                    .post(this.statusURL, data, {Headers: this.headers})
                    .then((res) => {
                        return res.data;
                    });
            },
            //  上传分片
            async uploadShard(shard) {
                return this.$axios
                    .post(this.uploadURL, shard, {Headers: this.headers})
                    .then((res) => {
                        let table = this.tableData;
                        table.forEach((val) => {
                            if (val.hashCode === res.data.fileInfo.hashCode) {
                                val.progress = this.getProgress(
                                    res.data.fileInfo.hasUploadShard.length,
                                    res.data.fileInfo.totalIndex
                                );
                            }
                        });
                        this.tableData = table;
                        return res.data;
                    });
            },

            //分片产生md5
            getMD5Code(file) {
                return new Promise((resolve) => {
                    let blobSlice =
                        File.prototype.slice ||
                        File.prototype.mozSlice ||
                        File.prototype.webkitSlice;
                    const chunkSize = 1024 * 1024 * 5;
                    const chunks = Math.ceil(file.size / chunkSize);
                    let currentChunk = 0;
                    let spark = new SparkMD5.ArrayBuffer();
                    let fileReader = new FileReader();

                    fileReader.onload = function (e) {
                        spark.append(e.target.result); // Append array buffer
                        currentChunk++;

                        if (currentChunk < chunks) {
                            loadNext();
                        } else {
                            resolve(spark.end());
                        }
                    };

                    fileReader.onerror = function () {
                        console.warn("oops, something went wrong.");
                    };

                    function loadNext() {
                        let start = currentChunk * chunkSize;
                        let end =
                            start + chunkSize >= file.size ? file.size : start + chunkSize;

                        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                    }

                    loadNext();
                });
            },

            /**选中的文件 */
            async fileChange(file) {
                //  定义常量
                const fileName = file.name,
                    fileSize = file.size,
                    hashCode = await this.getMD5Code(file.raw),
                    totalIndex = Math.ceil(fileSize / shardSize);

                const data = new FormData();
                data.append("fileName", fileName);
                data.append("fileSize", fileSize);
                data.append("hashCode", hashCode);
                data.append("totalIndex", totalIndex);
                //  存储file
                this.saveFile(file, hashCode);
                //  查询文件状态
                let fileStatus = await this.queryFileInfo(data);
                //  更新列表
                await this.getMemoryFileInfo();
                //  50MB以下并发传递,50MB以上8个并发传递
                if (fileStatus.fileInfo.noUploadShard.length * 10 < concurrencyLimit) {
                    //  直接并发传
                    //  根据返回的分片信息上传分片
                    fileStatus.fileInfo.noUploadShard.forEach((index) => {
                        //  检测是否暂停
                        this.tableData.forEach((val) => {
                            if (!val.pauseed) {
                                const shard = this.getShard(file, index);
                                const data2 = new FormData();
                                data2.append("shard", shard);
                                data2.append("hashCode", hashCode);
                                data2.append("shardIndex", index);
                                this.uploadShard(data2);
                            }
                        });
                    });
                } else {
                    //  8个并发传递
                    //  根据返回的分片信息上传分片
                    let len = fileStatus.fileInfo.noUploadShard.length;
                    let wheel = Math.ceil(len / concurrency);
                    let nowWheel = 0;
                    let loop = setInterval(() => {
                        if (nowWheel < wheel) {
                            let start = nowWheel * concurrency;
                            let end = (nowWheel + 1) * concurrency;
                            for (let i = start; i < end; i++) {
                                //  检测是否暂停
                                this.tableData.forEach((val) => {
                                    if (!val.pauseed) {
                                        const index = fileStatus.fileInfo.noUploadShard[i];
                                        if (index !== undefined) {
                                            const shard = this.getShard(file, index);
                                            const data2 = new FormData();
                                            data2.append("shard", shard);
                                            data2.append("hashCode", hashCode);
                                            data2.append("shardIndex", index);
                                            this.uploadShard(data2);
                                        }
                                    }
                                });
                            }
                        } else {
                            //  清除定时器退出
                            clearInterval(loop);
                            return;
                        }
                        nowWheel++;
                    }, 100);
                }
            },

            //  获取内存中文件信息
            async getMemoryFileInfo() {
                return this.$axios
                    .get(this.memoryFileList, {}, {Headers: this.headers})
                    .then((res) => {
                        //  封装表格数据
                        let arr = [];
                        for (const key in res.data) {
                            let progress = this.getProgress(
                                res.data[key].hasUploadShard.length,
                                res.data[key].totalIndex
                            );
                            let isShow = false;
                            //  是否显示按钮
                            this.list.forEach((val) => {
                                for (const k in val) {
                                    if (res.data[key].hashCode === k) {
                                        isShow = true;
                                    }
                                }
                            });
                            let obj = {
                                fileName: res.data[key].fileName,
                                hashCode: res.data[key].hashCode,
                                progress,
                                pauseed: false,
                                isShow,
                            };
                            arr.push(obj);
                        }
                        this.tableData = arr;
                    });
            },

            //  获取进度
            getProgress(len, total) {
                return Math.ceil((len / total) * 100);
            },

            //  存储file
            saveFile(file, hashCode) {
                let list = this.list;
                list.push({[hashCode]: file});
                this.list = list;
            },

            //  判断数组是否包含某个元素
            contains(arr, obj) {
                let len = arr.length;
                while (len > 0) {
                    len--;
                    if (arr[len] === obj) {
                        return true;
                    }
                }
                return false;
            },
            //  根据索引获取分片
            getShard(file, index) {
                return file.raw.slice(
                    index * shardSize,
                    Math.min((index + 1) * shardSize, file.size)
                );
            },

            //  恢复上传
            handleUpload(index, row) {
                this.changeStatus(false, row.hashCode);
                this.list.forEach((val) => {
                    for (const key in val) {
                        console.log(key);
                        if (row.hashCode === key) {
                            this.fileChange(val[key]);
                        }
                    }
                });
            },
            //  暂停上传
            handlePause(index, row) {
                this.changeStatus(true, row.hashCode);
            },
            //  改变上传状态
            changeStatus(boo, hashCode) {
                let table = this.tableData;
                table.forEach((val) => {
                    if (val.hashCode === hashCode) {
                        val.pauseed = boo;
                    }
                });
                this.tableData = table;
            },
        },
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259

总结

加油噢!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/528160
推荐阅读
相关标签
  

闽ICP备14008679号