原文地址


  • 分片上传(将文件拆分多个小文件,依次上传)
    • 创建切片,循环分解文件即可
  • 断点续传(控制只上传已经上传的文件内容)
  • 秒传(若存在就不重新上传便可)

凡事要知其然知其所以然

文件上传相信很多朋友都有遇到过,那或许你也遇到过当上传大文件时,上传时间较长,且经常失败的困扰,并且失败后,又得重新上传很是烦人。那我们先了解下失败的原因吧!
据我了解大概有以下原因:

  1. 服务器配置限制:例如在PHP中默认的文件上传大小为8M【post_max_size = 8m】,若你在一个请求体中放入8M以上的内容时,便会出现异常
  2. 请求超时:当你设置了接口的超时时间为10s,那么上传大文件时,一个接口响应时间超过10s,那么便会被Faild掉。
  3. 网络波动:这个就属于不可控因素,也是较常见的问题。


基于以上原因,聪明的人们就想到了,将文件拆分多个小文件,依次上传,不就解决以上1,2问题嘛,这便是分片上传。
网络波动这个实在不可控,也许一阵大风刮来,就断网了呢。那这样好了,既然断网无法控制,那我可以控制只上传已经上传的文件内容,不就好了,这样大大加快了重新上传的速度。所以便有了“断点续传”一说。

此时,人群中有人插了一嘴,有些文件我已经上传一遍了,为啥还要在上传,能不能不浪费我流量和时间。喔…这个嘛,简单,每次上传时判断下是否存在这个文件,若存在就不重新上传便可,于是又有了“秒传”一说。从此这”三兄弟” 便自行CP,统治了整个文件界。”

注意文中的代码并非实际代码,请移步至github查看最新代码 github.com/pseudo-god/…


分片上传

HTML

原生INPUT样式较丑,这里通过样式叠加的方式,放一个Button.

  1. <div class="btns">
  2. <el-button-group>
  3. <el-button :disabled="changeDisabled">
  4. <i class="el-icon-upload2 el-icon--left" size="mini"></i>选择文件
  5. <input
  6. v-if="!changeDisabled"
  7. type="file"
  8. :multiple="multiple"
  9. class="select-file-input"
  10. :accept="accept"
  11. @change="handleFileChange"
  12. />
  13. </el-button>
  14. <el-button :disabled="uploadDisabled" @click="handleUpload()"><i class="el-icon-upload el-icon--left" size="mini"></i>上传</el-button>
  15. <el-button :disabled="pauseDisabled" @click="handlePause"><i class="el-icon-video-pause el-icon--left" size="mini"></i>暂停</el-button>
  16. <el-button :disabled="resumeDisabled" @click="handleResume"><i class="el-icon-video-play el-icon--left" size="mini"></i>恢复</el-button>
  17. <el-button :disabled="clearDisabled" @click="clearFiles"><i class="el-icon-video-play el-icon--left" size="mini"></i>清空</el-button>
  18. </el-button-group>
  19. var chunkSize = 10 * 1024 * 1024; // 切片大小
  20. var fileIndex = 0; // 当前正在被遍历的文件下标
  21. data: () => ({
  22. container: {
  23. files: null
  24. },
  25. tempFilesArr: [], // 存储files信息
  26. cancels: [], // 存储要取消的请求
  27. tempThreads: 3,
  28. // 默认状态
  29. status: Status.wait
  30. }),

一个稍微好看的UI就出来了。
2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图1

选择文件

