ToB企服应用市场:ToB评测及商务社交产业平台

标题: Vue 大文件切片上传实现指南包会,含【并发上传切片,断点续传,服务器合并 [打印本页]

作者: 一给    时间: 2024-6-15 03:49
标题: Vue 大文件切片上传实现指南包会,含【并发上传切片,断点续传,服务器合并
Vue 大文件切片上传实现指南

背景

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

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

后端需要支持的API接口

为了支持前端的大文件上传和处置惩罚逻辑,后端需要提供以下API接口:
   
     
     
     
  这些API合起来支持了一个分块上传文件的完备流程,包罗文件的校验、切片的上传、切片的合并,以及上传进度查询。这个流程可以有用地处置惩罚大文件上传,减少网络传输的负担,提高上传的可靠性,并允许上传过程中的停息和恢复。
实行流程

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

步骤一:用户选择文件

        用户通过 <input type="file"> 选择文件后,handleFileChange 事故被触发。在这个事故处置惩罚函数中,我们首先获取到用户选择的文件,然后计算文件的 MD5 值,以此作为文件的唯一标识。这一步是实现断点续传和秒传功能的关键。
  1. <template>
  2.   <div>
  3.     <!-- 文件选择框,仅接受视频文件 -->
  4.     <input type="file" @change="handleFileChange" accept="video/*" />
  5.     <!-- 上传按钮 -->
  6.     <button @click="handleUpload">Upload</button>
  7.     <!-- 上传进度显示 -->
  8.     <div v-if="uploadProgress > 0">
  9.       Upload Progress: {{ uploadProgress }}%
  10.     </div>
  11.   </div>
  12. </template>
复制代码
步骤二:计算文件 MD5

        使用 spark-md5 库计算文件的 MD5 值。通过FileReader API 读取文件内容,然后计算其 MD5 值。这个过程大概会耗费一些时间,因此使用 Promise 来异步处置惩罚。
  1. async computeFileHash(file) {
  2.   const spark = new SparkMD5.ArrayBuffer();
  3.   const fileReader = new FileReader();
  4.   return new Promise((resolve) => {
  5.     fileReader.onload = (e) => {
  6.       spark.append(e.target.result);
  7.       const hash = spark.end();
  8.       resolve(hash);
  9.     };
  10.     fileReader.readAsArrayBuffer(file);
  11.   });
  12. }
复制代码
步骤三:检查文件状态,检查文件是否已经上传照旧部分上传

        在上传文件之前,先向服务器发送哀求,检查这个文件是否已经部分或全部上传过。这一步是实现断点续传的关键。服务器根据文件的 MD5 值返回已上传的切片信息或表示文件完全上传的状态。
  1. // 向服务器查询文件是否已经部分或完全上传
  2. async checkFile(fileHash) {  
  3. <---  此处应替换为你的接口调用代码  --->
  4. // 假设接口返回 { shouldUpload: boolean, uploadedChunks: Array<number> }
  5.   return { shouldUpload: true, uploadedChunks: [] };
  6. },
复制代码
步骤四:切片并预备上传

        根据服务器返回的信息,如果文件未完全上传,我们将文件分割成多个切片。然后根据已上传的切片信息,跳过那些已经上传的切片,仅上传剩余的切片。
        切片并预备上传在sliceFileAndUpload方法中实现。这个方法首先计算了整个文件应该被分割成多少切片(基于设定的切片巨细),然后根据服务器返回的已上传切片信息(uploadedChunks),它会跳过这些已经上传的切片,只将剩余的切片添加到哀求池(requestPool)中预备上传。
  1. // 切片并准备上传
  2. sliceFileAndUpload(fileHash, uploadedChunks) {
  3.     const chunkSize = 10 * 1024 * 1024; // 切片大小,这里是10MB
  4.     this.chunkCount = Math.ceil(this.selectedFile.size / chunkSize); // 计算总切片数
  5.     this.uploadProgress = 0; // 重置上传进度
  6.     for (let i = 0; i < this.chunkCount; i++) {
  7.       if (uploadedChunks.includes(i)) continue; // 跳过已上传的切片
  8.       const chunk = this.selectedFile.slice(i * chunkSize, (i + 1) * chunkSize); // 获取切片
  9.       this.requestPool.push({ chunk, index: i }); // 加入请求池
  10.     }
  11.     this.processPool(fileHash); // 开始处理请求池
  12.   },
