Spring Boot实现分片上传、断点续传与进度条功能

 更新时间:2026年02月28日 09:35:46   作者:点我设置昵称  
本文介绍了如何使用SpringBoot实现分片上传、断点续传和进度条等功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Spring Boot 实现分片上传、断点续传与进度条

# Spring Boot 实现分片上传、断点续传与进度条  
## —— 支持 MinIO / RustFS / SeaweedFS 可配置切换
在大文件上传场景下,传统单次上传存在明显问题:
- 文件过大,失败后需要整体重传
- 网络不稳定,用户体验差
- 无法展示上传进度
本文基于 **Spring Boot**,实现一套**生产可用**的大文件上传方案,支持:
- 分片上传
- 断点续传
- 上传进度查询
- MinIO / RustFS / SeaweedFS / 本地存储
- 通过 yml 配置文件切换存储类型
---
## 一、整体架构设计
系统整体采用「接口隔离 + 策略模式」设计:

Controller ↓ UploadService ↓ StorageService(统一抽象) ↓ MinIO / RustFS / SeaweedFS / Local

**核心思想:上传逻辑与底层存储解耦。**
---
## 二、统一配置设计
### 1. 存储类型切换
```yaml
file:
  path: file/
  prefix: pre
  domain: domain/
  storage:
    type: minio   # minio / rustfs / seaweedfs / local

只需修改 storage.type,即可切换存储实现。

2. MinIO 配置

minio:
  url: http://localhost:9000
  accessKey: minioadmin
  secretKey: minioadmin123
  bucketName: xxx

3. RustFS 配置(S3 协议)

rustfs:
  url: http://localhost:9000
  accessKey: rustfsadmin
  secretKey: rustfsadmin
  bucketName: xxx

RustFS 兼容 S3 协议,可直接复用 MinIO SDK。

4. SeaweedFS 配置

seaweedfs:
  url: http://127.0.0.1:8333
  accessKey: weed
  secretKey: weed
  bucketName: xxx

三、分片上传核心流程

1. 前端切片思路

前端将大文件切割为多个分片(如 5MB):

file
├── chunk_0
├── chunk_1
├── chunk_2
└── chunk_n

每个分片上传时携带:

  • 文件唯一标识(guid / md5)
  • 分片索引(chunkIndex)
  • 总分片数(totalChunk)

2. 分片上传接口

POST /file/chunk/upload

四、断点续传实现

1. 查询已上传分片

GET /file/chunk/uploaded?guid=xxx

返回示例:

[0,1,3,5]

前端只上传缺失分片即可。

2. 实现原则

  • 是否已上传以「存储层」为准
  • 不依赖内存或 Redis
  • 服务重启不影响续传

五、上传进度计算

进度 = 已上传分片数 / 总分片数 × 100%

示例返回:

{
  "uploaded": 6,
  "total": 10,
  "percent": 60
}

六、存储层抽象设计

1. 统一接口定义

public interface StorageService {
    void uploadChunk(String path, InputStream inputStream);
    boolean exists(String path);
    List<Integer> listChunks(String prefix);
    void mergeChunks(String chunkPrefix, String targetPath, int totalChunk);
    void deleteChunks(String chunkPrefix);
}

2. MinIO / RustFS 实现(S3 通用)

@Slf4j
public class MinioStorageService implements StorageService {
    private final MinioClient minioClient;
    private final String bucket;
    public MinioStorageService(MinioClient client, String bucket) {
        this.minioClient = client;
        this.bucket = bucket;
    }
    @Override
    public void uploadChunk(String path, InputStream inputStream) {
        try {
            minioClient.putObject(
                PutObjectArgs.builder()
                    .bucket(bucket)
                    .object(path)
                    .stream(inputStream, -1, 5 * 1024 * 1024)
                    .build()
            );
        } catch (Exception e) {
            throw new RuntimeException("分片上传失败", e);
        }
    }
    @Override
    public boolean exists(String path) {
        try {
            minioClient.statObject(
                StatObjectArgs.builder()
                    .bucket(bucket)
                    .object(path)
                    .build()
            );
            return true;
        } catch (Exception e) {
            return false;
        }
    }
    @Override
    public List<Integer> listChunks(String prefix) {
        List<Integer> chunks = new ArrayList<>();
        Iterable<Result<Item>> results =
            minioClient.listObjects(
                ListObjectsArgs.builder()
                    .bucket(bucket)
                    .prefix(prefix)
                    .build()
            );
        for (Result<Item> r : results) {
            String name = r.get().objectName();
            chunks.add(Integer.parseInt(
                name.substring(name.lastIndexOf("_") + 1)));
        }
        return chunks;
    }
    @Override
    public void mergeChunks(String chunkPrefix,
                            String targetPath,
                            int totalChunk) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            for (int i = 0; i < totalChunk; i++) {
                InputStream in = minioClient.getObject(
                    GetObjectArgs.builder()
                        .bucket(bucket)
                        .object(chunkPrefix + "/chunk_" + i)
                        .build()
                );
                IOUtils.copy(in, out);
            }
            uploadChunk(
                targetPath,
                new ByteArrayInputStream(out.toByteArray()));
        } catch (Exception e) {
            throw new RuntimeException("分片合并失败", e);
        }
    }
    @Override
    public void deleteChunks(String chunkPrefix) {
        // 可按需实现批量删除
    }
}

七、业务 Service 实现

@Service
public class FileUploadServiceImpl implements FileUploadService {
    @Autowired
    private StorageService storageService;
    @Override
    public void uploadChunk(ChunkUploadDTO dto) throws IOException {
        String path = dto.getGuid() + "/chunk_" + dto.getChunkIndex();
        if (storageService.exists(path)) {
            return;
        }
        storageService.uploadChunk(
            path, dto.getFile().getInputStream());
    }
    @Override
    public List<Integer> uploadedChunks(String guid) {
        return storageService.listChunks(guid + "/");
    }
    @Override
    public void merge(String guid, int totalChunk) {
        storageService.mergeChunks(
            guid, guid + ".final", totalChunk);
        storageService.deleteChunks(guid + "/");
    }
}

八、Controller 接口

@RestController
@RequestMapping("/file/chunk")
public class FileUploadController {
    @Autowired
    private FileUploadService fileUploadService;
    @PostMapping("/upload")
    public void upload(ChunkUploadDTO dto) throws IOException {
        fileUploadService.uploadChunk(dto);
    }
    @GetMapping("/uploaded")
    public List<Integer> uploaded(@RequestParam String guid) {
        return fileUploadService.uploadedChunks(guid);
    }
    @PostMapping("/merge")
    public void merge(@RequestParam String guid,
                      @RequestParam Integer totalChunk) {
        fileUploadService.merge(guid, totalChunk);
    }
}

九、总结

本文实现了一套 Spring Boot 大文件上传方案,具备:

  • 分片上传
  • 断点续传
  • 上传进度计算
  • 多存储后端解耦
  • yml 配置快速切换

适用于文件中心、数据平台、企业网盘等场景,可直接用于生产环境

到此这篇关于Spring Boot 实现分片上传、断点续传与进度条的文章就介绍到这了,更多相关springboot分片上传断点续传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Spring Boot 配置大全(小结)

    Spring Boot 配置大全(小结)

    本篇文章主要介绍了Spring Boot 配置大全(小结),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-11-11
  • java中的static{}块的实例详解

    java中的static{}块的实例详解

    这篇文章主要介绍了java中的static{}块的实例详解的相关资料,这里提供实例来帮助大家理解该如何使用static块,需要的朋友可以参考下
    2017-08-08
  • Java实现的文件上传下载工具类完整实例【上传文件自动命名】

    Java实现的文件上传下载工具类完整实例【上传文件自动命名】

    这篇文章主要介绍了Java实现的文件上传下载工具类,结合完整实例形式分析了java针对文件上传下载操作的相关实现技巧,并且针对上传文件提供了自动命名功能以避免文件命名重复,需要的朋友可以参考下
    2017-11-11
  • Spring中的FactoryBean与BeanFactory详细解析

    Spring中的FactoryBean与BeanFactory详细解析

    这篇文章主要介绍了Spring中的FactoryBean与BeanFactory详细解析,在Spring框架中,FactoryBean和BeanFactory是两个关键的接口,用于创建和管理对象实例,它们在Spring的IoC(Inversion of Control,控制反转)容器中发挥着重要的作用,需要的朋友可以参考下
    2023-11-11
  • Java虚拟机启动过程探索

    Java虚拟机启动过程探索

    当我们在编写Java应用的时候,很少会注意Java程序是如何被运行的,如何被操作系统管理和调度的,带着好奇心,探索一下Java虚拟机启动过程
    2022-05-05
  • 浅析SpringBoot中使用thymeleaf找不到.HTML文件的原因

    浅析SpringBoot中使用thymeleaf找不到.HTML文件的原因

    这篇文章主要介绍了SpringBoot中使用thymeleaf找不到.HTML文件的原因分析,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • Java集合之LinkedHashSet集合详解

    Java集合之LinkedHashSet集合详解

    这篇文章主要介绍了Java集合之LinkedHashSet集合详解,具有可预知迭代顺序的Set接口的哈希表和链表列表实现,此实现与HashSet不同的是,后者维护着一个运行于所有条目的双重链表列表,此链表定义了迭代顺序,需要的朋友可以参考下
    2023-09-09
  • Spring创建Bean的八种主要方式详解

    Spring创建Bean的八种主要方式详解

    Spring(尤其是 Spring Boot)提供了多种方式来让容器创建和管理 Bean,@Component、@Configuration + @Bean、@EnableConfigurationProperties 都是常见方式,下面我为你系统地梳理 Spring 创建 Bean 的所有主要方式,并说明它们的使用场景和区别,需要的朋友可以参考下
    2025-08-08
  • Java基础知识精通各种运算符

    Java基础知识精通各种运算符

    计算机的最基本用途之一就是执行数学运算,作为一门计算机语言,Java也提供了一套丰富的运算符来操纵变量,本篇对大家的学习或工作具有一定的价值,需要的朋友可以参考下
    2022-04-04
  • MyBatis 在 Spring Boot 中的实践记录

    MyBatis 在 Spring Boot 中的实践记录

    MyBatis是持久层框架,简化JDBC开发,通过接口+XML/注解实现数据访问,动态代理生成实现类,支持增删改查及参数映射,配置数据库连接与驼峰转换,接下来通过本文给大家介绍破茧JDBC:MyBatis在Spring Boot中的轻量实践指南,感兴趣的哦朋友一起看看吧
    2025-08-08

最新评论