选择文件过程中,需要对外暴露出几个钩子,熟悉elementUi的同学应该很眼熟,这几个钩子基本与其一致。onExceed:文件超出个数限制时的钩子、beforeUpload:文件上传之前
fileIndex 这个很重要,因为是多文件上传,所以定位当前正在被上传的文件就很重要,基本都靠它

  1. handleFileChange(e) {
  2. const files = e.target.files;
  3. if (!files) return;
  4. Object.assign(this.$data, this.$options.data()); // 重置data所有数据
  5. fileIndex = 0; // 重置文件下标
  6. this.container.files = files;
  7. // 判断文件选择的个数
  8. if (this.limit && this.container.files.length > this.limit) {
  9. this.onExceed && this.onExceed(files);
  10. return;
  11. }
  12. // 因filelist不可编辑,故拷贝filelist 对象
  13. var index = 0; // 所选文件的下标,主要用于剔除文件后,原文件list与临时文件list不对应的情况
  14. for (const key in this.container.files) {
  15. if (this.container.files.hasOwnProperty(key)) {
  16. const file = this.container.files[key];
  17. if (this.beforeUpload) {
  18. const before = this.beforeUpload(file);
  19. if (before) {
  20. this.pushTempFile(file, index);
  21. }
  22. }
  23. if (!this.beforeUpload) {
  24. this.pushTempFile(file, index);
  25. }
  26. index++;
  27. }
  28. }
  29. },
  30. // 存入 tempFilesArr,为了上面的钩子,所以将代码做了拆分
  31. pushTempFile(file, index) {
  32. // 额外的初始值
  33. const obj = {
  34. status: fileStatus.wait,
  35. chunkList: [],
  36. uploadProgress: 0,
  37. hashProgress: 0,
  38. index
  39. };
  40. for (const k in file) {
  41. obj[k] = file[k];
  42. }
  43. console.log('pushTempFile -> obj', obj);
  44. this.tempFilesArr.push(obj);
  45. }

