Vue 大文件切片上传实现指南包会,含【并发上传切片,断点续传,服务器合并切片,计算文件MD5,上传进度显示,秒传】等功能

Vue 大文件切片上传实现指南

背景

        在Web开发中,文件上传是一个常见的功能需求,尤其是当涉及到大文件上传时,为了提高上传的稳定性和效率,文件切片上传技术便显得尤为重要。通过将大文件切分成多个小块(切片)进行上传,不仅可以有效减少单次上传的数据量,降低网络波动对上传过程的影响,还能实现如断点续传、秒传等高级功能。本文将以Vue为框架,配合 Axios 进行 HTTP 请求,详细介绍如何实现一个支持文件切片上传的功能。

前端准备工作

        在开始编码之前,请确保你的项目中已经安装了 axiosspark-md5 两个库。axios 用于发起网络请求,spark-md5 用于计算文件的 MD5 值,从而支持秒传和断点续传功能。

前端需要实现的功能

  1. 文件选择和限制:

    通过<input type="file" @change="handleFileChange" accept="video/*" />实现了文件的选择,同时限制了用户只能选择视频文件进行上传。

  2. 计算文件的MD5值

    computeFileHash方法中,利用SparkMD5库计算用户选中文件的MD5值。这一步是为了之后能够校验文件的完整性和唯一性。

  3. 校验文件是否需要上传

    checkFile方法中,通过向服务器查询文件的MD5值,判断该文件是否已经上传过,以此实现秒传功能,避免重复上传相同文件。

  4. 文件切片

    sliceFileAndUpload方法中,将大文件切割成多个小片段(切片),这样做的目的是为了支持大文件的分块上传,提高上传效率,同时也便于出错时重新上传单个切片而不是整个文件。

  5. 并发上传切片

    通过processPooluploadChunk方法实现切片的并发上传,限制了最大并发数(MAX_REQUEST),以免过多并发请求压崩服务器。

  6. 上传进度反馈

    通过uploadProgress数据和模板中的进度显示,用户可以实时看到文件上传的进度。

  7. 服务器通知合并切片

    在所有切片上传完成后,通过notifyServerToMerge方法向服务器发送通知,请求服务器端进行切片的合并,以重建原始文件。

后端需要支持的API接口

为了支持前端的大文件上传和处理逻辑,后端需要提供以下API接口:

  1. 文件校验API
  • 功能:检查文件的完整性和上传状态。这通常通过文件的唯一标识(如MD5哈希值)来实现。
  • 输入参数:文件唯一标识(如MD5哈希值)。
  • 返回值:告知客户端该文件是否已经存在,如果存在,是否完整。如果文件已经存在且不完整,则返回已上传的切片信息。
  1. 切片上传API
  • 功能:接收文件的单个切片,并保存到服务器的临时存储位置。
  • 输入参数:文件的唯一标识,切片内容,切片的序号。
  • 返回值:确认切片上传成功或失败的状态。
  1. 切片合并API
  • 功能:将所有上传的切片合并成一个完整的文件。
  • 输入参数:文件的唯一标识,可能还包括文件名、总切片数等信息。
  • 返回值:合并操作的成功或失败状态,以及最终文件的访问URL(可选)。
  1. 上传进度查询API
  • 功能:查询文件上传的进度,这对于恢复上传和提供用户反馈非常有用。
  • 输入参数:文件的唯一标识。
  • 返回值:已上传的切片列表或上传进度百分比。

这些API合起来支持了一个分块上传文件的完整流程,包括文件的校验切片的上传切片的合并,以及上传进度查询。这个流程可以有效地处理大文件上传,减少网络传输的负担,提高上传的可靠性,并允许上传过程中的暂停和恢复

