MinIo 的操作与使用和避坑

文章目录

      • 一、介绍
      • 二、安装
      • 三、Client 连通与避坑
        • 踩坑1:jar 包引入冲突
          • 1. SpringBoot 项目
          • 2. 自己的 Maven 项目(非 SpringBoot 项目)
            • 思路1:尝试从 Maven 仓库中替换该 jar 包
            • 思路2:改造有问题的 jar 包
        • 踩坑2:磁盘不足导致上传文件失败
      • 四、封装一些简单的方法

官方 API 文档:Documentation
官方中文文档:MinIO中文文档

一、介绍

  MinIO 是开源的对象存储服务器,相当于免费版的 OSS。

  MinIO 是一款高性能、分布式的开源对象存储系统,它是一款软件产品。MinIO 公司旨在解决非结构化数据增长的需求,开发了流行于业界的开源云存储软件 MinIO。

  虽然 MinIO 是 100% 开源的,但它既是一家公司又是一个开源项目。它采用 GNU AGPL v3 开源证书,拥有 GNU AGPL 代码的版权,同时还是 MinIO 项目的主要贡献者,可独立对 MinIO 进行维护。

  MinIO 基于 Apache License 2.0 开源协议的对象存储服务。它兼容 Amazon S3 云存储接口。适合存储非结构化数据,如图片,音频,视频,日志等。

二、安装

官网下载链接:官网下载界面
MinIo 的操作与使用和避坑_第1张图片

# 下载安装包
[root@xiaoqiang ~]# wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230930070229.0.0_amd64.deb -O minio.deb
[root@xiaoqiang  ~]# sudo dpkg -i minio.deb

# 设置
[root@xiaoqiang  ~]# sudo touch  /etc/default/minio
[root@xiaoqiang  ~]# cat /etc/default/minio 
MINIO_ROOT_USER="xiaoiang"
MINIO_ROOT_PASSWORD="heheda123"
MINIO_VOLUMES="/dataspace/xiaoqiang-data-backup"

# 创建用户和组,分配目录权限
[root@xiaoqiang  ~]# sudo groupadd -r minio-user
[root@xiaoqiang  ~]# sudo useradd -M -r -g minio-user minio-user
[root@xiaoqiang  ~]# sudo chown minio-user:minio-user -R /dataspace/xiaoqiang-data-backup

# 启动服务
[root@xiaoqiang  ~]# sudo systemctl daemon-reload
[root@xiaoqiang  ~]# sudo systemctl enable minio.service
[root@xiaoqiang  ~]# sudo systemctl start minio.service

注意: minio 与 Prometheus、clickhouse 端口冲突,如果启动服务失败,注意关掉相关服务。 netstat -antulp | grep 9000,kill 掉该进程,minio 服务可以重启成功。

MinIo 的操作与使用和避坑_第2张图片

参考:MinIO使用及整合起步依赖

三、Client 连通与避坑

  Java 代码:

        MinioClient minioClient =
                MinioClient.builder()
                        .endpoint("http://192.168.110.110:9000")
                        .credentials("xiaoqiang", "heheda123")
                        .build();
        System.out.println(minioClient);
        // 检查桶是否存在
        boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket("test").build());
        if (!found) {
            // 创建桶
            minioClient.makeBucket(MakeBucketArgs.builder().bucket("test").build());
        }

        //列出所有桶名
        List<Bucket> buckets = minioClient.listBuckets();
        for (Bucket i : buckets){
            System.out.println(i.name());
        }

  Maven 引入:

        
        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.5.7version>
        dependency>
踩坑1:jar 包引入冲突

  报错:Unsupported OkHttp library found. Must use okhttp >= 4.11.0

MinIo 的操作与使用和避坑_第3张图片

        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.5.2version>
        dependency>
        
        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.4.5version>
        dependency>
        
        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.3.7version>
        dependency>

  报错:Unsupported OkHttp library found. Must use okhttp >= 4.8.1

