学会这款 🔥全新设计的 Java 脚手架 ,从此面试不再怕!
在现代 Web 应用中,文件上传和下载是非常常见的需求。然而,当文件较大时,传统的上传下载方式可能会遇到网络不稳定或传输中断的问题。为了解决这些问题,断点传输(Resumable Upload/Download)成为了一个重要的技术手段。Spring Boot 3 提供了更强大的支持,使得实现断点传输变得更加简单。
本文将详细介绍如何在 Spring Boot 3 中实现支持断点传输的文件上传和下载功能,并通过代码示例帮助你快速上手。
1. 什么是断点传输?
断点传输是指在文件传输过程中,如果传输中断(如网络故障或用户手动暂停),可以从断点处继续传输,而不需要重新开始。这种技术对于大文件传输尤为重要,因为它可以显著减少重复传输的时间和带宽消耗。
- 断点上传:客户端可以将文件分成多个块(Chunk),分批次上传,服务器端根据上传的块信息进行合并。
- 断点下载:客户端可以请求文件的某一部分,服务器端根据请求的范围返回对应的文件内容。
2. 环境准备
在开始之前,确保你已经具备以下环境:
- JDK 17 或更高版本(Spring Boot 3 要求的最低 JDK 版本)
- Maven 或 Gradle 构建工具
- Spring Boot 3.x
在 pom.xml
中添加以下依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>
</dependencies>
3. 实现断点上传
3.1 配置文件上传限制
在 application.properties
中配置文件上传的限制:
# 设置单个文件的最大大小
spring.servlet.multipart.max-file-size=1GB# 设置总上传文件的最大大小
spring.servlet.multipart.max-request-size=1GB
3.2 实现断点上传接口
为了实现断点上传,我们需要记录每个文件的上传进度。以下是一个简单的实现:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;@Controller
public class ResumableUploadController {// 保存文件块的临时目录private static final String UPLOAD_DIR = "uploads/";// 用于记录文件上传进度的缓存private final Map<String, Long> uploadProgressMap = new HashMap<>();@PostMapping("/upload")@ResponseBodypublic String handleChunkUpload(@RequestParam("file") MultipartFile file,@RequestParam("chunkNumber") int chunkNumber,@RequestParam("totalChunks") int totalChunks,@RequestParam("identifier") String identifier) throws IOException {// 获取文件块的大小long chunkSize = file.getSize();String fileName = file.getOriginalFilename();// 创建临时文件目录File uploadDir = new File(UPLOAD_DIR);if (!uploadDir.exists()) {uploadDir.mkdirs();}// 将文件块写入临时文件String tempFileName = UPLOAD_DIR + identifier + "_" + fileName;try (RandomAccessFile randomAccessFile = new RandomAccessFile(tempFileName, "rw")) {randomAccessFile.seek((chunkNumber - 1) * chunkSize);randomAccessFile.write(file.getBytes());}// 更新上传进度uploadProgressMap.put(identifier, (long) chunkNumber);// 如果所有块都已上传,合并文件if (chunkNumber == totalChunks) {mergeFileChunks(tempFileName, fileName);uploadProgressMap.remove(identifier);return "Upload complete!";}return "Chunk " + chunkNumber + " uploaded!";}private void mergeFileChunks(String tempFileName, String fileName) throws IOException {File mergedFile = new File(UPLOAD_DIR + fileName);try (RandomAccessFile randomAccessFile = new RandomAccessFile(mergedFile, "rw")) {for (int i = 1; i <= uploadProgressMap.size(); i++) {File chunkFile = new File(tempFileName + "_" + i);byte[] chunkData = Files.readAllBytes(chunkFile.toPath());randomAccessFile.write(chunkData);chunkFile.delete(); // 删除临时文件块}}}
}
3.3 前端实现断点上传
在前端,可以使用 JavaScript 将文件分块并发送到服务器。以下是一个简单的示例:
<input type="file" id="fileInput" />
<button onclick="uploadFile()">上传</button><script>async function uploadFile() {const file = document.getElementById('fileInput').files[0];const chunkSize = 1024 * 1024; // 1MBconst totalChunks = Math.ceil(file.size / chunkSize);const identifier = UUID.randomUUID();for (let i = 1; i <= totalChunks; i++) {const chunk = file.slice((i - 1) * chunkSize, i * chunkSize);const formData = new FormData();formData.append('file', chunk);formData.append('chunkNumber', i);formData.append('totalChunks', totalChunks);formData.append('identifier', identifier);await fetch('/upload', {method: 'POST',body: formData});}alert('文件上传完成!');}
</script>
4. 实现断点下载
4.1 实现断点下载接口
Spring Boot 3 支持通过 Range
请求头实现断点下载。以下是一个简单的实现:
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;@RestController
public class ResumableDownloadController {private static final String DOWNLOAD_DIR = "uploads/";@GetMapping("/download/{filename:.+}")public ResponseEntity<Resource> downloadFile(@PathVariable String filename,@RequestHeader(value = "Range", required = false) String rangeHeader) throws IOException {Path path = Paths.get(DOWNLOAD_DIR + filename);Resource resource = new UrlResource(path.toUri());if (!resource.exists() || !resource.isReadable()) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);}long fileLength = resource.contentLength();if (rangeHeader == null) {// 如果没有 Range 请求头,返回整个文件return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"").header(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileLength)).body(resource);} else {// 解析 Range 请求头List<HttpRange> ranges = HttpRange.parseRanges(rangeHeader);HttpRange range = ranges.get(0);long start = range.getRangeStart(fileLength);long end = range.getRangeEnd(fileLength);// 返回部分文件内容long rangeLength = end - start + 1;return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"").header(HttpHeaders.CONTENT_RANGE, "bytes " + start + "-" + end + "/" + fileLength).header(HttpHeaders.CONTENT_LENGTH, String.valueOf(rangeLength)).body(resource);}}
}
4.2 测试断点下载
使用支持断点下载的工具(如 curl
或浏览器)测试下载功能。例如:
curl -H "Range: bytes=0-1023" http://localhost:8080/download/example.txt -o example.txt
5. 总结
通过本文的介绍,你已经学会了如何在 Spring Boot 3 中实现支持断点传输的文件上传和下载功能。断点传输技术可以显著提升大文件传输的效率和可靠性,是现代 Web 应用中不可或缺的一部分。
希望本文的内容能够帮助你在实际项目中更好地处理文件上传和下载的需求。如果你有任何问题或建议,欢迎在评论区留言讨论。Happy coding!