Java大文件分片上传超详细教程(minio版)

 更新时间:2024年08月02日 09:41:28   作者:儿时可乖了  
Minio是一个开源的分布式对象存储系统,它允许用户在存储服务上存储和检索数据,下面这篇文章主要给大家介绍了关于Java大文件分片上传(minio版)的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

第一步:pom引入minio

 <!--minio大文件上传-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.0.3</version>
        </dependency>

第二步:配置minio

# minio 配置(大文件分片上传使用)
minio:
  endpoint: ${MINIO_ENDPOINT:http://minio服务IP:端口号}
  accesskey: ${MINIO_ASSESSKEY:密钥}
  secretkey: ${MINIO_SECRETKEY:密匙}
  bucket: ${MINIO_BUCKET:桶名称}

第三步:自定义minio客户端与工具类

1. 自定义客户端

package com.gcddd.visual.uav.largeFileUpload.util;

import com.google.common.collect.Multimap;
import io.minio.CreateMultipartUploadResponse;
import io.minio.ListPartsResponse;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.errors.*;
import io.minio.messages.Part;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * 自定义minio
 * @date 2023-09-15
 */
public class CustomMinioClient extends MinioClient {

    public CustomMinioClient(MinioClient client) {
        super(client);
    }

    public String initMultiPartUpload(String bucket, String region, String object, Multimap<String, String> headers, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {
        CreateMultipartUploadResponse response = this.createMultipartUpload(bucket, region, object, headers, extraQueryParams);

        return response.result().uploadId();
    }

    public ObjectWriteResponse mergeMultipartUpload(String bucketName, String region, String objectName, String uploadId, Part[] parts, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, ServerException, InternalException, XmlParserException, InvalidResponseException, ErrorResponseException {

        return this.completeMultipartUpload(bucketName, region, objectName, uploadId, parts, extraHeaders, extraQueryParams);
    }

    public ListPartsResponse listMultipart(String bucketName, String region, String objectName, Integer maxParts, Integer partNumberMarker, String uploadId, Multimap<String, String> extraHeaders, Multimap<String, String> extraQueryParams) throws NoSuchAlgorithmException, InsufficientDataException, IOException, InvalidKeyException, ServerException, XmlParserException, ErrorResponseException, InternalException, InvalidResponseException {
        return this.listParts(bucketName, region, objectName, maxParts, partNumberMarker, uploadId, extraHeaders, extraQueryParams);
    }
}

2.封装minio配置

package com.gcddd.visual.uav.largeFileUpload.configurer;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @Description: MinioProperties
 * minio配置类
 */
@ConfigurationProperties(prefix = "minio")
@Getter
@Setter
public class MinioProperties {

    private String endpoint;

    private String accesskey;

    private String secretkey;

    private String bucket;
}

3.上传工具类

package com.gcddd.visual.uav.largeFileUpload.configurer;

import cn.hutool.core.util.StrUtil;
import com.gcddd.visual.uav.largeFileUpload.util.CustomMinioClient;
import com.google.common.collect.HashMultimap;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Part;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
* @Description: MinIoUtils
 * 工具类
 */
@Component
@Configuration
@EnableConfigurationProperties({MinioProperties.class})
public class MinIoUtils {
    private final MinioProperties minioProperties;
    private CustomMinioClient customMinioClient;

    public MinIoUtils(MinioProperties minioProperties) {
        this.minioProperties = minioProperties;
    }

    @PostConstruct
    public void init() {
        MinioClient minioClient = MinioClient.builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccesskey(), minioProperties.getSecretkey())
                .build();
        customMinioClient = new CustomMinioClient(minioClient);
    }

    /**
     * 单文件签名上传
     *
     * @param objectName 文件全路径名称
     * @return /
     */
    public String getUploadObjectUrl(String objectName) {
        try {
            return customMinioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(minioProperties.getBucket())
                            .object(objectName)
                            .expiry(1, TimeUnit.DAYS)
                            //.extraHeaders(headers)
                            .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     *  初始化分片上传
     *
     * @param objectName 文件全路径名称
     * @param partCount 分片数量
     * @param contentType 类型,如果类型使用默认流会导致无法预览
     * @return /
     */
    public Map<String, Object> initMultiPartUpload(String objectName, int partCount, String contentType) {
        Map<String, Object> result = new HashMap<>();
        try {
            if (StrUtil.isBlank(contentType)) {
                contentType = "application/octet-stream";
            }
            HashMultimap<String, String> headers = HashMultimap.create();
            headers.put("Content-Type", contentType);
            String uploadId = customMinioClient.initMultiPartUpload(minioProperties.getBucket(), null, objectName, headers, null);

            result.put("uploadId", uploadId);
            List<String> partList = new ArrayList<>();

            Map<String, String> reqParams = new HashMap<>();
            //reqParams.put("response-content-type", "application/json");
            reqParams.put("uploadId", uploadId);
            for (int i = 1; i <= partCount; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String uploadUrl = customMinioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.PUT)
                                .bucket(minioProperties.getBucket())
                                .object(objectName)
                                .expiry(1, TimeUnit.DAYS)
                                .extraQueryParams(reqParams)
                                .build());
                partList.add(uploadUrl);
            }
            result.put("uploadUrls", partList);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

        return result;
    }

    /**
     * 分片上传完后合并
     *
     * @param objectName 文件全路径名称
     * @param uploadId 返回的uploadId
     * @return /
     */
    public boolean mergeMultipartUpload(String objectName, String uploadId) {
        try {
            //TODO::目前仅做了最大1000分片
            Part[] parts = new Part[1000];
            ListPartsResponse partResult = customMinioClient.listMultipart(minioProperties.getBucket(), null, objectName, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            customMinioClient.mergeMultipartUpload(minioProperties.getBucket(), null, objectName, uploadId, parts, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }
}

4.service

package com.gcddd.visual.uav.largeFileUpload.service;

import java.util.Map;

/**
 * @date 2023-09-13
 */
public interface UploadService {

    /**
     * 分片上传初始化
     *
     * @param path        路径
     * @param filename    文件名
     * @param partCount   分片数量
     * @param contentType /
     * @return /
     */
    Map<String, Object> initMultiPartUpload(String path, String filename, Integer partCount, String contentType);

    /**
     * 完成分片上传
     *
     * @param objectName 文件名
     * @param uploadId 标识
     * @return /
     */
     boolean mergeMultipartUpload(String objectName, String uploadId);
}

5.service实现

package com.gcddd.visual.uav.largeFileUpload.service.impl;

import com.gcddd.visual.uav.largeFileUpload.configurer.MinIoUtils;
import com.gcddd.visual.uav.largeFileUpload.service.UploadService;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * @date 2023-09-13
 */
@Service
@RequiredArgsConstructor
public class UploadServiceImpl implements UploadService {

    private final MinIoUtils minIoUtils;

    @Override
    public Map<String, Object> initMultiPartUpload(String path, String filename, Integer partCount, String contentType) {
        path = path.replaceAll("/+", "/");
        if (path.indexOf("/") == 0) {
            path = path.substring(1);
        }
        String filePath = path + "/" + filename;

        Map<String, Object> result;
        // 单文件,直接上传
        if (partCount == 1) {
            String uploadObjectUrl = minIoUtils.getUploadObjectUrl(filePath);
            result = ImmutableMap.of("uploadUrls", ImmutableList.of(uploadObjectUrl));
        } else {//多文件,分片上传
            result = minIoUtils.initMultiPartUpload(filePath, partCount, contentType);
        }

        return result;
    }

    @Override
    public boolean mergeMultipartUpload(String objectName, String uploadId) {
        return minIoUtils.mergeMultipartUpload(objectName, uploadId);
    }
}

6.接口控制器

import cn.hutool.core.lang.Assert;
import cn.hutool.json.JSONObject;
import com.gcddd.visual.uav.largeFileUpload.service.UploadService;
import com.gcddd.visual.uav.util.DateUtils;
import com.google.common.collect.ImmutableMap;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * 大文件上传控制器
 */
@RestController
@RequiredArgsConstructor
@RequestMapping("/largeFileUpload" )
@Tag(description = "largeFileUpload" , name = "大文件分片上传" )
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class uploadController {

    private final UploadService uploadService;

//    1.用户调用初始化接口,后端调用minio初始化,得到uploadId,生成每个分片的minio上传url
//初始化 示例: $ curl --location --request POST 'http://127.0.0.1:9998/admin/largeFileUpload/multipart/init' \
//            --header 'Content-Type: application/json' \
//            --data-raw '{
    //            "filename": "result.png",
    //            "partCount": 2
//              }'
    //    返回示例:
//{
//    "uploadId": "eb163e3d-bbf5-48ae-aa56-85cf2a3c0fa3",
//        "uploadUrls": [
//    "http://ip:端口号/uav/20230913/result.png?uploadId=eb163e3d-bbf5-48ae-aa56-85cf2a3c0fa3&partNumber=1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=lengleng%2F20230913%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230913T031427Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=9e7bd28d6f4d42d0ed447750cadcd8782ccc2648a66ecb25a1e7639593ad7dd6",
//            "http://ip:端口号/uav/20230913/result.png?uploadId=eb163e3d-bbf5-48ae-aa56-85cf2a3c0fa3&partNumber=2&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=lengleng%2F20230913%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230913T031427Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=60d31989b9dc3bdacf6bbc9c13cb5c982f79b1e3a0bbcb948e0ec90fede2f9f3"
//    ]
//}
//    2.用户调用对应分片的上传地址,多次上传会覆盖
//    示例:
//    curl --location --request PUT 'http://ip:端口号/uav/20230913/result.png?uploadId=eb163e3d-bbf5-48ae-aa56-85cf2a3c0fa3&partNumber=1&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=lengleng%2F20230913%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230913T031427Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=9e7bd28d6f4d42d0ed447750cadcd8782ccc2648a66ecb25a1e7639593ad7dd6' \
//            --header 'Content-Type: application/octet-stream' \
//            --data-binary '@/D:/jpgone'
//    curl --location --request PUT 'http://ip:端口号/uav/20230913/result.png?uploadId=eb163e3d-bbf5-48ae-aa56-85cf2a3c0fa3&partNumber=2&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=lengleng%2F20230913%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20230913T031427Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=60d31989b9dc3bdacf6bbc9c13cb5c982f79b1e3a0bbcb948e0ec90fede2f9f3' \
//            --header 'Content-Type: application/octet-stream' \
//            --data-binary '@/D:/jpgtwo'
//    3.调用完成接口,后端查询所有上传的分片并合并,uploadId为第一步返回的uploadId
//    示例:
//    curl --location --request PUT '127.0.0.1:8006/multipart/complete' \
//            --header 'Content-Type: application/json' \
//            --data-raw '{
                    //    "objectName":"20230913/result.png",
                    //    "uploadId":"eb163e3d-bbf5-48ae-aa56-85cf2a3c0fa3"
//                      }'

    /**
     * 分片初始化
     *
     * @param requestParam 请求参数
     * @return /
     */
    @PostMapping("/multipart/init")
    public ResponseEntity<Object> initMultiPartUpload(@RequestBody JSONObject requestParam) {
        // 路径(存入minio的路径)
        String pathText = DateUtils.getTodays("yyyyMMdd");//当前日期
        String path = requestParam.getStr("path", pathText);
        // 文件名
        String filename = requestParam.getStr("filename", "test.obj");
        // content-type
        String contentType = requestParam.getStr("contentType", "application/octet-stream");
        // md5-可进行秒传判断
        String md5 = requestParam.getStr("md5", "");
        // 分片数量
        Integer partCount = requestParam.getInt("partCount", 100);

        //TODO::业务判断+秒传判断

        Map<String, Object> result = uploadService.initMultiPartUpload(path, filename, partCount, contentType);

        return new ResponseEntity<>(result, HttpStatus.OK);
    }

    /**
     * 合并分片文件,完成上传
     *
     * @param requestParam 用户参数
     * @return /
     */
    @PutMapping("/multipart/complete")
    public ResponseEntity<Object> completeMultiPartUpload(
            @RequestBody JSONObject requestParam
    ) {
        // 文件名完整路径
        String objectName = requestParam.getStr("objectName");
        //初始化返回的uploadId
        String uploadId = requestParam.getStr("uploadId");
        Assert.notNull(objectName, "objectName must not be null");
        Assert.notNull(uploadId, "uploadId must not be null");
        boolean result = uploadService.mergeMultipartUpload(objectName, uploadId);

        return new ResponseEntity<>(ImmutableMap.of("success", result), HttpStatus.OK);
    }
}

第四步:文件切片

把result.png图片以500M大小切片,重命名为result.png.后缀

split -b 500m result.png result.png.

在这里插入图片描述

第五步:示例截图

这里测试为把result.png切为2片,返回了uploadId 和两个minio文件上传地址

分片上传的地址上传文件,请求方式为put

合并文件,完成上传

检查minio是否成功

总结 

到此这篇关于Java大文件分片上传(minio版)的文章就介绍到这了,更多相关Java大文件分片上传内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot webSocket实现发送广播、点对点消息和Android接收

    SpringBoot webSocket实现发送广播、点对点消息和Android接收

    这篇文章主要介绍了SpringBoot webSocket实现发送广播、点对点消息和Android接收,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-03-03
  • SpringBoot整合Swagger接口文档工具的流程步骤

    SpringBoot整合Swagger接口文档工具的流程步骤

    我们在开发接口的时候,会将接口文档给前端的开发者进行对接,我们可以通过Postman或者Yapi等接口管理工具进行编写管理,实际开发中,接口的管理确实也应该通过专业的工具管理,本文,我们就来谈谈怎么在SpringBoot整合Swagger接口文档工具
    2023-08-08
  • 使用springboot 获取控制器参数的几种方法小结

    使用springboot 获取控制器参数的几种方法小结

    这篇文章主要介绍了使用springboot 获取控制器参数的几种方法小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • SpringBoot Bean被加载时进行控制

    SpringBoot Bean被加载时进行控制

    很多时候我们需要根据不同的条件在容器中加载不同的Bean,或者根据不同的条件来选择是否在容器中加载某个Bean,这就是Bean的加载控制,一般我们可以通过编程式或注解式两种不同的方式来完成Bean的加载控制
    2023-02-02
  • Struts2学习笔记(2)-路径问题解决

    Struts2学习笔记(2)-路径问题解决

    本文主要介绍Struts2的路径问题,尽量不要使用相对路径,使用相对路径会让路径问题变得很繁琐很麻烦,推荐使用绝对路径,希望能给大家做一个参考。
    2016-06-06
  • springboot2.x整合tkmapper的示例代码

    springboot2.x整合tkmapper的示例代码

    这篇文章主要介绍了springboot2.x整合tkmapper,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-01-01
  • java 矩阵乘法的mapreduce程序实现

    java 矩阵乘法的mapreduce程序实现

    这篇文章主要介绍了java 矩阵乘法的mapreduce程序实现的相关资料,需要的朋友可以参考下
    2017-06-06
  • 选择java的理由:java之父访谈实录

    选择java的理由:java之父访谈实录

    选择java的理由:java之父访谈实录...
    2006-12-12
  • Java线程的并发工具类实现原理解析

    Java线程的并发工具类实现原理解析

    本文给大家讲解Java线程的并发工具类的一些知识,通过适用场景分析大数据量统计类任务的实现原理和封装,多个示例代码讲解的非常详细,对java线程并发工具类相关知识感兴趣的朋友一起学习下吧
    2021-06-06
  • Java MeteoInfo解析与绘图代码教程详解

    Java MeteoInfo解析与绘图代码教程详解

    这篇文章主要介绍了Java MeteoInfo解析与绘图代码教程,对于后端导出图片的话,就需要添加色阶了,这一文很简单,就涉及色阶,名称,网格刻度线,感兴趣的朋友一起看看吧
    2021-10-10

最新评论