# SpringBoot实现文件下载的完整指南
文件下载是Web应用中常见的功能需求,无论是导出报表、下载用户上传的文件还是提供资源下载,SpringBoot都提供了简洁高效的实现方式。本文将详细介绍在SpringBoot中实现文件下载的多种方法。
## 一、基础环境搭建
首先创建一个SpringBoot项目,添加必要的依赖:
```xml
org.springframework.boot
spring-boot-starter-web
```
## 二、使用ResponseEntity实现文件下载
这是最常用且推荐的方式,通过`ResponseEntity`可以精确控制HTTP响应头。
### 2.1 基础实现
```java
@RestController
@RequestMapping("/download")
public class FileDownloadController {
@GetMapping("/file1")
public ResponseEntity downloadFile1() throws IOException {
// 读取文件
File file = new File("uploads/sample.pdf");
Path path = Paths.get(file.getAbsolutePath());
// 创建Resource对象
ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));
// 设置响应头
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"");
headers.add(HttpHeaders.CONTENT_TYPE,
MediaType.APPLICATION_OCTET_STREAM_VALUE);
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.body(resource);
}
}
```
### 2.2 支持大文件下载
对于大文件,使用流式传输避免内存溢出:
```java
@GetMapping("/large-file")
public ResponseEntity downloadLargeFile() throws IOException {
File file = new File("uploads/large-video.mp4");
Path path = Paths.get(file.getAbsolutePath());
InputStreamResource resource = new InputStreamResource(
new FileInputStream(file));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"")
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
```
## 三、使用HttpServletResponse直接操作
这种方式提供了更底层的控制:
```java
@GetMapping("/direct")
public void downloadDirect(HttpServletResponse response) throws IOException {
File file = new File("uploads/document.docx");
// 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + file.getName() + "\"");
response.setContentLength((int) file.length());
// 使用缓冲流提高性能
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream())) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
bos.flush();
}
}
```
## 四、文件下载的进阶功能
### 4.1 支持断点续传
```java
@GetMapping("/resume")
public ResponseEntity downloadWithResume(
@RequestHeader(value = "Range", required = false) String rangeHeader)
throws IOException {
File file = new File("uploads/large-file.zip");
long fileLength = file.length();
if (rangeHeader == null) {
// 普通下载
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"")
.contentLength(fileLength)
.body(new InputStreamResource(new FileInputStream(file)));
}
// 解析Range头
String[] ranges = rangeHeader.substring("bytes=".length()).split("-");
long start = Long.parseLong(ranges[0]);
long end = ranges.length > 1 ? Long.parseLong(ranges[1]) : fileLength - 1;
long contentLength = end - start + 1;
InputStreamResource resource = new InputStreamResource(
new FileInputStream(file) {{ skip(start); }});
return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT)
.header(HttpHeaders.CONTENT_RANGE,
"bytes " + start + "-" + end + "/" + fileLength)
.contentLength(contentLength)
.body(resource);
}
```
### 4.2 文件下载进度监控
```java
@GetMapping("/with-progress")
public ResponseEntity downloadWithProgress() {
File file = new File("uploads/large-file.iso");
StreamingResponseBody responseBody = outputStream -> {
try (FileInputStream fis = new FileInputStream(file)) {
byte[] buffer = new byte[8192];
long totalBytes = file.length();
long bytesRead = 0;
int read;
while ((read = fis.read(buffer)) != -1) {
outputStream.write(buffer, 0, read);
bytesRead += read;
// 计算并记录进度(实际应用中可存储到Redis或Session)
double progress = (double) bytesRead / totalBytes * 100;
System.out.printf("下载进度: %.2f%%\n", progress);
}
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getName() + "\"")
.contentLength(file.length())
.body(responseBody);
}
```
## 五、安全考虑和最佳实践
### 5.1 文件路径安全验证
```java
private boolean isSafePath(String filename) {
// 防止路径遍历攻击
Path filePath = Paths.get("uploads", filename).normalize();
Path basePath = Paths.get("uploads").normalize();
return filePath.startsWith(basePath);
}
@GetMapping("/secure/{filename:.+}")
public ResponseEntity secureDownload(@PathVariable String filename) {
if (!isSafePath(filename)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
// ... 下载逻辑
}
```
### 5.2 下载频率限制
```java
@GetMapping("/rate-limited")
public ResponseEntity rateLimitedDownload(
HttpServletRequest request) {
String clientIp = request.getRemoteAddr();
// 实现下载频率检查逻辑
// 可以使用Redis记录下载次数
// ... 下载逻辑
}
```
## 六、前端调用示例
```html
下载文件
```
## 七、性能优化建议
1. **使用NIO**:对于大文件,考虑使用`Files.copy()`或NIO通道
2. **启用压缩**:对文本文件启用GZIP压缩
3. **缓存策略**:对静态资源设置合适的缓存头
4. **连接池**:配置合适的HTTP连接池参数
5. **异步处理**:对于大文件下载,考虑使用异步响应
## 总结
SpringBoot提供了多种灵活的方式来实现文件下载功能。在实际开发中,建议:
1. 使用`ResponseEntity`作为首选方案,它提供了良好的封装和控制
2. 对于大文件,务必使用流式传输避免内存问题
3. 始终验证文件路径,防止安全漏洞
4. 根据业务需求考虑添加进度显示、断点续传等高级功能
5. 在生产环境中添加适当的监控和日志记录
通过合理选择实现方式并遵循最佳实践,可以构建出既安全又高效的文件下载功能。