SpringBoot 整合MinIO 8.1.0 上传、下载、断点下载、分片上传

Springboot工程

1.Maven:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.1.0</version>
</dependency>

2.配置类:

@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    @ApiModelProperty("endPoint是一个URL,域名,IPv4或者IPv6地址")
    private String endpoint;

    @ApiModelProperty("TCP/IP端口号")
    private int port;

    @ApiModelProperty("accessKey类似于用户ID,用于唯一标识你的账户")
    private String accessKey;

    @ApiModelProperty("secretKey是你账户的密码")
    private String secretKey;

    @ApiModelProperty("如果是true,则用的是https而不是http,默认值是true")
    private Boolean secure;

    @ApiModelProperty("默认存储桶")
    private String bucketName;

    @ApiModelProperty("配置目录")
    private String configDir;

    @ApiModelProperty("网关")
    private String gateway;

    private CustomMinioClient minioClient;

    @Bean
    public CustomMinioClient getMinioClient() {
        try {
            MinioClient minioClient = CustomMinioClient.builder()
            		.endpoint(endpoint, port, secure)
                    .credentials(accessKey, secretKey)
                    .build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return minioClient;
    }

}

3.yml配置:

minio:
  endpoint: {个人部署的服务器IP}
  port: 9000
  accessKey: minioadmin
  secretKey: minioadmin
  secure: false
  bucketName: "oss-test"
  configDir: "/home/data/"
  gateway: "https://{个人部署的服务器IP}:9000/oss-test"
spring:
  servlet:
  # 附件上传限制大小
    multipart:
      enabled: true
      # 单个文件的最大值
      max-file-size: 500MB
      # 最大请求文件的大小
      max-request-size: 500MB
      file-size-threshold: 500MB

4.MinIOUtil

@Slf4j
@Component
public class MinioUtil {

    @Autowired
    private MinioConfig minioConfig;

    @Autowired
    private CustomMinioClient minioClient;

    private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600;

    /**
     * url分隔符
     */
    public static final String URI_DELIMITER = "/";

    private static int RETRY_NUM = 3;

    /**
     * 检查存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        boolean flag = false;
        BucketExistsArgs bArgs = BucketExistsArgs.builder().bucket(bucketName).build();
        flag = minioClient.bucketExists(bArgs);
        if (flag) {
            return true;
        }
        return false;
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public boolean makeBucket(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (!flag) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            return true;
        } else {
            return false;
        }
    }

    /**
     * 列出所有存储桶名称
     *
     * @return
     */
    @SneakyThrows
    public List<String> listBucketNames() {
        List<Bucket> bucketList = listBuckets();
        List<String> bucketListName = new ArrayList<>();
        for (Bucket bucket : bucketList) {
            bucketListName.add(bucket.name());
        }
        return bucketListName;
    }