执行流程

        一开始用户通过界面选择一个文件进行上传,进行文件选择,用户通过文件选择框悬着一个大文件,比如视频文件,触发handleFileChange方法,然后再计算这个大文件的MD5,使用computeFileHash方法计算选中文件的MD5哈希值,计算完成后检查文件是否需要上传,向服务器发起请求,根据文件的MD5哈希值执行checkFile方法检查文件是否已经存在,如果文件已经存在通知用户秒传功能并将上传进度设为100%,如果文件需要上传,则使用sliceFileAndUpload方法将文件切成很多个小块,每个切片及其索引都被添加到requestPool请求池中,从requestPool中并发上传切片processPool方法,对每个切片调用uploadChunk方法进行实际上传,通过MAX_REQUEST控制并发上传的数量,没上传一个切片,uploadChunksCount增加,并更新上传进度。所有切片上传完成后,通知服务器合并这些切片notifyServerToMerge,当服务器成功合并所有切片成原始后,整个切片上传流程完成。

实现步骤

步骤一:用户选择文件

        用户通过 <input type="file"> 选择文件后,handleFileChange 事件被触发。在这个事件处理函数中,我们首先获取到用户选择的文件,然后计算文件的 MD5 值,以此作为文件的唯一标识。这一步是实现断点续传秒传功能的关键。

<template>
  <div>
    <!-- 文件选择框,仅接受视频文件 -->
    <input type="file" @change="handleFileChange" accept="video/*" />
    <!-- 上传按钮 -->
    <button @click="handleUpload">Upload</button>
    <!-- 上传进度显示 -->
    <div v-if="uploadProgress > 0">
      Upload Progress: {{ uploadProgress }}%
    </div>
  </div>
</template>

步骤二:计算文件 MD5

        使用 spark-md5 库计算文件的 MD5 值。通过FileReader API 读取文件内容,然后计算其 MD5 值。这个过程可能会花费一些时间,因此使用 Promise 来异步处理。

async computeFileHash(file) {
  const spark = new SparkMD5.ArrayBuffer();
  const fileReader = new FileReader();
  return new Promise((resolve) => {
    fileReader.onload = (e) => {
      spark.append(e.target.result);
      const hash = spark.end();
      resolve(hash);
    };
    fileReader.readAsArrayBuffer(file);
  });
}

步骤三:检查文件状态,检查文件是否已经上传还是部分上传

        在上传文件之前,先向服务器发送请求,检查这个文件是否已经部分或全部上传过。这一步是实现断点续传的关键。服务器根据文件的 MD5 值返回已上传的切片信息或表示文件完全上传的状态。

 // 向服务器查询文件是否已经部分或完全上传
async checkFile(fileHash) {  
 <---  此处应替换为你的接口调用代码  --->
 // 假设接口返回 { shouldUpload: boolean, uploadedChunks: Array<number> }
  return { shouldUpload: true, uploadedChunks: [] };
},

步骤四:切片并准备上传

        根据服务器返回的信息,如果文件未完全上传,我们将文件分割成多个切片。然后根据已上传的切片信息,跳过那些已经上传的切片,仅上传剩余的切片。
        切片并准备上传在sliceFileAndUpload方法中实现。这个方法首先计算了整个文件应该被分割成多少切片(基于设定的切片大小),然后根据服务器返回的已上传切片信息(uploadedChunks),它会跳过这些已经上传的切片,只将剩余的切片添加到请求池(requestPool)中准备上传。

 // 切片并准备上传
sliceFileAndUpload(fileHash, uploadedChunks) {
    const chunkSize = 10 * 1024 * 1024; // 切片大小,这里是10MB
    this.chunkCount = Math.ceil(this.selectedFile.size / chunkSize); // 计算总切片数
    this.uploadProgress = 0; // 重置上传进度
    for (let i = 0; i < this.chunkCount; i++) {
      if (uploadedChunks.includes(i)) continue; // 跳过已上传的切片
      const chunk = this.selectedFile.slice(i * chunkSize, (i + 1) * chunkSize); // 获取切片
      this.requestPool.push({ chunk, index: i }); // 加入请求池
    }
    this.processPool(fileHash); // 开始处理请求池
  },