分片上传

  • 创建切片,循环分解文件即可 ```javascript createFileChunk(file, size = chunkSize) { const fileChunkList = []; var count = 0; while (count < file.size) {
    1. fileChunkList.push({
    2. file: file.slice(count, count + size)
    3. });
    4. count += size;
    } return fileChunkList; }
  1. - 循环创建切片,既然咱们做的是多文件,所以这里就有循环去处理,依次创建文件切片,及切片的上传。
  2. ```javascript
  3. async handleUpload(resume) {
  4. if (!this.container.files) return;
  5. this.status = Status.uploading;
  6. const filesArr = this.container.files;
  7. var tempFilesArr = this.tempFilesArr;
  8. for (let i = 0; i < tempFilesArr.length; i++) {
  9. fileIndex = i;
  10. //创建切片
  11. const fileChunkList = this.createFileChunk(
  12. filesArr[tempFilesArr[i].index]
  13. );
  14. tempFilesArr[i].fileHash ='xxxx'; // 先不用看这个,后面会讲,占个位置
  15. tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({
  16. fileHash: tempFilesArr[i].hash,
  17. fileName: tempFilesArr[i].name,
  18. index,
  19. hash: tempFilesArr[i].hash + '-' + index,
  20. chunk: file,
  21. size: file.size,
  22. uploaded: false,
  23. progress: 0, // 每个块的上传进度
  24. status: 'wait' // 上传状态,用作进度状态显示
  25. }));
  26. //上传切片
  27. await this.uploadChunks(this.tempFilesArr[i]);
  28. }
  29. }
  • 上传切片,这个里需要考虑的问题较多,也算是核心吧,uploadChunks方法只负责构造传递给后端的数据,核心上传功能放到sendRequest方法中 ```javascript async uploadChunks(data) { var chunkData = data.chunkList; const requestDataList = chunkData .map(({ fileHash, chunk, fileName, index }) => {

    1. const formData = new FormData();
    2. formData.append('md5', fileHash);
    3. formData.append('file', chunk);
    4. formData.append('fileName', index); // 文件名使用切片的下标
    5. return { formData, index, fileName };

    });

    try { await this.sendRequest(requestDataList, chunkData); } catch (error) { // 上传有被reject的 this.$message.error(‘亲 上传失败了,考虑重试下呦’ + error); return; }

    // 合并切片 const isUpload = chunkData.some(item => item.uploaded === false); console.log(‘created -> isUpload’, isUpload); if (isUpload) { alert(‘存在失败的切片’); } else { // 执行合并 await this.mergeRequest(data); } }

  1. sendReques。上传这是最重要的地方,也是容易失败的地方,假设有10个分片,那我们若是直接发10个请求的话,很容易达到浏览器的瓶颈,所以需要对请求进行并发处理。
  2. - 并发处理:这里我使用for循环控制并发的初始并发数,然后在 handler 函数里调用自己,这样就控制了并发。在handler中,通过数组API.shift模拟队列的效果,来上传切片。
  3. - 重试: retryArr 数组存储每个切片文件请求的重试次数,做累加。比如[1,0,2],就是第0个文件切片报错1次,第2个报错2次。为保证能与文件做对应,const index = formInfo.index; 我们直接从数据中拿之前定义好的index 若失败后,将失败的请求重新加入队列即可。
  4. - 关于并发及重试我写了一个小Demo,若不理解可以自己在研究下,文件地址:[github.com/pseudo-god/…](https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fpseudo-god%2Fvue-simple-upload%2Fblob%2Fmaster%2Fsrc%2Futils%2FsendRequest-domo.js) , 重试代码好像被我弄丢了,大家要是有需求,我再补吧!
  5. ```javascript
  6. // 并发处理
  7. sendRequest(forms, chunkData) {
  8. var finished = 0;
  9. const total = forms.length;
  10. const that = this;
  11. const retryArr = []; // 数组存储每个文件hash请求的重试次数,做累加 比如[1,0,2],就是第0个文件切片报错1次,第2个报错2次
  12. return new Promise((resolve, reject) => {
  13. const handler = () => {
  14. if (forms.length) {
  15. // 出栈
  16. const formInfo = forms.shift();
  17. const formData = formInfo.formData;
  18. const index = formInfo.index;
  19. instance.post('fileChunk', formData, {
  20. onUploadProgress: that.createProgresshandler(chunkData[index]),
  21. cancelToken: new CancelToken(c => this.cancels.push(c)),
  22. timeout: 0
  23. }).then(res => {
  24. console.log('handler -> res', res);
  25. // 更改状态
  26. chunkData[index].uploaded = true;
  27. chunkData[index].status = 'success';
  28. finished++;
  29. handler();
  30. })
  31. .catch(e => {
  32. // 若暂停,则禁止重试
  33. if (this.status === Status.pause) return;
  34. if (typeof retryArr[index] !== 'number') {
  35. retryArr[index] = 0;
  36. }
  37. // 更新状态
  38. chunkData[index].status = 'warning';
  39. // 累加错误次数
  40. retryArr[index]++;
  41. // 重试3次
  42. if (retryArr[index] >= this.chunkRetry) {
  43. return reject('重试失败', retryArr);
  44. }
  45. this.tempThreads++; // 释放当前占用的通道
  46. // 将失败的重新加入队列
  47. forms.push(formInfo);
  48. handler();
  49. });
  50. }
  51. if (finished >= total) {
  52. resolve('done');
  53. }
  54. };
  55. // 控制并发
  56. for (let i = 0; i < this.tempThreads; i++) {
  57. handler();
  58. }
  59. });
  60. }
  • 切片的上传进度,通过axios的onUploadProgress事件,结合createProgresshandler方法进行维护 ```javascript // 切片上传进度 createProgresshandler(item) { return p => { item.progress = parseInt(String((p.loaded / p.total) * 100)); this.fileProgress(); }; }
  1. <a name="D4JvN"></a>
  2. ## Hash计算
  3. 其实就是算一个文件的MD5值,MD5在整个项目中用到的地方也就几点。
  4. - 秒传,需要通过MD5值判断文件是否已存在。
  5. - 续传:需要用到MD5作为key值,当唯一值使用。
  6. 本项目主要使用worker处理,性能及速度都会有很大提升.<br />由于是多文件,所以HASH的计算进度也要体现在每个文件上,所以这里使用全局变量fileIndex来定位当前正在被上传的文件<br />![](https://cdn.nlark.com/yuque/0/2022/webp/21425465/1643446563941-af3eacb4-4dc9-4b45-ab13-185128f8ee4e.webp#clientId=u210cd86b-71fd-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u0acd8701&margin=%5Bobject%20Object%5D&originHeight=399&originWidth=1009&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u468db543-dc7b-456c-b51f-d237aed64c7&title=)<br />![](https://cdn.nlark.com/yuque/0/2022/webp/21425465/1643446572354-c2dc0181-d365-4f94-90f1-1871f0bcd0a9.webp#clientId=u210cd86b-71fd-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u37186d0a&margin=%5Bobject%20Object%5D&originHeight=549&originWidth=1009&originalType=url&ratio=1&rotation=0&showTitle=false&status=done&style=none&taskId=u44a3f9a5-8453-4250-8ef9-7e49d2bffdb&title=)
  7. ```javascript
  8. // 生成文件 hash(web-worker)
  9. calculateHash(fileChunkList) {
  10. return new Promise(resolve => {
  11. this.container.worker = new Worker('./hash.js');
  12. this.container.worker.postMessage({ fileChunkList });
  13. this.container.worker.onmessage = e => {
  14. const { percentage, hash } = e.data;
  15. if (this.tempFilesArr[fileIndex]) {
  16. this.tempFilesArr[fileIndex].hashProgress = Number(
  17. percentage.toFixed(0)
  18. );
  19. }
  20. if (hash) {
  21. resolve(hash);
  22. }
  23. };
  24. });
  25. }