    /**
     * 列出所有存储桶
     *
     * @return
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 删除存储桶
     *
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public boolean removeBucket(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                // 有对象文件,则删除失败
                if (item.size() > 0) {
                    return false;
                }
            }
            // 删除存储桶,注意,只有存储桶为空时才能删除成功。
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
            flag = bucketExists(bucketName);
            if (!flag) {
                return true;
            }

        }
        return false;
    }

    /**
     * 列出存储桶中的所有对象名称
     *
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public List<String> listObjectNames(String bucketName) {
        List<String> listObjectNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            Iterable<Result<Item>> myObjects = listObjects(bucketName);
            for (Result<Item> result : myObjects) {
                Item item = result.get();
                listObjectNames.add(item.objectName());
            }
        }
        return listObjectNames;
    }

    /**
     * 列出存储桶中的所有对象
     *
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public Iterable<Result<Item>> listObjects(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        }
        return null;
    }

    /**
     * 通过文件上传到对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param fileName   文件名(包含文件路径及其后缀)
     * @return
     */
    @SneakyThrows
    public boolean putObject(String bucketName, String objectName, String fileName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            UploadObjectArgs uploadArgs = UploadObjectArgs.builder().filename(fileName).bucket(bucketName).object(objectName).build();
            ObjectWriteResponse response = minioClient.uploadObject(uploadArgs);
            log.info("response:" + JSON.toJSON(response));
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                return true;
            }
        }
        return false;

    }

    /**
     * 上传文件
     *
     * @param multipartFile
     * @return
     */
    public String putObject(MultipartFile multipartFile) {
        return putObject(new MultipartFile[]{multipartFile}).get(0);
    }

    /**
     * 上传文件
     *
     * @param multipartFiles
     * @return
     */
    public List<String> putObject(MultipartFile... multipartFiles) {
        try {
            List<String> retVal = new LinkedList<>();
            String[] folders = getDateFolder();
            for (MultipartFile multipartFile : multipartFiles) {
                // UUID重命名
                String fileName = UUID.randomUUID().toString().replace("-", "") + "." + FilenameUtils.getExtension(multipartFile.getOriginalFilename());
                // 年/月/日/file
                String finalPath = new StringBuilder(String.join(URI_DELIMITER, folders))
                        .append(URI_DELIMITER)
                        .append(fileName).toString();

                minioClient.putObject(PutObjectArgs.builder()
                        .stream(multipartFile.getInputStream(), multipartFile.getSize(), -1)
                        .object(finalPath)
                        .contentType(multipartFile.getContentType())
                        .bucket(minioConfig.getBucketName())
                        .build());

                retVal.add(gateway(finalPath));
            }
            return retVal;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 获取访问网关
     *
     * @param path
     * @return
     */
    protected String gateway(String path) {
        String gateway = minioConfig.getGateway();
        if (!gateway.endsWith(URI_DELIMITER)) {
            gateway += URI_DELIMITER;
        }
        return gateway + path;
    }

    /**
     * 获取年月日[2020, 09, 01]
     *
     * @return
     */
    protected String[] getDateFolder() {
        String[] retVal = new String[3];

        LocalDate localDate = LocalDate.now();
        retVal[0] = localDate.getYear() + "";

        int month = localDate.getMonthValue();
        retVal[1] = month < 10 ? "0" + month : month + "";

        int day = localDate.getDayOfMonth();
        retVal[2] = day < 10 ? "0" + day : day + "";

        return retVal;
    }

    /**
     * 文件上传
     *
     * @param bucketName
     * @param file
     */
    @SneakyThrows
    public void putObject(String bucketName, MultipartFile file, String objectName) {
        PutObjectArgs args = PutObjectArgs.builder().
                bucket(bucketName).object(objectName).
                stream(file.getInputStream(), file.getSize(), -1).
                contentType(file.getContentType()).build();
        minioClient.putObject(args);
    }

    /**
     * 文件对象上传
     *
     * @param objectName 服务器中对象名称(包含文件后缀)
     * @param fileName   文件名(包含文件路径及其后缀)
     * @return
     */
    public String uploadFileByPath(String objectName, String fileName) {
        putObject(minioConfig.getBucketName(), objectName, fileName);
        return "http://" + minioConfig.getEndpoint() + ":" +
                minioConfig.getPort() + "/" +
                minioConfig.getBucketName() + "/" + objectName;
    }

    /**
     * 以流的形式获取一个文件对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                GetObjectArgs args = GetObjectArgs.builder().bucket(bucketName).object(objectName).build();
                InputStream stream = minioClient.getObject(args);
                return stream;
            }
        }
        return null;
    }

    /**
     * 以流的形式获取一个文件对象(断点下载)
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度 (可选,如果无值则代表读到文件结尾)
     * @return
     */
    @SneakyThrows
    public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                GetObjectArgs args = GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build();
                InputStream stream = minioClient.getObject(args);
                return stream;
            }
        }
        return null;
    }

    /**
     * 下载并将文件保存到本地
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param fileName   File name
     * @return
     */
    @SneakyThrows
    public boolean getObject(String bucketName, String objectName, String fileName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectResponse statObject = statObject(bucketName, objectName);
            if (statObject != null && statObject.size() > 0) {
                GetObjectArgs args = GetObjectArgs.builder().bucket(bucketName).object(objectName).build();
                minioClient.getObject(args);
                return true;
            }
        }
        return false;
    }

    /**
     * 删除一个对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     */
    @SneakyThrows
    public boolean removeObject(String bucketName, String objectName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            RemoveObjectArgs args = RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build();
            minioClient.removeObject(args);
            return true;
        }
        return false;
    }

    /**
     * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
     *
     * @param bucketName  存储桶名称
     * @param objectNames 含有要删除的多个object名称的迭代器对象
     * @return
     */
    @SneakyThrows
    public List<String> removeObject(String bucketName, List<String> objectNames) {
        List<String> deleteErrorNames = new ArrayList<>();
        boolean flag = bucketExists(bucketName);
        if (flag) {
            List<DeleteObject> deleteObjects = objectNames.stream().parallel().map(o -> {
                DeleteObject deleteObject = new DeleteObject(o);
                return deleteObject;
            }).collect(Collectors.toList());
            RemoveObjectsArgs args = RemoveObjectsArgs.builder().bucket(bucketName).objects(deleteObjects).build();
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(args);
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrorNames.add(error.objectName());
            }
        }
        return deleteErrorNames;
    }

    /**
     * 生成一个给HTTP GET请求用的presigned URL。
     * 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param expires    失效时间(以秒为单位),默认是7天,不得大于七天
     * @return
     */
    @SneakyThrows
    public String presignedGetObject(String bucketName, String objectName, Integer expires) {
        boolean flag = bucketExists(bucketName);
        String url = "";
        if (flag) {
            if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
                throw new McloudHandlerException(expires + ": expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
            }
            url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder().
                            method(Method.GET).
                            bucket(bucketName).
                            object(objectName).
                            expiry(expires).
                            build());
        }
        return url;
    }

    /**
     * 生成一个给HTTP PUT请求用的presigned URL。
     * 浏览器/移动端的客户端可以用这个URL进行上传,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param expires    失效时间(以秒为单位),默认是7天,不得大于七天
     * @return
     */
    @SneakyThrows
    public String presignedPutObject(String bucketName, String objectName, Integer expires) {
        boolean flag = bucketExists(bucketName);
        String url = "";
        if (flag) {
            if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
                throw new McloudHandlerException(expires + ": expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
            }
            url = minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.PUT)
                            .bucket(bucketName)
                            .object(objectName)
                            .expiry(expires)
                            .build());
        }
        return url;
    }

    /**
     * 获取对象的元数据
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    public StatObjectResponse statObject(String bucketName, String objectName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            StatObjectArgs objectArgs = StatObjectArgs.builder().bucket(bucketName).object(objectName).build();
            StatObjectResponse statObject = minioClient.statObject(objectArgs);
            return statObject;
        }
        return null;
    }

    /**
     * 文件访问路径
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return
     */
    @SneakyThrows
    public String getObjectUrl(String bucketName, String objectName) {
        boolean flag = bucketExists(bucketName);
        String url = "";
        if (flag) {
            url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).build());
        }
        return url;
    }

    /**
     * 获取存储桶策略
     *
     * @param bucketName 存储桶名称
     * @return
     */
    @SneakyThrows
    public String getBucketPolicy(String bucketName) {
        return minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
    }

    /**
     * 文件下载
     *
     * @param bucketName   存储桶名称
     * @param objectName   文件名称
     * @param originalName 文件原始名
     * @param response     响应
     */
    public void downloadFile(String bucketName, String objectName, String originalName, HttpServletResponse response) {
        try {
            GetObjectArgs args = GetObjectArgs.builder().bucket(bucketName).object(objectName).build();
            InputStream file = minioClient.getObject(args);
            String filename = new String(objectName.getBytes("ISO8859-1"), StandardCharsets.UTF_8);
            if (StringUtils.isNotEmpty(originalName)) {
                objectName = originalName;
            }
            response.setHeader("Content-Disposition", "attachment;filename=" + filename);
            ServletOutputStream servletOutputStream = response.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = file.read(buffer)) > 0) {
                servletOutputStream.write(buffer, 0, len);
            }
            servletOutputStream.flush();
            file.close();
            servletOutputStream.close();
        } catch (ErrorResponseException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 文件下载
     *
     * @param filename
     * @param bucketName
     * @param objectName
     */
    @SneakyThrows
    public void downloadFile(String filename, String bucketName, String objectName) {
        DownloadObjectArgs args = DownloadObjectArgs.builder().filename(filename).bucket(bucketName).object(objectName).build();
        minioClient.downloadObject(args);
    }

    /**
     * 初始化获取分片上传URLS
     *
     * @param bucketName
     * @param objectName
     * @param totalPart
     * @return
     */
    public Map<String, Object> initMultiPartUpload(String bucketName, String objectName, int totalPart) {
        Map<String, Object> result = new HashMap<>();
        try {
            String uploadId = minioClient.initMultiPartUpload(bucketName, null, objectName, null, null);
            result.put("uploadId", uploadId);
            List<String> partList = new ArrayList<>();
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("uploadId", uploadId);
            for (int i = 1; i <= totalPart; i++) {
                reqParams.put("partNumber", String.valueOf(i));
                String uploadUrl = minioClient.getPresignedObjectUrl(
                        GetPresignedObjectUrlArgs.builder()
                                .method(Method.PUT)
                                .bucket(bucketName)
                                .object(objectName)
                                .expiry(1, TimeUnit.DAYS)
                                .extraQueryParams(reqParams)
                                .build());
                partList.add(uploadUrl);
            }
            result.put("uploadUrls", partList);
        } catch (Exception e) {
            log.error("initMultiPartUpload error: {}", e.getMessage(), e);
            return null;
        }

        return result;
    }

    /**
     * 合并分段上传
     *
     * @param bucketName
     * @param objectName
     * @param uploadId
     * @return
     */
    public boolean mergeMultipartUpload(String bucketName, String objectName, String uploadId) {
        try {
            ListPartsResponse partResult = minioClient.listMultipart(bucketName, null, objectName, 1000, 0, uploadId, null, null);
            int partNumber = 1;
            Part[] parts = new Part[partResult.result().partList().size()];
            for (Part part : partResult.result().partList()) {
                parts[partNumber - 1] = new Part(partNumber, part.etag());
                partNumber++;
            }
            minioClient.mergeMultipartUpload(bucketName, null, objectName, uploadId, parts, null, null);
        } catch (Exception e) {
            log.error("mergeMultipartUpload error: {}", e.getMessage(), e);
            return false;
        }

        return true;
    }

}