上面这段代码中,uploadedChunks参数是一个数组,包含了所有已上传切片的索引。通过检查当前切片的索引是否包含在这个数组中,代码决定是否跳过当前切片的上传。如果索引不在uploadedChunks中,这意味着该切片还没有被上传,因此需要将其添加到requestPool中等待上传。这样,只有那些未上传的切片会被实际上传,从而实现了断点续传的功能。processPool进行并发切片上传

步骤五:并发上传切片

        为了提高上传效率,我们使用并发上传的方式。设置最大并发数,控制同时上传的切片数量。通过逐一上传切片,并监听每个上传请求的完成,从而动态调整并发请求。
        并发上传切片的逻辑主要在processPool方法中实现。这个方法负责管理并发请求,确保同时只有一定数量的上传请求在处理中。这通过一个简单的请求池(requestPool)和控制最大并发数量(MAX_REQUEST)来实现。

// 处理请求池中的切片上传
processPool(fileHash) {
  while (this.requestPool.length > 0 && this.MAX_REQUEST > 0) {
    const { chunk, index } = this.requestPool.shift(); // 取出一个待上传的切片
    this.uploadChunk(chunk, fileHash, index) // 上传切片
      .then(() => {
        this.uploadedChunksCount++; // 更新已上传切片数量
        this.uploadProgress = ((this.uploadedChunksCount / this.chunkCount) * 100).toFixed(2); // 更新上传进度
        if (this.requestPool.length > 0) {
          this.processPool(fileHash); // 继续处理请求池
        } else if (this.uploadedChunksCount === this.chunkCount) {
          // 所有切片都已上传,通知服务器合并
          this.notifyServerToMerge(fileHash);
        }
      })
      .finally(() => {
        this.MAX_REQUEST++; // 释放一个请求槽
      });
    this.MAX_REQUEST--; // 占用一个请求槽
  }
},

        在这个方法中,while循环检查请求池中是否还有待处理的切片,并且当前活跃的请求数量是否小于允许的最大并发数量MAX_REQUEST。如果这两个条件都满足,它会从请求池中取出一个切片,并调用uploadChunk方法来上传它,同时减少MAX_REQUEST的值来反映一个新的请求已经开始。
        当一个切片上传完成后,then回调函数会增加已上传切片的计数并更新上传进度。如果请求池中还有待上传的切片,它会递归调用processPool来处理下一个切片。一旦所有切片都上传完成,它会调用notifyServerToMerge来通知服务器所有切片已经上传完毕,可以合并成一个完整的文件。通过这种方式,代码能够在保持最大并发限制的同时,高效地处理切片的上传。

步骤六:服务器合并切片

        所有切片上传完成后,客户端向服务器发送一个合并切片的请求。服务器接收到请求后,将所有切片合并成原始文件,并返回合并结果。

// 通知服务器合并切片
notifyServerToMerge(fileHash) {
  // 通知服务器合并切片,应替换为真实的合并API调用
  console.log(`通知服务器将文件与哈希合并: ${fileHash}`);
},

        一个API调用,向服务器发送一个请求来触发合并已上传切片的操作。这个请求通常会携带一些必要的信息,比如文件的唯一标识(在这个例子中是fileHash),以及可能还有其他诸如文件名文件大小切片数量等信息,这些信息取决于服务器端合并切片的具体要求。
        服务器收到合并请求后,会根据提供的信息找到所有相关的切片,按正确的顺序将它们合并成一个完整的文件,并将该文件存储在服务器上的适当位置。完成这个过程后,服务器可能还会向客户端发送一个响应,通知合并操作的结果(成功或失败),以及可能的后续步骤或需要的信息。
        通过上述步骤,实现了一个高效稳定的大文件上传功能,极大提升了用户体验。

全部代码

<template>
  <div>
    <!-- 文件选择框,仅接受视频文件 -->
    <input type="file" @change="handleFileChange" accept="video/*" />
    <!-- 上传按钮 -->
    <button @click="handleUpload">Upload</button>
    <!-- 上传进度显示 -->
    <div v-if="uploadProgress > 0">
      Upload Progress: {{ uploadProgress }}%
    </div>
  </div>
</template>

