Spring Boot 文件上传大小限制配置全解析

Spring Boot 文件上传大小限制配置全解析

一、问题本质

当客户端上传文件超过服务端限制时,Servlet 容器(Tomcat)直接拒绝请求,返回 HTTP 413 Request Entity Too Large,请求根本不会到达你的 Controller 代码。

复制代码
客户端上传 5MB 文件
    ↓
Tomcat 检查:max-file-size = 1MB(默认)
    ↓
5MB > 1MB → 直接拒绝,返回 413
    ↓
你的 Controller 代码完全不会执行

二、Spring Boot 文件上传相关配置项

yaml 复制代码
spring:
  servlet:
    multipart:
      enabled: true              # 是否启用 multipart 支持
      max-file-size: 10MB        # 单个文件最大大小
      max-request-size: 30MB     # 整个请求体最大大小
      file-size-threshold: 0B    # 超过多大写入临时文件(0表示始终写磁盘)
      location:                  # 临时文件存储目录
      resolve-lazily: false      # 是否延迟解析 multipart 请求

2.1 各配置项详解

配置项 默认值 含义 触发时机
enabled true 是否开启文件上传功能 应用启动时
max-file-size 1MB 单个上传文件的最大大小 Tomcat 解析请求体时
max-request-size 10MB 整个 multipart 请求的最大大小(所有文件+表单字段之和) Tomcat 解析请求体时
file-size-threshold 0B 文件大小超过此值时写入磁盘临时文件,否则保留在内存 解析文件时
location 系统临时目录 临时文件存放路径 写入临时文件时
resolve-lazily false true 则延迟到实际使用 file 时才解析 请求进入时

2.2 max-file-size vs max-request-size

复制代码
一个 multipart 请求可能包含:
├── file1: 8MB(图片)
├── file2: 5MB(文档)
├── name: "张三"(文本字段,几字节)
└── 总请求体大小: ~13MB + boundary 开销

max-file-size = 10MB  → file1(8MB) 通过,如果 file1 是 12MB 则被拒
max-request-size = 30MB → 整体 13MB 通过,如果总体超过 30MB 则被拒

单文件接口max-request-size 略大于 max-file-size 即可(额外空间给表单字段和 boundary)。

多文件接口max-request-size 应为 max-file-size × 文件数 + 余量

2.3 file-size-threshold 的作用

复制代码
上传文件 → Tomcat 解析
    ├── 文件 < file-size-threshold → 保留在 JVM 内存中(快,但占堆内存)
    └── 文件 >= file-size-threshold → 写入磁盘临时文件(慢,但不占堆内存)
场景 建议值
文件很小(头像、图标) 256KB~1MB(保留内存,减少磁盘IO)
文件较大(Excel、视频) 0B(默认,始终写磁盘,保护堆内存)
高并发小文件 适当调大,减少磁盘写入

2.4 resolve-lazily 的作用

yaml 复制代码
resolve-lazily: true
  • false(默认):请求一进来就解析整个 multipart body
  • true :延迟到 Controller 中第一次使用 MultipartFile 参数时才解析

用途:配合异常处理,在解析失败时能被全局异常处理器捕获(默认模式下解析在 Filter 层就完成了,异常可能无法被 @ControllerAdvice 捕获)。


注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

三、大小单位写法

Spring Boot 支持以下写法:

写法 含义
10MB 10 兆字节
10485760 精确字节数(10×1024×1024)
1GB 1 吉字节
512KB 512 千字节
-1 不限制大小

四、超出限制时的异常处理

4.1 默认行为

超出 max-file-sizemax-request-size 时,Spring 抛出 MaxUploadSizeExceededException(继承自 MultipartException)。

默认情况下返回 413500 错误页,对用户不友好。

4.2 全局异常处理器捕获

java 复制代码
@RestControllerAdvice
public class GlobalExceptionHandler {