5.自定义MinIO客户端

感谢分享学习
参考:https://blog.csdn.net/Tuine/article/details/113996848

  • 0
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当我们需要在Spring Boot项目中使用MinIO对象存储服务,并且需要支持文件分片时,可以按照以下步骤进行整合: 1. 添加MinIO和Spring Boot的依赖:在项目的pom.xml文件中添加MinIO和Spring Boot的依赖,例如: ```xml <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.0.6</version> </dependency> ``` 2. 配置MinIO连接信息:在Spring Boot的配置文件(如application.properties或application.yml)中配置MinIO的连接信息,包括endpoint、accessKey和secretKey等,例如: ```yaml minio: endpoint: http://localhost:9000 access-key: your-access-key secret-key: your-secret-key ``` 3. 创建MinIO客户端:在Spring Boot的配置类中创建MinIO客户端的Bean,例如: ```java @Configuration public class MinioConfig { @Value("${minio.endpoint}") private String endpoint; @Value("${minio.access-key}") private String accessKey; @Value("${minio.secret-key}") private String secretKey; @Bean public MinioClient minioClient() { return MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); } } ``` 4. 实现文件分片:在业务逻辑中使用MinIO客户端进行文件分片,可以使用`putObject`方法将文件分片MinIO服务器,例如: ```java @Autowired private MinioClient minioClient; public void uploadFile(String bucketName, String objectName, InputStream inputStream, long size) throws Exception { // 设置分片大小 long partSize = 5 * 1024 * 1024; // 5MB // 初始化分片 CreateMultipartUploadResponse response = minioClient.createMultipartUpload( CreateMultipartUploadArgs.builder() .bucket(bucketName) .object(objectName) .build()); String uploadId = response.uploadId(); // 分片 int partNumber = 1; long offset = 0; while (offset < size) { // 读取分片数据 byte[] data = new byte[(int) Math.min(partSize, size - offset)]; inputStream.read(data); // 上分片 UploadPartResponse uploadPartResponse = minioClient.uploadPart( UploadPartArgs.builder() .bucket(bucketName) .object(objectName) .uploadId(uploadId) .partNumber(partNumber) .stream(new ByteArrayInputStream(data), data.length, -1) .build()); // 记录已上分片 Part part = Part.builder() .partNumber(partNumber) .eTag(uploadPartResponse.etag()) .build(); parts.add(part); partNumber++; offset += data.length; } // 完成分片 CompleteMultipartUploadResponse completeResponse = minioClient.completeMultipartUpload( CompleteMultipartUploadArgs.builder() .bucket(bucketName) .object(objectName) .uploadId(uploadId) .parts(parts) .build()); } ``` 以上就是Spring Boot整合MinIO实现文件分片的基本步骤。在实际应用中,还可以根据需求进行更多的配置和处理,例如设置文件分片大小、处理上进度等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值