<script>
import axios from "axios";
import SparkMD5 from "spark-md5"; // 引入SparkMD5用于计算文件的MD5值

export default {
  data() {
    return {
      selectedFile: null, // 用户选择的文件
      uploadProgress: 0, // 上传进度
      requestPool: [], // 请求池,存储待上传的切片信息
      MAX_REQUEST: 6, // 最大并发请求数量
      chunkCount: 0, // 文件切片总数
      uploadedChunksCount: 0, // 已上传的切片数量
    };
  },
  methods: {
    // 处理文件选择事件
    async handleFileChange(event) {
      this.selectedFile = event.target.files[0];
      if (!this.selectedFile) return; // 未选择文件则返回
      // 可以在这里添加文件格式校验
      const fileHash = await this.computeFileHash(this.selectedFile); // 计算文件hash
      const { shouldUpload, uploadedChunks } = await this.checkFile(fileHash); // 检查文件是否需要上传
      if (!shouldUpload) {
        alert("文件已存在,秒传成功!");
        this.uploadProgress = 100; // 直接设置进度为100%
        return;
      }
      this.sliceFileAndUpload(fileHash, uploadedChunks); // 切片并上传
    },
    // 计算文件的MD5
    computeFileHash(file) {
      return new Promise((resolve) => {
        const spark = new SparkMD5.ArrayBuffer();
        const fileReader = new FileReader();
        fileReader.onload = (e) => {
          spark.append(e.target.result);
          const hash = spark.end();
          resolve(hash); // 返回计算得到的hash值
        };
        fileReader.readAsArrayBuffer(file);
      });
    },
    // 检查文件是否已经上传过
    async checkFile(fileHash) {
      // 应替换为真实的API调用来检查文件状态
      return { shouldUpload: true, uploadedChunks: [] }; // 模拟返回值
    },
    // 切片并准备上传
    sliceFileAndUpload(fileHash, uploadedChunks) {
      const chunkSize = 10 * 1024 * 1024; // 切片大小,这里是10MB
      this.chunkCount = Math.ceil(this.selectedFile.size / chunkSize); // 计算总切片数
      this.uploadProgress = 0; // 重置上传进度
      for (let i = 0; i < this.chunkCount; i++) {
        if (uploadedChunks.includes(i)) continue; // 跳过已上传的切片
        const chunk = this.selectedFile.slice(i * chunkSize, (i + 1) * chunkSize); // 获取切片
        this.requestPool.push({ chunk, index: i }); // 加入请求池
      }
      this.processPool(fileHash); // 开始处理请求池
    },
    // 处理请求池中的切片上传
    processPool(fileHash) {
      while (this.requestPool.length > 0 && this.MAX_REQUEST > 0) {
        const { chunk, index } = this.requestPool.shift(); // 取出一个待上传的切片
        this.uploadChunk(chunk, fileHash, index) // 上传切片
          .then(() => {
            this.uploadedChunksCount++; // 更新已上传切片数量
            this.uploadProgress = ((this.uploadedChunksCount / this.chunkCount) * 100).toFixed(2); // 更新上传进度
            if (this.requestPool.length > 0) {
              this.processPool(fileHash); // 继续处理请求池
            } else if (this.uploadedChunksCount === this.chunkCount) {
              // 所有切片都已上传,通知服务器合并
              this.notifyServerToMerge(fileHash);
            }
          })
          .finally(() => {
            this.MAX_REQUEST++; // 释放一个请求槽
          });
        this.MAX_REQUEST--; // 占用一个请求槽
      }
    },
    // 上传单个切片
    async uploadChunk(chunk, fileHash, index) {
      const formData = new FormData();
      formData.append("chunk", chunk);
      formData.append("hash", fileHash);
      formData.append("index", index);
      // 替换为真实的上传URL,并根据需要实现onUploadProgress
      await axios.post("上传URL", formData);
    },
    // 通知服务器合并切片
    notifyServerToMerge(fileHash) {
      // 通知服务器合并切片,应替换为真实的合并API调用
      console.log(`通知服务器将文件与哈希合并: ${fileHash}`);
    },
  },
};
</script>

