news 2026/10/17 2:42:39

Spring Boot 文件上传限制1024MB导致磁盘爆满:临时文件清理与四道防线完整方案

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot 文件上传限制1024MB导致磁盘爆满:临时文件清理与四道防线完整方案

老炮踩坑录 · F04 · 翻车现场系列
基于「企业融合评估平台」真实源码,复盘一个上传限制配置引发的磁盘爆满事故
关键词:max-file-size 1024MB · MultipartConfig 临时目录 · 全局异常未启用 · 磁盘爆满

👋 欢迎阅读

🏠个人主页:知守观
📘我的专栏:老炮踩坑录
💻当前内容:文件上传大小限制

文章目录

    • 前言
    • 先把上传代码过一遍
    • 问题出在哪?
      • 缺陷一:1024MB 这个数字是拍脑袋来的
      • 缺陷二:临时文件落在应用根目录
      • 缺陷三:全局异常处理被注释掉了
      • 缺陷四:业务层面没有文件大小校验
    • 为什么这个坑埋了三年才爆
    • 正确写法:四道防线
      • 防线一:上传限制按业务实际情况设
      • 防线二:临时文件目录用系统临时目录
      • 防线三:全局异常处理要开着
      • 防线四:业务层校验文件大小
    • 自查清单
    • 老炮点评

前言

我离职后大概半年的时间,有一天下午运维的老周给我打电话,语气不太好:“你们那个申报系统,服务器磁盘又满了,上周我刚清理过。”

我第一反应是日志没切分,让他去看看日志目录下文件大小。

过了没几分钟他又回电话过来:“不是日志,是应用根目录,一堆.tmp文件,加起来快 200G了。”

我愣了一下。临时文件怎么会跑到应用根目录去?

打开项目源码翻了翻配置,看到这两行:

# application.ymlspring:servlet:multipart:max-file-size:1024MBmax-request-size:1024MB

单个文件 1GB,整个请求也 1GB。

再往下翻,看到这个:

// MultipartConfig.java@ConfigurationpublicclassMultipartConfig{@BeanMultipartConfigElementmultipartConfigElement(){MultipartConfigFactoryfactory=newMultipartConfigFactory();Stringlocation=System.getProperty("user.dir");FiletmpFile=newFile(location);if(!tmpFile.exists()){tmpFile.mkdirs();}factory.setLocation(location);returnfactory.createMultipartConfig();}}

临时文件目录指向user.dir——也就是应用启动时的工作目录。

那一刻我大概知道发生什么了。

先把上传代码过一遍

这个项目是企业申报系统,企业要上传营业执照、财务报告、诊断报告这些附件。文件最终存到华为云 OBS,中间会在本地落一次临时文件。

上传入口在FileController:

// FileController.java:501@PostMapping("/huawei/upload")publicResultuploadToHuaweiyun(@RequestParam("file")MultipartFile[]files,@Param("enterpriseid")Stringenterpriseid,@RequestParam(value="rapplyid",required=false)Stringrapplyid,@Param("type")Integertype){try{if(files.length==SysContants.SIZE_ZERO){returnnewResult().fail(ResultEnum.FAIL_NOT_ATTACHMENT);}// 校验文件名长度for(MultipartFilemf:files){if(mf.getSize()==SysContants.SIZE_ZERO){returnnewResult().fail(ResultEnum.FAIL_EMPTY_FILE);}StringoriginalFilename=mf.getOriginalFilename();if(originalFilename.length()>64){fns.append(";").append(originalFilename);}}// ...Stringret=huaweiyunFileServiceImpl.uploadFileType(files,type,enterpriseid,rapplyid,endPoint,ak,sk,bucketName);

进到uploadFileType,核心就两步:先transferTo落本地,再调uploadFile分片传华为云。

// HuaweiyunFileServiceImpl.java:204for(inti=0;i<files.length;i++){Filefile1=newFile(path+"/"+UUID.randomUUID()+files[i].getOriginalFilename());try{files[i].transferTo(file1);// 落本地临时文件this.uploadFile(file1,endPoint,ak,sk,bucketName);// 分片传华为云}catch(Exceptione){log.error(e.getMessage(),e);returnnull;}finally{if(file1!=null){file1.delete();// 删临时文件}}}

