我们前端在大文件上传的时候基本上都会考虑切片上传的方式,那么在切片上传的场景中,断网是最常见的异常情况之一,如果想要断网恢复之后,继续上传,就需要通过断点续传机制来处理。刚好公司做了云盘的功能,遇到了这个切片上传的问题,总结一下,分享给大家~
这个问题的核心目标是:断网后不丢失已上传进度,网络恢复后能从断点继续上传,避免重复上传已完成的切片。
一、断网检测与状态捕获
首先需要准确检测网络状态变化,以便及时触发中断处理逻辑:
- 网络状态监听
利用浏览器提供的navigator.onLine
属性和online
/offline
事件监听网络状态:
javascript
// 监听网络断开
window.addEventListener('offline', () => {
console.log('网络已断开,暂停上传');
pauseUpload(); // 触发上传暂停逻辑
});
// 监听网络恢复
window.addEventListener('online', () => {
console.log('网络已恢复,尝试继续上传');
resumeUpload(); // 触发续传逻辑
});
React 结合Hooks来实现
使用 useEffect 进行事件绑定 / 解绑,确保与组件生命周期同步
初始状态通过 navigator.onLine 获取,避免初始值与实际状态不一致
javascript
import { useState, useEffect } from 'react';
function NetworkStatus() {
// 状态管理:是否在线
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
// 定义事件处理函数
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
// 绑定事件监听
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
// 组件卸载时清除监听(避免内存泄漏)
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []); // 空依赖数组:只在组件挂载时执行一次
return (
<div>
<p>网络状态:{isOnline ? '在线' : '离线'}</p>
</div>
);
}
- 请求层面的异常捕获
即使网络未完全断开(如弱网超时),单个切片上传请求也可能失败,需在fetch
或axios
中捕获异常:
javascript
async function uploadChunk(chunk) {
try {
const response = await fetch('/upload-chunk', {
method: 'POST',
body: chunk.formData,
});
if (!response.ok) throw new Error('请求失败');
return { success: true, chunkIndex: chunk.index };
} catch (error) {
console.log(`切片 ${chunk.index} 上传失败`, error);
return { success: false, chunkIndex: chunk.index };
}
}
二、断网时的核心处理逻辑
断网后需保存当前上传状态,为后续续传提供依据:
- 保存上传进度状态
将已上传的切片信息、文件元数据等保存到本地存储(如localStorage
或IndexedDB
,后者更适合大量数据):
javascript
// 定义需要保存的上传状态结构
const uploadState = {
fileId: 'xxx', // 文件唯一标识(如哈希值)
fileName: 'large-file.zip',
totalChunks: 50, // 总切片数
uploadedChunks: [0, 1, 2, ...], // 已成功上传的切片索引
chunkSize: 2 * 1024 * 1024, // 切片大小
lastUploadedChunk: 23, // 最后一次成功上传的切片索引
fileSize: 100 * 1024 * 1024, // 文件总大小
};
// 保存到 IndexedDB(适合大文件状态)
function saveUploadState(state) {
return indexedDB.transaction('rw', db.uploadStates, () => {
db.uploadStates.put(state);
});
}
- 暂停上传队列
如果使用并发上传(如同时上传3个切片),断网后需终止当前正在进行的请求,并清空等待队列:
ini
let uploadQueue = []; // 等待上传的切片队列
let activeRequests = []; // 当前正在进行的请求
function pauseUpload() {
// 终止所有活跃请求
activeRequests.forEach(abortController => abortController.abort());
// 清空队列(后续续传时重新生成)
uploadQueue = [];
}
- 用户体验提示
向用户展示明确的状态提示(如"网络已断开,已保存上传进度"),避免用户误以为上传失败或需要重新开始。
三、网络恢复后的续传逻辑
网络恢复后,核心是基于已保存的状态,仅上传未完成的切片:
- 恢复上传状态
从本地存储中读取之前保存的uploadState
,确认文件信息和已上传切片:
javascript
async function getSavedState(fileId) {
return indexedDB.transaction('r', db.uploadStates, () => {
return db.uploadStates.get(fileId);
});
}
- 与服务端校验已上传切片
为避免本地状态与服务端不一致(如断网时某切片已上传成功但前端未记录),需向服务端查询当前文件已接收的切片:
javascript
async function getServerUploadedChunks(fileId) {
const response = await fetch(`/get-uploaded-chunks?fileId=${fileId}`);
return response.json(); // 服务端返回已接收的切片索引数组
}
- 生成待上传切片队列
对比本地已上传切片和服务端返回的切片,计算出未上传的切片,生成新的上传队列:
javascript
async function resumeUpload(fileId) {
// 1. 读取本地保存的状态
const localState = await getSavedState(fileId);
if (!localState) {
console.log('无保存的上传状态,需重新上传');
return;
}
// 2. 向服务端校验已上传切片
const serverChunks = await getServerUploadedChunks(fileId);
// 3. 计算未上传的切片(取本地和服务端的并集,排除已上传的)
const allChunks = Array.from({ length: localState.totalChunks }, (_, i) => i);
const uploadedChunks = [...new Set([...localState.uploadedChunks, ...serverChunks])];
const pendingChunks = allChunks.filter(index => !uploadedChunks.includes(index));
// 4. 重新发起上传(仅上传未完成的切片)
uploadPendingChunks(localState, pendingChunks);
}
- 继续上传未完成切片
按原有的并发策略(如同时上传3个),逐个上传pendingChunks
中的切片,并实时更新本地状态和服务端记录。
四、额外的可靠性保障
- 文件唯一标识
用文件内容的哈希值(如md5
或sha256
)作为fileId
,确保即使文件名相同但内容不同时,也能正确区分上传进度。 - 切片上传的幂等性
服务端需支持重复上传同一切片(即多次上传同一fileId
+chunkIndex
时,不会重复存储,直接返回成功),避免网络抖动导致的重复上传问题。 - 定期保存状态
不仅在断网时保存状态,每成功上传一个切片后都更新本地存储,降低因页面刷新/关闭导致的进度丢失风险。 - 超时重试机制
网络恢复后,若首次续传失败(如服务端暂时不可用),可设置有限次数的重试(如最多重试3次),进一步提升可靠性。
总结
断网处理的核心是 "状态保存-校验-续传" 闭环:
- 断网时通过事件监听捕获异常,保存已上传进度到本地;
- 网络恢复后,结合本地状态和服务端校验,确定未上传的切片;
- 仅上传未完成的切片,最终完成整个文件的上传和合并。
总结一下,以免遗忘~