minio分布式存储系列(二)__SpringBoot集成minio API相关使用
上篇文章我们介绍了minio存储服务是什么,如何部署,如何通过页面去使用,如果还没看上篇文章介绍的,请点击Minio分布式存储系列(一)__介绍及开箱使用
众所周知,我们开发编程的时候,不可能总是通过页面去上传文件,这时候我们视乎要通过Minio提供的API去操作呢?此篇文章重点介绍:
1、 如何通过Java语言操作Minio,重点介绍项目中常用的API方法;
2、SpringBoot如何集成Minio Java Client API,如何将一个minioClient注入到Spring中进行使用
3、 针对Minio 的Java Java Client API 提供工具类(文章末尾有彩蛋)。
在介绍Minio 常用Minio之前,我们先回顾一下Minio的相关概念:
桶的概念:
通俗理解为:类似于计算机里面的文件夹,但是又区分与文件夹的概念,因为文件夹里面可以创建文件夹,但是Minio的概念里,创建的桶是唯一的,不允许在桶里面在创建新桶。
什么是文件对象:
通俗理解为:类似于计算机里面的文件,但是在Minio的概念里,所有的文件都统称为对象,所以后续的AIP操作都是针对文件对象进行操作。
在知道这两个概念之后,我们接下来介绍minio的常用方法,
- 如何通过Java语言操作Minio,重点介绍项目中常用的API方法
1、如何创建一个Minio Client对象
1 | MinioClient minioClient = new MinioClient(url,port, accessKey, secretKey,secure); |
注明:
url - http访问地址;
port - 访问地址端口;
accessKey - 用户名
secretKey - 密码
secure - url是否采用HTTPS,是则填写“true”,否则填写“false”
示例:
1 | MinioClient minioClient = new MinioClient("https://play.min.io", 9000, "Q3AM3UQ867SPQQA43P2F", "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG", true); |
2、判断一个桶是否存在
1 | public boolean bucketExists(String bucketName) |
注明:
bucketName - 桶名称
示例:
1 2 3 4 5 6 | boolean found = minioClient.bucketExists("mybucket"); if (found) { System.out.println("mybucket 存在"); } else { System.out.println("mybucket 不存在"); } |
3、创建一个桶
1 | public void makeBucket(String bucketName) |
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | try { // 如果桶不存在,则创建。 boolean found = minioClient.bucketExists("mybucket"); if (found) { System.out.println("mybucket 存在"); } else { // 创建名为'mybucket'的桶。 minioClient.makeBucket("mybucket"); System.out.println("mybucket创建成功"); } } catch (MinioException e) { System.out.println("Error occurred: " + e); } |
4、列出所有存在的桶
1 | public List<Bucket> listBuckets() |
示例:
1 2 3 4 5 6 7 8 9 | try { // 列出所有存在的桶 List<Bucket> bucketList = minioClient.listBuckets(); for (Bucket bucket : bucketList) { System.out.println(bucket.creationDate() + ", " + bucket.name()); } } catch (MinioException e) { System.out.println("Error occurred: " + e); } |
5、删除一个桶
1 | public void removeBucket(String bucketName) |
注意:通过API源码发现,如果桶里面有文件对象,则会把文件对象一并删除。
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 | try { // 删除之前先检查`mybucket`是否存在。 boolean found = minioClient.bucketExists("mybucket"); if (found) { // 删除`mybucket`存储桶,注意,只有存储桶为空时才能删除成功。 minioClient.removeBucket("mybucket"); System.out.println("mybucket 删除成功"); } else { System.out.println("mybucket 不存在"); } } catch(MinioException e) { System.out.println("Error occurred: " + e); } |
- 文件对象的相关操作API如下:
1、获取文件对象输入流
1 | public InputStream getObject(String bucketName, String objectName) |
注明:
bucketName - 桶名称
objectName - 文件对象名称
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | try { // 调用statObject()来判断对象是否存在。 // 如果不存在, statObject()抛出异常。 // 否则代表对象存在。 minioClient.statObject("mybucket", "myobject"); // 获取"myobject"的输入流。 InputStream stream = minioClient.getObject("mybucket", "myobject"); // 读取输入流直到EOF并打印到控制台。 byte[] buf = new byte[16384]; int bytesRead; while ((bytesRead = stream.read(buf, 0, buf.length)) >= 0) { System.out.println(new String(buf, 0, bytesRead)); } // 关闭流,此处为示例,流关闭最好放在finally块。 stream.close(); } catch (MinioException e) { System.out.println("Error occurred: " + e); } |
注明:
statObject(String bucketName, String objectName)方法用于判断文件对象是否存在,如果不存在,则报异常,我下一个章节会通过修改源码来讲解如何通过返回true或者false的方式告知调用者文件对象是否存在
2、下载文件对象指定区域的字节数组流
1 | public InputStream getObject(String bucketName, String objectName, long offset, Long length) |
注明:
offset - 起始字节的位置
length - 要读取的长度 (可选,如果无值则代表读到文件结尾)
3、下载并将文件保存到本地
1 | public void getObject(String bucketName, String objectName, String fileName) |
示例:
1 2 3 4 5 6 7 | try { // 获取myobject的流并保存到wj.jpg文件中。 minioClient.getObject("mybucket", "myobject", "wj.jpg"); } catch (MinioException e) { System.out.println("Error occurred: " + e); } |
4、通过字节流的方式上传文件对象
1 | public void putObject(String bucketName, String objectName, InputStream stream, PutObjectOptions options) |
注明:
通过查看API源码发现,上传的文件流大小访问在5M到5T之间,我下一个章节分析源码时,会给大家讲到为什么这么设计,这么设计的好处。
示例:
1 2 3 4 5 | File file =new File("/home/wj/myobject.txt"); FileInputStream img =new FileInputStream(file); PutObjectOptions options = new PutObjectOptions(imgs.length(), -1); //文件对象名称的名称定义,最好把文件的后缀名给写上,这样方便知道获取的文件都是什么类型 client.putObject("mybucket", "myobject.txt",inputStream,options); |
5、通过文件上传到对象中
1 | public void putObject(String bucketName, String objectName, String filename, PutObjectOptions options) |
6、通过使用服务器端副本组合来自不同源对象的数据来创建新的对象
1 | public void composeObject(String bucketName,String objectName,List<ComposeSource> sources,Map<String, String> headerMap,ServerSideEncryption sse) |
示例:
1 2 3 4 5 6 7 8 9 10 | // Create a ComposeSource to compose Object. ComposeSource s1 = new ComposeSource("test","wj1.txt"); ComposeSource s2 = new ComposeSource("test","wj2.txt"); ComposeSource s3 = new ComposeSource("test","wj3.txt"); // Adding the ComposeSource to an ArrayList List<ComposeSource> sourceObjectList = new ArrayList<ComposeSource>(); sourceObjectList.add(s1); sourceObjectList.add(s2); sourceObjectList.add(s3); client.composeObject("test","wj4.txt", sourceObjectList, null, null); |
注明:
将存储已存在的文件对象wj1.txt、wj2.txt、wj3.txt合并成一个wj4.txt文件对象,
合并的原理,我会在下一个章节通过源码分析,进行解释说明。
7、 获取对象的元数据
1 | public ObjectStat statObject(String bucketName, String objectName) |
8、删除一个对象
1 | public void removeObject(String bucketName, String objectName) |
9、生成一个给HTTP PUT请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7
1 | public String presignedPutObject(String bucketName, String objectName, Integer expires) |
到此,操作Minio Java Client API常用的方法到这里就介绍完了,具体剩下的方法请读者自行参考官网:Minio Java Client API官网
- SpringBoot如何集成minio Java Client API,如何将一个minioClient注入到Spring中进行使用
1、引入操作Minio客户端的最新jar包
1 2 3 4 5 | <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>7.0.2</version> </dependency> |
2、在application.yml中填写Minio服务端配置文件
1 2 3 4 5 6 | #minio配置 minio: url: https://play.min.io #对象存储服务的URL port: 9000 accessKey: Q3AM3UQ867SPQQA43P2F #Access key就像用户ID,可以唯一标识你的账户 secretKey: tfteSlswRu7BJ86wekitnifILbZam1KYY3TG #Secret key是你账户的密码 |
3、编写Minio客户端配置类,并注入到Spring中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import io.minio.MinioClient; import io.minio.errors.InvalidEndpointException; import io.minio.errors.InvalidPortException; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MinioConfig { @Value("${minio.url}") private String url; @Value("${minio.port}") private int port; @Value("${minio.accessKey}") private String accessKey; @Value("${minio.secretKey}") private String secretKey; @Bean public MinioClient getMinioClient() throws InvalidPortException, InvalidEndpointException { MinioClient minioClient = new MinioClient(url,port, accessKey, secretKey,true); return minioClient; } } |
可以在代码里面通过注解@Autowired获取一个minioClient客户端,如何调用minioClient内的方法进行桶的创建和上传文件对象。
1 2 | @Autowired private MinioClient minioClient; |
到此,SpringBoot如何集成minio Java Client API,如何将一个minioClient注入到Spring中进行使用,就算是介绍完了,接下来给大家提供MinioClient工具类
- 针对minio 的Java Java Client API 提供工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | import io.minio.MinioClient; import io.minio.ObjectStat; import io.minio.Result; import io.minio.errors.*; import io.minio.messages.Bucket; import io.minio.messages.DeleteError; import io.minio.messages.Item; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; @Component public class MinioUtils { private Logger logger = LoggerFactory.getLogger(MinioUtils.class); @Autowired private MinioClient minioClient; private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600; /** * 判断桶是否存在 */ public boolean isBucker(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { boolean found = minioClient.bucketExists(bucketName); if (found) { return true; } return false; } /** * 如果桶不存在,则创建 **/ public boolean mkdirBucket(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, RegionConflictException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { // 如存储桶不存在,创建之。 boolean found = minioClient.bucketExists(bucketName); if (!found) { // 创建名为bucketName的存储桶。 minioClient.makeBucket(bucketName); return true; } else { return false; } } /** * 列出所有存储桶的名称 */ public List<String> listBuckets() throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { List<Bucket> bucketList = minioClient.listBuckets(); List<String> bucketListName = new ArrayList<>(); for (Bucket bucket : bucketList) { bucketListName.add(bucket.name()); } return bucketListName; } /** * 删除一个桶 **/ public boolean removeBucket(String bucketName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { // 删除之前先检查`my-bucket`是否存在。 boolean found = isBucker(bucketName); if (found) { Iterable<Result<Item>> myObjects = minioClient.listObjects(bucketName); for (Result<Item> result : myObjects) { Item item = result.get(); //有对象文件,则删除失败 if (item.size()>0){ return false; } } // 删除`bucketName`存储桶,注意,只有存储桶为空时才能删除成功。 minioClient.removeBucket(bucketName); found = isBucker(bucketName); if(!found){ return true; } } return false; } /** * 获取单个桶中的所有文件对象名称 * **/ public List<String> getBucketObjectName(String bucketName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { boolean found = isBucker(bucketName); List<String> listObjetcName=new ArrayList<>(); if (found){ Iterable<Result<Item>> myObjects = minioClient.listObjects(bucketName); for (Result<Item> result : myObjects) { Item item = result.get(); listObjetcName.add(item.objectName()); } } return listObjetcName; } /** * 上传文件 * **/ public boolean putObject(String bucketName, String objectName, String Path,String fileName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { boolean found=isBucker(bucketName); if (found){ minioClient.putObject(bucketName,objectName,Path+fileName,null); ObjectStat objectStat= minioClient.statObject(bucketName, objectName); //判断文件对象是否上传成功 if (objectStat.length()>0){ return true; } } return false; } /** * 以流的形式获取一个文件对象 * **/ public InputStream getObjectInputStream(String bucketName, String objectName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { boolean found= isBucker(bucketName); if (found){ try { ObjectStat objectStat= minioClient.statObject(bucketName, objectName); if (objectStat.length()>0){ // 获取objectName的输入流。 InputStream stream = minioClient.getObject(bucketName, objectName); return stream; } }catch (Exception e){ } } return null; } /** *下载并将文件保存到本地,下载成功返回true,下载失败返回false * **/ public boolean getObject(String bucketName, String objectName,String path,String fileName) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException { boolean found= isBucker(bucketName); if (found){ try { ObjectStat objectStat= minioClient.statObject(bucketName, objectName); if (objectStat.length()>0){ minioClient.getObject(bucketName, objectName,path+fileName); return true; } }catch (Exception e){ } } return false; } /** * 删除指定桶的单个文件对象 */ public boolean removeObject(String bucketName, String objectName) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { // 从bucketName中删除objectName boolean found=isBucker(bucketName); if (found){ minioClient.removeObject(bucketName, objectName); return true; } return false; } /** * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表 * **/ public List<String> removeObjectList(String bucketName, List<String> objectNames) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, InternalException, XmlParserException, InvalidBucketNameException, ErrorResponseException { List<String> listerrorObjectName=new ArrayList<>(); // 从bucketName中删除objectName boolean found=isBucker(bucketName); if (found){ Iterable<Result<DeleteError>> results = minioClient.removeObjects(bucketName, objectNames); for (Result<DeleteError> result : results) { DeleteError error = result.get(); listerrorObjectName.add(error.objectName()); } } return listerrorObjectName; } /** * 生成一个给HTTP GET请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 * * **/ public String presignedGetObject(String bucketName, String objectName,Integer expires) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException, InvalidExpiresRangeException { boolean found=isBucker(bucketName); String url =""; if (found){ if (expires < 1 || expires>DEFAULT_EXPIRY_TIME){ throw new InvalidExpiresRangeException(expires, "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); } url= minioClient.presignedGetObject(bucketName, objectName, expires); } return url; } /** * 生成一个给HTTP PUT请求用的presigned URL。浏览器/移动端的客户端可以用这个URL进行上传,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。 * **/ public String presignedPutObject(String bucketName, String objectName,Integer expires) throws IOException, InvalidResponseException, InvalidKeyException, NoSuchAlgorithmException, ErrorResponseException, XmlParserException, InvalidBucketNameException, InsufficientDataException, InternalException, InvalidExpiresRangeException { boolean found=isBucker(bucketName); String url =""; if (found){ if (expires < 1 || expires>DEFAULT_EXPIRY_TIME){ throw new InvalidExpiresRangeException(expires, "expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME); } url= minioClient.presignedPutObject(bucketName, objectName, expires); } return url; } } |
到此,Minio客户端常用方法、SpringBoot集成Minio API相关使用及Minio客户端工具类,到这里就介绍结束了,我们下期将通过源码分析介绍Minio原理和思想,针对个别重要的方法进行源码解释说明,修改源码,使其功能更完善,使用通俗易懂的语言给大家解密Minio源码的神秘面纱。
如果你对我感兴趣,欢迎随时关注我,点赞,赠人玫瑰,手有余香,如果你还有任何疑问或者不理解的地方,或者有更好的建议,或大家想了解某方面的知识点,欢迎大家在下方留言,我会第一时间回复大家!