Spring Boot 后端接收多个文件的方法
在 Spring Boot 中接收多个文件有多种方式,下面我将详细介绍各种方法及其实现。
1. 使用 MultipartFile 数组接收
这是最直接的方式,适用于前端使用相同字段名上传多个文件的情况。
java
@RestController
@RequestMapping("/api/upload")
public class FileUploadController {
@PostMapping("/multiple")
public ResponseEntity<String> uploadMultipleFiles(
@RequestParam("files") MultipartFile[] files) {
if (files.length == 0) {
return ResponseEntity.badRequest().body("请选择至少一个文件");
}
try {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 保存文件到指定位置
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
Path path = Paths.get("uploads", fileName);
Files.createDirectories(path.getParent());
Files.write(path, file.getBytes());
// 可以在这里添加文件信息到数据库等操作
}
}
return ResponseEntity.ok("成功上传 " + files.length + " 个文件");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("文件上传失败: " + e.getMessage());
}
}
}
2. 使用 MultipartFile 列表接收
与数组方式类似,但使用列表可能在某些情况下更方便。
java
@PostMapping("/multiple-list")
public ResponseEntity<String> uploadMultipleFilesList(
@RequestParam("files") List<MultipartFile> files) {
if (files == null || files.isEmpty()) {
return ResponseEntity.badRequest().body("请选择至少一个文件");
}
// 处理文件逻辑同上
// ...
return ResponseEntity.ok("成功上传 " + files.size() + " 个文件");
}
3. 使用 DTO 对象接收文件和其他表单数据
当需要同时接收文件和其他表单数据时,可以使用 DTO 对象。
java
public class FileUploadDTO {
private List<MultipartFile> files;
private String category;
private String description;
// 构造函数、getter和setter
public FileUploadDTO() {}
public List<MultipartFile> getFiles() {
return files;
}
public void setFiles(List<MultipartFile> files) {
this.files = files;
}
// 其他getter和setter...
}
java
@PostMapping(value = "/with-data", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<String> uploadFilesWithData(FileUploadDTO fileUploadDTO) {
List<MultipartFile> files = fileUploadDTO.getFiles();
String category = fileUploadDTO.getCategory();
String description = fileUploadDTO.getDescription();
if (files == null || files.isEmpty()) {
return ResponseEntity.badRequest().body("请选择至少一个文件");
}
// 处理文件和其他数据
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 保存文件,同时可以使用category和description
// ...
}
}
return ResponseEntity.ok("成功上传 " + files.size() + " 个文件,分类: " + category);
}
4. 处理大文件和分块上传
对于大文件,可以使用分块上传的方式。
java
@PostMapping("/chunk")
public ResponseEntity<String> uploadChunk(
@RequestParam("file") MultipartFile file,
@RequestParam("chunkNumber") int chunkNumber,
@RequestParam("totalChunks") int totalChunks,
@RequestParam("originalFileName") String originalFileName,
@RequestParam(value = "fileId", required = false) String fileId) {
try {
// 生成唯一文件标识(如果未提供)
String uniqueFileId = fileId != null ? fileId : UUID.randomUUID().toString();
// 创建临时目录存储分块
Path chunkPath = Paths.get("temp", uniqueFileId, String.valueOf(chunkNumber));
Files.createDirectories(chunkPath.getParent());
Files.write(chunkPath, file.getBytes());
// 如果是最后一块,合并所有分块
if (chunkNumber == totalChunks - 1) {
mergeChunks(uniqueFileId, totalChunks, originalFileName);
return ResponseEntity.ok("文件上传完成");
}
return ResponseEntity.ok("分块上传成功");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body("分块上传失败: " + e.getMessage());
}
}
private void mergeChunks(String fileId, int totalChunks, String originalFileName)
throws IOException {
Path mergedPath = Paths.get("uploads", originalFileName);
Files.createDirectories(mergedPath.getParent());
try (OutputStream os = new FileOutputStream(mergedPath.toFile())) {
for (int i = 0; i < totalChunks; i++) {
Path chunkPath = Paths.get("temp", fileId, String.valueOf(i));
Files.copy(chunkPath, os);
// 删除已合并的分块
Files.deleteIfExists(chunkPath);
}
}
// 删除临时目录
Path tempDir = Paths.get("temp", fileId);
Files.deleteIfExists(tempDir);
}
5. 配置文件上传属性
在 application.properties
或 application.yml
中配置文件上传属性:
properties
# 配置文件上传大小限制
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=50MB
# 启用多部分文件上传
spring.servlet.multipart.enabled=true
# 指定临时文件存储目录(可选)
spring.servlet.multipart.location=/tmp
或者使用 YAML 格式:
yaml
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
enabled: true
location: /tmp
6. 自定义文件上传配置类
如果需要更高级的配置,可以创建一个配置类:
java
@Configuration
public class FileUploadConfig {
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 单个文件最大
factory.setMaxFileSize(DataSize.ofMegabytes(10));
// 总上传数据最大
factory.setMaxRequestSize(DataSize.ofMegabytes(50));
return factory.createMultipartConfig();
}
@Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver resolver = new CommonsMultipartResolver();
resolver.setDefaultEncoding("UTF-8");
resolver.setMaxUploadSize(52428800); // 50MB
resolver.setMaxUploadSizePerFile(10485760); // 10MB
return resolver;
}
}
7. 完整的文件上传服务示例
下面是一个更完整的文件上传服务示例,包含异常处理和文件存储逻辑:
java
@Service
public class FileStorageService {
private final Path fileStorageLocation;
@Autowired
public FileStorageService(FileStorageProperties fileStorageProperties) {
this.fileStorageLocation = Paths.get(fileStorageProperties.getUploadDir())
.toAbsolutePath().normalize();
try {
Files.createDirectories(this.fileStorageLocation);
} catch (Exception ex) {
throw new FileStorageException(
"无法创建文件存储目录", ex);
}
}
public String storeFile(MultipartFile file) {
// 标准化文件名
String fileName = StringUtils.cleanPath(file.getOriginalFilename());
try {
// 检查文件名是否包含非法字符
if (fileName.contains("..")) {
throw new FileStorageException(
"抱歉! 文件名包含无效的路径序列 " + fileName);
}
// 生成唯一文件名(避免重名覆盖)
String uniqueFileName = UUID.randomUUID().toString() + "_" + fileName;
// 复制文件到目标位置
Path targetLocation = this.fileStorageLocation.resolve(uniqueFileName);
Files.copy(file.getInputStream(), targetLocation,
StandardCopyOption.REPLACE_EXISTING);
return uniqueFileName;
} catch (IOException ex) {
throw new FileStorageException(
"无法存储文件 " + fileName + ". 请重试!", ex);
}
}
public Resource loadFileAsResource(String fileName) {
try {
Path filePath = this.fileStorageLocation.resolve(fileName).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists()) {
return resource;
} else {
throw new FileNotFoundException("文件未找到 " + fileName);
}
} catch (MalformedURLException ex) {
throw new FileNotFoundException("文件未找到 " + fileName);
}
}
}
8. 异常处理
创建自定义异常和全局异常处理器:
java
public class FileStorageException extends RuntimeException {
public FileStorageException(String message) {
super(message);
}
public FileStorageException(String message, Throwable cause) {
super(message, cause);
}
}
@ControllerAdvice
public class FileUploadExceptionAdvice {
@ResponseBody
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.PAYLOAD_TOO_LARGE)
public String handleMaxSizeException(MaxUploadSizeExceededException exc) {
return "文件太大! 最大允许大小是 10MB";
}
@ResponseBody
@ExceptionHandler(FileStorageException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleFileStorageException(FileStorageException exc) {
return "文件存储错误: " + exc.getMessage();
}
@ResponseBody
@ExceptionHandler(MultipartException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handleMultipartException(MultipartException exc) {
return "文件上传格式错误: " + exc.getMessage();
}
}
9. 前端调用示例
前端使用 Vue.js 调用后端接口的示例:
javascript
// 使用axios上传多个文件
const uploadFiles = async () => {
const formData = new FormData();
// 添加多个文件到FormData
selectedFiles.forEach(file => {
formData.append('files', file);
});
// 添加其他表单数据(如果需要)
formData.append('category', 'documents');
formData.append('description', '一些重要文件');
try {
const response = await axios.post('/api/upload/multiple', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
// 更新进度显示
}
});
console.log('上传成功:', response.data);
} catch (error) {
console.error('上传失败:', error);
}
};
总结
Spring Boot 接收多个文件的主要方式包括:
-
使用
MultipartFile[]
数组接收多个文件 -
使用
List<MultipartFile>
列表接收多个文件 -
使用 DTO 对象同时接收文件和其他表单数据
-
实现分块上传处理大文件
关键配置点:
-
配置文件上传大小限制
-
处理文件上传异常
-
实现文件存储逻辑
-
提供适当的错误反馈
这些方法可以根据实际需求进行组合和扩展,以满足不同的文件上传场景。