SpringBoot+Minio实现文件断点续传

大家好,我是程序员阿药。今天和大家分享的是一个上传文件的方式:断点续传

一次性上传的弊端

较大的文件一次性上传会比较慢,中途退出或网络延迟、故障等,容易出现超时等问题。待问题修复后再次上传时仍需要从头开始上传。

断点续传的优势

断点续传允许在上传中断后继续上传,而无需从头开始。

断点续传的原理

断点续传需要先把上传文件按一定大小分块,然后将所有分块上传,最后通过上传的所有分块合并出原文件。如图:

断点续传流程

1. 执行文件分块(文件分块)、2. 检查分块是否已经上传、3. 上传分块、4. 合并分块

如图:

断点续传代码

public void uploadFile() throws IOException {
    // 原文件路径
    String sourcePath = "D:\\Data\\videos\\test.mp4";
    // 本地分块文件存储路径
    String localChunkFilePath = "D:\\Data\\chunk\\";
    // 原文件md5
    String sourceMd5 = DigestUtils.md5DigestAsHex(new FileInputStream(sourcePath));
    // 1.执行文件分块,返回分块总数
    int chunkTotal = fileToChunk(sourcePath, localChunkFilePath);
    int i = 0;
    for (; i < chunkTotal; i++) {
        // 2.检查分块是否已经上传
        boolean isSuccess = uploadController.checkChunks(sourceMd5, i);
        if (isSuccess) {
            System.out.println(i + "号分块已经上传成功");
        } else {
            // 3.上传分块
            isSuccess = uploadController.uploadChunks(sourceMd5, i, localChunkFilePath + i);
            if (isSuccess) {
                System.out.println("执行上传" + i + "号分块成功");
            } else {
                System.out.println("执行上传" + i + "号分块失败");
                break;
            }
        }
    }
    // 4.合并分块
    if (i == chunkTotal) {
        System.out.println("所有分块均上传成功,开始合并分块");
        boolean isSuccess = uploadController.mergeChunks(sourceMd5, chunkTotal, sourcePath);
        if (isSuccess) {
            System.out.println("合并分块成功");
        } else {
            System.out.println("合并分块失败");
        }
    }
}

/**
 * 检查分块是否已经上传
 * @param fileMd5  文件md5
 * @param chunkIndex  分块序号
 */
public boolean checkChunks(String fileMd5, int chunkIndex) {
    // 根据md5得到分块文件所在目录的路径
    String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
    // 分块路径
    String chunkPath = chunkFileFolderPath + chunkIndex;
    // 判断分块是否已经存在
    StatObjectArgs statObjectArgs = StatObjectArgs.builder()
            .bucket(bucket)
            .object(chunkPath)
            .build();
    try {
        minioClient.statObject(statObjectArgs);
    } catch (Exception e) {
        // 分块不存在
        return false;
    }
    // 分块存在
    return true;
}

/**
 * 上传分块
 * @param fileMd5  文件md5
 * @param chunkIndex  分块序号
 * @param localChunkFilePath  分块文件本地路径
 */
public boolean uploadChunks(String fileMd5, int chunkIndex, String localChunkFilePath) {
    // 分块路径
    String chunkPath = getChunkFileFolderPath(fileMd5) + chunkIndex;
    // 获取文件的mimeType
    String mimeType = getMimeType(".temp");
    // 将分块文件上传到minio
    try {
        UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                .bucket(bucket) // 存储桶
                .filename(localChunkFilePath) // 分块文件本地路径
                .object(chunkPath) // 对象名(分块路径)
                .contentType(mimeType) // 设置媒体文件类型
                .build();
        // 将分块从本地 localChunkFilePath 位置上传到 bucket 桶中的 chunkPath 位置
        minioClient.uploadObject(uploadObjectArgs);
    } catch (Exception e) {
        // 上传失败
        return false;
    }
    // 上传成功
    return true;
}

/**
 * 合并分块
 * @param fileMd5  文件md5
 * @param chunkTotal 分块总数
 * @param sourceFileName 原文件名称
 */
