MinIO 是GlusterFS创始人之一Anand Babu Periasamy发布的开源项目,基于Apache V2 license 100% 开放源代码。MinIO采用Golang实现,客户端支持Java、Python、Javacript、Golang语言等。
其设计的主要目标是作为私有云对象存储的标准方案。非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据、容器和虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T。
MinIO有中文文档,见:http://docs.minio.org.cn/
1、MinIO的优点
(1)安装部署(运维简单)
MinIO在安装过程是黑盒的,不用深入关注它的架构,也不需要进行零件组装,基本上可以做到开箱即用。普通的技术人员就能够参与后期的运维。
MinIO提供了两种部署方式:单机部署和分布式,两种部署方式都非常简单,其中分布式部署还提供了纠删码功能来降低数据丢失的风险。
(2)UI界面
MinIO自带UI界面,且页面不需要你单独的部署,和服务端一并安装。开箱即用,爱了爱了。
(3)高性能
MinIO号称是世界上速度最快的对象存储服务器。在标准硬件上,对象存储的读/写速度最高可以达到183 GB/s和171 GB/s。对象存储可以充当主存储层,以处理Spark、Presto、TensorFlow、H2O.ai等各种复杂工作负载以及成为Hadoop HDFS的替代品。MinIO用作云原生应用程序的主要存储,与传统对象存储相比,云原生应用程序需要更高的吞吐量和更低的延迟。而这些都是MinIO能够达成的性能指标。
(4)容器化支持
MinIO 符合一切原生云计算的架构和构建过程,并且包含最新的云计算的全新的技术和概念。其中包括支持Kubernetes 、Docker、微服和多租户的的容器技术。
(5)丰富的SDK支持
MinIO几乎提供了所有主流开发语言的SDK以及文档。
(6)AWS S3标准兼容
亚马逊云的 S3 API(接口协议) 是在全球范围内达到共识的对象存储的协议,是全世界内大家都认可的标准。MinIO 在很早的时候就采用了 S3 兼容协议,并且MinIO 是第一个支持 S3 Select 的产品. MinIO对其兼容性的全面性感到自豪, 并且得到了 750多个组织的认同, 包括Microsoft Azure使用MinIO的S3网关 - 这一指标超过其他同类产品的总和。
怎么理解呢?可以这么说你目前为了节约成本使用MinIO,等你的公司壮大了、有钱了。不想自己运维基础设施了,你就可以把对象存储放到云上,只要云厂商支持S3标准,你的应用程序是不需要重新开发的。
(7)可扩展性
MinIO利用了Web缩放器的来之不易的知识,为对象存储带来了简单的缩放模型。这是我们坚定的理念 “简单可扩展.” 在 MinIO, 扩展从单个群集开始,该群集可以与其他MinIO群集联合以创建全局名称空间, 并在需要时可以跨越多个不同的数据中心。通过添加更多集群可以扩展名称空间, 更多机架,直到实现目标。
2、MinIO的缺点
MinIO不支持动态增加节点,MinIO创始人的设计理念就是动态增加节点太复杂,后续会采用其它方案来支持扩容。目前只能是新增节点后手动重启系统才生效,系统会自动平衡数据,这种设计到底对系统后续有什么影响,我觉得使用者需要考虑清楚点。
这里有个方案可以参考下,就是事先准备好一套容量适中且是开启状态的MinIO集群,当业务量陡增、原MinIO集群容量告警时,应用自动启用备份MinIO集群,后续再整合两个集群的文件成一个更大的集群。否则不支持在线动态扩容,确实是个硬伤。
Java之MinIO存储桶和对象API使用
MinIO Java Client SDK提供简单的API来访问任何与Amazon S3兼容的对象存储服务。
- 官方demo: https://github.com/minio/minio-java
- 官方文档:https://docs.min.io/docs/java-client-api-reference.html
新版 MinIO和旧版在API使用上还是有一定的区别,比如:新版 MinIO采用 Builder构建者模式来构造 MinioClient对象。所以,官方demo仅供参考,尽量查看英文官方文档。
一、环境搭建
1、创建一个 maven项目,引入依赖:
<!-- minio依赖--><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.3.3</version></dependency><!-- 官方 miniodemo需要的依赖--><dependency><groupId>me.tongfei</groupId><artifactId>progressbar</artifactId><version>0.7.4</version></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.2</version></dependency>
这里我把 官方demo 放到了我的项目中,所以必须引入 progressbar依赖。否则忽略它。
2、初始化Minio客户端
注意:
-
新版 MinIO 采用 Builder构建者模式来构造 MinioClient对象。
-
API端口和 访问控制台端口不要搞混了。
// 初始化Minio客户端MinioClient minioClient = MinioClient.builder().endpoint("http://xxx.xxx.xxx.xxx:9000/").credentials("admin", "xxx").build();System.out.println(minioClient);
获取到 MinioClient对象,就可以进行 MinIO的 API操作使用了。
二、存储桶基本使用
1、检查存储桶是否存在。
- boolean bucketExists(BucketExistsArgs args):检查存储桶是否存在。
示例:
BucketExistsArgs btest2 = BucketExistsArgs.builder().bucket("btest2").build();
boolean existFlag = minioClient.bucketExists(btest2);
注意:
旧版参数是字符串 - 存储桶名,新版需要 build来构造 BucketExistsArgs对象。
2、创建存储桶
- public void makeBucket(MakeBucketArgs args):创建一个启用给定区域和对象锁定功能的存储桶。
示例1:存储桶不存在则创建
String bucketName = "java.minio.demo";// 存储桶不存在则创建if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());System.out.printf("%s,创建成功
", bucketName);}else{System.out.printf("%s,已存在
", bucketName);}
3、查询存储桶
3.1 查询所有桶的列表信息
- public List listBuckets():列出所有桶的桶信息。
示例:
List<Bucket> bucketList = minioClient.listBuckets();bucketList.forEach(bucket -> {System.out.printf("存储桶名:%s,创建时间:%s
", bucket.name(), bucket.creationDate());});
注意:
桶的创建时间默认是美国时间,创建桶时我们可以指定桶的时区或者设置 MinIO服务器时区。
4、删除存储桶
- public void removeBucket(RemoveBucketArgs args):删除一个空桶。
示例:
String bucketName2 = "btest3"; minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName2).build());
注意:
如果存储桶存在对象不为空时,删除会报错。
二、对象基本使用
1、上传对象
1.1 PutObject方法
- public ObjectWriteResponse putObject(PutObjectArgs args):将给定的流上传为存储桶中的对象。
示例1,InputStream上传:
String bucketName = "java.minio.demo";// 创建InputStream上传File file = new File("D:\DownUpLoadTempFiles\100元.jpg");InputStream inputStream = new FileInputStream(file);long start = System.currentTimeMillis();// 上传流minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object("2021/11/28/" + file.getName()).stream(inputStream, inputStream.available(), -1).build());inputStream.close();System.out.println("uploaded successfully 耗时:" + (System.currentTimeMillis() - start));
注意:
- 添加的Object大小不能超过 5GB。
- 默认情况下,如果已存在同名Object且对该Object有访问权限,则新添加的Object将覆盖原有的Object,并返回 200 OK。
- OSS没有文件夹的概念,所有资源都是以文件来存储,但您可以通过创建一个以正斜线(/)结尾,大小为 0的Object来创建模拟文件夹(指定 /后,默认会自动创建)。
上传文件是也可以使用SSE-C加密,添加自定义元数据及消息头等操作。
1.2 uploadObject方法
- public void uploadObject(UploadObjectArgs args):将文件中的内容作为存储桶中的对象上传。
不太常用,一般适合上传磁盘文件(mc cp命令更方便)。
示例:
String bucketName = "java.minio.demo";long start = System.currentTimeMillis();// 上传文件minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object("2021/11/28/100元2.jpg").filename("D:\DownUpLoadTempFiles\100元.jpg").build());System.out.println("uploaded successfully 耗时:" + (System.currentTimeMillis() - start));
2、获取对象
2.1 getObject方法
- public GetObjectResponse getObject(GetObjectArgs args):获取对象的数据。
示例:
String bucketName = "java.minio.demo";// GetObjectResponse 继承了 InputStream类GetObjectResponse objectResponse = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object("2021/11/28/100元2.jpg").build());System.out.println(objectResponse.bucket());System.out.println(objectResponse.object());byte[] allBytes = objectResponse.readAllBytes();System.out.println(allBytes);// Close the input stream.objectResponse.close();
注意:
- 返回结果是
GetObjectResponse类,它继承了 InputStream类
。 - GetObjectResponse使用后返回必须关闭以释放网络资源。
- 此操作需要对此Object具有读权限。
2.2 downloadObject方法
-
public void downloadObject(DownloadObjectArgs args):将对象的数据下载到磁盘。
String bucketName = "java.minio.demo";minioClient.downloadObject(DownloadObjectArgs.builder().bucket(bucketName).object("2021/11/28/100元2.jpg").filename("D:\TempFiles\100元2.jpg") // 必须指定文件名.build());
2.3 getPresignedObjectUrl方法
- public String getPresignedObjectUrl(GetPresignedObjectUrlArgs args):获取 HTTP 方法、到期时间和自定义请求参数的对象的预签名 URL。
示例:返回获取文件对象的URL GET请求,此 URL过期时间为一分钟。
String bucketName = "java.minio.demo";String objectUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object("2021/11/28/100元2.jpg").method(Method.GET)//.expiry(60) // 单位秒.expiry(30, TimeUnit.SECONDS).build());System.out.println(objectUrl);
返回带签名的URL有点小长,过期之后访问会报错。
使用此方法,可以提供给不用登录进行图片浏览,第三方共享访问等。
我们还可以对返回 URL,根据业务做一些参数验签等控制。
2.4 getPresignedPostFormData方法
-
public Map<String,String> getPresignedPostFormData(PostPolicy policy):获取对象的PostPolicy的表单数据以使用 POST 方法上传其数据。
String bucketName = "java.minio.demo";String objectName = "2021/11/28/100元3.jpg";// 1. 创建一个Post 策略// 为存储桶创建一个上传策略,过期时间为7天PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusDays(7));// 设置一个参数key-value,值为上传对象的名称(保存在桶中的名字)policy.addEqualsCondition("key", objectName);// 添加 Content-Type以"image/"开头,表示只能上传照片policy.addStartsWithCondition("Content-Type", "image/");// 设置上传文件的大小 10kiB to 10MiB.policy.addContentLengthRangeCondition(10 * 1024, 10 * 1024 * 1024);// 2. 获取策略的 认证令牌、签名等信息Map<String, String> formData = minioClient.getPresignedPostFormData(policy);// 3.模拟第三方,使用 OkHttp调用 Post上传对象// 创建 MultipartBody对象MultipartBody.Builder multipartBuilder = new MultipartBody.Builder();multipartBuilder.setType(MultipartBody.FORM);for (Map.Entry<String, String> entry : formData.entrySet()) {multipartBuilder.addFormDataPart(entry.getKey(), entry.getValue());}multipartBuilder.addFormDataPart("key", objectName);// 必须要和策略参数一样multipartBuilder.addFormDataPart("Content-Type", "image/png");File uploadFile = new File("D:\DownUpLoadTempFiles\100元.jpg");// 上传文件的 fileName自定义,这里方便就用 objectNamemultipartBuilder.addFormDataPart("file", objectName, RequestBody.create(uploadFile, null));// 使用OkHttp调用Post上传对象Request request =new Request.Builder().url("http://192.168.198.110:9000/" + bucketName).post(multipartBuilder.build()).build();OkHttpClient httpClient = new OkHttpClient().newBuilder().build();Response response = httpClient.newCall(request).execute();if (response.isSuccessful()) {System.out.println("uploaded successfully using POST object");} else {System.out.println("Failed to upload Pictures");}
使用此方法,获取对象的上传策略(包含签名、文件信息、路径等),然后使用这些信息采用 POST 方法的表单数据上传数据。也就是可以生成一个临时上传的信息对象,第三方可以使用这些信息,就可以上传文件。
注意:
- 第三方请求中的签名必须和 创建策略中的签名参数等一致,不符合策略要求的就会上传失败。
一般使用场景:
- 第三方请求应用服务器接口,来获取一个上传策略信息。
- 第三方使用 Http+访问策略信息直接请求应用服务器接口进行上传文件。
3、复制对象
3.1 copyObject方法
- public ObjectWriteResponse copyObject(CopyObjectArgs args):通过服务器端从另一个对象复制数据来创建一个对象。
示例:
String bucketName = "java.minio.demo";String bucketName2 = "btest2";// 将 bucketName中的100元2.jpg文件,复制到bucketName2桶下minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object("2021/11/28/100元2.jpg").build()).bucket(bucketName2).object("2021/11/28/100元copy.jpg").build());
mc cp命令更方便。
4、删除对象
4.1 removeObject方法
- public void removeObject(RemoveObjectArgs args) :移除一个对象。
示例:
String bucketName = "java.minio.demo";minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object("2021/11/28/100元2.jpg")//.versionId("my-versionid") //还可以删除指定版本号的对象.build());
4.2 removeObjects方法
- public Iterable removeObjects(RemoveObjectsArgs args):懒惰地删除多个对象。它需要迭代返回的 Iterable 以执行删除。
示例:
String bucketName = "java.minio.demo";// 构建需要删除的对象List<DeleteObject> objects = new LinkedList<>();objects.add(new DeleteObject("2021/11/28/100元.jpg"));objects.add(new DeleteObject("2021/11/28/100元2.jpg"));objects.add(new DeleteObject("2021/11/28/100元3.jpg"));// 删除Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());for (Result<DeleteError> result : results) {// 删除文件不存在时,不会报错DeleteError error = result.get();System.out.println("Error in deleting object " + error.objectName() + "; " + error.message());}
5、桶的对象信息查询
- public Iterable listObjects(ListObjectsArgs args):列出桶的对象信息。
5.1 查询桶下对象
String bucketName2 = "btest2";Iterable<Result<Item>> listObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName2).build());for (Result<Item> result : listObjects) {Item item = result.get();System.out.println(item.objectName() + " " + item.size() );}
5.2 递归查询桶下对象
String bucketName2 = "btest2";Iterable<Result<Item>> listObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName2).recursive(true).build());for (Result<Item> result : listObjects) {Item item = result.get();System.out.println(item.objectName() + " " + item.size() );}
5.3 条件查询
// 条件查询Iterable<Result<Item>> listObjects = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName2).startAfter("2021").prefix("2") // 指定前缀.maxKeys(100) // 最大数量.recursive(true) // 递归.build());for (Result<Item> result : listObjects) {Item item = result.get();System.out.println(item.objectName() + " " + item.size() );}
MinIO api创建文件夹
近期公司业务需求,将OSS迁移到MinIO上,发现创建空文件夹有很大的问题,请注意,**一定要引*7.1.0*版本的minio SDK,才能实现创建空文件夹。
代码
minioClient.putObject(PutObjectArgs.builder().bucket("my-bucketname").object("path/to/").stream(new ByteArrayInputStream(new byte[] {}), 0, -1).build());
实际创建的还是文件对象,只不过以"/"结尾,模拟了文件夹或目录
其他版本,同样以"/"结尾
3.0.10:创建文件夹报错
7.0.2:传入"/“结尾ObjectName,创建出来的是文件,自动去掉了”/"
MinIO工具类
pom.xml中引依赖
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>7.1.0</version>
</dependency>
配置
minio:endpoint: http://127.0.0.1:9000 #MinIO服务所在地址bucketName: test #存储桶名称accessKey: admin #访问的keysecretKey: admin123 #访问的秘钥
项目启动,创建bean:MinioConfig.java
import com.mgmiot.dlp.file.utils.MinioUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;@Configuration
@IntegrationComponentScan
@Slf4j
public class MinioConfig {@Value("${minio.endpoint}")private String endpoint;@Value("${minio.bucketName}")private String bucketName;@Value("${minio.accessKey}")private String accessKey;@Value("${minio.secretKey}")private String secretKey;@Beanpublic MinioUtils creatMinioClient() {return new MinioUtils(endpoint, bucketName, accessKey, secretKey);}}
Minio工具类:MinioUtils.java
import com.alibaba.fastjson.JSONObject;
import io.minio.BucketExistsArgs;
import io.minio.CopyObjectArgs;
import io.minio.CopySource;
import io.minio.GetBucketPolicyArgs;
import io.minio.GetObjectArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectStat;
import io.minio.ObjectWriteResponse;
import io.minio.PostPolicy;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.UploadObjectArgs;
import io.minio.errors.BucketPolicyTooLargeException;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidBucketNameException;
import io.minio.errors.InvalidExpiresRangeException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.RegionConflictException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;/*** @Author: zrs* @Date: 2020/12/01/10:02* @Description: Minio工具类*/
@Slf4j
public class MinioUtils {private static MinioClient minioClient;private static String endpoint;private static String bucketName;private static String accessKey;private static String secretKey;private static final String SEPARATOR = "/";private MinioUtils() {}public MinioUtils(String endpoint, String bucketName, String accessKey, String secretKey) {MinioUtils.endpoint = endpoint;MinioUtils.bucketName = bucketName;MinioUtils.accessKey = accessKey;MinioUtils.secretKey = secretKey;createMinioClient();}/*** 创建minioClient*/public void createMinioClient() {try {if (null == minioClient) {log.info("minioClient create start");minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();createBucket();log.info("minioClient create end");}} catch (Exception e) {log.error("连接MinIO服务器异常:{}", e);}}/*** 获取上传文件的基础路径** @return url*/public static String getBasisUrl() {return endpoint + SEPARATOR + bucketName + SEPARATOR;}操作存储桶 /*** 初始化Bucket** @throws Exception 异常*/private static void createBucket()throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException, RegionConflictException {if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 验证bucketName是否存在** @return boolean true:存在*/public static boolean bucketExists()throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());}/*** 创建bucket** @param bucketName bucket名称*/public static void createBucket(String bucketName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException, RegionConflictException {if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());}}/*** 获取存储桶策略** @param bucketName 存储桶名称* @return json*/private JSONObject getBucketPolicy(String bucketName)throws IOException, InvalidKeyException, InvalidResponseException, BucketPolicyTooLargeException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, InsufficientDataException, ErrorResponseException {String bucketPolicy = minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());return JSONObject.parseObject(bucketPolicy);}/*** 获取全部bucket* <p>* https://docs.minio.io/cn/java-client-api-reference.html#listBuckets*/public static List<Bucket> getAllBuckets()throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.listBuckets();}/*** 根据bucketName获取信息** @param bucketName bucket名称*/public static Optional<Bucket> getBucket(String bucketName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();}/*** 根据bucketName删除信息** @param bucketName bucket名称*/public static void removeBucket(String bucketName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());}操作文件对象 /*** 判断文件是否存在** @param bucketName 存储桶* @param objectName 对象* @return true:存在*/public static boolean doesObjectExist(String bucketName, String objectName) {boolean exist = true;try {minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());} catch (Exception e) {exist = false;}return exist;}/*** 判断文件夹是否存在** @param bucketName 存储桶* @param objectName 文件夹名称(去掉/)* @return true:存在*/public static boolean doesFolderExist(String bucketName, String objectName) {boolean exist = false;try {Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());for (Result<Item> result : results) {Item item = result.get();if (item.isDir() && objectName.equals(item.objectName())) {exist = true;}}} catch (Exception e) {exist = false;}return exist;}/*** 根据文件前置查询文件** @param bucketName bucket名称* @param prefix 前缀* @param recursive 是否递归查询* @return MinioItem 列表*/public static List<Item> getAllObjectsByPrefix(String bucketName, String prefix,boolean recursive)throws ErrorResponseException, InsufficientDataException, InternalException, InvalidBucketNameException, InvalidKeyException, InvalidResponseException,IOException, NoSuchAlgorithmException, ServerException, XmlParserException {List<Item> list = new ArrayList<>();Iterable<Result<Item>> objectsIterator = minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());if (objectsIterator != null) {for (Result<Item> o : objectsIterator) {Item item = o.get();list.add(item);}}return list;}/*** 获取文件流** @param bucketName bucket名称* @param objectName 文件名称* @return 二进制流*/public static InputStream getObject(String bucketName, String objectName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 断点下载** @param bucketName bucket名称* @param objectName 文件名称* @param offset 起始字节的位置* @param length 要读取的长度* @return 流*/public InputStream getObject(String bucketName, String objectName, long offset, long length)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length).build());}/*** 获取路径下文件列表** @param bucketName bucket名称* @param prefix 文件名称* @param recursive 是否递归查找,如果是false,就模拟文件夹结构查找* @return 二进制流*/public static Iterable<Result<Item>> listObjects(String bucketName, String prefix,boolean recursive) {return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());}/*** 通过MultipartFile,上传文件** @param bucketName 存储桶* @param file 文件* @param objectName 对象名*/public static ObjectWriteResponse putObject(String bucketName, MultipartFile file,String objectName, String contentType)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {InputStream inputStream = file.getInputStream();return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).contentType(contentType).stream(inputStream, inputStream.available(), -1).build());}/*** 上传本地文件** @param bucketName 存储桶* @param objectName 对象名称* @param fileName 本地文件路径*/public static ObjectWriteResponse putObject(String bucketName, String objectName,String fileName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.uploadObject(UploadObjectArgs.builder().bucket(bucketName).object(objectName).filename(fileName).build());}/*** 通过流上传文件** @param bucketName 存储桶* @param objectName 文件对象* @param inputStream 文件流*/public static ObjectWriteResponse putObject(String bucketName, String objectName,InputStream inputStream)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(inputStream, inputStream.available(), -1).build());}/*** 创建文件夹或目录** @param bucketName 存储桶* @param objectName 目录路径*/public static ObjectWriteResponse putDirObject(String bucketName, String objectName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());}/*** 获取文件信息, 如果抛出异常则说明文件不存在** @param bucketName bucket名称* @param objectName 文件名称*/public static ObjectStat statObject(String bucketName, String objectName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 拷贝文件** @param bucketName bucket名称* @param objectName 文件名称* @param srcBucketName 目标bucket名称* @param srcObjectName 目标文件名称*/public static ObjectWriteResponse copyObject(String bucketName, String objectName,String srcBucketName, String srcObjectName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.copyObject(CopyObjectArgs.builder().source(CopySource.builder().bucket(bucketName).object(objectName).build()).bucket(srcBucketName).object(srcObjectName).build());}/*** 删除文件** @param bucketName bucket名称* @param objectName 文件名称*/public static void removeObject(String bucketName, String objectName)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());}/*** 批量删除文件** @param bucketName bucket* @param keys 需要删除的文件列表* @return*//*public static Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> keys) {List<DeleteObject> objects = new LinkedList<>();keys.forEach(s -> {objects.add(new DeleteObject(s));});return minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());}*/public static void removeObjects(String bucketName, List<String> keys) {List<DeleteObject> objects = new LinkedList<>();keys.forEach(s -> {objects.add(new DeleteObject(s));try {removeObject(bucketName, s);} catch (Exception e) {log.error("批量删除失败!error:{}",e);}});}操作Presigned /*** 获取文件外链** @param bucketName bucket名称* @param objectName 文件名称* @param expires 过期时间 <=7 秒级* @return url*/public static String getPresignedObjectUrl(String bucketName, String objectName,Integer expires)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, InvalidExpiresRangeException, ServerException, InternalException, NoSuchAlgorithmException, XmlParserException, InvalidBucketNameException, ErrorResponseException {return minioClient.presignedGetObject(bucketName, objectName, expires);}/*** 给presigned URL设置策略** @param bucketName 存储桶* @param objectName 对象名* @param expires 过期策略* @return map*/public static Map<String, String> presignedGetObject(String bucketName, String objectName,Integer expires)throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, InvalidExpiresRangeException, ServerException, InternalException, NoSuchAlgorithmException, XmlParserException, InvalidBucketNameException, ErrorResponseException {PostPolicy policy = new PostPolicy(bucketName, objectName,ZonedDateTime.now().plusDays(7));policy.setContentType("image/png");return minioClient.presignedPostPolicy(policy);}/*** 将URLDecoder编码转成UTF8** @param str* @return* @throws UnsupportedEncodingException*/public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");return URLDecoder.decode(url, "UTF-8");}}
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import java.util.ArrayList;
import java.util.List;@Service
@RequiredArgsConstructor
public class AttachmentManagementService {private final MinIOFileManagementService fileManagementService;public String save(ModuleTypeEnum moduleTypeEnum, Long sourceId, MultipartFile attachment) {return save(moduleTypeEnum, sourceId.toString(), attachment);}public String save(ModuleTypeEnum moduleTypeEnum, String sourceId, MultipartFile attachment) {if (attachment != null) {return fileManagementService.storage(moduleTypeEnum, sourceId, attachment);}return null;}public List<String> save(ModuleTypeEnum moduleTypeEnum, Long sourceId, MultipartFile[] attachments) {List<String> result = new ArrayList<>();if (attachments != null) {for (MultipartFile attachment : attachments) {String fileName = save(moduleTypeEnum, sourceId, attachment);if (fileName != null) {result.add(fileName);}}}return result;}public void delete(ModuleTypeEnum moduleTypeEnum, Long sourceId) {fileManagementService.delete(moduleTypeEnum, sourceId.toString());}public void delete(ModuleTypeEnum moduleTypeEnum, List<Long> sourceIdList) {if (DataStructureUtils.isValid(sourceIdList)) {for (Long sourceId : sourceIdList) {delete(moduleTypeEnum, sourceId);}}}public String update(ModuleTypeEnum moduleTypeEnum, Long sourceId, MultipartFile attachment) {delete(moduleTypeEnum, sourceId);return save(moduleTypeEnum, sourceId, attachment);}public List<String> update(ModuleTypeEnum moduleTypeEnum, Long sourceId, MultipartFile[] attachments, List<String> delAttachmentNameList) {if (DataStructureUtils.isValid(delAttachmentNameList)) {for (String delAttachmentName : delAttachmentNameList) {fileManagementService.delete(delAttachmentName);}}return save(moduleTypeEnum, sourceId, attachments);}public void delete(List<String> delAttachmentNameList) {if (DataStructureUtils.isValid(delAttachmentNameList)) {for (String delAttachmentName : delAttachmentNameList) {fileManagementService.delete(delAttachmentName);}}}}
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;import javax.annotation.PostConstruct;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;@Log4j
@Service
public class MinIOFileManagementService {@Value("${url}")private String url;@Value("${accessKey}")private String accessKey;@Value("${secretKey}")private String secretKey;@Value("${bucket}")private String bucket;private MinioClient minioClient;@PostConstructprivate void init() {try {minioClient = MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());}} catch (Exception ignore) {log.warn("Initialization MinIO client failed.");}}public List<String> listFile(String directory) {List<String> result = new ArrayList<>();try {for (Result<Item> next : minioClient.listObjects(ListObjectsArgs.builder().bucket(bucket).prefix(directory).build())) {Item item = next.get();result.add(item.objectName());}} catch (Exception e) {log.warn(e);}return result;}public List<String> listName(String prefix) {List<String> result = new ArrayList<>();try {for (Result<Item> next : minioClient.listObjects(ListObjectsArgs.builder().bucket(bucket).prefix(prefix).build())) {Item item = next.get();if (!item.isDir()) {result.add(item.objectName());}}} catch (Exception e) {log.warn(e);}return result;}public boolean isExists(String name) {return listName(name).contains(name);}public String getAccessUrl(String name) {if (isExists(name)) {try {return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucket).method(Method.GET).object(name).build());} catch (Exception e) {log.warn(e);}}return null;}public String getContent(String name) {if (isExists(name)) {try (BufferedReader reader = new BufferedReader(new InputStreamReader(minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(name).build())))) {return reader.lines().collect(Collectors.joining("\n"));} catch (Exception e) {log.warn(e);}}return null;}public void download(OutputStream outputStream, String name) throws Exception {try (GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(name).build())) {int len;byte[] buffer = new byte[1024];while ((len = object.read(buffer)) > 0) {outputStream.write(buffer, 0, len);}}}public void createDirectory(String directory) {try {minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(directory).stream(new ByteArrayInputStream(new byte[]{}), 0, -1).build());} catch (Exception e) {log.warn(e);}}public String storage(String name, MultipartFile file) {try {minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(name).contentType(file.getContentType()).stream(file.getInputStream(), file.getSize(), file.getSize() > -1 ? -1 : 5 * 1024 * 1024).build());} catch (Exception e) {log.warn(e);}return name;}@SneakyThrowspublic String storage(String name, String content) {byte[] bytes = content.getBytes();minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(name).contentType("text/plain").stream(new ByteArrayInputStream(bytes), bytes.length, -1).build());return name;}public void delete(String name) {List<String> objectNameList = listName(name);if (DataStructureUtils.isValid(objectNameList)) {try {for (String objectName : objectNameList) {minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());}} catch (Exception e) {log.warn(e);}}}private String getObjectName(Object... keys) {return Arrays.stream(keys).map(Object::toString).collect(Collectors.joining("/"));}public List<String> listName(ModuleTypeEnum moduleType, String directory) {return listName(getObjectName(moduleType.name(), directory, ""));}public String getName(ModuleTypeEnum moduleType, String directory) {List<String> nameList = listName(moduleType, directory);return DataStructureUtils.isValid(nameList) ? nameList.get(0) : null;}public boolean isExists(ModuleTypeEnum moduleType, String directory, String fileName) {return isExists(getObjectName(moduleType.name(), directory, fileName));}public String getAccessUrl(ModuleTypeEnum moduleType, String directory, String fileName) {return getAccessUrl(getObjectName(moduleType.name(), directory, fileName));}public String getContent(ModuleTypeEnum moduleType, String directory) {return getContent(getName(moduleType, directory));}public String getContent(ModuleTypeEnum moduleType, String directory, String fileName) {return getContent(getObjectName(moduleType.name(), directory, fileName));}public String storage(ModuleTypeEnum moduleType, String directory, MultipartFile file) {String originalFileName = file.getOriginalFilename();if (isExists(moduleType, directory, originalFileName)) {originalFileName = GeniusFileUtils.modifyDuplicateFileName(originalFileName);}return storage(getObjectName(moduleType.name(), directory, originalFileName), file);}public String storage(ModuleTypeEnum moduleType, String directory, String fileName, MultipartFile file) {return storage(getObjectName(moduleType.name(), directory, fileName), file);}public String storage(ModuleTypeEnum moduleType, String directory, String fileName, String content) {return storage(getObjectName(moduleType.name(), directory, fileName), content);}public void delete(ModuleTypeEnum moduleType, String directory) {delete(getObjectName(moduleType.name(), directory, ""));}public void delete(ModuleTypeEnum moduleType, String directory, String fileName) {delete(getObjectName(moduleType.name(), directory, fileName));}}