哎我跟你说,我这两天真是被大文件上传折磨得脑壳疼……昨晚十二点多还在公司楼下啃着个凉掉的煎饼果子,一边等接口日志刷完,一边跟我们组的小李吐槽,说 SpringBoot 这玩意儿吧,小文件好好的,一到几个 G 的视频,直接卡死,线程吊着不放,人都看傻了。
我就边吃边想,这不行啊,再这么整下去我们那台 8 核的小服务器得被上传搞冒烟。就是那个……叫什么来着……对,multipart,那玩意儿根本顶不住。后来我想了想,咋办?切块呗,分片上传,人类智慧结晶。
那天是晚上十一点半,我们测试妹子在传一个 3.7G 的压缩包,本地网还行,结果我们服务直接 CPU 飙 300%,Tomcat 那线程全在等 I/O,日志都刷不出来,整个人都麻了。
我当时还怀疑是不是 SpringBoot 默认限制搞的鬼(我之前确实踩过这个坑,默认 upload 限制才 1MB,那会儿被客户怼得不行,后来改成了 100MB 才好)
不过这回不是大小限制,是整包上传的问题。凡是走 MultipartResolver 的,Spring 会把文件整段读到内存或者临时文件里,G 级别那肯定凉。
我当时在椅子上晃来晃去,突然想到:切片!
你想嘛,既然一次性传不动,那就切嘛。就像小时候搬砖,搬一个搬不动那就拆成小块搬,两千多年的智慧可不是闹着玩的。
我当时简单拉了个 demo,就是前端切 5MB 一块,再一块一块 POST 上来。后台用 SpringBoot 写个分片接收接口,代码大概就是——等下我找下我当时写的——哦在这:
@PostMapping("/upload/chunk")
public String uploadChunk(
@RequestParam("file") MultipartFile chunk,
@RequestParam("index") Integer index,
@RequestParam("total") Integer total,
@RequestParam("fileId") String fileId
) throws Exception {
File dir = new File("/data/upload_tmp/" + fileId);
if (!dir.exists()) {
dir.mkdirs();
}
File part = new File(dir, index + ".part");
chunk.transferTo(part);
return "ok";
}
其实没啥技术含量,就是保存分片。那会儿我们组的小王看到还说:“东哥你这不就拼积木吗?”我说哎你别瞧不起,很多大厂也都这么干。
真正麻烦的是合并文件…
第二天早上困得眼睛都睁不开,我在办公室里一边喝速溶咖啡一边写合并代码,结果因为路径写错,给我合并出个 12G 的怪物文件,把服务器硬盘写爆了,小李从工位那头喊:“东哥你又干啥啦!!”
后来修好了,合并代码像这样:
@PostMapping("/upload/merge")
public String merge(@RequestParam String fileId,
@RequestParam String fileName,
@RequestParam Integer total) throws Exception {
File dir = new File("/data/upload_tmp/" + fileId);
File output = new File("/data/upload/" + fileName);
try (FileOutputStream fos = new FileOutputStream(output, true)) {
for (int i = 1; i <= total; i++) {
File part = new File(dir, i + ".part");
Files.copy(part.toPath(), fos);
}
}
// 清理临时目录
for (File f : dir.listFiles()) f.delete();
dir.delete();
return "merge done";
}
这段其实要注意几个点,我当时踩过坑:
-
千万不能一次把文件读进内存我第一次用
chunk.getBytes(),直接 OOM,人傻了。 -
要顺序合并前端切片的时候 index 要从 1 开始,不然后端不好判断。
-
最好搞个 MD5 校验不然后面拼出来坏了你都不知道是谁的锅。
我后来还顺手搞了断点续传,舒服多了
因为你们懂的嘛,办公室的 WiFi 那天不知道咋回事,一到下午点就跟猫一样抓不着网,上传一半断了气能急死人。
断点续传其实也不难,就是:
-
前端每片都带上 fileId -
后端维护一个“已上传分片列表” -
前端开始上传前问下后台: “老哥我这些片你收了吗?” -
后端返回缺少的分片号,前端补传即可
我后台写法大概是这样:
@GetMapping("/upload/check")
public Set<Integer> check(String fileId) {
File dir = new File("/data/upload_tmp/" + fileId);
Set<Integer> uploaded = new HashSet<>();
if (dir.exists()) {
for (File f : dir.listFiles()) {
uploaded.add(Integer.valueOf(f.getName().replace(".part", "")));
}
}
return uploaded;
}
你看,也不复杂,就是这玩意儿真解决了我们 3GB 视频、4GB ZIP 的那类痛点。
最后我得提醒一句,我真是被 SpringBoot 默认值坑怕了
之前那个文件上传限制太小的问题,我特么在生产吃过一次大亏。SpringBoot 默认:
-
单文件 1MB -
整个请求 10MB (这个坑我真的是记到骨髓里)
要记得调大:
spring:
servlet:
multipart:
max-file-size: 5GB
max-request-size: 5GB
file-size-threshold: 2KB
不过就算你调到再大,也千万别走“整包上传”,不然 JVM 迟早给你来一下“亲情断电”。
昨晚忙完之后我跟小李说:“以后只要有大文件,就按这个分片搞,不然老命都搭进去。”
你要是现在也被大文件上传卡得怀疑人生,不妨就按我这套试试,五百行代码不到,直接把 G 级传输搞得跟几 MB 一样丝滑。
我先去倒杯热水,嗓子都说干了……你要是想我再补个前端示例也行,咱回头接着聊。
-END-
我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html
🔥东哥私藏精品🔥
东哥作为一名老码农,整理了全网最全《Java高级架构师资料合集》。总量高达650GB。