public boolean mergeChunks(String fileMd5, int chunkTotal, String sourceFileName) {
    // 分块文件所在目录
    String chunkFileFolderPath = getChunkFileFolderPath(fileMd5);
    // 找到所有的分块文件
    List<ComposeSource> sources = Stream.iterate(0, i -> ++i)
            .limit(chunkTotal)
            .map(i -> ComposeSource.builder().bucket(bucket).object(chunkFileFolderPath + i).build())
            .collect(Collectors.toList());
    // 源文件扩展名
    String extension = sourceFileName.substring(sourceFileName.lastIndexOf("."));
    // 分块合并后文件的路径(对象名)
    String objectName = getFilePathByMd5(fileMd5, extension);
    // 指定合并后文件的objectName等信息
    ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
            .bucket(bucket)
            .object(objectName) // 合并后文件的路径(对象名)
            .sources(sources) // 所有分块
            .build();
    // 合并文件
    // 报错size 1048576 must be greater than 5242880,minio默认的分块文件大小为5M
    try {
        minioClient.composeObject(composeObjectArgs);
    } catch (Exception e) {
        return false;
    }
    // 校验合并后的和源文件是否一致,一致则上传成功
    // 先下载合并后的文件
    File file = downloadFileFromMinIO(objectName);
    try (FileInputStream fileInputStream = new FileInputStream(file)) {
        // 计算合并后文件的md5
        String mergeFileMd5 = DigestUtils.md5DigestAsHex(fileInputStream);
        // 比较原始md5和合并后文件的md5
        if (!fileMd5.equals(mergeFileMd5)) {
            // 合并失败
            return false;
        }
    } catch (Exception e) {
        // 合并失败
        return false;
    }
    // 合并成功
    return true;
}

测试结果

注意:报错size 1048576 must be greater than 5242880(Minio默认的分块文件大小为5M

1. 上传过程未中断。文件分块后全部上传成功,且分块合并原文件成功。如图:

 

 

2. 制造上传分块过程中断场景,将已经上传的6号分块及以后分块全部删除,同时删除合并后的文件。如图:

 

 

再次运行代码,查看分块是否可以从断点处(6号分块)开始上传。测试结果如图: 

今天的分享到此结束啦,喜欢的小伙伴可以点赞关注支持一下。

有需要全部源码的可以关注我的微信公众号:”程序员阿药“,回复:”断点续传“ 即可获得全部源码。 

  • 9
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
实现Spring Boot和Vue.js的MinIO秒传和断点续传,您可以遵循以下步骤: 1. 创建一个Spring Boot项目并添加MinIO依赖: ``` <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.1.0</version> </dependency> ``` 2. 在您的Spring Boot应用程序中配置MinIO。您可以使用`MinioClient`类来进行这个操作。在配置文件中添加以下属性: ``` minio.accessKey=your-access-key minio.secretKey=your-secret-key minio.endpoint=your-minio-endpoint ``` 3. 在Vue.js中,您可以使用`axios`库来上传文件。以下是上传文件的示例代码: ``` <template> <div> <input type="file" v-on:change="uploadFile" /> </div> </template> <script> import axios from 'axios'; export default { methods: { async uploadFile(event) { const file = event.target.files[0]; const url = 'http://localhost:8080/uploadFile'; const data = new FormData(); data.append('file', file); try { const response = await axios.post(url, data, { headers: { 'Content-Type': 'multipart/form-data', }, }); console.log(response.data); } catch (error) { console.log(error); } }, }, }; </script> ``` 4. 在Spring Boot中,您可以创建一个REST API端点来处理文件上传请求。以下是一个示例代码: ``` @RestController public class FileUploadController { @Autowired private MinioClient minioClient; @PostMapping("/uploadFile") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) throws Exception { String fileName = file.getOriginalFilename(); InputStream inputStream = file.getInputStream(); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType(file.getContentType()); metadata.setContentLength(file.getSize()); PutObjectOptions options = new PutObjectOptions(metadata); minioClient.putObject("my-bucket", fileName, inputStream, options); return ResponseEntity.ok("File uploaded successfully!"); } } ``` 5. 要实现MinIO断点续传,您可以使用MinIO Java客户端库中提供的`MultiFileUploader`类。以下是一个示例代码: ``` @RestController public class FileUploadController { @Autowired private MinioClient minioClient; @PostMapping("/uploadFile") public ResponseEntity<String> uploadFile(@RequestParam("file") MultipartFile file) throws Exception { String fileName = file.getOriginalFilename(); InputStream inputStream = file.getInputStream(); ObjectMetadata metadata = new ObjectMetadata(); metadata.setContentType(file.getContentType()); metadata.setContentLength(file.getSize()); PutObjectOptions options = new PutObjectOptions(metadata); MultiFileUploader uploader = new MultiFileUploader(minioClient, "my-bucket", fileName); uploader.setPartSize(5 * 1024 * 1024); // 5MB uploader.upload(inputStream, options); return ResponseEntity.ok("File uploaded successfully!"); } } ``` 以上是Spring Boot和Vue.js的MinIO秒传和断点续传的基本实现。您可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值