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 对象同时接收文件和其他表单数据
实现分块上传处理大文件
关键配置点:
配置文件上传大小限制
处理文件上传异常
实现文件存储逻辑
提供适当的错误反馈
这些方法可以根据实际需求进行组合和扩展,以满足不同的文件上传场景。