首先,我们需要使用Blob对象的 slice 方法将文件切分成多个切片。
const CHUNK_SIZE = 1024 * 1024; // 我们选择1MB作为每个切片的大小
let file = document.getElementById("upload").files[0]; // 得到所选文件
let totalSize = file.size;
let chunks = []; // 用于存储文件切片的数组
let start = 0; // 初始切片位置while (start < totalSize) {chunks.push(file.slice(start, start + CHUNK_SIZE));start += CHUNK_SIZE; // 移动切片的开始位置
}
(2) 文件合并:
有了文件切片后,我们需要在服务器端合并这些切片以重建文件。这通常在服务端完成,前端需要发送一个带有文件唯一标识的请求通知服务器所有切片已发送完毕,可以进行合并。
let fileName = file.name;
let fileId = fileName + '-' + totalSize; // 这是文件的唯一标识,也可使用其他方式
fetch('serverURL/merge', {method: 'POST',body: JSON.stringify({fileName, fileId}),headers: new Headers({'Content-Type': 'application/json'})
});
当我们在进行大文件切片上传时,可以通过在每个分片中包含一个切片的索引来保持切片的顺序。在接收分片的服务器端,可以根据这个索引来为每个分片分配合适的位置,这样即使上传的请求顺序发生了变化,服务器端仍然可以按照原始文件的顺序来组合这些分片。
在前端,你可以像下面这样为每个分片添加一个索引:
chunks.forEach((chunk, index) => {let chunkForm = new FormData();chunkForm.append('chunk', chunk);chunkForm.append('index', index); // 这里的 'index' 就是切片的索引
});
在服务器端,你可以根据传入的 ‘index’ 来重新组合文件切片,即使分片以无序的方式接收,你仍然可以使用 ‘index’ 参数来保持它们的正确顺序,确保最后合并出来的文件与原文件一致。
例如,如果你在服务器端使用的是Node.js,可能会有类似下面的代码:
// 一个假设的处理文件分片上传的路由处理器
app.post('/upload', (req, res) => {let index = req.body.index; // 获得切片的索引// ...接下来,使用 'index' 来正确放置文件切片...
});
因此,通过为每个文件切片附加一个索引,我们可以确保在服务器端正确地重新组合这些切片,无论客户端以何种顺序发送这些切片。
(3) 断点续传:
要实现断点续传,需要在每次上传切片完成后在本地存储信息。在上传前检查本地是否有相关记录,若有,则跳过该切片,只上传未完成的部分。
let doneList= JSON.parse(localStorage.getItem(fileId)) || [];chunks.forEach((chunk, index) => {if(!doneList.includes(index)){ // 如果列表中没有本切片的索引,表示该切片未上传成功过let chunkForm = new FormData(); // 使用 FormData的方式发送切片chunkForm.append('chunk', chunk);chunkForm.append('index', index);chunkForm.append('fileId', fileId);fetch('serverURL/upload', {method: 'POST',body: chunkForm}).then(res => {doneList.push(index); // 上传完成后,将切片的索引放入doneList,并且存到localStoragelocalStorage.setItem(fileId, JSON.stringify(doneList));});}
});
切片传输失败
判断文件切片是否上传成功的方式通常是通过服务器的响应来实现。在前端发送分片数据到服务器后,服务器会进行处理,如果分片数据被成功接收和存储,服务器通常会返回一个包含状态信息的响应。
在JavaScript中,你可以使用fetch API的.then()方法来处理服务器的响应:
chunks.forEach((chunk, index) => {fetch('serverURL/upload', {method: 'POST',body: chunkForm}).then(response => {if(response.ok) {console.log('切片 '+ index + ' 上传成功!');doneList.push(index); // 将成功上传的切片索引加入到doneList中localStorage.setItem(fileId, JSON.stringify(doneList));} else {console.log('切片 '+ index + ' 上传失败!');}}).catch(error => {console.log('网络错误:', error);})
});
在这个例子中,如果服务器成功处理了上传的切片,会返回一个状态为200的响应,这时,response.ok将为true,我们可以将该切片的索引加入到保存已上传切片的数组中。如果上传失败,或者网络出现错误,我们可以据此进行重试或者报告错误信息。
重试策略
1.处理上传失败的分片并重新上传的最常见策略是使用重试策略。我们可以设置一个最大重试次数,如果上传操作失败,我们会重新尝试上传分片,直到成功或达到最大重试次数。另一个常见策略是增加一个延时,在每次失败后等待一段时间再进行重试,这可以防止短时间内的过多无效尝试。
const MAX_RETRIES = 3; // 最大尝试次数function uploadChunk(chunk, index, retries = 0) {if (retries >= MAX_RETRIES) {console.log('切片 '+ index + ' 上传失败!');return;}fetch('serverURL/upload', {method: 'POST',body: chunkForm}).then(response => {if (!response.ok) throw new Error('Upload failed');console.log('切片 '+ index + ' 上传成功!');doneList.push(index); // 将成功上传的切片索引加入到doneList中localStorage.setItem(fileId, JSON.stringify(doneList));}).catch(error => {console.log('出现错误,准备重试:', error);setTimeout(() => uploadChunk(chunk, index, retries + 1), 1000); // 如果失败,等待1秒后重试});
}chunks.forEach((chunk, index) => {uploadChunk(chunk, index);
});
这个示例首先限制了最大尝试次数,如果达到这个次数,将停止尝试并记录失败信息。如果接收到分片的上传失败的信息,它将等待1秒钟然后重新尝试上传。这种方式可以有效地解决由暂时的网络问题引起的上传失败。
- 指数退避:
指数退避(Exponential Backoff)是一种普遍用于优化网络通信的算法,这种算法通过控制网络请求之间的间隔时间来减少网络拥堵,特别是在由于网络错误而重试请求的场景中。
若要在切片上传失败时应用指数退避策略,你可以使用以下代码:
const MAX_RETRIES = 5; // 最大尝试次数
const RETRY_DELAY = 1000; // 1秒,基准等待时间function uploadChunk(chunk, index, retries = 0) {if (retries >= MAX_RETRIES) {console.log("切片 "+ index + " 上传失败!");return;}fetch('serverURL/upload', {method: "POST",body: chunkForm // 假设 chunkForm 是你的 FormData 对象}).then(response => {if (!response.ok) throw new Error("Upload failed");console.log('切片 '+ index + ' 上传成功!');doneList.push(index); // 将成功上传的切片索引加入到doneList中localStorage.setItem(fileId, JSON.stringify(doneList));}).catch(error => {console.log("上传失败,准备重试", error);// 如果上传切片失败,等待 RETRY_DELAY * (2 ** retries) 毫秒后再次尝试上传setTimeout(() => uploadChunk(chunk, index, retries + 1), RETRY_DELAY * (2 ** retries));});
}chunks.forEach((chunk, index) => {uploadChunk(chunk, index);
});
在这个例子中,RETRY_DELAY是重试之间的裸等待时间,并且每次失败后会增加重试的等待时间,等待时间计算方式为 RETRY_DELAY * (2 ** retries),即每次重试都将等待时间翻倍。这样可以给网络和服务器更多的恢复时间。
当达到MAX_RETRIES最大尝试次数后,就会停止重试并记录上传失败。
-
斐波那契退避:斐波那契退避是一种类似于指数退避的策略,不过它使用的是斐波那契数列而不是指数函数来计算下一次重试之前的暂停时间。
-
随机化间隔:为了防止多个客户端同时发起重试请求并再次使服务器过载(这种情况有时被称为“重试风暴”),一种策略是在每次重试之间添加一个随机化的等待时间。
-
使用重试库:有许多开源库可以使实施复杂的重试策略变得更容易。例如,在JavaScript中,你可以使用async-retry库来实现带有指数退避的重试。
以上就是文章全部内容了,如果喜欢这篇文章的话,还希望三连支持一下,感谢!