因使用worker,所以我们不能直接使用NPM包方式使用MD5。需要单独去下载spark-md5.js文件,并引入

  1. //hash.js
  2. self.importScripts("/spark-md5.min.js"); // 导入脚本
  3. // 生成文件 hash
  4. self.onmessage = e => {
  5. const { fileChunkList } = e.data;
  6. const spark = new self.SparkMD5.ArrayBuffer();
  7. let percentage = 0;
  8. let count = 0;
  9. const loadNext = index => {
  10. const reader = new FileReader();
  11. reader.readAsArrayBuffer(fileChunkList[index].file);
  12. reader.onload = e => {
  13. count++;
  14. spark.append(e.target.result);
  15. if (count === fileChunkList.length) {
  16. self.postMessage({
  17. percentage: 100,
  18. hash: spark.end()
  19. });
  20. self.close();
  21. } else {
  22. percentage += 100 / fileChunkList.length;
  23. self.postMessage({
  24. percentage
  25. });
  26. loadNext(count);
  27. }
  28. };
  29. };
  30. loadNext(0);
  31. };

文件合并

当我们的切片全部上传完毕后,就需要进行文件的合并,这里我们只需要请求接口即可

  1. mergeRequest(data) {
  2. const obj = {
  3. md5: data.fileHash,
  4. fileName: data.name,
  5. fileChunkNum: data.chunkList.length
  6. };
  7. instance.post('fileChunk/merge', obj,
  8. {
  9. timeout: 0
  10. })
  11. .then((res) => {
  12. this.$message.success('上传成功');
  13. });
  14. }

Done: 至此一个分片上传的功能便已完成

断点续传

顾名思义,就是从那断的就从那开始,明确思路就很简单了。一般有2种方式,一种为服务器端返回,告知我从那开始,还有一种是浏览器端自行处理。2种方案各有优缺点。本项目使用第二种。
思路:已文件HASH为key值,每个切片上传成功后,记录下来便可。若需要续传时,直接跳过记录中已存在的便可。本项目将使用Localstorage进行存储,这里我已提前封装好addChunkStorage、getChunkStorage方法。
存储在Stroage的数据

