大文件上传一直是文件上传开发过程中的一个重要的功能点。它是指能够在网络的环境下,通过HTTP文件上传功能,将一个大型的文件传送到服务器上的功能。在传统的文件上传开发中可能会因为网络带宽、服务器性能、客户端限制等诸多因素的影响导致上传大文件的时候出现超时、上传失败等问题。那么为了解决这个问题,对于大文件的上传通常采用的就是将大文件进行切分,切分成多个小文件进行上传,将这些小文件分片进行上传之后,然后将上传之后的小文件合并成一个大文件。
下面我们就来看看Spring Boot如何实现大文件的分片上传操作。
前端设计实现
创建一个上传文件的表单页面,包括文件选择按钮和上传按钮。然后通过JavaScript来监听文件的选择事件,然后将选择的文件实现分片之后,提交到后端上传的接口中。代码如下所示。
<!DOCTYPE html>
<html>
<head>
<title>大文件分片上传</title>
</head>
<body>
<input type="file" id="fileInput" multiple>
<button onclick="uploadFile()">上传文件</button>
<progress id="progressBar" value="0" max="100"></progress>
</body>
<script>
function uploadFile() {
var files = document.getElementById('fileInput').files;
var totalChunks = Math.ceil(files[0].size / (10 * 1024 * 1024)); // 假设每个分片大小为 10MB
var uploadedChunks = 0;
function sendChunk(chunkIndex) {
var chunkSize = 10 * 1024 * 1024;
var start = chunkIndex * chunkSize;
var end = Math.min(start + chunkSize, files[0].size);
var chunk = files[0].slice(start, end);
var formData = new FormData();
formData.append('file', chunk);
formData.append('totalChunks', totalChunks);
formData.append('chunkIndex', chunkIndex);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload', true);
xhr.onload = function() {
uploadedChunks++;
var progress = uploadedChunks / totalChunks * 100;
document.getElementById('progressBar').value = progress;
if (uploadedChunks < totalChunks) {
sendChunk(uploadedChunks);
} else {
mergeChunks();
}
};
xhr.send(formData);
}
function mergeChunks() {
var xhr = new XMLHttpRequest();
xhr.open('POST', '/merge', true);
xhr.send();
}
sendChunk(0);
}
</script>
</html>
在上述操作中完成了如下的四步操作,来支持文件分片上传。
- 第一步、创建一个HTML页面,包括文件选择表单和上传按钮。
- 第二步、使用JavaScript监听文件选择事件,并使用File.slice()方法将文件分割成多个分片。
- 第三步、使用AJAX请求将每个分片发送到后端的分片上传接口。
- 第四步、在所有分片上传完成后,发送请求告知后端开始合并分片。
后端设计实现
文件上传服务类实现,需要完成分片文件的接收,并且将接收到的分片文件存储到一个临时的目录中。接下来就是在上传完成之后,将分片数据进行合并的操作。如下所示。
@Service
public class FileService {
private final String UPLOAD_DIR = "uploads/";
private final String TEMP_DIR = "temp/";
public void saveFileChunk(MultipartFile file, int totalChunks, int chunkIndex) throws IOException {
byte[] bytes = file.getBytes();
String fileName = file.getOriginalFilename();
String tempFilePath = TEMP_DIR + fileName + "-" + chunkIndex;
try (FileOutputStream fos = new FileOutputStream(tempFilePath)) {
fos.write(bytes);
}
}
public void mergeFileChunks() throws IOException {
File tempDir = new File(TEMP_DIR);
File[] files = tempDir.listFiles();
Arrays.sort(files, Comparator.comparing(File::getName));
String mergedFilePath = UPLOAD_DIR + "merged-file";
try (FileOutputStream fos = new FileOutputStream(mergedFilePath)) {
for (File file : files) {
byte[] bytes = Files.readAllBytes(Paths.get(file.getAbsolutePath()));
fos.write(bytes);
file.delete(); // 删除临时文件
}
}
}
}
控制层代码实现,实现一个接收文件分片的POST接口,接收文件分片,并将分片保存到临时目录中。实现一个合并文件分片的POST接口,接收所有分片上传完成的请求,读取临时目录中的分片文件,并将它们合并成完整的文件。将完整的文件保存到指定目录,返回上传成功的响应。
@RestController
public class FileUploadController {
private final String UPLOAD_DIR = "uploads/";
private final String TEMP_DIR = "temp/";
@Autowired
private FileService fileService;
@PostMapping("/upload")
public String uploadFileChunk(@RequestParam("file") MultipartFile file,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("chunkIndex") int chunkIndex) throws IOException {
fileService.saveFileChunk(file, totalChunks, chunkIndex);
return "Chunk uploaded successfully.";
}
@PostMapping("/merge")
public String mergeFileChunks() throws IOException {
fileService.mergeFileChunks();
return "File uploaded successfully.";
}
}
注意
在实际生产场景中,需要对可能出现的文件处理异常进行处理,并且添加文件上传接口安全策略,确保不会被黑客利用上传脚本攻击服务器。在分片上传的过程中,也要对各个分片的大小做验证,如果分片不合理的话,需要进行重新分片,另外就是上传分片的顺序问题,导致最终是否能够被合并成一个完整的文件,还有考虑到断点续传、并发上传,如果后端使用了对象存储,还要考虑到对象存储系统如何对接等等问题。
本文暂时没有评论,来添加一个吧(●'◡'●)