大数跨境
0
0

SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升!

SpringBoot大文件上传卡死?分块切割术搞定GB级传输,速度飙升! Java技术图谱
2025-12-05
4

哎我跟你说,我这两天真是被大文件上传折磨得脑壳疼……昨晚十二点多还在公司楼下啃着个凉掉的煎饼果子,一边等接口日志刷完,一边跟我们组的小李吐槽,说 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 那天不知道咋回事,一到下午点就跟猫一样抓不着网,上传一半断了气能急死人。

断点续传其实也不难,就是:

  1. 前端每片都带上 fileId
  2. 后端维护一个“已上传分片列表”
  3. 前端开始上传前问下后台: “老哥我这些片你收了吗?”
  4. 后端返回缺少的分片号,前端补传即可

我后台写法大概是这样:

@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

【声明】内容源于网络
0
0
Java技术图谱
回复 java,领取Java面试题。分享AI编程,AI工具,Java教程,Java下载,Java技术栈,Java源码,Java课程,Java技术架构,Java基础教程,Java高级教程,idea教程,Java架构师,Java微服务架构。
内容 1111
粉丝 0
Java技术图谱 回复 java,领取Java面试题。分享AI编程,AI工具,Java教程,Java下载,Java技术栈,Java源码,Java课程,Java技术架构,Java基础教程,Java高级教程,idea教程,Java架构师,Java微服务架构。
总阅读56
粉丝0
内容1.1k