{T}

Spring Boot 文件上传与 Multipart

文件上传是 Web 应用的基础能力。Spring Boot 基于 Servlet 3.0 的 Multipart 规范,用 MultipartFile 抽象屏蔽了底层解析细节,既支持简单表单上传,也能配合流式处理大文件。本篇覆盖接收、配置、限制与落盘全流程。

版本基准

本文档以 Spring Boot 3.x 为主。文件上传由 MultipartAutoConfiguration 自动装配 StandardServletMultipartResolver(基于 Servlet 容器原生能力),无需额外依赖。

一、基础接收

1.1 单文件

java
@PostMapping("/upload")
public ResponseEntity<String> upload(@RequestParam("file") MultipartFile file) {
    if (file.isEmpty()) {
        return ResponseEntity.badRequest().body("文件为空");
    }
    String filename = file.getOriginalFilename();
    long size = file.getSize();
    return ResponseEntity.ok("收到文件:" + filename + ",大小:" + size);
}

1.2 多文件与混合字段

java
@PostMapping("/batch")
public ResponseEntity<List<String>> batch(
        @RequestParam("files") List<MultipartFile> files,
        @RequestParam("dir") String dir) {
    return ResponseEntity.ok(files.stream()
            .map(MultipartFile::getOriginalFilename)
            .toList());
}

1.3 绑定到对象

java
public class UploadForm {
    private String bizType;
    private MultipartFile file;
    // getter/setter
}

@PostMapping("/form")
public ResponseEntity<Void> form(@Valid UploadForm form, BindingResult result) {
    // form.getFile() 即上传文件
    return ResponseEntity.ok().build();
}

二、落盘:本地与对象存储

2.1 本地落盘(注意安全)

java
public void saveLocal(MultipartFile file) throws IOException {
    // 防路径穿越:只取文件名,去掉路径
    String name = StringUtils.getFilename(file.getOriginalFilename());
    Path dest = Paths.get("/data/uploads", name);
    Files.createDirectories(dest.getParent());
    file.transferTo(dest);   // 推荐,比 getBytes() 更省内存
}
防路径穿越

getOriginalFilename() 可能包含 ../../ 等路径片段。务必用 StringUtils.getFilename()(或自行截断目录部分)只保留纯文件名,避免写入任意路径造成覆盖或 RCE。

2.2 上传到对象存储(OSS/S3)

java
public void saveToOss(MultipartFile file) {
    String key = "uploads/" + UUID.randomUUID() + "-" + file.getOriginalFilename();
    s3Client.putObject(PutObjectRequest.builder()
        .bucket("my-bucket").key(key).build(),
        RequestBody.fromBytes(file.getBytes()));   // 大文件改用 InputStream
}

三、大小限制与配置

application.yml

yaml
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 10MB        # 单个文件上限
      max-request-size: 50MB     # 整个请求上限
      file-size-threshold: 2KB   # 超过则写临时文件而非内存
      location: /tmp/uploads      # 临时目录

超限时抛出 MaxUploadSizeExceededException,用全局异常处理返回友好提示:

java
@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseEntity<Map<String,String>> handleTooLarge(MaxUploadSizeExceededException e) {
    return ResponseEntity.status(413).body(Map.of("error", "文件超过大小限制"));
}

四、进度与流式(大文件)

对超大文件,避免 getBytes() 全量入内存,用 InputStream 流式转发:

java
public void stream(MultipartFile file) throws IOException {
    try (InputStream in = file.getInputStream()) {
        // 边读边写/上传,控制内存占用
        IOUtils.copy(in, targetStream);
    }
}

上传进度需前端配合(如分片 + 自定义进度回调),后端 MultipartFile 不直接提供进度百分比。

五、常见陷阱

陷阱表现解决
413 被网关拦文件没到 Controller网关(Nginx)也有限制,需同步调整 client_max_body_size
路径穿越覆盖系统文件StringUtils.getFilename() 规范化文件名
内存溢出大文件 getBytes()transferTo / getInputStream 流式处理
中文名乱码文件名乱码前端 Content-Typecharset=utf-8,或后端 new String(name.getBytes(ISO_8859_1), UTF_8) 兜底
临时文件堆积磁盘占满确认 location 有清理策略,或用对象存储直传
无 @RequestParam 名绑定失败参数名需与表单字段名一致,或显式指定 @RequestParam("file")

六、小结

Spring Boot 文件上传核心是 MultipartFile:单/多文件用 @RequestParam 接收,落盘优先 transferTo(大文件走 InputStream 流式),并用 spring.servlet.multipart 配置大小上限。务必做文件名规范化防路径穿越、超大文件流式处理,并在网关与应用两层都放开大小限制。

相关阅读:全局异常处理机制见 Java 异常深度处理;请求处理链路见 Spring MVC 请求处理流程

版本差异(旧版 → Spring Boot 3.5.x)

特性旧版(Spring Boot 2.x)Spring Boot 3.5.x
MultipartFilejavax.servlet.*jakarta.servlet.http.*(包名迁移)
上传配置spring.servlet.multipart不变;属性名保持兼容
大文件手动流式不变;支持虚拟线程并发上传
存储本地磁盘不变;可集成 OSS/MinIO