1. 大文件分片上传技术解析
在Web开发中,处理大文件上传一直是个棘手的问题。传统的文件上传方式在面对GB级别的文件时,往往会遇到浏览器内存溢出、上传超时、网络不稳定导致失败等问题。分片上传技术通过将大文件切割成多个小块分别上传,最后在服务器端合并,完美解决了这些痛点。
1.1 为什么需要分片上传
当文件超过100MB时,常规上传方式就会暴露出诸多问题:
- 内存压力:浏览器需要将整个文件加载到内存中,大文件容易导致内存溢出
- 网络不稳定:单次上传时间长,网络波动容易导致上传失败
- 缺乏进度控制:无法实现精细的上传进度展示
- 无法断点续传:一旦失败必须从头开始
分片上传的核心优势在于:
- 将大文件分解为可控的小块(通常2-5MB)
- 支持并行上传提高速度
- 失败后只需重传特定分片
- 精确的进度控制
1.2 分片上传技术架构
一个完整的分片上传系统通常包含以下组件:
code复制[前端] → [分片切割] → [分片上传] → [后端接收] → [临时存储] → [分片合并] → [最终存储]
关键流程说明:
- 前端计算文件哈希值作为唯一标识
- 按固定大小(如5MB)切割文件
- 并发上传各分片到服务器
- 服务器将分片存入临时目录
- 全部分片上传完成后触发合并
- 合并后的文件转移到最终存储位置
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 前端实现详解
2.1 文件分片处理
前端实现分片上传的核心是File API的Blob.slice方法:
javascript复制// 计算总分片数
const chunkSize = 5 * 1024 * 1024; // 5MB
const totalChunks = Math.ceil(file.size / chunkSize);
// 切割文件
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
// 上传chunk...
}
提示:分片大小需要权衡 - 太小会增加请求次数,太大会降低分片优势。一般2-5MB是较优选择。
2.2 并发控制策略
不加控制地并发上传会占用过多带宽和服务器资源。推荐使用令牌桶算法控制并发:
javascript复制class UploadQueue {
constructor(maxConcurrent = 3) {
this.maxConcurrent = maxConcurrent;
this.activeCount = 0;
this.queue = [];
}
add(task) {
this.queue.push(task);
this.run();
}
run() {
while (this.activeCount < this.maxConcurrent && this.queue.length) {
const task = this.queue.shift();
this.activeCount++;
task().finally(() => {
this.activeCount--;
this.run();
});
}
}
}
// 使用示例
const uploadQueue = new UploadQueue(3);
files.forEach(file => {
uploadQueue.add(() => uploadFile(file));
});
2.3 断点续传实现
断点续传需要记录已上传的分片信息:
javascript复制// 存储上传记录
function saveUploadRecord(fileId, uploadedChunks) {
localStorage.setItem(`upload_${fileId}`, JSON.stringify({
uploadedChunks,
timestamp: Date.now()
}));
}
// 读取上传记录
function getUploadRecord(fileId) {
const record = localStorage.getItem(`upload_${fileId}`);
return record ? JSON.parse(record) : null;
}
// 清理过期记录(7天)
function cleanupRecords() {
const now = Date.now();
Object.keys(localStorage).forEach(key => {
if (key.startsWith('upload_')) {
const record = JSON.parse(localStorage.getItem(key));
if (now - record.timestamp > 7 * 24 * 60 * 60 * 1000) {
localStorage.removeItem(key);
}
}
});
}
3. 后端实现详解
3.1 分片接收与存储
SpringBoot接收分片的核心代码:
java复制@PostMapping("/upload/chunk")
public ResponseEntity uploadChunk(
@RequestParam String fileId,
@RequestParam int chunkIndex,
@RequestParam MultipartFile chunk) {
// 临时存储目录
String tempDir = "/tmp/upload/" + fileId;
File dir = new File(tempDir);
if (!dir.exists()) dir.mkdirs();
// 存储分片
File chunkFile = new File(tempDir, String.valueOf(chunkIndex));
try {
chunk.transferTo(chunkFile);
return ResponseEntity.ok().build();
} catch (IOException e) {
return ResponseEntity.status(500).build();
}
}
注意:临时目录应定期清理,避免磁盘空间被占满。
3.2 分片合并策略
当所有分片上传完成后,需要合并为完整文件:
java复制public File mergeChunks(String fileId, String filename) throws IOException {
String tempDir = "/tmp/upload/" + fileId;
File[] chunks = new File(tempDir).listFiles();
// 按分片序号排序
Arrays.sort(chunks, Comparator.comparingInt(f -> Integer.parseInt(f.getName())));
// 创建输出文件
File outputFile = new File("/data/uploads", filename);
try (