uploadFile里用的是华为云的分片上传,每片 5MB:

// HuaweiyunFileServiceImpl.java:125longpartSize=5*1024*1024l;longpartCount=file.length()%partSize==0?file.length()/partSize:file.length()/partSize+1;if(partCount>10000){thrownewRuntimeException("所有部分不能超过10000个");}

算一下:10000 片 × 5MB = 50000MB,差不多 48.8GB。理论上单个文件能传到 48GB。

看起来没毛病,那么问题出在哪呢?

问题出在哪?

缺陷一:1024MB 这个数字是拍脑袋来的

营业执照是 PDF,几 MB。财务报告是 Excel 扫描件,几十 MB 顶天。诊断报告是系统生成的 PDF,也就几 MB。

整个业务里没有任何一个场景需要传 1GB 的文件。

那 1024MB 怎么来的?我猜是当年有人上传一个大一点的附件被 Spring 默认的 1MB 限制拦住了,报错MaxUploadSizeExceededException,然后改配置的时候直接拉满到 1024MB。

这一改,等于把"上传限制"这道门拆了。

配置写1024MB,不是技术决策,是情绪决策——怕报错,直接拉满。

缺陷二:临时文件落在应用根目录

MultipartConfig里这行:

Stringlocation=System.getProperty("user.dir");factory.setLocation(location);

user.dir是 JVM 启动时的工作目录,在这个项目里就是应用的根目录。

正常情况下,Spring 的MultipartFile会根据文件大小决定存在内存还是临时目录,超过阈值就落磁盘。请求结束后这些临时文件会被自动清理。

但有两个情况会导致临时文件残留:

  1. 进程异常终止:比如上传到一半 OOM 了,或者被kill -9,临时文件来不及删。
  2. transferTo之后的异常:虽然finally里有file1.delete(),但如果delete()失败(Windows 下文件被占用),或者 catch 里return null之前又抛了异常,临时文件就留下了。

我没亲眼见过生产上的残留情况,但老周说有 200G 的.tmp文件,说明确实在发生。

缺陷三:全局异常处理被注释掉了

// SysControllerAdvice.java// @RestControllerAdvicepublicclassSysControllerAdvice{// @ExceptionHandler(SystemException.class)// @ResponseBody// public Result<String> myerroe(Exception e) {// return new Result<String>().fail(e.getMessage());// }}

整个类的注解和方法全被注释了。等于没有全局异常处理。

如果有人传一个超过 1024MB 的文件,Spring 会抛MaxUploadSizeExceededException。这个异常没有被任何地方捕获,直接返回 500 给前端。

前端拿到 500,用户看到"系统异常",不知道是文件太大的问题。运维也不知道为什么突然一堆 500 错误。

缺陷四:业务层面没有文件大小校验

FileController里只做了两件事:

