赞
踩
本文主要利用vue和SpringBoot实现了断点续传,大文件分割传递。本章主要讲述前端实现,后端实现请查看 断点续传二
断点续传的核心就是文件分割,对于大文件来说断点续传是十分有必要的,如若直接上传大文件则很有可能会使网络请求时间过长,并且不能保证在上传过程中网络没有波动,因此大文件上传需要使用分片上传,分片的大小需要根据实际情况确定,本文则粗略以5MB计算
vue2、element-ui2.3.6、 spark-md5 3.0.1
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>
.upload-demo {
display: flex;
flex-direction: column;
align-items: center;
}
代码如下(示例):以下代码并没有考虑实际的生产环境,还有很多未完善之处,比如缓存的清除逻辑、并发的控制是否合理等等问题,还请同学们自行完善
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; }, }, };
加油噢!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。