Spring Boot 后端接收多个文件的方法
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和setterpublic 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: 10MBmax-request-size: 50MBenabled: truelocation: /tmp
6. 自定义文件上传配置类
如果需要更高级的配置,可以创建一个配置类:
java
@Configuration public class FileUploadConfig {@Beanpublic MultipartConfigElement multipartConfigElement() {MultipartConfigFactory factory = new MultipartConfigFactory();// 单个文件最大factory.setMaxFileSize(DataSize.ofMegabytes(10));// 总上传数据最大factory.setMaxRequestSize(DataSize.ofMegabytes(50));return factory.createMultipartConfig();}@Beanpublic CommonsMultipartResolver multipartResolver() {CommonsMultipartResolver resolver = new CommonsMultipartResolver();resolver.setDefaultEncoding("UTF-8");resolver.setMaxUploadSize(52428800); // 50MBresolver.setMaxUploadSizePerFile(10485760); // 10MBreturn resolver;} }
7. 完整的文件上传服务示例
下面是一个更完整的文件上传服务示例,包含异常处理和文件存储逻辑:
java
@Service public class FileStorageService {private final Path fileStorageLocation;@Autowiredpublic 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();// 添加多个文件到FormDataselectedFiles.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 对象同时接收文件和其他表单数据
实现分块上传处理大文件
关键配置点:
配置文件上传大小限制
处理文件上传异常
实现文件存储逻辑
提供适当的错误反馈
这些方法可以根据实际需求进行组合和扩展,以满足不同的文件上传场景。