效果:
Vue 大文件切片上传实现指南包会,含【并发上传切片,断点续传,服务器合并切片,计算文件MD5,上传进度显示,秒传】等功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/514656.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小程序滑动删除组件+全选批量删除组件+附源码

小程序滑动删除组件全选批量删除组件附源码 说明 使用 uni-app、uview 组件开发&#xff0c;全端&#xff08;微信小程序、QQ小程序、抖音小程序等等&#xff09; 支持滑动删除组件、支持左滑删除、长按进入批量删除、全选删除、长按弹窗删除、 组件式开发&#xff0c;文章…

LabVIEW太赫兹波扫描成像系统

LabVIEW太赫兹波扫描成像系统 随着科技的不断发展&#xff0c;太赫兹波成像技术因其非电离性、高穿透性和高分辨率等特点&#xff0c;在生物医学、材料质量无损检测以及公共安全等领域得到了广泛的应用。然而&#xff0c;在实际操作中&#xff0c;封闭性较高的信号采集软件限制…

Leetcode 674. 最长连续递增序列

心路历程&#xff1a; 这道题和递增子序列的一样&#xff0c;由于题目中要求连续&#xff0c;实际上会让状态转移更加简单&#xff0c;因为候选的动作集合相当于更小了。 状态&#xff1a;nums的区间[0, i]&#xff0c;第i个元素和第i-1个元素的大小关系 动作&#xff1a;是否…

Vue3_2024_7天【回顾上篇watch常见的后两种场景】___续

Vue3中监听多条数据的两种使用 1.watch【使用上一章写法&#xff0c;监听两个属性&#xff0c;然后执行相应操作…】 2.watchEffect【相对于使用watch&#xff0c;watchEffect默认页面初始加载&#xff0c;有点类似加配置&#xff1a;立即执行 immediate】 代码&#xff1a; …

无熟人难办事?--迪米特法则

1.1 第一天上班 第一天上班&#xff0c;电脑安装工作但是安装的同事小张刚巧有事要忙&#xff0c;主管有事也出去了&#xff0c;没有人搭理。小张快下班的时候才回来&#xff0c;开始帮我装系统&#xff0c;加域&#xff0c;设置密码等。 1.2 无熟人难办事 管理上出了问题&a…

文献阅读:将条形码神经解剖学与空间转录分析相结合,可以识别投射神经元相关基因

文献介绍 「文献题目」 Integrating barcoded neuroanatomy with spatial transcriptional profiling enables identification of gene correlates of projections 「研究团队」 Anthony M. Zador&#xff08;美国冷泉港实验室&#xff09; 「发表时间」 2021-05-10 「发表期…

Rust---复合数据类型之枚举、数组

目录 枚举的使用Option 枚举数组的使用输出结果 枚举&#xff08;Enum&#xff09;&#xff1a;表示一个类型可以有多个不同的取值。枚举类型可以包含不同的变体&#xff08;variants&#xff09;&#xff0c;每个变体可以有不同的数据类型。 枚举的使用 enum Direction {Up,…

[QOpenGLWidget+QMouseEvent]实时绘制长方形

复现moho-打卡第1天 - 20240402 1.1--QOpenGLWidget中显示长方形 实现方法&#xff1a;顶点着色器中给定长方形的四个顶点数据&#xff0c;代码如下&#xff1a; // 顶点位置 GLfloat vertics[1][4][3] { {{mousePressPosX,mousePressPosY,0.0},{mousePressPosX,mouseMoveP…

hexo博客7:构建简单的多层安全防御体系

【hexo博客7】构建简单的多层安全防御体系 写在最前面理解全面安全策略的重要性防御常见的网络攻击1. SQL注入攻击2. 文件上传漏洞3. 跨站脚本攻击&#xff08;XSS&#xff09;4. 跨站请求伪造&#xff08;CSRF&#xff09;5. 目录遍历/本地文件包含&#xff08;LFI/RFI&#x…

