minio springboot 实现大文件的分片上传、断点续传、秒传的功能

前言

本文将分享下本人做大文件上传的一些思路,以及相关代码的实现。至于minio的搭建,还是比较简单的。本文就不再赘述。本文搭建的🌰例子也仅仅是把主要流程走通,相关的demo代码可能会有bug
有不同思路的大佬也可以在评区分享下,开拓下思路。
其实主要需要实现的就是分片上传。断点续传,秒传仅仅是在分片上传的基础上增加的逻辑扩张。

demo源码地址

https://gitee.com/Gary2016/minio-upload

演示

在这里插入图片描述

大致步骤

流程图

在这里插入图片描述

  1. 前端获取到文件流,计算出文件的唯一标识identifier(md5摘要)。
  2. 将获取到的identifier传递给后端,查询该文件的上传任务记录。如果没有则初始化一个上传任务
  3. 校验上传任务记录是否完成上传(成功执行合并分片的操作后视为完成上传)
    3.1 任务完成,直接返回文件地址
    3.2 任务未完成,获取已上传的分片。前端按照分片任务中记录的分片大小将文件分片。然后遍历所有分片进行单片上传,如果分块存在于已上传的分片列表中,则跳过该分块的上传。所有分片完成上传后,请求后端合并分片的接口进行合并。合并完成后,返回文件地址

单片上传

单片上传是通过预签名上传的方式:获取到minio经过签名的上传地址后由前端直接向minio服务器发起真正的上传请求。避免上传时占用应用服务器的带宽,影响系统稳定。

代码实现

主要技术栈

vue 3.0
element plus
promise-queue-plus
springboot 2.7.3
mybatis-plus 3.5.1
aws-java-sdk-s3 1.12.263
mysql8
minio 最新版

后端实现

数据库设计

实现断点续传,秒传的前提就是服务端需要记录文件的上传进度。因此,需要一张表来记录文件的上传记录。至于已上传的分块记录由minio提供的接口来获取。
以下是表设计

