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. 实现分块上传处理大文件

关键配置点:

  • 配置文件上传大小限制

  • 处理文件上传异常

  • 实现文件存储逻辑

  • 提供适当的错误反馈

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

相关推荐
hui函数6 小时前
订单后台管理系统-day07菜品模块
数据库·后端·python·flask
wr6 小时前
解决 NetMQ 创建Demo调试失败问题
后端
DashVector6 小时前
如何通过Java SDK获取Doc
大数据·后端·阿里巴巴
架构师沉默6 小时前
同事查日志太慢,我现场教他一套 grep 组合拳
java·后端·架构
小李小李无与伦比7 小时前
MinerU环境部署——PDF转Markdown
开发语言·python·深度学习·conda
胡耀超7 小时前
AI:大语言模型微调的真相:打破迷思,理性选择
python·ai·大模型·llm·微调·提示词·大模型应用指南
前端fighter7 小时前
Express vs Koa vs Egg.js:Node.js 后端框架选型指南
前端·后端·面试
大学生毕业题目7 小时前
毕业项目推荐:52-基于yolov8/yolov5/yolo11的红绿灯检测识别系统(Python+卷积神经网络)
人工智能·python·yolo·目标检测·cnn·pyqt·红绿灯检测