大文件上传断点续传的实现方法

大文件上传断点续传的实现是现代Web应用中常见的需求,尤其是在处理大文件上传时,网络不稳定或用户中断操作的情况下,能够从中断处继续上传,而不是重新开始。以下是实现大文件上传断点续传的关键步骤和技术细节:
1. 文件分片
首先,将大文件分割成多个小块(chunks),每个小块的大小可以根据实际情况调整,通常为1MB到5MB。分片的目的是为了减少单次上传的数据量,提高上传的稳定性和效率。
function sliceFile(file, chunkSize) {
const chunks = [];
let start = 0;
while (start < file.size) {
const chunk = file.slice(start, start + chunkSize);
chunks.push(chunk);
start += chunkSize;
}
return chunks;
}
2. 记录上传进度
为了实现断点续传,需要记录每个分片的上传状态。可以使用localStorage
、IndexedDB
或服务器端存储来保存上传进度。
function saveProgress(fileName, chunkIndex) {
localStorage.setItem(`upload_${fileName}`, chunkIndex);
}
function getProgress(fileName) {
return parseInt(localStorage.getItem(`upload_${fileName}`)) || 0;
}
3. 上传分片
使用XMLHttpRequest
或fetch
API将每个分片上传到服务器。在上传之前,检查该分片是否已经上传过,避免重复上传。
async function uploadChunk(fileName, chunk, chunkIndex) {
const formData = new FormData();
formData.append('file', chunk);
formData.append('chunkIndex', chunkIndex);
formData.append('fileName', fileName);
const response = await fetch('/upload', {
method: 'POST',
body: formData,
});
if (response.ok) {
saveProgress(fileName, chunkIndex + 1);
} else {
throw new Error('Upload failed');
}
}
4. 服务器端处理
服务器端需要接收分片并保存到临时目录中。同时,服务器需要记录每个文件的上传进度,以便在客户端请求时返回已上传的分片信息。
// 伪代码示例
app.post('/upload', (req, res) => {
const { fileName, chunkIndex } = req.body;
const chunk = req.files.file;
const tempDir = `./temp/${fileName}`;
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
fs.writeFileSync(`${tempDir}/${chunkIndex}`, chunk.data);
res.sendStatus(200);
});
5. 合并分片
当所有分片上传完成后,服务器端需要将这些分片合并成完整的文件。
// 伪代码示例
app.post('/merge', (req, res) => {
const { fileName } = req.body;
const tempDir = `./temp/${fileName}`;
const chunks = fs.readdirSync(tempDir).sort((a, b) => a - b);
const output = fs.createWriteStream(`./uploads/${fileName}`);
chunks.forEach(chunk => {
const chunkPath = `${tempDir}/${chunk}`;
const chunkData = fs.readFileSync(chunkPath);
output.write(chunkData);
fs.unlinkSync(chunkPath);
});
output.end();
res.sendStatus(200);
});
6. 断点续传
在上传开始时,客户端首先向服务器查询已上传的分片信息,然后从断点处继续上传。
async function startUpload(file) {
const fileName = file.name;
const chunks = sliceFile(file, 1024 * 1024); // 1MB chunks
const startChunk = getProgress(fileName);
for (let i = startChunk; i < chunks.length; i++) {
try {
await uploadChunk(fileName, chunks[i], i);
} catch (error) {
console.error('Upload failed:', error);
break;
}
}
if (getProgress(fileName) === chunks.length) {
await fetch('/merge', {
method: 'POST',
body: JSON.stringify({ fileName }),
headers: { 'Content-Type': 'application/json' },
});
localStorage.removeItem(`upload_${fileName}`);
}
}
7. 错误处理与重试机制
在上传过程中,可能会遇到网络错误或其他异常情况。为了提高上传的可靠性,可以实现重试机制,当上传失败时自动重试几次。
async function uploadWithRetry(fileName, chunk, chunkIndex, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
await uploadChunk(fileName, chunk, chunkIndex);
return;
} catch (error) {
if (i === retries - 1) throw error;
}
}
}
8. 前端框架集成
如果使用React、Vue等前端框架,可以将上述逻辑封装成可复用的组件或Hook,方便在多个项目中复用。
// React Hook 示例
function useFileUpload() {
const [progress, setProgress] = useState(0);
const uploadFile = async (file) => {
const chunks = sliceFile(file, 1024 * 1024);
const startChunk = getProgress(file.name);
for (let i = startChunk; i < chunks.length; i++) {
await uploadWithRetry(file.name, chunks[i], i);
setProgress(((i + 1) / chunks.length) * 100);
}
await fetch('/merge', {
method: 'POST',
body: JSON.stringify({ fileName: file.name }),
headers: { 'Content-Type': 'application/json' },
});
localStorage.removeItem(`upload_${file.name}`);
};
return { uploadFile, progress };
}
总结
大文件上传断点续传的实现涉及文件分片、上传进度记录、分片上传、服务器端处理、分片合并等多个步骤。通过合理的设计和实现,可以有效提高大文件上传的稳定性和用户体验。