CREATE TABLE `sys_upload_task` (
  `id` bigint NOT NULL,
  `upload_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '分片上传的uploadId',
  `file_identifier` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件唯一标识(md5)',
  `file_name` varchar(500) COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件名',
  `bucket_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '所属桶名',
  `object_key` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文件的key',
  `total_size` bigint NOT NULL COMMENT '文件大小(byte)',
  `chunk_size` bigint NOT NULL COMMENT '每个分片大小(byte)',
  `chunk_num` int NOT NULL COMMENT '分片数量',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uq_file_identifier` (`file_identifier`) USING BTREE,
  UNIQUE KEY `uq_upload_id` (`upload_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='分片上传-分片任务记录';
接口设计

以下接口的响应参数均被包装在Result对象的data字段中

Result

名称 类型 说明
code int 自定义状态码(成功:200000,失败:500000)
data object 接口真实数据
msg string 信息

1.根据文件唯一标识获取上传任务

主要流程就是查询数据库记录,存在上传任务再通过amazon s3的sdk方法:amazonS3.doesObjectExist,判断是否存在文件对象,存在则说明已经合并完成。

接口地址:/v1/minio/tasks/{identifier}
请求方式:GET
响应参数:

名称 类型 说明
finished boolean 是否完成上传
path string 文件地址
taskRecord TaskRecordDTO 任务记录信息

TaskRecordDTO

名称 类型 说明
id long 任务id
uploadId string minio的uploadId
fileIdentifier string 文件唯一标识(MD5)
fileName string 文件名称
bucketName string 所属桶名
objectKey string 文件的key
totalSize long 文件大小(byte)
chunkSize long 每个分片大小(byte)
chunkNum int 分片数量
exitPartList PartSummary[] 已上传完的分片 (finished为true时,该字段为null)

PartSummary(该类由s3的sdk提供)

名称 类型 说明
partNumber int 分片编号
lastModified Date 最后修改时间
eTag string 分片的eTag(MD5)
size long 分片大小

主要代码

/**
 * 获取上传进度
 * @param identifier 文件md5
 * @return
 */
@GetMapping("/{identifier}")
public Result<TaskInfoDTO> taskInfo (@PathVariable("identifier") String identifier) {
   
    return Result.ok(sysUploadTaskService.getTaskInfo(identifier));
}
@Override
public TaskInfoDTO getTaskInfo(String identifier) {
   
    SysUploadTask task = getByIdentifier(identifier);
    if (task == null) {
   
        return null;
    }
    TaskInfoDTO result = new TaskInfoDTO().setFinished(true).setTaskRecord(TaskRecordDTO.convertFromEntity(task)).setPath(getPath(task.getBucketName(), task.getObjectKey()));

    boolean doesObjectExist = amazonS3.doesObjectExist(task.getBucketName(), task.getObjectKey());
    if (!doesObjectExist) {
   
        // 未上传完,返回已上传的分片
        ListPartsRequest listPartsRequest = new ListPartsRequest(task.getBucketName(), task.getObjectKey(), task.getUploadId());
        PartListing partListing = amazonS3.listParts(listPartsRequest);
        result.setFinished(false).getTaskRecord().setExitPartList(partListing.getParts());
    }
    return result;
}

2.初始化一个上传任务

当接口1返回的数据为null时,调用此接口初始化一个上传任务。

接口地址:/v1/minio/tasks
请求方式:POST
请求参数(body):

名称 类型 说明
identifier string 文件唯一标识(MD5)
totalSize long 文件大小(byte)
chunkSize long 分片大小(byte)
fileName string 文件名称

响应参数:与接口1的响应参数一致,此处就不再重复

主要代码

/**
 * 创建一个上传任务
 * @return
 */
@PostMapping
public Result<TaskInfoDTO> initTask (@Valid @RequestBody 
  • 23
    点赞
  • 133
    收藏
    觉得还不错? 一键收藏
  • 23
    评论
Spring Boot实现MinIO文件服务器的分片上传断点续传,可以使用MinIO Java SDK提供的API和Spring Boot提供的Multipart File上传功能。具体实现步骤如下: 1. 引入MinIO Java SDK和Spring Boot的依赖 ```xml <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.0.4</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> ``` 2. 配置MinIO客户端 在Spring Boot的配置文件中,配置MinIO客户端的连接信息。 ```yaml minio: endpoint: minio.example.com access-key: ACCESS_KEY secret-key: SECRET_KEY secure: false ``` 3. 初始化Multipart上传上传文件之前,使用 `InitiateMultipartUpload` API 方法初始化一个Multipart上传会话,并获取一个上传ID。上传ID用于标识一个Multipart上传会话。 ```java import io.minio.MinioClient; import io.minio.errors.MinioException; import io.minio.messages.Part; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.util.ArrayList; import java.util.List; @Service public class MinioService { @Autowired private MinioClient minioClient; @Value("${minio.bucket-name}") private String bucketName; public String initiateMultipartUpload(String objectName) throws MinioException { String uploadId = minioClient.initiateMultipartUpload(bucketName, objectName).uploadId(); return uploadId; } } ``` 4. 上传文件块 将大文件分成多个小块,每个小块的大小可以根据需求自定义。使用 `UploadPart` API 方法将每个小块独立上传上传时需要指定上传文件名、块编号、块大小以及上传ID等信息。 ```java public class MinioService { // ... public List<Part> uploadParts(String objectName, String uploadId, MultipartFile file, int partSize) throws IOException, MinioException { List<Part> parts = new ArrayList<>(); int partNumber = 1; byte[] buffer = new byte[partSize]; while (true) { int bytesRead = file.getInputStream().read(buffer); if (bytesRead < 0) { break; } byte[] partData = new byte[bytesRead]; System.arraycopy(buffer, 0, partData, 0, bytesRead); Part part = minioClient.uploadPart( bucketName, objectName, partNumber, uploadId, partData, bytesRead); parts.add(part); partNumber++; } return parts; } } ``` 5. 完成Multipart上传 上传所有文件块后,使用 `CompleteMultipartUpload` API 方法将它们合并成一个完整的文件,最终得到上传文件。 ```java public class MinioService { // ... public void completeMultipartUpload(String objectName, String uploadId, List<Part> parts) throws MinioException { minioClient.completeMultipartUpload(bucketName, objectName, uploadId, parts); } } ``` 6. 断点续传 如果上传中断,可以使用 `ListParts` API 方法获取已上传文件块信息,然后从中断处继续上传。 ```java public class MinioService { // ... public List<Part> listParts(String objectName, String uploadId) throws MinioException { return minioClient.listParts(bucketName, objectName, uploadId).getParts(); } } ``` 使用Spring Boot的Multipart File上传功能上传文件时,需要在Controller中添加 `@RequestParam("file") MultipartFile file` 注解,自动将文件转换为MultipartFile类型。 ```java @RestController @RequestMapping("/file") public class FileController { @Autowired private MinioService minioService; @PostMapping("/upload") public void uploadFile(@RequestParam("file") MultipartFile file) throws IOException, MinioException { // 获取文件名和文件大小 String fileName = file.getOriginalFilename(); long fileSize = file.getSize(); // 定义块大小(5MB) int partSize = 5 * 1024 * 1024; // 初始化Multipart上传会话 String uploadId = minioService.initiateMultipartUpload(fileName); // 上传文件块 List<Part> parts = minioService.uploadParts(fileName, uploadId, file, partSize); // 完成Multipart上传 minioService.completeMultipartUpload(fileName, uploadId, parts); } @PostMapping("/resume") public void resumeUpload(@RequestParam("file") MultipartFile file, String objectName, String uploadId) throws IOException, MinioException { // 获取已上传文件块信息 List<Part> parts = minioService.listParts(objectName, uploadId); // 上传文件块 parts = minioService.uploadParts(objectName, uploadId, file, partSize); // 完成Multipart上传 minioService.completeMultipartUpload(objectName, uploadId, parts); } } ``` 以上是使用Spring Boot实现MinIO文件服务器的分片上传断点续传的基本步骤,具体实现还需要根据实际需求进行调整。
评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值