文件上传
约 1017 字大约 3 分钟
布欧-Lewyon
2026-05-15
首页 › Spring Boot › Web 与 REST › 文件上传
文件上传是 Web 应用的高频功能。Spring Boot 通过 MultipartResolver 简化了 multipart/form-data 类型的请求处理。
基础配置
spring:
servlet:
multipart:
enabled: true # 启用 multipart 支持(默认 true)
max-file-size: 10MB # 单个文件最大大小
max-request-size: 50MB # 整个请求最大大小(含多文件)
file-size-threshold: 2KB # 超过此大小写入磁盘临时文件
location: /tmp/uploads # 临时文件目录(默认 Servlet 容器临时目录)生产意识:
max-file-size仅在MultipartResolver阶段拦截——超过此大小的请求会在进入 Controller 之前返回 413 Payload Too Large。location不配置时使用 Servlet 容器的默认临时目录(如 Tomcat 的$CATALINA_BASE/work/),在容器化场景中可能被 Pod 自动清理或空间不足。建议显式配置为持久卷路径。
单文件上传
@RestController
@RequestMapping("/api/files")
public class FileUploadController {
private final Path uploadDir = Path.of("/data/uploads");
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error(400, "文件不能为空");
}
try {
String originalName = file.getOriginalFilename();
String extension = "";
if (originalName != null && originalName.contains(".")) {
extension = originalName.substring(originalName.lastIndexOf("."));
}
String storedName = UUID.randomUUID() + extension;
Path targetPath = uploadDir.resolve(storedName);
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
return Result.success(storedName);
} catch (IOException e) {
log.error("文件上传失败", e);
return Result.error(500, "文件上传失败");
}
}
}多文件上传
@PostMapping("/uploads")
public Result<List<String>> uploadMultiple(
@RequestParam("files") MultipartFile[] files) {
List<String> fileNames = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String name = saveFile(file);
fileNames.add(name);
}
}
return Result.success(fileNames);
}文件下载
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> download(@PathVariable String fileName) {
Path filePath = uploadDir.resolve(fileName);
if (!Files.exists(filePath)) {
return ResponseEntity.notFound().build();
}
Resource resource = new UrlResource(filePath.toUri());
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"")
.body(resource);
}文件类型与大小校验
@PostMapping("/upload")
public Result<String> upload(@RequestParam("file") MultipartFile file) {
// 校验文件类型(ContentType + 扩展名双重验证)
String contentType = file.getContentType();
if (contentType == null || !contentType.startsWith("image/")) {
return Result.error(400, "仅允许上传图片文件");
}
// 校验扩展名
String originalName = file.getOriginalFilename();
if (originalName == null || !originalName.matches(".*\\.(jpg|jpeg|png|gif)$")) {
return Result.error(400, "不支持的文件格式");
}
// 校验文件大小(在 application.yml 中已控制,这里做二次校验)
if (file.getSize() > 10 * 1024 * 1024) {
return Result.error(400, "文件大小不能超过 10MB");
}
// 保存...
}生产意识:文件类型校验不能仅依赖
ContentType(可由客户端伪造),应结合魔数(Magic Number)验证——读取文件头几个字节判断真实类型。例如 JPEG 文件头为FF D8 FF、PNG 为89 50 4E 47。生产环境中不建议将上传文件保存在本地磁盘——推荐上传到对象存储(OSS / S3 / MinIO),应用只保存 URL 引用。
源码与架构:MultipartResolver 解析链
文件上传的处理入口在 DispatcherServlet.doDispatch() 中——第一步即检查是否为 multipart 请求:
DispatcherServlet.doDispatch(request, response)
│
├── checkMultipart(request)
│ └── 判断 Content-Type 是否以 multipart/ 开头
│ └── 是 → 通过 MultipartResolver 解析
│ ├── StandardServletMultipartResolver(默认,基于 Servlet 3.0 Part API)
│ │ └── request.getParts() → 解析为 StandardMultipartFile
│ └── CommonsMultipartResolver(需引入 commons-fileupload)
│ └── FileUploadBase.parseRequest() → 解析为 CommonsMultipartFile
│
└── 返回 MultipartHttpServletRequest 包装对象
└── @RequestParam("file") MultipartFile → RequestParamMethodArgumentResolver
└── 从包装请求中按参数名提取 MultipartFile性能要点:file.getBytes() 将整个文件加载到 JVM 堆内存——上传大文件(>100MB)时易触发 OOM。推荐使用 file.getInputStream() 或 file.transferTo() 流式处理。transferTo() 在文件未超过 file-size-threshold 时直接从内存写入目标路径(零拷贝),超过阈值时从临时文件复制。
小结
spring.servlet.multipart.*配置全局上传参数(大小、临时路径)。MultipartFile提供文件流、元数据和方法,推荐流式处理避免 OOM。- 文件类型须从魔数 + ContentType 双重验证;文件名用 UUID 重命名防冲突。
- 易错:
@RequestParam("file")的参数名必须与表单name属性一致。max-file-size超出时返回 413 而不是业务错误码——如需自定义错误信息需在@RestControllerAdvice中捕获MaxUploadSizeExceededException。临时目录/tmp在 Linux 下可能被 systemd 自动清理,生产环境的临时目录必须配置为持久卷。 - 思考任务:实现一个带有文件类型校验和大小控制的文件上传接口,支持多文件同时上传;增加
uploadDir通过@ConfigurationProperties配置。
上一节:参数校验与异常处理
下一节:测试
