Spring Boot超大文件上传的正确方式

开发 前端
分片上传文件指的是将大文件分割成较小的部分(即分片),然后依次或并行地将这些分片上传到服务器的过程。一旦所有分片都上传完毕,服务器会将它们合并以重新创建出原始文件。

环境:SpringBoot3.4.0

1. 简介

文件上传功能是个非常常见的需求,它允许用户将本地计算机上的文件通过网络传输到远程服务器。然而,如果不对大文件的上传进行适当的控制,很可能会对服务器造成以下不良影响:

  • 网络不稳定性:大文件上传耗时较长,期间网络的任何不稳定性都可能导致上传失败,需要重新上传整个文件,这不仅耗时而且效率低下。
  • 带宽限制:在带宽有限的网络环境中,大文件上传可能会占用大量带宽,导致其他网络活动受阻,影响用户体验。
  • 服务器负担:一次性处理大量数据会给服务器带来巨大负担,尤其是在高并发的情况下,可能导致服务器响应缓慢或崩溃。

如何解决大文件上传的问题呢?接下来,我们将介绍一种有效的解决方案——分片上传。

分片上传文件指的是将大文件分割成较小的部分(即分片),然后依次或并行地将这些分片上传到服务器的过程。一旦所有分片都上传完毕,服务器会将它们合并以重新创建出原始文件。

分片上传原理:

  • 在客户端将文件分割成较小的分片
  • 将每个分片单独上传到服务器
  • 所有分片上传完成后,利用这些分片重新构建出原始文件

接下来,我们将通过Spring Boot 3与Vue 3的结合来实现大文件的分片上传功能。

2. 实战案例

2.1 前端页面

我们仅为了演示文件的分片上传功能,所以设计的页面非常简洁,仅包含三个按钮,页面效果如下所示:图片

前端代码

<el-upload ref="upload" class="upload-demo"
  :limit="1" :auto-upload="false" :http-request="uploadFile">
  <template #trigger>
    <el-button type="primary" style="margin-right: 10px;">选择文件</el-button>
  </template>
  <el-button class="ml-3" type="success" @click="submitUpload">
    上传文件
  </el-button>
</el-upload>
<el-button class="ml-3" type="primary" @click="mergeFile">合并文件</el-button>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

JavaScript代码

<script setup name="upload">
  import { ref } from 'vue'


  const upload = ref('')
  let fileName = ''
  /**拆分文件,这里估计将文件每2M进行拆分*/
  const uploadFileInChunks = file => {
    const chunkSize = 1024 * 1024 * 2
    let start = 0
    let chunkIndex = 0


    while (start < file.size) {
      const chunk = file.slice(start, start + chunkSize)
      console.log(chunk)
      fileName = file.name
      uploadChunk(chunk, chunkIndex, fileName)
      start += chunkSize
      chunkIndex++
    }
  }
  /**对每一个拆分的文件进行上传;这就就成了小文件上传*/
  const uploadChunk = (chunk, chunkIndex, fileName) => {
    const formData = new FormData()
    formData.append('chunk', chunk)
    formData.append('chunkIndex', chunkIndex)
    formData.append('fileName', fileName)


    fetch('http://localhost:8080/upload-chunk', {
      method: 'POST',
      body: formData
    }).then(resp => {
      console.log(resp)
    })
  }
  const uploadFile = (opt) => {
    uploadFileInChunks(opt.file)
  }
  const submitUpload = () => {
    upload.value.submit()
  }
  /**合并文件*/
  const mergeFile = () => {
    const formData = new FormData()
    formData.append('fileName', fileName)
    fetch('http://localhost:8080/merge-chunks', {
      method: 'POST',
      body: formData
    }).then(resp => {
      console.log(resp)
    })
  }
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.

前端代码还是非常简单的;其核心就是拿到上传文件的File对象,然后对文件进行拆分。

2.2 文件上传接口

@RestController
public class ChunkController {
  private static final String TEMP_DIR = "d:\\upload\\";
  @PostMapping("/upload-chunk")
  public ResponseEntity<String> uploadChunk(
      @RequestParam("chunk") MultipartFile chunk,
      @RequestParam("chunkIndex") int chunkIndex, 
      @RequestParam("fileName") String fileName) throws IOException {
    File dir = new File(TEMP_DIR + fileName);
    if (!dir.exists()) {
      dir.mkdirs();
    }
    File chunkFile = new File(dir, "chunk_" + chunkIndex);
    try (OutputStream os = new FileOutputStream(chunkFile)) {
      os.write(chunk.getBytes());
    }
    return ResponseEntity.ok("Chunk " + chunkIndex + " uploaded successfully.");
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

这里非常的简单与我们平时的文件上传一模一样。

接下来,我们就可以进行文件的上传了

图片图片

这里选择了一个25MB大小的文件,点击上传后控制台输出:

图片图片

这里拆分成了13个小文件进行上传。

最终,后台服务上的文件如下:

图片

以上传的文件名创建了目录,存分块后的小文件。

文件上传完成后,最后我们就要对这些文件进行合并处理了。

2.3 合并文件接口

@RestController
public class ChunkController {
  private static final String TEMP_DIR = "d:\\upload\\";
  private static final String TARGET_DIR = "d:\\upload\\result\\";
  
  @PostMapping("/merge-chunks")
  public ResponseEntity<String> mergeChunks(
      @RequestParam("fileName") String fileName) throws IOException {
    File dir = new File(TEMP_DIR + fileName);
    File mergedFile = new File(TARGET_DIR + fileName);
    try (OutputStream os = new FileOutputStream(mergedFile)) {
      for (int i = 0, len = dir.listFiles().length; i < len; i++) {
        File chunkFile = new File(dir, "chunk_" + i);
        Files.copy(chunkFile.toPath(), os);
        chunkFile.delete();
      }
    }
    dir.delete();
    return ResponseEntity.ok("文件合并完成");
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

这里就是遍历目录中的所有文件,然后按照顺序写入到一个目标文件中即可。这样我们就完成了文件的合并。

图片

到此我们实现了文件的分块上传功能。

责任编辑:武晓燕 来源: Springboot全家桶实战案例源码
相关推荐

2009-12-07 09:45:23

PHP上传大文件设置

2021-01-25 14:10:49

Spring BootVueJava

2021-09-15 16:20:02

Spring BootFilterJava

2021-05-10 07:33:10

Java开源工具

2009-11-16 11:41:19

PHP上传大文件

2022-06-13 14:06:33

大文件上传前端

2021-01-15 12:02:25

java 大文件工具

2025-03-11 00:55:00

Spring停机安全

2023-03-09 12:04:38

Spring文件校验

2022-05-17 10:45:55

项目VueElementUI

2021-03-11 14:16:47

Spring Boo开发脚手架

2009-07-21 15:38:31

2024-12-26 11:01:22

2024-01-23 08:47:13

BeanSpring加载方式

2023-11-01 15:07:51

环境配置方式

2021-01-15 11:40:44

文件Java秒传

2024-07-02 10:18:18

2024-10-12 08:18:21

Spring接口组件

2009-07-20 16:09:39

2020-04-02 20:07:17

前端vuenote.js
点赞
收藏

51CTO技术栈公众号