  /**
   * 文件大小超出限制.
   */
  @ExceptionHandler(MaxUploadSizeExceededException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Map<String, Object> handleMaxUploadSize(MaxUploadSizeExceededException e) {
    return Map.of(
        "success", false,
        "errorMsg", "上传文件过大,请控制在规定大小以内"
    );
  }

  /**
   * Multipart 解析异常(文件损坏、格式错误等).
   */
  @ExceptionHandler(MultipartException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Map<String, Object> handleMultipart(MultipartException e) {
    return Map.of(
        "success", false,
        "errorMsg", "文件上传失败:" + e.getMessage()
    );
  }
}

4.3 配合 resolve-lazily 使用

如果全局异常处理器捕获不到 MaxUploadSizeExceededException,可以开启延迟解析:

yaml 复制代码
spring:
  servlet:
    multipart:
      resolve-lazily: true

这样异常会在 Controller 方法执行时抛出,能被 @ControllerAdvice 正常捕获。


五、两层限制策略

生产环境推荐「Servlet 容器兜底 + 代码精确校验」双层策略:

复制代码
                 ┌─────────────────────────────────────┐
请求进入 ──→     │  Servlet 容器层(yml 配置)            │
                 │  max-file-size: 30MB                 │
                 │  作用:防御恶意超大请求,保护服务器      │
                 │  超出时:直接 413 拒绝                 │
                 └─────────────────────┬───────────────┘
                                       ↓ 通过
                 ┌─────────────────────────────────────┐
                 │  Controller 代码层                    │
                 │  if (file.getSize() > 20MB)          │
                 │  作用:业务级精确校验                   │
                 │  超出时:返回友好提示                   │
                 └─────────────────────┬───────────────┘
                                       ↓ 通过
                 ┌─────────────────────────────────────┐
                 │  Service 业务逻辑                     │
                 └─────────────────────────────────────┘

为什么需要两层

只有 yml 只有代码 两层配合
用户看到 413 错误页,不友好 恶意超大文件能打进来,Tomcat 解析消耗内存 兼顾安全和体验

设值策略

复制代码
yml(兜底)> 代码(业务限制)> 实际最大文件

示例:yml 设 30MB > 代码限制 20MB > 实际文件最大 ~8MB

六、完整示例

6.1 application.yml

yaml 复制代码
spring:
  servlet:
    multipart:
      enabled: true
      max-file-size: 30MB         # Servlet 容器兜底:允许最大30MB进来
      max-request-size: 35MB      # 请求体总大小:文件+表单字段
      file-size-threshold: 0B     # 始终写临时文件,保护堆内存
      resolve-lazily: true        # 延迟解析,便于异常处理器捕获

server:
  port: 8080
  tomcat:
    max-swallow-size: 30MB        # Tomcat 读取请求体的最大大小(需与上面一致)

6.2 Controller

java 复制代码
package com.example.controller;

import java.util.Map;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/file")
public class FileUploadController {

  /** 业务层文件大小限制:20MB. */
  private static final long MAX_FILE_SIZE = 20L * 1024 * 1024;

  /** 允许的文件类型. */
  private static final String[] ALLOWED_EXTENSIONS = {".xlsx", ".xls", ".csv"};

  @PostMapping("/upload")
  public Map<String, Object> upload(
      @RequestParam("file") MultipartFile file,
      @RequestParam("operator") String operator) {

    // 1. 空文件校验
    if (file == null || file.isEmpty()) {
      return Map.of("success", false, "errorMsg", "请选择要上传的文件");
    }

    // 2. 文件大小校验(业务级,给用户友好提示)
    if (file.getSize() > MAX_FILE_SIZE) {
      return Map.of("success", false, "errorMsg",
          "文件大小不能超过20MB,当前文件大小:" + formatSize(file.getSize()));
    }

    // 3. 文件类型校验
    String fileName = file.getOriginalFilename();
    if (!isAllowedExtension(fileName)) {
      return Map.of("success", false, "errorMsg",
          "不支持的文件格式,仅支持:xlsx、xls、csv");
    }

    // 4. 业务处理...
    return Map.of("success", true, "data", Map.of(
        "fileName", fileName,
        "fileSize", formatSize(file.getSize()),
        "operator", operator
    ));
  }

  private boolean isAllowedExtension(String fileName) {
    if (fileName == null) return false;
    String lower = fileName.toLowerCase();
    for (String ext : ALLOWED_EXTENSIONS) {
      if (lower.endsWith(ext)) return true;
    }
    return false;
  }

  private String formatSize(long bytes) {
    if (bytes < 1024) return bytes + "B";
    if (bytes < 1024 * 1024) return String.format("%.1fKB", bytes / 1024.0);
    return String.format("%.1fMB", bytes / (1024.0 * 1024));
  }
}

6.3 全局异常处理器

java 复制代码
package com.example.config;

import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.multipart.MultipartException;

/**
 * 全局异常处理器.
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

  /**
   * 处理文件超出 Servlet 容器层限制的情况.
   * 当文件超过 yml 中配置的 max-file-size 时触发.
   */
  @ExceptionHandler(MaxUploadSizeExceededException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Map<String, Object> handleMaxUploadSize(MaxUploadSizeExceededException e) {
    return Map.of(
        "success", false,
        "errorMsg", "文件过大,服务器最大允许30MB"
    );
  }

  /**
   * 处理其他 Multipart 解析异常.
   */
  @ExceptionHandler(MultipartException.class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  public Map<String, Object> handleMultipartException(MultipartException e) {
    return Map.of(
        "success", false,
        "errorMsg", "文件上传失败"
    );
  }
}

6.4 测试效果

bash 复制代码
# 正常文件(5MB)→ 通过两层检查,正常处理
curl -F "file=@small.xlsx" -F "operator=test" http://localhost:8080/api/file/upload
# {"success":true,"data":{"fileName":"small.xlsx","fileSize":"5.2MB","operator":"test"}}

# 中等文件(25MB)→ 通过 yml 30MB 限制,但被代码 20MB 校验拦截
curl -F "file=@medium.xlsx" -F "operator=test" http://localhost:8080/api/file/upload
# {"success":false,"errorMsg":"文件大小不能超过20MB,当前文件大小:25.3MB"}

# 超大文件(50MB)→ 被 yml 30MB 限制拦截,触发全局异常处理器
curl -F "file=@large.xlsx" -F "operator=test" http://localhost:8080/api/file/upload
# {"success":false,"errorMsg":"文件过大,服务器最大允许30MB"}

七、常见问题

7.1 Tomcat 的 max-swallow-size

yaml 复制代码
server:
  tomcat:
    max-swallow-size: 30MB

Tomcat 有一个独立的配置 max-swallow-size(默认 2MB),作用是:当请求被拒绝(如413)时,Tomcat 是否继续读取剩余请求体。如果不配置,客户端可能收到 connection reset 而非正常的错误响应。

建议设为和 max-request-size 一致。

7.2 Nginx 反向代理的限制

如果服务前面有 Nginx,还需要配置 Nginx 的 client_max_body_size

nginx 复制代码
server {
    client_max_body_size 30m;  # 必须 >= Spring Boot 的 max-request-size
}

否则 Nginx 层面就返回 413 了,请求到不了 Spring Boot。

7.3 完整链路的大小限制

复制代码
客户端 → Nginx(client_max_body_size) → Tomcat(max-swallow-size) 
       → Spring(max-request-size / max-file-size) → Controller(代码校验)

每一层都要 >= 你期望允许的最大文件大小

八、不同场景的推荐配置

场景 max-file-size max-request-size 代码校验
头像上传(< 2MB) 5MB 5MB 2MB
Excel 导入(< 20MB) 30MB 35MB 20MB
视频上传(< 500MB) 600MB 600MB 500MB
不限制 -1 -1 按需

原则:yml 配置比代码校验宽松 30%~50%,作为安全兜底。

相关推荐
Java面试题总结1 小时前
SpringBoot API参数校验
java·spring boot·后端
何以解忧,唯有..1 小时前
Go 语言安装与环境配置完整指南
开发语言·后端·golang
alwaysrun1 小时前
C++之常量体系const
c++·后端·程序员
武子康1 小时前
Java-24 深入浅出 Spring 全景:从起源到 Spring 6 一文打通 IoC / AOP / 发展史
java·后端·spring
zyk_computer1 小时前
AI Agent ,让循环收敛的那套闭环控制系统
人工智能·后端·python·ai·架构·agent·ai agent
地铁潜行者2 小时前
消息堆积后,为什么一扩容消费者,MySQL 先被打崩了?
java·后端
地铁潜行者2 小时前
订单状态更新成功了,分账消息却没发出去:聊聊本地消息表的一致性坑
java·后端
别叫我老干部2 小时前
一键给整个库造测试数据:外键、约束一个都不能少
后端·mysql
摇滚侠2 小时前
SpringMVC 入门到实战 拦截器 78-82
java·后端·spring·maven·intellij-idea