MinIo 的操作与使用和避坑_第4张图片

        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.2.1version>
        dependency>
        
        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.1.0version>
        dependency>
        
        <dependency>
            <groupId>io.miniogroupId>
            <artifactId>minioartifactId>
            <version>8.0.3version>
        dependency>

  报错:Exception in thread "main" java.lang.NoSuchMethodError: kotlin.collections.ArraysKt.copyInto([B[BIII)[B

MinIo 的操作与使用和避坑_第5张图片
  尝试解决1:

MinIo 的操作与使用和避坑_第6张图片
  结论:不好使。

  尝试解决2:新建一个纯 Maven 项目。好使

MinIo 的操作与使用和避坑_第7张图片
MinIo 的操作与使用和避坑_第8张图片
  接下来的解决思路:将项目中的依赖依次注释最终只留 Minio 的依赖,看到底是哪个引入的依赖和 Minio 的依赖有冲突。

1. SpringBoot 项目

  最终发现是 spring-boot-starter-parent 的依赖导致的。

MinIo 的操作与使用和避坑_第9张图片
MinIo 的操作与使用和避坑_第10张图片

  居然是 SpringBoot 的顶层依赖导致的,也是绝了。尝试了下低版本居然可以。

MinIo 的操作与使用和避坑_第11张图片

  更高的版本居然也可以。

MinIo 的操作与使用和避坑_第12张图片
  经测试,框起来的版本可以用。
MinIo 的操作与使用和避坑_第13张图片

2. 自己的 Maven 项目(非 SpringBoot 项目)

  经排查,居然是 phoenix 的引入导致的,该 jar 包是从 Cloudera Manager 集群中下载后通过 mvn install 命令手动引入依赖,网上 Maven 仓库并没有这个 jar 包,现在该 jar 包中的 okio 和 minio 中的 okio 冲突导致的。报错:Exception in thread "main" java.lang.NoSuchFieldError: Companion

MinIo 的操作与使用和避坑_第14张图片
MinIo 的操作与使用和避坑_第15张图片
MinIo 的操作与使用和避坑_第16张图片

  而且通过 exclusion 排除该 jar 包的方式并不好使,可能得重新编译该 jar 包。

思路1:尝试从 Maven 仓库中替换该 jar 包

MinIo 的操作与使用和避坑_第17张图片
MinIo 的操作与使用和避坑_第18张图片
  但总是导入一个依赖就缺另一个依赖。

思路2:改造有问题的 jar 包

  打开 phoenix-5.0.0-cdh6.2.0-client.jar 文件删除 okio 再重新手动安装。测试后发现该方法管用。
MinIo 的操作与使用和避坑_第19张图片

踩坑2:磁盘不足导致上传文件失败

  上传文件的时候报错:Error: Storage backend has reached its minimum free drive threshold. Please delete a few objects to proceed

MinIo 的操作与使用和避坑_第20张图片
  原因:磁盘空间不足导致的。参考:【MinIO异常】Storage reached its minimum free drive threshold 的解决方案

在这里插入图片描述

四、封装一些简单的方法

来自:minio的基本使用——java

package com.jdh.minio.config;
 
import io.minio.*;
import io.minio.errors.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
 
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
 
/**
 * @ClassName: MinioFile
 * @Author: jdh
 * @CreateTime: 2022-04-15
 * @Description:
 */
@Configuration
@Slf4j
public class MinioFileUtil {
 
    @Resource
    private MinioProperties minioProperties;
 
    private MinioClient minioClient;
 
    /**
     * 这个是6.0.左右的版本
     * @return MinioClient
     */
//    @Bean
//    public MinioClient getMinioClient(){
//
//        String url = "http:" + minioProperties.getIp() + ":" + minioProperties.getPort();
//
//        try {
//            return new MinioClient(url, minioProperties.getAccessKey(), minioProperties.getSecretKey());
//        } catch (InvalidEndpointException | InvalidPortException e) {
//            e.printStackTrace();
//            log.info("-----创建Minio客户端失败-----");
//            return null;
//        }
//    }
 
    /**
     * 下面这个和上面的意思差不多,但是这个是新版本
     * 获取一个连接minio服务端的客户端
     *
     * @return MinioClient
     */
    @Bean
    public MinioClient getClient() {
 
        String url = "http:" + minioProperties.getIp() + ":" + minioProperties.getPort();
        MinioClient minioClient = MinioClient.builder()
                .endpoint(url)    //两种都可以,这种全路径的其实就是下面分开配置一样的
//                        .endpoint(minioProperties.getIp(),minioProperties.getPort(),minioProperties.getSecure())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
        this.minioClient = minioClient;
        return minioClient;
    }
 
    /**
     * 创建桶
     *
     * @param bucketName 桶名称
     */
    public void createBucket(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("创建桶的时候,桶名不能为空!");
        }
 
        // Create bucket with default region.
        minioClient.makeBucket(MakeBucketArgs.builder()
                .bucket(bucketName)
                .build());
    }
 
    /**
     * 创建桶,固定minio容器
     *
     * @param bucketName 桶名称
     */
    public void createBucketByRegion(String bucketName, String region) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("创建桶的时候,桶名不能为空!");
        }
        MinioClient minioClient = this.getClient();
 
        // Create bucket with specific region.
        minioClient.makeBucket(MakeBucketArgs.builder()
                .bucket(bucketName)
                .region(region) //
                .build());
 
//        // Create object-lock enabled bucket with specific region.
//        minioClient.makeBucket(
//                MakeBucketArgs.builder()
//                        .bucket("my-bucketname")
//                        .region("us-west-1")
//                        .objectLock(true)
//                        .build());
    }
 
    /**
     * 修改桶名
     * (minio不支持直接修改桶名,但是可以通过复制到一个新的桶里面,然后删除老的桶)
     *
     * @param oldBucketName 桶名称
     * @param newBucketName 桶名称
     */
    public void renameBucket(String oldBucketName, String newBucketName) throws Exception {
        if (!StringUtils.hasLength(oldBucketName) || !StringUtils.hasLength(newBucketName)) {
            throw new RuntimeException("修改桶名的时候,桶名不能为空!");
        }
 
    }
 
    /**
     * 删除桶
     *
     * @param bucketName 桶名称
     */
    public void deleteBucket(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("删除桶的时候,桶名不能为空!");
        }
 
        minioClient.removeBucket(
                RemoveBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
    }
 
    /**
     * 检查桶是否存在
     *
     * @param bucketName 桶名称
     * @return boolean true-存在 false-不存在
     */
    public boolean checkBucketExist(String bucketName) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("检测桶的时候,桶名不能为空!");
        }
 
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }
 
    /**
     * 列出所有的桶
     *
     * @return 所有桶名的集合
     */
    public List<Bucket> getAllBucketInfo() throws Exception {
 
        //列出所有桶
        List<Bucket> buckets = minioClient.listBuckets();
        return buckets;
    }
 
    /**
     * 列出某个桶中的所有文件名
     * 文件夹名为空时,则直接查询桶下面的数据,否则就查询当前桶下对于文件夹里面的数据
     *
     * @param bucketName 桶名称
     * @param folderName 文件夹名
     * @param isDeep     是否递归查询
     */
    public Iterable<Result<Item>> getBucketAllFile(String bucketName, String folderName, Boolean isDeep) throws Exception {
        if (!StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("获取桶中文件列表的时候,桶名不能为空!");
        }
        if (!StringUtils.hasLength(folderName)) {
            folderName = "";
        }
        System.out.println(folderName);
        Iterable<Result<Item>> listObjects = minioClient.listObjects(
                ListObjectsArgs
                        .builder()
                        .bucket(bucketName)
                        .prefix(folderName + "/")
                        .recursive(isDeep)
                        .build());
 
//        for (Result result : listObjects) {
//            Item item = result.get();
//            System.out.println(item.objectName());
//        }
 
        return listObjects;
    }
 
    /**
     * 删除文件夹
     *
     * @param bucketName 桶名
     * @param objectName 文件夹名
     * @param isDeep     是否递归删除
     * @return
     */
    public Boolean deleteBucketFile(String bucketName, String objectName) {
        if (!StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("删除文件的时候,桶名或文件名不能为空!");
        }
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        } catch (Exception e) {
            log.info("删除文件失败");
            return false;
        }
    }
 
    /**
     * 删除文件夹
     *
     * @param bucketName 桶名
     * @param objectName 文件夹名
     * @param isDeep     是否递归删除
     * @return
     */
    public Boolean deleteBucketFolder(String bucketName, String objectName, Boolean isDeep) {
        if (!StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("删除文件夹的时候,桶名或文件名不能为空!");
        }
        try {
            ListObjectsArgs args = ListObjectsArgs.builder().bucket(bucketName).prefix(objectName + "/").recursive(isDeep).build();
            Iterable<Result<Item>> listObjects = minioClient.listObjects(args);
            listObjects.forEach(objectResult -> {
                try {
                    Item item = objectResult.get();
                    minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(item.objectName()).build());
                } catch (Exception e) {
                    log.info("删除文件夹中的文件异常", e);
                }
            });
            return true;
        } catch (Exception e) {
            log.info("删除文件夹失败");
            return false;
        }
    }
 
    /**
     * 获取文件下载地址
     *
     * @param bucketName 桶名
     * @param objectName 文件名
     * @param expires    过期时间,默认秒
     * @return
     * @throws Exception
     */
    public String getFileDownloadUrl(String bucketName, String objectName, Integer expires) throws Exception {
 
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)//下载地址的请求方式
                .bucket(bucketName)
                .object(objectName)
                .expiry(expires, TimeUnit.SECONDS)//下载地址过期时间
                .build();
        String objectUrl = minioClient.getPresignedObjectUrl(args);
        return objectUrl;
    }
 
    /**
     * 获取文件上传地址(暂时还未实现)
     *
     * @param bucketName 桶名
     * @param objectName 文件名
     * @param expires    过期时间,默认秒
     * @return
     * @throws Exception
     */
    public String getFileUploadUrl(String bucketName, String objectName, Integer expires) throws Exception {
 
        // 过期时间
        ZonedDateTime zonedDateTime = ZonedDateTime.now().plusSeconds(60);
        PostPolicy postPolicy = new PostPolicy(bucketName, zonedDateTime);
 
        // 获取对象的默认权限策略
        StatObjectResponse statObjectResponse = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
        String objectPolicy = statObjectResponse.headers().get("x-amz-object-policy");
 
        String presignedObjectUrl = minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .method(Method.POST)
                        .expiry(expires) // 预签名的 URL 有效期为 1 小时
                        .build());
 
        MyMinioClient client = new MyMinioClient(minioClient);
 
        return presignedObjectUrl;
    }
 
    /**
     * 创建文件夹
     *
     * @param bucketName 桶名
     * @param folderName 文件夹名称
     * @return
     * @throws Exception
     */
    public ObjectWriteResponse createBucketFolder(String bucketName, String folderName) throws Exception {
 
        if (!checkBucketExist(bucketName)) {
            throw new RuntimeException("必须在桶存在的情况下才能创建文件夹");
        }
        if (!StringUtils.hasLength(folderName)) {
            throw new RuntimeException("创建的文件夹名不能为空");
        }
        PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                .bucket(bucketName)
                .object(folderName + "/")
                .stream(new ByteArrayInputStream(new byte[0]), 0, 0)
                .build();
        ObjectWriteResponse objectWriteResponse = minioClient.putObject(putObjectArgs);
 
 
        return objectWriteResponse;
    }
 
    /**
     * 检测某个桶内是否存在某个文件
     *
     * @param objectName 文件名称
     * @param bucketName 桶名称
     */
    public boolean getBucketFileExist(String objectName, String bucketName) throws Exception {
        if (!StringUtils.hasLength(objectName) || !StringUtils.hasLength(bucketName)) {
            throw new RuntimeException("检测文件的时候,文件名和桶名不能为空!");
        }
 
        try {
            // 判断文件是否存在
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()) &&
                    minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build()) != null;
            return exists;
        } catch (ErrorResponseException e) {
            log.info("文件不存在 ! Object does not exist");
            return false;
        } catch (Exception e) {
            throw new Exception(e);
        }
    }
 
    /**
     * 判断桶中是否存在文件夹
     *
     * @param bucketName 同名称
     * @param objectName 文件夹名称
     * @param isDeep     是否递归查询(暂不支持)
     * @return
     */
    public Boolean checkBucketFolderExist(String bucketName, String objectName, Boolean isDeep) {
 
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(isDeep).build());
 
        return results.iterator().hasNext(); // 文件夹下存在文件
    }
 
    /**
     * 根据MultipartFile file上传文件
     * minio 采用文件流上传,可以换成下面的文件上传
     *
     * @param file       上传的文件
     * @param bucketName 上传至服务器的桶名称
     */
    public boolean uploadFile(MultipartFile file, String bucketName) throws Exception {
 
        if (file == null || file.getSize() == 0 || file.isEmpty()) {
            throw new RuntimeException("上传文件为空,请重新上传");
        }
 
        if (!StringUtils.hasLength(bucketName)) {
            log.info("传入桶名为空,将设置默认桶名:" + minioProperties.getBucketName());
            bucketName = minioProperties.getBucketName();
            if (!this.checkBucketExist(minioProperties.getBucketName())) {
                this.createBucket(minioProperties.getBucketName());
            }
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        // 获取上传的文件名
        String filename = file.getOriginalFilename();
        assert filename != null;
        //可以选择生成一个minio中存储的文件名称
        String minioFilename = UUID.randomUUID().toString() + "_" + filename;
        String url = "http:" + minioProperties.getIp() + ":" + minioProperties.getPort();
 
        InputStream inputStream = file.getInputStream();
        long size = file.getSize();
        String contentType = file.getContentType();
 
        // Upload known sized input stream.
        minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName) //上传到指定桶里面
                        .object(minioFilename)//文件在minio中存储的名字
                        //p1:上传的文件流;p2:上传文件总大小;p3:上传的分片大小
                        .stream(inputStream, size, -1) //上传分片文件流大小,如果分文件上传可以采用这种形式
                        .contentType(contentType) //文件的类型
                        .build());
 
        return this.getBucketFileExist(minioFilename, bucketName);
    }
 
    /**
     * 上传本地文件,根据路径上传
     * minio 采用文件内容上传,可以换成上面的流上传
     *
     * @param filePath 上传本地文件路径
     * @Param bucketName 上传至服务器的桶名称
     */
    public boolean uploadPath(String filePath, String bucketName) throws Exception {
 
        File file = new File(filePath);
        if (!file.isFile()) {
            throw new RuntimeException("上传文件为空,请重新上传");
        }
 
        if (!StringUtils.hasLength(bucketName)) {
            log.info("传入桶名为空,将设置默认桶名:" + minioProperties.getBucketName());
            bucketName = minioProperties.getBucketName();
            if (!this.checkBucketExist(minioProperties.getBucketName())) {
                this.createBucket(minioProperties.getBucketName());
            }
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        String minioFilename = UUID.randomUUID().toString() + "_" + file.getName();//获取文件名称
        String fileType = minioFilename.substring(minioFilename.lastIndexOf(".") + 1);
 
        minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(minioFilename)//文件存储在minio中的名字
                        .filename(filePath)//上传本地文件存储的路径
                        .contentType(fileType)//文件类型
                        .build());
 
        return this.getBucketFileExist(minioFilename, bucketName);
    }
 
    /**
     * 文件下载,通过http返回,即在浏览器下载
     *
     * @param response   http请求的响应对象
     * @param bucketName 下载指定服务器的桶名称
     * @param objectName 下载的文件名称
     */
    public void downloadFile(HttpServletResponse response, String bucketName, String objectName) throws Exception {
        if (response == null || !StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("下载文件参数不全!");
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        //获取一个下载的文件输入流操作
        GetObjectResponse objectResponse = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
 
        OutputStream outputStream = response.getOutputStream();
        int len = 0;
        byte[] buf = new byte[1024 * 8];
        while ((len = objectResponse.read(buf)) != -1) {
            outputStream.write(buf, 0, len);
        }
        if (outputStream != null) {
            outputStream.close();
            outputStream.flush();
        }
        objectResponse.close();
    }
 
    /**
     * 文件下载到指定路径
     *
     * @param downloadPath 下载到本地路径
     * @param bucketName   下载指定服务器的桶名称
     * @param objectName   下载的文件名称
     */
    public void downloadPath(String downloadPath, String bucketName, String objectName) throws Exception {
        if (downloadPath.isEmpty() || !StringUtils.hasLength(bucketName) || !StringUtils.hasLength(objectName)) {
            throw new RuntimeException("下载文件参数不全!");
        }
 
        if (!new File(downloadPath).isDirectory()) {
            throw new RuntimeException("本地下载路径必须是一个文件夹或者文件路径!");
        }
 
        if (!this.checkBucketExist(bucketName)) {
            throw new RuntimeException("当前操作的桶不存在!");
        }
 
        downloadPath += objectName;
 
        minioClient.downloadObject(
                DownloadObjectArgs.builder()
                        .bucket(bucketName) //指定是在哪一个桶下载
                        .object(objectName)//是minio中文件存储的名字;本地上传的文件是user.xlsx到minio中存储的是user-minio,那么这里就是user-minio
                        .filename(downloadPath)//需要下载到本地的路径,一定是带上保存的文件名;如 d:\\minio\\user.xlsx
                        .build());
    }
 
}

你可能感兴趣的:(Java,Minio,S3,对象存储)