2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图2

缓存处理

在切片上传的axios成功回调中,存储已上传成功的切片

  1. instance.post('fileChunk', formData, )
  2. .then(res => {
  3. // 存储已上传的切片下标
  4. + this.addChunkStorage(chunkData[index].fileHash, index);
  5. handler();
  6. })

在切片上传前,先看下localstorage中是否存在已上传的切片,并修改uploaded

  1. async handleUpload(resume) {
  2. + const getChunkStorage = this.getChunkStorage(tempFilesArr[i].hash);
  3. tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({
  4. + uploaded: getChunkStorage && getChunkStorage.includes(index), // 标识:是否已完成上传
  5. + progress: getChunkStorage && getChunkStorage.includes(index) ? 100 : 0,
  6. + status: getChunkStorage && getChunkStorage.includes(index)? 'success'
  7. + : 'wait' // 上传状态,用作进度状态显示
  8. }));
  9. }

构造切片数据时,过滤掉uploaded为true的

  1. async uploadChunks(data) {
  2. var chunkData = data.chunkList;
  3. const requestDataList = chunkData
  4. + .filter(({ uploaded }) => !uploaded)
  5. .map(({ fileHash, chunk, fileName, index }) => {
  6. const formData = new FormData();
  7. formData.append('md5', fileHash);
  8. formData.append('file', chunk);
  9. formData.append('fileName', index); // 文件名使用切片的下标
  10. return { formData, index, fileName };
  11. })
  12. }

垃圾文件清理

随着上传文件的增多,相应的垃圾文件也会增多,比如有些时候上传一半就不再继续,或上传失败,碎片文件就会增多。解决方案我目前想了2种

  • 前端在localstorage设置缓存时间,超过时间就发送请求通知后端清理碎片文件,同时前端也要清理缓存。
  • 前后端都约定好,每个缓存从生成开始,只能存储12小时,12小时后自动清理


以上2中方案似乎都有点问题,极有可能造成前后端因时间差,引发切片上传异常的问题,后面想到合适的解决方案再来更新吧。
Done: 续传到这里也就完成了。

秒传

这算是最简单的,只是听起来很厉害的样子。原理:计算整个文件的HASH,在执行上传操作前,向服务端发送请求,传递MD5值,后端进行文件检索。若服务器中已存在该文件,便不进行后续的任何操作,上传也便直接结束。大家一看就明白

  1. async handleUpload(resume) {
  2. if (!this.container.files) return;
  3. const filesArr = this.container.files;
  4. var tempFilesArr = this.tempFilesArr;
  5. for (let i = 0; i < tempFilesArr.length; i++) {
  6. const fileChunkList = this.createFileChunk(
  7. filesArr[tempFilesArr[i].index]
  8. );
  9. // hash校验,是否为秒传
  10. + tempFilesArr[i].hash = await this.calculateHash(fileChunkList);
  11. + const verifyRes = await this.verifyUpload(
  12. + tempFilesArr[i].name,
  13. + tempFilesArr[i].hash
  14. + );
  15. + if (verifyRes.data.presence) {
  16. + tempFilesArr[i].status = fileStatus.secondPass;
  17. + tempFilesArr[i].uploadProgress = 100;
  18. + } else {
  19. console.log('开始上传切片文件----》', tempFilesArr[i].name);
  20. await this.uploadChunks(this.tempFilesArr[i]);
  21. }
  22. }
  23. }
  1. // 文件上传之前的校验: 校验文件是否已存在
  2. verifyUpload(fileName, fileHash) {
  3. return new Promise(resolve => {
  4. const obj = {
  5. md5: fileHash,
  6. fileName,
  7. ...this.uploadArguments //传递其他参数
  8. };
  9. instance
  10. .post('fileChunk/presence', obj)
  11. .then(res => {
  12. resolve(res.data);
  13. })
  14. .catch(err => {
  15. console.log('verifyUpload -> err', err);
  16. });
  17. });
  18. }