CAN的协议层

CAN属于异步通讯&#xff0c;没有时钟信号线&#xff0c;连接在同一个总线网络中的各个节点会像串口异步通讯那样&#xff0c;节点间使用约定好的波特率进行通信&#xff0c;特别的&#xff0c;CAN还会使用“位同步”的方式来抗干扰&#xff0c;吸收误差&#xff0c;实现对总线…

Docker实战教程 第2章 Docker基础

3-1 Docker介绍 什么是Docker 虚拟化&#xff0c;容器 Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从Apache2.0协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&…

【活动预告】SLMOps 系列(三)|SLMOps 的最后一公里 - 模型评估技巧

点击蓝字 关注我们 编辑&#xff1a;Alan Wang 排版&#xff1a;Rani Sun 2023年&#xff0c;Azure OpenAI Service 引领了 AI 2.0 时代的热潮&#xff0c;各行业企业都在 AI 模型的探索与应用中持续发力。相比复杂度更高的大模型&#xff0c;有时候轻量且高效的小模型&#xf…

【windows自带exe】使用`findstr.exe`来搜索包含某个字符串的文件

【windows自带exe】使用findstr.exe来搜索包含某个字符串的文件 写在最前面什么是findstr.exe?如何使用findstr.exe查找文件中的字符串&#xff1f;示例&#xff1a;查找JSP文件中包含"jsonp"的文件 注意事项小结 &#x1f308;你好呀&#xff01;我是 是Yu欸 &…

基于Python的简单颜色替换

当我们临时需要改变一个照片的颜色&#xff0c;使其符合我们想要的主题色时&#xff0c;对于不会PS的我就只能使用一下Python来实现这个简单的过程 比如我想要中国农大农学院的院徽&#xff0c;但在官网上提取出来的图片是白色的 而我想要符合农学主题的绿色&#xff0c;将图片…

go juc 线程中的子类

1.go test() 主死随从 package mainimport ("fmt""strconv""time" )func test() {for i : 1; i < 10; i {fmt.Println("hello " strconv.Itoa(i))//阻塞time.Sleep(time.Second)} } func main() {//开启协程go test()for i : 1; …

使用pip安装geopandas(24.4更新)

geopandas是我们用Python进行地理分析常用的库&#xff0c;在数据处理、分析、制图等场景中有着极为广泛的应用&#xff0c;但是在安装过程中会出现各种问题。​geopandas的安装方式有很多&#xff0c;今天我们选取较为简单的pip来进行geopandas的安装。 ​首先&#xff0c;我…

Java多态世界(day18)

多态&#xff1a;重写的方法调用和执行 1.静态绑定&#xff1a;编译器在父类中找方法&#xff0c;如&#xff1a; 上面的eat&#xff08;&#xff09;方法是先在父类中找方法&#xff0c;父类没有的话&#xff0c;就算子类有编译也会报错。&#xff08;如果引用方法在父类中存…

深度学习:神经网络模型的剪枝和压缩简述

深度学习的神经网路的剪枝和压缩&#xff0c;大致的简述&#xff0c; 主要采用&#xff1a; network slimming&#xff0c;瘦身网络... 深度学习网络&#xff0c;压缩的主要方式&#xff1a; 1.剪枝&#xff0c;nerwork pruing&#xff0c; 2.稀疏表示&#xff0c;sparse rep…

React + 项目(从基础到实战) -- 第三期

react内置hooks useState 如何让页面动起来(实时更新) import React,{FC,useState} from "react";const Demo:FC()>{let count0; //普通js变量无法触发组件更新function add(){count;console.log("count: ",count);}return <div><button onCl…

(免费分享)基于微信小程序自助停取车收费系统

本项目的开发和制作主要采用Java语言编写&#xff0c;SpringBoot作为项目的后端开发框架&#xff0c;vue作为前端的快速开发框架&#xff0c;主要基于ES5的语法&#xff0c;客户端采用微信小程序作为开发。Mysql8.0作为数据库的持久化存储。 获取完整源码&#xff1a; 大家点赞…