大文件分块上传和续传
给出一个Spring Boot项目中完成大文件分块上传和续传功能的完整示例代码解释,下面的示例将集中展示前端与后端的交互过程,将分别从客户端(前端)以及服务器端(后端)实现的角度来看实现思路。
定前端使用了axios进行与后端的交互,后端则利用Spring Boot来响应前端的请求和处理文件。
客户端(前端)实现
客户端的实现主要是利用axios
或任何HTTP库,如fetch
,来分块上传文件。在中断或失败后,能够获取已上传的信息并从断点处续传。
const axios = require('axios');
const FormData = require('form-data');
let currentOffset = 0;
const uploadFile = async (file, url) => {
const chunkSize = 100 * 1024 * 1024; // 设定每块为100MB
const totalSize = file.size;
let formData = null;
for (let offset = currentOffset; offset < totalSize; offset += chunkSize) {
let end = Math.min(totalSize, offset + chunkSize);
formData = new FormData();
formData.append('file', file.slice(offset, end));
formData.append('offset', offset.toString());
try {
const response = await axios.post(url, formData, {
headers: {
'Content-Type': `multipart/form-data; boundary=${formData._boundary}`,
},
});
if (response.data && response.data.status === 'ok') {
currentOffset = end;
}
} catch (error) {
console.error(`上传失败, 尝试重新上传片段: offset=${offset}`);
await new Promise(resolve => setTimeout(resolve, 3000)); // 简易重试机制
}
}
};
// 使用
uploadFile(myFile, 'http://localhost:8080/uploadChunk');
在上述前端代码中,以固定大小(例如100MB)的chunk size分割文件,每个块由axios发送到后端服务器。同时会从断点处继续上传前块未传完的部分。
服务器端(后端)实现
在服务器端(后端)实现要确保正确拼接接收到的文件块,使用AtomicLong
或其他数据库来持久化偏移量情况,便于跟踪文件的上传状态。
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.http.HttpStatus;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicLong;
@RestController
public class FileUploadController {
AtomicLong currentOffset = new AtomicLong(0);
private final static String UPLOAD_FOLDER = Paths.get(System.getProperty("user.home"), "uploads").toString();
private final static long CHUNK_SIZE = 100 * 1024 * 1024; // 与前端chunk size相同
@PostMapping("/uploadChunk")
public ResponseEntity<String> handleFileUpload(@RequestParam("file") MultipartFile file,
@RequestParam("offset") long offset) throws IOException {
long end = Math.min(offset + file.getSize(), file.getSize());
if (currentOffset.get() < offset) {
currentOffset.set(offset);
}
Path filePath = Paths.get(UPLOAD_FOLDER, "uploadedFile");
Files.createDirectories(filePath.getParent());
Files.write(filePath, file.getBytes(), StandardOpenOption.APPEND);
currentOffset.set(end);
return ResponseEntity.status(HttpStatus.OK).body("{\"status\": \"ok\"}");
}
}
在后端代码段中,接收分块并拼接文件,同时通过AtomicLong
或其他方法储存断点上传的状态。
总结
从嵌入上述示例代码中,控制与处理大文件的断点续传与分块上传,关键技能遵照如下:
- 连续和可恢复的上传方案 – 前端处理正确分块的文件,后端则保证按文件块合并存储。
- 状态追踪 – 最佳实践是将状态信息持久化,以便在长时间运行或服务重启之后可以重新获取信息。
- 健壮的错误处理 – 在可能的失败场景下提供适当的异常处理与重试机制。
- 前后端一致 – 持有相同的chunk大小,可以避免编码中对于边界处理的复杂逻辑,从而减少出错的几率。
增进完善与优化策略
上述方案的直观,初步简推出了大文件的分块上传与断点续传结构。然而,通常在生产系统中,会添加更多高级特性以优化性能和可靠性。比如使用数据库存储上传状态(而非在内存中存储),采用更复杂的错误恢复策略,建立互操作协议报告上传进度,引入健康检查机制确认网络或系统状态,利用分布式系统(如云存储)优势中实现数据冗余等。