下面我将为你提供一个完整的Spring Boot后端实现,用于支持Vue3前端的文件下载功能。这个实现包括文件下载API、中文文件名支持、安全检查和错误处理。
Spring Boot文件下载实现
java
复制
下载
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
@RestController
@RequestMapping("/api/files")
public class FileDownloadController {
// 文件存储的基础目录(根据实际情况修改)
private static final String FILE_DIRECTORY = "/path/to/your/files";
/**
* 基础文件下载API
* @param filename 文件名
* @return 文件资源响应
*/
@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam String filename) {
try {
// 安全校验:防止路径遍历攻击
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) {
throw new SecurityException("非法文件名");
}
Path filePath = Paths.get(FILE_DIRECTORY).resolve(filename).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
// 处理中文文件名
String encodedFilename = new String(filename.getBytes(StandardCharsets.UTF_8), "ISO-8859-1");
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFilename + "\"")
.body(resource);
} catch (MalformedURLException e) {
return ResponseEntity.badRequest().body(null);
} catch (SecurityException e) {
return ResponseEntity.status(403).body(null);
}
}
/**
* 从数据库下载文件的API
* @param fileId 文件ID
* @return 文件流响应
*/
@GetMapping("/download-from-db")
public ResponseEntity<StreamingResponseBody> downloadFromDatabase(@RequestParam Long fileId) {
// 实际项目中从数据库获取文件信息
FileInfo fileInfo = fileService.getFileInfo(fileId);
if (fileInfo == null) {
return ResponseEntity.notFound().build();
}
// 处理中文文件名
String encodedFilename = new String(fileInfo.getFilename().getBytes(StandardCharsets.UTF_8), "ISO-8859-1");
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = fileService.getFileContent(fileId)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + encodedFilename + "\"")
.contentType(MediaType.parseMediaType(fileInfo.getContentType()))
.body(responseBody);
}
/**
* 大文件下载(流式传输)
* @param filename 文件名
* @return 流式响应体
*/
@GetMapping("/download-large")
public ResponseEntity<StreamingResponseBody> downloadLargeFile(@RequestParam String filename) {
Path filePath = Paths.get(FILE_DIRECTORY).resolve(filename).normalize();
java.io.File file = filePath.toFile();
if (!file.exists()) {
return ResponseEntity.notFound().build();
}
// 处理中文文件名
String encodedFilename = new String(filename.getBytes(StandardCharsets.UTF_8), "ISO-8859-1");
StreamingResponseBody responseBody = outputStream -> {
try (InputStream inputStream = new java.io.FileInputStream(file)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
}
};
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + encodedFilename + "\"")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(responseBody);
}
}
// 文件信息实体类(示例)
@Data
class FileInfo {
private Long id;
private String filename;
private String contentType;
private long size;
}
// 文件服务接口(示例)
@Service
class FileService {
@Autowired
private JdbcTemplate jdbcTemplate;
public FileInfo getFileInfo(Long fileId) {
// 实际数据库查询逻辑
return jdbcTemplate.queryForObject(
"SELECT id, filename, content_type, size FROM files WHERE id = ?",
(rs, rowNum) -> {
FileInfo info = new FileInfo();
info.setId(rs.getLong("id"));
info.setFilename(rs.getString("filename"));
info.setContentType(rs.getString("content_type"));
info.setSize(rs.getLong("size"));
return info;
},
fileId
);
}
public InputStream getFileContent(Long fileId) {
// 实际获取文件流的逻辑
return jdbcTemplate.queryForObject(
"SELECT content FROM files WHERE id = ?",
(rs, rowNum) -> rs.getBinaryStream("content"),
fileId
);
}
}
关键配置说明
文件存储路径:
java
复制
下载
private static final String FILE_DIRECTORY = "/path/to/your/files";
替换为你的实际文件存储路径
中文文件名处理:
java
复制
下载
String encodedFilename = new String(filename.getBytes(StandardCharsets.UTF_8), "ISO-8859-1");
确保浏览器正确显示中文文件名
安全措施:
java
复制
下载
if (filename.contains("..") || filename.contains("/") || filename.contains("\\")) { throw new SecurityException("非法文件名"); }防止路径遍历攻击
大文件处理:
使用StreamingResponseBody实现流式传输,避免内存溢出:java
复制
下载
StreamingResponseBody responseBody = outputStream -> { // 流式读写逻辑 };
前端对应调整
根据后端API调整前端请求:
typescript
复制
下载
// 下载本地文件
const handleDownload = async () => {
try {
const response = await axios.get('/api/files/download', {
params: { filename: '报告.pdf' },
responseType: 'blob'
});
// ... 处理下载逻辑
} catch (error) {
// 错误处理
}
};
// 下载数据库文件
const downloadFromDB = async (fileId: number) => {
try {
const response = await axios.get('/api/files/download-from-db', {
params: { fileId },
responseType: 'blob'
});
// ... 处理下载逻辑
} catch (error) {
// 错误处理
}
};
错误处理增强
在Spring Boot中添加全局异常处理:
java
复制
下载
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(SecurityException.class)
public ResponseEntity<String> handleSecurityException(SecurityException ex) {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body("文件访问被拒绝: " + ex.getMessage());
}
@ExceptionHandler(FileNotFoundException.class)
public ResponseEntity<String> handleFileNotFound(FileNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body("文件不存在: " + ex.getMessage());
}
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleGeneralException(Exception ex) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("服务器错误: " + ex.getMessage());
}
}
响应头说明
后端设置的关键响应头:
Content-Type:
application/octet-stream(通用二进制流)Content-Disposition:
attachment; filename="filename.ext"(触发浏览器下载)Content-Length:自动设置(大文件下载建议设置)
数据库设计示例
文件存储表结构:
sql
复制
下载
CREATE TABLE files (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
filename VARCHAR(255) NOT NULL,
content_type VARCHAR(100) NOT NULL,
size BIGINT NOT NULL,
content LONGBLOB NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
最佳实践建议
文件存储策略:
小文件(<1MB)可直接存数据库
大文件建议存储在文件系统或对象存储(如S3)
敏感文件需要加密存储
访问控制:
java
复制
下载
@PreAuthorize("hasPermission(#fileId, 'FILE', 'DOWNLOAD')") @GetMapping("/download-secured") public ResponseEntity<Resource> downloadSecuredFile(@RequestParam Long fileId) { // 需要权限校验的文件下载 }下载限流:
java
复制
下载
@RateLimiter(name = "fileDownload", fallbackMethod = "downloadLimitExceeded") @GetMapping("/download") public ResponseEntity<Resource> downloadFile() { // ... }日志记录:
java
复制
下载
@PostFilter("hasPermission(filterObject, 'READ')") public void logDownload(String filename, String user) { log.info("用户 {} 下载了文件 {}", user, filename); }
这个实现提供了完整的文件下载功能,包括:
本地文件系统下载
数据库文件下载
大文件流式传输
中文文件名支持
安全防护
错误处理
权限控制
前端可以无缝对接这些API实现文件下载功能。