SpringBoot 文件上传那些坑,业务系统真实故障复盘,面向开发、测试、运维,偏业务实战,段落通俗,减少晦涩源码
标题:SpringBoot文件上传踩坑实录:本地上传正常,线上文件丢失、报错、大小限制失效,看完少走半年弯路
> 导语:几乎每一个业务系统都会用到文件上传功能,头像上传、附件上传、报表导入导出。很多开发者写完本地测试没问题,部署服务器之后,各种奇怪问题接踵而至:文件上传之后找不到、大文件直接报错、配置参数不生效、临时文件磁盘占满服务器磁盘告警。今天结合项目中遇到的真实线上问题,把文件上传全套坑点、错误代码、修复方案一次性讲清楚。
做业务开发这么多年,文件上传看着简单,实际踩坑的人非常多。很多人网上复制一段配置,本地跑通就直接上线,等到用户量上来才爆发问题。我之前参与的一个后台管理系统,用户上传Excel报表,测试环境一切正常,上线之后部分用户上传直接失败,日志提示临时文件无法创建;还有一次线上磁盘告警,发现服务器磁盘被大量上传产生的临时文件占满。
很多人只知道配置`spring.servlet.multipart.max‑file‑size`,却不知道底层的执行逻辑,导致配置写了根本不生效。
坑点一:配置写在yml,但是完全不生效
错误写法
application.yml
```yaml
spring:
servlet:
multipart:
max-file-size: 20MB
max-request-size: 100MB
```
controller代码
```java
@PostMapping("/upload")
public String upload(@RequestParam MultipartFile file){
//业务保存文件逻辑
return "ok";
}
```
现象:配置明明写了20MB限制,但是上传大于1MB的文件直接报错,配置没有生效。
**根因:SpringBoot版本升级之后,依赖引入问题。**
如果项目手动引入spring‑boot‑starter‑web,但是排除了内置multipart自动配置,或者项目中手动注入自定义StandardServletMultipartResolver Bean,yml里面的multipart配置会直接失效。
还有一种情况:部分老版本SpringBoot,如果同时引入第三方文件上传组件,会覆盖默认自动配置。
排查方式:开启debug日志,观察MultipartAutoConfiguration是否被加载。
修复方案两种:
方案1:不要手动覆盖MultipartResolver,保证自动配置生效,yml配置才会读取;
方案2:手动在Java代码里面硬编码设置大小限制,优先级高于yml配置。
```java
@Bean
public MultipartResolver multipartResolver(){
StandardServletMultipartResolver resolver = new StandardServletMultipartResolver();
resolver.setResolveLazily(true);
return resolver;
}
```
同时在ServletRegistrationBean设置multipart配置。
坑点二:直接使用file.transferTo(),线上出现文件丢失
这是高频线上bug,本地windows环境完全正常,Linux生产环境偶尔出现上传的文件为空或者直接消失。
错误代码:
```java
@PostMapping("/upload")
public void upload(@RequestParam MultipartFile file) throws IOException {
File dest = new File("/data/upload/" + file.getOriginalFilename());
file.transferTo(dest);
}
```
很多同学不知道MultipartFile的底层逻辑:
当上传文件小于阈值,文件保存在内存;大于阈值,会生成服务器临时目录下的临时文件。
**请求结束之后,Spring会自动删除临时文件。**
transferTo有一个坑:如果dest是相对路径,部分Linux环境下,不会复制文件,而是直接移动临时文件。如果请求处理完毕,临时文件被框架删除,目标文件就直接丢失。
修复方案:
1. 使用绝对路径;
2. 不要直接transferTo,改用IO流复制,避开文件移动行为;
```java
// 安全写法
File dest = new File("/data/upload/" + file.getOriginalFilename());
try(InputStream in = file.getInputStream();
FileOutputStream out = new FileOutputStream(dest)){
in.transferTo(out);
}
```
坑点三:临时文件堆积,磁盘被打满
现象:服务器磁盘使用率持续上涨,df -h查看磁盘爆满,查看/tmp目录,大量spring生成的multipart临时文件没有清理。
根因:
当程序抛出异常,业务代码中断,部分场景下临时文件清理逻辑执行不到位;服务异常重启,大量旧临时文件残留在磁盘。
SpringBoot默认临时目录,部分容器化部署docker环境,tmp目录不会自动回收。
解决方案:
1. 自定义临时文件目录,不要使用系统默认/tmp;
```yaml
spring:
servlet:
multipart:
location: /data/upload/tmp
```
2. 写定时任务,定期清理超过24小时的临时上传文件;
3. k8s部署,设置emptyDir的时候注意生命周期。
坑点四:文件名中文、特殊符号,线上乱码、路径穿越漏洞
错误示例,直接拿getOriginalFilename直接拼接路径。
```java
String fileName = file.getOriginalFilename();
File out = new File("/data/upload/"+fileName);
```
风险:用户上传文件名带 `../../etc/passwd`,发生路径穿越,覆盖服务器系统文件。同时中文文件名,不同操作系统编码不一致,出现乱码,文件无法访问。
安全处理代码:
```java
// 过滤特殊字符,生成新文件名,不要直接使用原始文件名
String suffix = FilenameUtils.getExtension(file.getOriginalFilename());
String newFileName = UUID.randomUUID() + "." + suffix;
// 校验后缀,只允许业务允许的后缀,拒绝exe、sh脚本文件
List allowSuffix = Arrays.asList("jpg","png","xlsx","pdf");
if(!allowSuffix.contains(suffix.toLowerCase())){
throw new RuntimeException("不支持的文件类型");
}
File out = new File("/data/upload/"+newFileName);
```
坑点五:Nginx反向代理层截断大文件
很多业务代码SpringBoot配置好了,但是Nginx没有配置client_max_body_size,大文件上传直接413 Request Entity Too Large。
很多开发只改SpringBoot,忽略Nginx网关层限制。
nginx.conf增加配置:
```nginx
http {
client_max_body_size 100M;
}
```
生产环境完整建议
1. 生产环境不要直接存储原始文件名,使用UUID生成文件名,原始文件名存入数据库;
2. 做好文件后缀白名单校验,防止上传恶意脚本;
3. 上传目录和应用程序目录分开,不要放在jar包内部;
4. docker/k8s部署,上传目录挂载持久化存储,不要存在容器内部;
5. 监控上传临时目录磁盘占用,定时清理过期临时文件;
6. 网关Nginx和SpringBoot两层都配置文件大小限制,网关数值要大于后端。
很多问题本地复现不出来,Windows和Linux文件系统行为差异很大,本地开发一切ok,上线直接出故障。开发完成之后,尽量在测试环境模拟Linux环境,测试大文件、特殊文件名、异常中断场景。
---
凡尘版权 © 2026
热门跟贴