大文件切片上传
背景介绍:当涉及大文件上传时,一种有效的方法是将大文件分割成小切片并逐个上传。这种技术不仅可以减轻服务器的负担,还可以避免上传过程中的中断和内存问题。本文将介绍如何使用JavaScript实现大文件切片上传,并解释如何处理断点续传、并发控制以及上传取消等问题,用到的知识点有大文件切片、中止上传 、上传进度、断点续传、并发数量控制。
切片上传原理
大文件切片上传的基本原理是将文件分成多个固定大小的小切片,然后逐个上传到服务器。服务器收到这些切片后,可以按顺序合并它们,还原为完整的文件。这种方法不仅能提高上传效率,还能应对网络波动和服务器性能限制。
实现步骤
下面是实现大文件切片上传的基本步骤:
切片生成: 使用JavaScript将大文件切割成小切片,可以使用File API中的File.slice()方法。
切片上传: 将切片逐个上传到服务器,可以使用XMLHttpRequest或Fetch API发送切片数据。
服务器处理: 服务器接收并保存切片,并记录每个切片的索引。
切片合并: 当所有切片上传完成后,服务器按照索引顺序合并切片(或者后端提供合并接口将hash值传给后端进行合并),还原为完整文件。
断点续传
通过记录已上传切片的索引,即使上传过程中断,也可以从断点处继续上传。服务器只需判断已存在的切片,避免重新上传。
并发控制
为了避免同时上传大量切片导致网络拥塞,可以控制并发上传数。例如,一次最多只上传3个切片,等待其中一个完成后再上传下一个。
上传取消
允许用户在上传过程中取消操作是很重要的。为此,可以使用一个标记来判断是否取消上传,如果取消,则终止上传过程。
接下来,我们将深入探讨如何在JavaScript中实现这些功能。从切片生成和上传开始,逐步讲解如何处理断点续传、并发控制和上传取消。
话不多说,上代码
<template><div class="manage"><input type="file" @change="handleFileChange" multiple /><button @click="stop">停止上传</button><span>当前已上传:{{progress}}%</span></div>
</template><script>
export default {name: 'Menu',props: {},mounted () {},data () {return {progress: 0,stopUpload: false,totalChunks: null,selectedFiles: [],uploadPromises: [],concurrentLimit: 2 // 控制并发数量}},methods: {handleFileChange (event) {this.selectedFiles = Array.from(event.target.files)this.startUpload(event.target.files)},async uploadFile (file, index, total) {return new Promise((resolve, reject) => {// 模拟上传操作,实际上传操作可能需要使用 XMLHttpRequest 或其他上传库// setTimeout(async () => {// console.log(`Uploaded: ${file.name}`)// const formData = new FormData();// formData.append('fileChunk', file);// formData.append('chunkIndex', index);// formData.append('totalChunks', total);// const response = await fetch('upload-url', {// method: 'POST',// body: formData// });// const result = await response.json();// resolve(result)// }, 1000) // 假设上传耗时 1 秒setTimeout(async () => {console.log(`Uploaded: ${file.name}`)resolve()}, 2000) // 假设上传耗时 1 秒})},async startUpload (file) {console.log('file', file)this.uploadPromises = []const size = 1024 * 1024const formNum = Math.ceil(file[0].size / size)const hashes = []for (let i = 0; i <= formNum; i++) {const start = size * i;const end = size * (i + 1)const chunk = file[0].slice(start, end);const hash = await this.calculateHash(chunk);console.log('this.stopUpload', this.stopUpload)if (this.stopUpload) return falsehashes.push(hash)// 将需要上传的切片和对应的哈希值添加到FormData中// 断点续传逻辑需要后端判断,前端根据片段生成唯一hash值,后端存储hash,续传时候需要判断若已上传直接给出进度const promise = this.uploadFile(chunk, i, formNum)this.uploadPromises.push(promise)if (this.uploadPromises.length >= this.concurrentLimit) {await Promise.race(this.uploadPromises) // 控制并发this.uploadPromises.shift() // 移除已完成的 Promise}this.progress = (i / formNum) * 100}console.log('hashes', hashes)await Promise.all(this.uploadPromises) // 等待剩余的上传完成console.log('全部完成上传!')// 调用后端提供合并接口// const response = await fetch('upload-merge', {// method: 'POST',// body: hashes// })},async calculateHash (chunk) {const buffer = await chunk.arrayBuffer()const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)const hashArray = Array.from(new Uint8Array(hashBuffer))const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('')return hashHex},stop () {this.stopUpload = true}}
}
</script>