Done: 秒传到这里也就完成了。


后端处理

文章好像有点长了,具体代码逻辑就先不贴了,除非有人留言要求,嘻嘻,有时间再更新

Node版

请前往 github.com/pseudo-god/… 查看

JAVA版

下周应该会更新处理

PHP版

1年多没写PHP了,抽空我会慢慢补上来

待完善

  • 切片的大小:这个后面会做出动态计算的。需要根据当前所上传文件的大小,自动计算合适的切片大小。避免出现切片过多的情况。
  • 文件追加:目前上传文件过程中,不能继续选择文件加入队列。(这个没想好应该怎么处理。)

    更新记录

    组件已经运行一段时间了,期间也测试出几个问题,本来以为没BUG的,看起来BUG都挺严重

    1、当同时上传多个内容相同但是文件名称不同的文件时,出现上传失败的问题。

    预期结果:第一个上传成功后,后面相同的问文件应该直接秒传
    实际结果:第一个上传成功后,其余相同的文件都失败,错误信息,块数不对。
    原因:当第一个文件块上传完毕后,便立即进行了下一个文件的循环,导致无法及时获取文件是否已秒传的状态,从而导致失败。
    解决方案:在当前文件分片上传完毕并且请求合并接口完毕后,再进行下一次循环。
    将子方法都改为同步方式,mergeRequest 和 uploadChunks 方法

2、当每次选择相同的文件并触发beforeUpload方法时,若第二次也选择了相同的文件,beforeUpload方法失效,从而导致整个流程失效。

原因:之前每次选择文件时,没有清空上次所选input文件的数据,相同数据的情况下,是不会触发input的change事件。
解决方案:每次点击input时,清空数据即可。我顺带优化了下其他的代码,具体看提交记录吧。

  1. <input
  2. v-if="!changeDisabled"
  3. type="file"
  4. :multiple="multiple"
  5. class="select-file-input"
  6. :accept="accept"
  7. + οnclick="f.outerHTML=f.outerHTML"
  8. @change="handleFileChange"/>

重写了暂停和恢复的功能,实际上,主要是增加了暂停和恢复的状态
2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图3
2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图4
之前的处理逻辑太简单粗暴,存在诸多问题。现在将状态定位在每一个文件之上,这样恢复上传时,直接跳过即可
2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图5
2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图6

3、重写文件选择逻辑、增加追加文件功能、代码优化

因fileList无法修改,所以我之前是拷贝处理了一份,实现比较麻烦,增加了整个代码的冗余度,也不好处理文件追加。查看ElementUi源码发现了更好的解决方案,通过Array.prototype.slice.call(files) 将FileList转为数组。
以下只列出重点修改部分,实际整个代码都做了优化,健壮性及可读性提升了一小段,后面会考虑引入部分设计模式进行更好的处理。

  1. handleFileChange(e) {
  2. const files = e.target.files;
  3. if (!files) return;
  4. fileIndex = 0; // 重置文件下标
  5. // 判断文件选择的个数
  6. if (this.limit && files.length > this.limit) {
  7. this.onExceed && this.onExceed(files);
  8. return;
  9. }
  10. this.status = Status.wait;
  11. + const postFiles = Array.prototype.slice.call(files);
  12. + postFiles.forEach((item) => {
  13. + this.handleStart(item);
  14. });
  15. }
  16. //格式化files、beforeUpload钩子处理、文件的追加也是通过此函数控制
  17. + handleStart(rawFile) {
  18. // 初始化部分自定义属性
  19. + rawFile.status = fileStatus.wait;
  20. + rawFile.chunkList = [];
  21. + rawFile.uploadProgress = 0;
  22. + rawFile.hashProgress = 0;
  23. + if (this.beforeUpload) {
  24. + const before = this.beforeUpload(rawFile);
  25. + if (before) {
  26. + this.uploadFiles.push(rawFile); //追加文件
  27. + }
  28. + }
  29. + if (!this.beforeUpload) {
  30. + this.uploadFiles.push(rawFile); //追加文件
  31. + }
  32. + }

