vue断点续传一
程序开发
2023-09-05 10:53:10
vue+spring 断点续传(一)
本文主要利用vue和SpringBoot实现了断点续传,大文件分割传递。本章主要讲述前端实现,后端实现请查看 断点续传二
一、断点续传要点
断点续传的核心就是文件分割,对于大文件来说断点续传是十分有必要的,如若直接上传大文件则很有可能会使网络请求时间过长,并且不能保证在上传过程中网络没有波动,因此大文件上传需要使用分片上传,分片的大小需要根据实际情况确定,本文则粗略以5MB计算
二、前端实现
1.环境
vue2、element-ui2.3.6、 spark-md5 3.0.1
2.html
html主要是运用的element的upload组件和table表格,没有进行任何美化(只进行了一点点美化-_-)
将文件拖到此处,或点击上传{{ scope.row.fileName }} 上传 暂停
.upload-demo {display: flex;flex-direction: column;align-items: center;
}
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;});},//分片产生md5getMD5Code(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 buffercurrentChunk++;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);// 存储filethis.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);},// 存储filesaveFile(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;},},};
总结
加油噢!
标签:
上一篇:
element table报错‘tableId‘ of undefined“
下一篇:
相关文章
-
无相关信息