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和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.propertiesapplication.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 接收多个文件的主要方式包括:

  1. 使用 MultipartFile[] 数组接收多个文件

  2. 使用 List<MultipartFile> 列表接收多个文件

  3. 使用 DTO 对象同时接收文件和其他表单数据

  4. 实现分块上传处理大文件

关键配置点:

  • 配置文件上传大小限制

  • 处理文件上传异常

  • 实现文件存储逻辑

  • 提供适当的错误反馈

这些方法可以根据实际需求进行组合和扩展,以满足不同的文件上传场景。

相关推荐
半夏知半秋13 小时前
rust学习-Option与Result
开发语言·笔记·后端·学习·rust
天才测试猿13 小时前
自动化测试基础知识总结
自动化测试·软件测试·python·测试工具·程序人生·职场和发展·测试用例
独自破碎E13 小时前
Spring Boot支持哪些嵌入Web容器?
前端·spring boot·后端
疯狂成瘾者13 小时前
后端Spring Boot 核心知识点
java·spring boot·后端
技术净胜13 小时前
Python 连接 MySQL 数据库步骤
数据库·python·mysql
xj75730653314 小时前
《python web开发 测试驱动方法》
开发语言·前端·python
叫我:松哥14 小时前
基于Flask框架开发的智能旅游推荐平台,采用复合推荐算法,支持管理员、导游、普通用户三种角色
python·自然语言处理·flask·旅游·数据可视化·推荐算法·关联规则
IT 行者14 小时前
Spring Boot 4.x 安全监控新篇章:基于 ObservationFilterChainDecorator 的可观测性实践
java·spring boot·后端
pyniu14 小时前
Spring Boot租房管理系统
java·spring boot·后端
No0d1es14 小时前
2025年12月 GESP CCF编程能力等级认证Python四级真题
开发语言·python·青少年编程·等级考试·gesp·ccf