4、处理暂停恢复后,进度条后退的问题

当我们点击恢复后,由于会重新上传未上传成功的切片,所以,进度条就会有倒退的情况,针对这个问题,处理也简单。
定义临时变量fakeUploadProgress在每次暂停时存储当前的进度,在上传恢复后,当当前进度大于fakeUploadProgress的进度,再进行赋值即可。

  1. handleStart(rawFile) {
  2. // 初始化部分自定义属性
  3. ...
  4. rawFile.uploadProgress = 0;
  5. + rawFile.fakeUploadProgress = 0; // 假进度条,处理恢复上传后,进度条后移的问题
  6. rawFile.hashProgress = 0;
  7. if (this.beforeUpload)...

在暂停时,将当前的进度赋值给临时变量

  1. // 暂停上传
  2. handlePause() {
  3. this.status = Status.pause;
  4. if (this.uploadFiles.length) {
  5. const currentFile = this.uploadFiles[fileIndex];
  6. currentFile.status = fileStatus.pause;
  7. // 将当前进度赋值给假进度条
  8. + currentFile.fakeUploadProgress = currentFile.uploadProgress;
  9. }
  10. while (this.cancels.length > 0) {
  11. this.cancels.pop()('取消请求');
  12. }
  13. }


在计算进度时,处理真假进度条

  1. // 文件总进度
  2. fileProgress() {
  3. const currentFile = this.uploadFiles[fileIndex];
  4. if (currentFile) {
  5. const uploadProgress = currentFile.chunkList.map((item) => item.size * item.progress).reduce((acc, cur) => acc + cur);
  6. const currentFileProgress = parseInt((uploadProgress / currentFile.size).toFixed(2));
  7. // 真假进度条处理--处理进度条后移
  8. + if (!currentFile.fakeUploadProgress) {
  9. + currentFile.uploadProgress = currentFileProgress;
  10. + this.$set(this.uploadFiles, fileIndex, currentFile);
  11. + } else if (currentFileProgress > currentFile.fakeUploadProgress) {
  12. + currentFile.uploadProgress = currentFileProgress;
  13. + this.$set(this.uploadFiles, fileIndex, currentFile);
  14. + }
  15. }
  16. }

封装组件

写了一大堆,其实以上代码你直接复制也无法使用,这里我将此封装了一个组件。大家可以去github下载文件,里面有使用案例 ,若有用记得随手给个star,谢谢!
偷个懒,具体封装组件的代码就不列出来了,大家直接去下载文件查看,若有不明白的,可留言。

组件文档

Attribute

参数 类型 说明 默认 备注
headers Object 设置请求头
before-upload Function 上传文件前的钩子,返回false则停止上传
accept String 接受上传的文件类型
upload-arguments Object 上传文件时携带的参数
with-credentials Boolean 是否传递Cookie false
limit Number 最大允许上传个数 0 0为不限制
on-exceed Function 文件超出个数限制时的钩子
multiple Boolean 是否为多选模式 true
base-url String 由于本组件为内置的AXIOS,若你需要走代理,可以直接在这里配置你的基础路径
chunk-size Number 每个切片的大小 10M
threads Number 请求的并发数 3
chunk-retry Number 错误重试次数 3
### Slot
方法名 说明 参数 备注
:——————: :—————————————————————————: :—: :—:
header 按钮区域
tip 提示说明文字


后端接口文档:按文档实现即可

2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图7 2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图8 2022/01/17  【一个多文件断点续传、分片上传、秒传、重试机制的组件】 - 图9
代码地址:github.com/debug-null/…
接口文档地址 docs.apipost.cn/view/0e19f1…