阅读 351

Element的upload实现分片上传和断点续传功能

最近领导安排实习生做分片上传和断点续传的功能,实习生找我,之前刚好弄过了解一些就记录一下。我们需要上传一个大文件,比如上G的视频文件,通常我们后端会对上传文件进行限制,一般不宜过大,5MB左右最好。如果文件过大,超出了http服务端请求大小限制,请求时间超时,传输中断导致上传失败,那么我们可以将文件进行分片上传。

分片上传的原理

分片上传的原理就是在前端将文件分片,然后一片一片的传给服务端,由服务端对分片的文件进行合并,从而完成大文件的上传。分片上传可以解决文件上传过程中超时、传输中断造成的上传失败,而且一旦上传失败后,已经上传的分片不用再次上传,不用重新上传整个文件,因此采用分片上传可以实现断点续传以及跨浏览器上传文件。

使用Element的upload来实现

至于Element的引入就不多做赘述,直接贴代码。

<template>   <div>     <el-upload       action       :auto-upload="false"       :show-file-list="false"       :on-change="changeFile"     >       <el-button size="small" type="primary">选择文件</el-button>       <div slot="tip" class="el-upload__tip">         1.上传文件不超过100M<br />2.只能上传一个文件<br />3.等待进度条出现√才是上传完成       </div>     </el-upload>     <!-- PROGRESS -->     <div class="progress">       <span>上传进度:{{ total | totalText }}%</span>       <el-link         type="primary"         v-if="total > 0 && total < 100"         @click="handleBtn"         >{{ btn | btnText }}</el-link       >     </div>   </div> </template> 复制代码

上面这部分是上传的页面代码部分,有上传、暂停、继续等基础功能。

<script> import SparkMD5 from "spark-md5"; import { fileParse } from "@/public/utils.js"; import { getStorage } from "lesso-common/public/utils"; import axios from "axios"; export default {   data() {     return {       total: 0,       btn: false,       abort: false,       uploadSuc: false,       slicesNum: null,       chunkNumber: null,       userInfo: {         userName: getStorage("userData").user.employeeName,         userId: getStorage("userData").user.userId,         groupName: "AVM",       },     };   },   filters: {     btnText(btn) {       return btn ? "继续" : "暂停";     },     totalText(total) {       return total > 100 ? 100 : total;     },   },   methods: {     // 分片上传     async changeFile(file) {       if (!file) return;       file = file.raw;       this.total = 0;       this.abort = false;       this.btn = false;       let buffer = await fileParse(file, "buffer"),         spark = new SparkMD5.ArrayBuffer(),         hash,         suffix;       console.log(file, "file");       spark.append(buffer);       hash = spark.end();       suffix = /\.([0-9a-zA-Z]+)$/i.exec(file.name)[1];       let multiple = [];       for (let i = 1; i < file.size / 2; i++) {         if (file.size % i == 0 && i <= 100) {           multiple.push(i);         }       }       // 切片个数       this.slicesNum = multiple.pop();       // 创建切片       let partList = [],         partsize = file.size / this.slicesNum,         cur = 0;       for (let i = 1; i <= this.slicesNum; i++) {         let item = {           chunk: file.slice(cur, cur + partsize),           filename: `${hash}_${i}.${suffix}`,           chunkNumber: `${i}`,           file: file.slice(cur, cur + partsize),         };         cur += partsize;         partList.push(item);       }       this.requestData = {         groupName: this.userInfo.groupName,         fileMd5: hash,         name: file.name,         size: file.size,         totalChunks: this.slicesNum,         chunkSize: partsize,         uid: this.userInfo.userId,         uname: this.userInfo.userName,       };       this.partList = partList;       this.sendRequest();     },     async sendRequest() {       this.uploadSuc = false;       // 根据100个切片创造100个请求集合       let requestList = [];       this.partList.forEach((item, index) => {         // 每一个函数都发送一个切片请求         let fn = async (chunkNumber) => {           let formData = new FormData(),             shardFile = new SparkMD5.ArrayBuffer(),             shardFileBuffer = await fileParse(item.chunk, "buffer"),             shardFileHash;           shardFile.append(shardFileBuffer);           shardFileHash = shardFile.end();           formData.append(             "chunkNumber",             chunkNumber ? chunkNumber : item.chunkNumber           );           formData.append("groupName", this.requestData.groupName);           formData.append("file", item.file);           formData.append("fileMd5", this.requestData.fileMd5);           formData.append("name", this.requestData.name);           formData.append("size", this.requestData.size);           formData.append("totalChunks", this.requestData.totalChunks);           formData.append("chunkSize", this.requestData.chunkSize);           formData.append("chunkMd5", shardFileHash);           formData.append("uid", this.requestData.uid);           formData.append("uname", this.requestData.uname);           return axios             .post(               "http://iotupdateapi.lesso.com/uploadFastdfs/uploadPart",               formData,               {                 headers: { "Content-Type": "multipart/form-data" },               }             )             .then((res) => {               const { code, data } = res.data;               if (code == 206) {                 this.total += parseInt(100 / this.slicesNum);                 // 传完的切片我们把它移除掉                 if (chunkNumber) {                   this.partList.splice(data.chunkNumber - 1, 1);                 }                 return data.chunkNumber;               } else if (code == 200) {                 this.uploadSuc = true;               }             });         };         requestList.push(fn);       });       let i = 0;       let send = async () => {         if (this.abort) return;         if (i >= requestList.length && !this.uploadSuc) {           this.$message.warning("上传失败,请重新上传");           this.total = 0;           return;         }         if (this.uploadSuc) {           this.$message.success("上传成功");           this.total = 100;           return;         }         await requestList[i](this.chunkNumber).then((res) => {           this.chunkNumber = res;         });         i++;         send();       };       send();     },     handleBtn() {       if (this.btn) {         this.abort = false;         this.btn = false;         this.sendRequest();         return;       }       this.btn = true;       this.abort = true;     },   }, }; </script> 复制代码

上面这部分是分片上传和断点续传的代码逻辑实现。
本来是想每次都直接创建100个切片进行上传,后来接口的参数要求每片切片的大小必须要是整数,所有就又做了一次处理,算出文件可以整除的数字,取最接近小于等于100的的数字进行切片处理。
把每个切片转换成每个请求放进数组,根据每次调接口的返回值来处理数组实现分片上传和断点续传。

utils的fileParse方法

export function fileParse(file, type = "base64") {     return new Promise(resolve => {         let fileRead = new FileReader();         if (type === "base64") {             fileRead.readAsDataURL(file);         } else if (type === "buffer") {             fileRead.readAsArrayBuffer(file);         }         fileRead.onload = (ev) => {             resolve(ev.target.result);         };     }); }; 复制代码

再贴一下接口的参数

WechatIMG744.png

分片上传和断点续传demo


作者:王小辉
链接:https://juejin.cn/post/7020321256582938655

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