大文件分片上传与断点续传实现指南
目录
1. 核心原理
1.1 分片上传
- 将大文件切割为多个小分片(如 5MB/片)
- 并行上传分片,提升传输效率
1.2 断点续传
- 通过文件哈希值唯一标识文件
- 记录已上传分片索引
- 网络中断后跳过已传分片
1.3 自动合并
- 服务器检测分片完整性
- 按序号合并分片生成完整文件
2. 前端实现
2.1 文件分片与哈希计算
javascript
// 文件分片
function splitFile(file, chunkSize = 5 * 1024 * 1024) {
const chunks = [];
let start = 0;
while (start < file.size) {
chunks.push(file.slice(start, start + chunkSize));
start += chunkSize;
}
return chunks;
}
// 计算文件哈希(使用 SparkMD5)
async function calculateFileHash(file) {
// ...(同前文实现)
}
2.2 上传控制(带并发限制)
javascript
class Uploader {
constructor() {
this.MAX_CONCURRENT = 3; // 最大并发数
this.retryLimit = 3; // 分片重试次数
}
async upload(file) {
const fileHash = await calculateFileHash(file);
const { uploadedChunks } = await checkProgress(fileHash);
const chunks = splitFile(file);
const queue = chunks
.map((chunk, index) => ({ chunk, index }))
.filter(({ index }) => !uploadedChunks.includes(index));
// 并发控制
while (queue.length > 0) {
const tasks = queue.splice(0, this.MAX_CONCURRENT);
await Promise.all(tasks.map(task => this.uploadChunk(task)));
}
}
async uploadChunk({ chunk, index }) {
for (let attempt = 0; attempt < this.retryLimit; attempt++) {
try {
const formData = new FormData();
formData.append('chunk', chunk);
formData.append('hash', fileHash);
formData.append('index', index);
await fetch('/api/upload', { method: 'POST', body: formData });
saveProgress(fileHash, index);
return;
} catch (error) {
if (attempt === this.retryLimit - 1) throw error;
}
}
}
}
3. 后端实现
3.1 接口设计
端点 | 方法 | 功能 |
---|---|---|
/api/upload | POST | 上传分片 |
/api/progress | GET | 查询上传进度 |
/api/merge-check | GET | 检查合并状态(可选) |
3.2 核心逻辑
javascript
// 分片存储结构
uploads/
├─ {fileHash}/
│ ├─ 0
│ ├─ 1
│ └─ ... (分片文件)
// 自动合并实现
app.post('/api/upload', (req, res) => {
const { hash, index, total } = req.body;
// 存储分片
saveChunk(hash, index, req.file);
// 检查合并条件
if (isUploadComplete(hash, total)) {
mergeChunks(hash); // 异步执行合并
}
res.sendStatus(200);
});
// 合并操作(异步)
async function mergeChunks(hash) {
const chunkDir = path.join(UPLOAD_DIR, hash);
const chunks = fs.readdirSync(chunkDir).sort((a, b) => a - b);
await new Promise((resolve) => {
const writeStream = fs.createWriteStream(`${UPLOAD_DIR}/${hash}.file`);
chunks.forEach(chunk => {
writeStream.write(fs.readFileSync(path.join(chunkDir, chunk)));
fs.unlinkSync(path.join(chunkDir, chunk)); // 删除分片
});
writeStream.end(() => {
fs.rmdirSync(chunkDir);
resolve();
});
});
}
4. 时序图
5. 优化与注意事项
5.1 性能优化
- 分片大小调整:根据网络状况动态调整分片大小(1MB~10MB)
- 内存管理:使用流式处理避免大文件内存溢出
- CDN加速:分片上传使用不同CDN节点
5.2 可靠性保障
- 分片校验:对每个分片进行MD5校验
- 断点记录:使用IndexedDB存储本地进度
- 过期清理:服务器定时清理未完成的上传任务
5.3 安全防护
- 身份验证:上传接口添加JWT校验
- 大小限制:限制单文件最大尺寸(如10GB)
- 防重放攻击:分片上传添加时间戳签名
5.4 扩展功能
- 进度恢复:页面刷新后自动恢复上传
- 秒传功能:服务器存在相同哈希文件时直接返回
- 分片压缩:在客户端对分片进行gzip压缩
通过以上方案,可实现支持百万级大文件上传、网络中断自动恢复、服务器负载均衡的高可靠性文件上传系统。实际部署时建议结合对象存储(如AWS S3)实现分片直传,进一步降低服务器压力。