复制代码
上面这段代码中,uploadedChunks参数是一个数组,包罗了所有已上传切片的索引。通过检查当前切片的索引是否包罗在这个数组中,代码决定是否跳过当前切片的上传。如果索引不在uploadedChunks中,这意味着该切片还没有被上传,因此需要将其添加到requestPool中等待上传。如许,只有那些未上传的切片会被实际上传,从而实现了断点续传的功能。processPool进行并发切片上传
步骤五:并发上传切片

        为了提高上传效率,我们使用并发上传的方式。设置最大并发数,控制同时上传的切片数量。通过逐一上传切片,并监听每个上传哀求的完成,从而动态调解并发哀求。
        并发上传切片的逻辑主要在processPool方法中实现。这个方法负责管理并发哀求,确保同时只有一定数量的上传哀求在处置惩罚中。这通过一个简单的哀求池(requestPool)和控制最大并发数量(MAX_REQUEST)来实现。
  1. // 处理请求池中的切片上传
  2. processPool(fileHash) {
  3.   while (this.requestPool.length > 0 && this.MAX_REQUEST > 0) {
  4.     const { chunk, index } = this.requestPool.shift(); // 取出一个待上传的切片
  5.     this.uploadChunk(chunk, fileHash, index) // 上传切片
  6.       .then(() => {
  7.         this.uploadedChunksCount++; // 更新已上传切片数量
  8.         this.uploadProgress = ((this.uploadedChunksCount / this.chunkCount) * 100).toFixed(2); // 更新上传进度
  9.         if (this.requestPool.length > 0) {
  10.           this.processPool(fileHash); // 继续处理请求池
  11.         } else if (this.uploadedChunksCount === this.chunkCount) {
  12.           // 所有切片都已上传,通知服务器合并
  13.           this.notifyServerToMerge(fileHash);
  14.         }
  15.       })
  16.       .finally(() => {
  17.         this.MAX_REQUEST++; // 释放一个请求槽
  18.       });
  19.     this.MAX_REQUEST--; // 占用一个请求槽
  20.   }
  21. },
复制代码
        在这个方法中,while循环检查哀求池中是否还有待处置惩罚的切片,而且当前活泼的哀求数量是否小于允许的最大并发数量MAX_REQUEST。如果这两个条件都满意,它会从哀求池中取出一个切片,并调用uploadChunk方法来上传它,同时减少MAX_REQUEST的值来反映一个新的哀求已经开始。
        当一个切片上传完成后,then回调函数会增长已上传切片的计数并更新上传进度。如果哀求池中还有待上传的切片,它会递归调用processPool来处置惩罚下一个切片。一旦所有切片都上传完成,它会调用notifyServerToMerge来通知服务器所有切片已经上传完毕,可以合并成一个完备的文件。通过这种方式,代码可以或许在保持最大并发限制的同时,高效地处置惩罚切片的上传。
步骤六:服务器合并切片

        所有切片上传完成后,客户端向服务器发送一个合并切片的哀求。服务器接收到哀求后,将所有切片合并成原始文件,并返回合并效果。
  1. // 通知服务器合并切片
  2. notifyServerToMerge(fileHash) {
  3.   // 通知服务器合并切片,应替换为真实的合并API调用
  4.   console.log(`通知服务器将文件与哈希合并: ${fileHash}`);
  5. },
复制代码
        一个API调用,向服务器发送一个哀求来触发合并已上传切片的操纵。这个哀求通常会携带一些须要的信息,比如文件的唯一标识(在这个例子中是fileHash),以及大概还有其他诸如文件名、文件巨细、切片数量等信息,这些信息取决于服务器端合并切片的详细要求。
        服务器收到合并哀求后,会根据提供的信息找到所有相关的切片,按正确的次序将它们合并成一个完备的文件,并将该文件存储在服务器上的适当位置。完成这个过程后,服务器大概还会向客户端发送一个相应,通知合并操纵的效果(乐成或失败),以及大概的后续步骤或需要的信息。
        通过上述步骤,实现了一个高效稳定的大文件上传功能,极大提升了用户体验。
全部代码

  1. <template>
  2.   <div>
  3.     <!-- 文件选择框,仅接受视频文件 -->
  4.     <input type="file" @change="handleFileChange" accept="video/*" />
  5.     <!-- 上传按钮 -->
  6.     <button @click="handleUpload">Upload</button>
  7.     <!-- 上传进度显示 -->
  8.     <div v-if="uploadProgress > 0">
  9.       Upload Progress: {{ uploadProgress }}%
  10.     </div>
  11.   </div>
  12. </template>
  13. <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>
复制代码
效果:


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4