if(mf.getSize()==SysContants.SIZE_ZERO){returnnewResult().fail(ResultEnum.FAIL_EMPTY_FILE);// 不能是空文件}if(originalFilename.length()>64){fns.append(";").append(originalFilename);// 文件名不能超过64字符}

校验了空文件,校验了文件名长度,唯独没校验文件大小。

再看ApplyFileServiceImpl.validateFile:

// ApplyFileServiceImpl.java:185privateStringvalidateIsIndustryInternet(MultipartFile[]files){returnnull;}privateStringvalidateBenchmarking(MultipartFile[]files){returnnull;}// ... 所有 validateXxx 方法全是 return null

八个校验方法,八个空实现。

等于业务层完全没有文件大小限制,全靠 Spring 的max-file-size兜底。一旦 Spring 那层被绕过或者配大了,就没有第二道防线。

为什么这个坑埋了三年才爆

这个项目从 2020 年上线到我离职,上传一直没出过事。原因很简单:企业传的文件都很小。

营业执照几 MB,财务报告几十 MB,诊断报告几 MB。从来没人传过接近 1GB 的文件。

那 200G 临时文件从哪来的?

我后来问了老周,他说大概是某个企业的财务人员,把一整年的财务凭证扫描成一个 PDF,大概 600 多 MB,上传了好几次都失败(网络超时或者前端报错),每次失败都在应用根目录留下一个 600MB 的临时文件。

传了十几次,磁盘就满了。

这个问题隐蔽在哪:

  1. 平时不传大文件,1024MB 的配置形同虚设,谁也不会注意到。
  2. 临时文件目录在应用根目录,运维监控一般看/tmp和日志目录,不会盯着应用根目录。
  3. 没有全局异常处理,上传失败返回 500,前端没有明确提示,用户就反复重试。
  4. finally里的delete()不一定成功,特别是 Windows 环境或者文件句柄没释放的情况。

四个问题叠在一起,平时不出事,一碰上大文件就崩。

正确写法:四道防线

防线一:上传限制按业务实际情况设

不要拍脑袋写 1024MB。先搞清楚业务上最大的文件有多大,再留个余量。

这个项目里,最大的文件是财务报告,算它 100MB 顶天了,那就设 128MB:

spring:servlet:multipart:max-file-size:128MBmax-request-size:128MBfile-size-threshold:10MB

file-size-threshold也别漏了,超过 10MB 的文件才落磁盘,小文件直接走内存,减少磁盘 IO。

防线二:临时文件目录用系统临时目录

不要用user.dir,用java.io.tmpdir,或者干脆不配置location,让 Spring 用默认值:

@ConfigurationpublicclassMultipartConfig{@BeanMultipartConfigElementmultipartConfigElement(){MultipartConfigFactoryfactory=newMultipartConfigFactory();// 用系统临时目录,不要用 user.dirfactory.setLocation(System.getProperty("java.io.tmpdir"));returnfactory.createMultipartConfig();}}

或者更简单,直接删掉这个配置类,让 Spring Boot 用默认配置。默认的临时目录就是系统临时目录,请求结束后自动清理。

防线三:全局异常处理要开着

@RestControllerAdvicepublicclassSysControllerAdvice{@ExceptionHandler(MaxUploadSizeExceededException.class)@ResponseBodypublicResult<String>handleMaxUploadSize(MaxUploadSizeExceededExceptione){returnnewResult<String>().fail("文件大小超过限制");}@ExceptionHandler(SystemException.class)@ResponseBodypublicResult<String>handleSystem(SystemExceptione){returnnewResult<String>().fail(e.getMessage());}}

至少把MaxUploadSizeExceededException接住,返回一个明确的提示。用户看到"文件太大"就不会反复重试了。

防线四:业务层校验文件大小

不要把所有希望寄托在 Spring 的配置上。业务层也要校验,而且要按不同上传类型区别对待:

publicStringvalidateFile(MultipartFile[]files,Integertype){longmaxSize;switch(type){caseSysContants.UPLOADFILE_TYPE_LICENSEFILEPATH:maxSize=10*1024*1024;// 营业执照 10MBbreak;caseSysContants.UPLOADFILE_TYPE_FINANCEFILEPATH:maxSize=100*1024*1024;// 财务报告 100MBbreak;default:maxSize=50*1024*1024;// 其他 50MB}for(MultipartFilefile:files){if(file.getSize()>maxSize){return"文件大小超过限制,最大允许 "+maxSize/1024/1024+"MB";}}returnnull;}

这样即使 Spring 的配置被改大了,业务层还有第二道防线。

有个细节我没完全确认:Spring Boot 默认的临时目录是 java.io.tmpdir,在 Windows 和 Linux 上行为是不是完全一致?我本地验证的是 Linux 环境,Windows 上没逐个版本试过,有踩过坑的兄弟评论区指点一下。

自查清单

自查打卡:打开你项目的 application.yml,搜 max-file-size,打在评论区——我看看谁配的是 1024MB。

老炮点评

这个事故的本质是“配置脱离了业务实际”。

1024MB 不是一个经过测算的值,是一个"先设大一点免得报错"的值。这个思路在开发阶段很爽——不用改配置,不用跟产品确认文件大小,直接拉满。

但拉满的代价是把风险转嫁给了运维。磁盘满了、服务挂了,运维半夜爬起来处理,开发已经下班了。

文件上传这个事,限制不是越小越好,也不是越大越好,是要跟业务匹配。营业执照 10MB 够了,财务报告 100MB 够了,诊断报告系统生成的根本不用用户传。搞清楚每个上传口的实际文件大小,再留个 20% 的余量,就够了。

临时文件目录也是一样。Spring 默认用系统临时目录,请求结束自动清理,挺好的。非要改成user.dir,等于自己给自己挖坑。

还有全局异常处理和业务层校验,这两个是底线。配置可能被改、网络可能超时、用户可能传错文件,但只要异常能被正确捕获、业务层有兜底,就不会出大事。

"设大一点"是懒人思维,"算清楚"才是工程师该干的事。


最后问一句:你们项目的上传限制是怎么定的?按业务算出来的,还是拍脑袋定了个整数?评论区聊聊,我看看有多少是“先设大一点”的。


下期预告:《番外篇:多模块工程没 install,跑起来的代码是上周的》

主线项目的坑先缓一缓。下期继续翻base-archives的旧账——改了common包的代码,web模块跑起来行为没有任何变化,排查半天才想起来自己根本没install。

Maven reactor、SNAPSHOT缓存、IDEA工作区解析,三套规则混在一起,下期讲清楚怎么确认自己运行的到底是哪个版本的代码。

如果本文对你有帮助,欢迎:

👍 点赞 | ⭐ 收藏 | 👤 关注 | 💬 留言

你的每一次点赞互动都是我继续更新的动力,我们下一篇见!🚀

我是老炮,18 年 Java 老兵,仍在一线。关注「Java老炮踩坑录」,不错过每一篇真实案例,少踩坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/17 2:41:47

goDataFake数据使用说明

## DataFake 使用说明DataFake 是一个用于生成模拟测试数据的工具类&#xff0c;主要用于生成试卷&#xff08;Papers&#xff09;、句子&#xff08;Sentences&#xff09;和单词标记&#xff08;Tokens&#xff09;的假数据。### 1. 获取实例通过依赖注入获取单例&#xff1a…

作者头像 李华
网站建设 2026/10/17 2:41:15

SaaS套餐怎么设计?能力项、资源配额、版本与租户订阅的建模方法

一个 SaaS 系统刚上线时&#xff0c;常见做法是在租户表上放 plan_type&#xff0c;再用 if (plan_type PRO) 判断能不能使用批量报表。只要套餐永远不变&#xff0c;这样似乎够用。但当平台把专业版每月报表任务上限从 20 次调整为 30 次时&#xff0c;问题就来了&#xff1a…

作者头像 李华
网站建设 2026/10/17 2:40:12

Git团队协作实战:从快照模型到分支管理与故障排查

我最早把Git版本控制系统当作正经工具来用&#xff0c;是在维护一个多端共用的业务项目时。当时仓库里已经堆了几十个分支&#xff0c;有人习惯直接在主干上改&#xff0c;有人开了分支三个月不合并&#xff0c;每次发版前最怕的不是业务复杂度&#xff0c;而是把线上分支合并回…

作者头像 李华
网站建设 2026/10/17 2:39:50

服务账号+域级授权:跨域用户管理API批量操作实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/17 2:38:59

OpenSSL 1.1.1源码编译与TLS 1.3配置实战指南

简介&#xff1a;OpenSSL 1.1.1 是面向 Windows 32 位系统的 SSL/TLS 协议实现库&#xff0c;适合程序员、运维人员及安全测试人员在 Windows 环境下进行加密通信开发、证书管理和安全连接调试&#xff0c;能够有效解决数据在传输过程中的保密性、完整性与身份认证等问题。压缩…

作者头像 李华
网站建设 2026/10/17 2:37:46

信息论|联合熵、条件熵、互信息、熵不等式、典型习题训练

** 信息论&#xff5c;联合熵、条件熵、互信息、熵不等式、典型习题训练 内容&#xff1a;信息论基础 &#xff0c;联合熵 &#xff0c;条件熵 &#xff0c;互信息 &#xff0c;信息论计算题 前言 上篇学习了单变量信源的自信息量与信息熵。在实际通信系统中&#xff0c;发送端…

作者头像 李华