Minio概念
MinIO is an object storage solution that provides an Amazon Web Services S3-compatible API and supports all core S3 features. MinIO is built to deploy anywhere - public or private cloud, baremetal infrastructure, orchestrated environments, and edge infrastructure.
Minio搭建
在linux服务器上执行
mkdir -p /blessing/minio/data
mkdir -p /blessing/minio/config
docker run -dit \
--restart always \
-p 9000:9000 \
-p 9090:9090 \
--name minio \
-v /blessing/minio/data:/data \
-v /blessing/minio/config:/root/.minio \
-e "MINIO_ROOT_USER=DawnSilverGravel" \
-e "MINIO_ROOT_PASSWORD=DawnSilverGravel" \
quay.io/minio/minio:RELEASE.2023-06-02T23-17-26Z.fips server /data --console-address ":9090" --address ":9000"
使用docker启动参数具体在参考文档的第二个链接,此时RedHat Mminio镜像仓库没有最新的latest,故使用RELEASE.2023-06-02T23-17-26Z.fips
版本。
如果没有docker,同样在参考文档的第二链接,然后点击Linux标签页找到安装命令,在Minio版本可以找到对应的版本,以下是下载minio二进制的命令。
mkdir -p /blessing/minio/data
# 查看liunx系统架构,如果是x86_64就是AMD的
arch
# 下载minio二进制文件
wget https://dl.min.io/server/minio/release/linux-amd64/minio -P /blessing/minio
# 赋予权限
cd /blessing/minio
chmod +x minio
# 启动minio --console-address控制台端口,默认是9001,默认上传端口是9000
# 账号密码默认:minioadmin/minioadmin
MINIO_ROOT_USER=DawnSilverGravel MINIO_ROOT_PASSWORD=DawnSilverGravel ./minio server /blessing/minio/data --console-address ":9090"
启动之后在浏览器访问http://yourAddress:9090 在Access Keys页面生成密钥得到access-key
和secret-key
。
Minio使用
Java API
在官方的文档中(下方参考文档第一个链接),以及MinioClient
类中,和下方参考文档的第三个链接中,都可以找到相关方法的示例,这些方法参数都有一个共同的特点,使用了Builder
模式,从中也使用了Consumer<T>
函数式接口,可以学习一下其思想。
bucket method | 描述 |
---|---|
bucketExists | 检查bucket是否存在 |
listBuckets | 获取所有bucket的信息 |
makeBucket | 创建给定区域和对象锁特性的bucket |
removeBucket | 删除一个无数据的bucket |
getBucketTags | 获取bucket标签 |
setBucketTags | 设置bucket标签,之前的tags会被删除 |
deleteBucketTags | 删除bucket标签 |
getBucketVersioning | 获取bucket版本 |
setBucketVersioning | 设置bucket版本(版本默认是无,设置了之后就只能配置Enabled 和Suspended ) |
getObjectLockConfigurationArgs | 获取bucket的对象锁配置,makeBucket 新建bucketobjectLock 参数为true |
setObjectLockConfigurationArgs | 设置bucket对象锁配置,makeBucket 新建bucketobjectLock 参数为true ,限制对对象的修改 |
deleteObjectLockConfigurationArgs | 删除bucket对象锁配置makeBucket 新建bucketobjectLock 参数为true |
...... | 其余暂不了解 |
object method | 描述 |
---|---|
putObject | 以流的形式上传对象 |
uploadObject | 以文件的形式上传对象 |
copyObject | 创建一个对象并复制minio服务器上的另一个对象的数据,两个对象不能相同 |
composeObject | 组合minio服务器端不同的源对象来创建一个对象,每个对象大小不可以低于5M |
uploadSnowballObjects | 在单个put中上传多个文件,SnowballObject 中size与指定的流大小要一致 |
removeObject | 删除一个对象 |
removeObjects | 删除多个对象 |
statObject | 获取一个对象的对象信息和元数据 |
getObject | 获取对象数据,并返回InputStream 流;,该流必须关闭以释放网络资源 |
getPresignedObjectUrl | 获取一个对象的http方法、过期时间、自定义参数的URL,GetPresignedObjectUrlArgs 有GET 、PUT 、HEAD 三种类型的http方法 |
downloadObject | 以本地文件的形式下载一个对象,DownloadObjectArgs 中的fileName 中文件名如果本地存在将会下载失败 |
getPresignedPostFormData | 获取一个对象PostPolicy的表单数据的POST 方法URL,然后使用该URL上传文件 |
listObjects | 获取一个bucket里对象列表 |
getObjectTags | 获取对象的标签 |
setObjectTags | 设置对象标签 |
deleteObjectTags | 删除对象标签 |
getObjectRetention | 获取对象保留策略配置,指定创建bucket创建时需要设置objectLock 为true |
setObjectRetention | 设置对象保留策略配置,指定创建bucket创建时需要设置objectLock 为true |
enableObjectLegalHold | 启用对对象的合法保留,默认为false ,上传对象时可设置,指定bucket创建时要设置objectLock 为true |
disableObjectLegalHold | 禁用对对象的合法保留,指定bucket创建时设置objectLock 为true |
isObjectLegalHoldEnabled | 返回对象是否启用合法保留,启用为true ,指定bucket创建时要设置objectLock 为true |
selectObjectContent | 通过SQL表达式选择对象的内容(不知如何使用) |
restoreObject | 归档一个对象(不知如何使用) |
以PutObjectArgs
为例:
minio没有文件夹的概念,但是可以使用object(/blessing/yourFileName)
可以创建一个blessing文件夹
针对于putObject
方法中的参数对象PutObjectArgs
中的stream
方法有其中两个参数objectSize
与partSize
,有以下描述:
- 允许最大对象为5TB
- 允许最小的分片为5M
- 允许最大分片为5GB(这里注释看了一下代码应该是写错了,版本
8.5.3
) - 最大的分片数量为10000
从图中可以得知这是一个父类,则下图中的子类同样有上述规则。
项目结构与配置
项目结构
pom.xml
<dependencies>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.3</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.15</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.12</version>
</dependency>
</dependencies>
application.yml
minio:
init-buckets:
- dawn-silver-gravel
- blessing-star
operation-bucket: dawn-star
access-key: yourAccessKey
secret-key: yourSecretKey
# 默认端口9000
endpoint: http://yourAddress:yourPort
config配置
package com.example.config;
import io.minio.MinioClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* description:
*
* @author DawnStar
* date: 2023/6/14
*/
@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfiguration {
@Resource
private MinioProperties minioProperties;
@Bean
public MinioClient minioClient() {
return MinioClient.builder().credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
.endpoint(minioProperties.getEndpoint())
.build();
}
}
package com.example.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* description:
*
* @author DawnStar
* date: 2023/6/14
*/
@ConfigurationProperties(prefix = "minio")
@Component
public class MinioProperties {
private String accessKey;
private String secretKey;
private String endpoint;
private String[] initBuckets;
private String operationBucket;
public String getOperationBucket() {
return operationBucket;
}
public void setOperationBucket(String operationBucket) {
this.operationBucket = operationBucket;
}
public String[] getInitBuckets() {
return initBuckets;
}
public void setInitBuckets(String[] initBuckets) {
this.initBuckets = initBuckets;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
}
文件工具类
package com.example.util;
import java.io.*;
import java.nio.file.Files;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* description:
* 生成文件工具类
*
* @author DawnStar
* date: 2023/6/15
*/
public class MinioFileUtils {
private final static ThreadLocalRandom RANDOM = ThreadLocalRandom.current();
public static InputStream createFile(String fileName) {
File file = new File(fileName);
try (BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(Files.newOutputStream(file.toPath())))) {
bufferedWriter.write("这是一个测试文件");
return Files.newInputStream(file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static File getUploadFile(String fileName) {
File file = new File(fileName);
try (BufferedWriter bufferedWriter = new BufferedWriter(
new OutputStreamWriter(Files.newOutputStream(file.toPath())))) {
bufferedWriter.write(generateContent(fileName));
return file;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String generateContent(String fileName) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(fileName).append("\n");
int anInt = RANDOM.nextInt(300, 500);
int lineLength = 50;
for (int i = 0; i < anInt; i++) {
for (int i1 = 0; i1 < lineLength; i1++) {
char c = (char) RANDOM.nextInt(30, 122);
stringBuilder.append(c);
}
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
public static String generateContentPartSize(String fileName,long partSize) {
if (partSize < 5 * 1024 * 1024) {
throw new IllegalArgumentException("非法参数");
}
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(fileName).append("\n");
int lineLength = 50;
long l = partSize / 50 + 1;
for (long i = 0; i < l; i++) {
for (int i1 = 0; i1 < lineLength; i1++) {
char c = (char) RANDOM.nextInt(30, 122);
stringBuilder.append(c);
}
stringBuilder.append("\n");
}
return stringBuilder.toString();
}
}
Minio 部分Java API 使用
MinioBucketApi
package com.example.template;
import io.minio.*;
import io.minio.messages.*;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
/**
* description:
* minio 方法测试
*
* @author DawnStar
* date: 2023/6/15
*/
@Component
public class MinioBucketApi {
@Resource
private MinioClient minioClient;
@Value("${minio.operation-bucket}")
private String buckName;
/**
* 桶标签操作
*/
@SneakyThrows
public void bucketTagsOperation() {
System.out.println("***********************桶标签操作*******************************");
Tags tags = Tags.newBucketTags(new HashMap<String, String>(2) {{
this.put("blessing", "star");
this.put("images", "image");
}});
SetBucketTagsArgs setBucketTagsArgs = SetBucketTagsArgs.builder().bucket(buckName).tags(tags).build();
minioClient.setBucketTags(setBucketTagsArgs);
System.out.println("================获取桶标签========================");
GetBucketTagsArgs getBucketTagsArgs = GetBucketTagsArgs.builder().bucket(buckName).build();
Tags bucketTags = minioClient.getBucketTags(getBucketTagsArgs);
System.out.println(buckName + "标签:" + bucketTags.get());
System.out.println("================设置桶标签========================");
SetBucketTagsArgs newSetBucketTagsArgs = SetBucketTagsArgs.builder().bucket(buckName).tags(new HashMap<String, String>(1) {{
this.put("newTag", "newTag");
}}).build();
minioClient.setBucketTags(newSetBucketTagsArgs);
Tags bucketTags1 = minioClient.getBucketTags(getBucketTagsArgs);
System.out.println(buckName + "标签:" + bucketTags1.get());
System.out.println("================删除桶标签=======================");
DeleteBucketTagsArgs deleteBucketTagsArgs = DeleteBucketTagsArgs.builder().bucket(buckName).build();
minioClient.deleteBucketTags(deleteBucketTagsArgs);
Tags newTags = minioClient.getBucketTags(getBucketTagsArgs);
System.out.println(buckName + "标签:" + newTags.get());
System.out.println("***********************************************************************************");
}
/**
* 桶版本操作
*/
@SneakyThrows
public void bucketVersionOperation() {
System.out.println("***********************桶版本操作*******************************");
System.err.println("桶版本默认没有,开启了就只有两种状态:Enable 和 Suspended状态");
System.out.println("=================获取桶版本===================");
GetBucketVersioningArgs getBucketVersioningArgs = GetBucketVersioningArgs.builder().bucket(buckName).build();
VersioningConfiguration bucketVersioning = minioClient.getBucketVersioning(getBucketVersioningArgs);
System.out.println(buckName + "版本:" + bucketVersioning.status().toString() + bucketVersioning.isMfaDeleteEnabled());
System.out.println("===================设置桶版本==================");
VersioningConfiguration.Status enabled = VersioningConfiguration.Status.ENABLED;
VersioningConfiguration configuration = new VersioningConfiguration(enabled, true);
SetBucketVersioningArgs setBucketVersioningArgs = SetBucketVersioningArgs.builder().bucket(buckName).config(configuration).build();
minioClient.setBucketVersioning(setBucketVersioningArgs);
bucketVersioning = minioClient.getBucketVersioning(getBucketVersioningArgs);
System.out.println(buckName + "版本:" + bucketVersioning.status().toString() + bucketVersioning.isMfaDeleteEnabled());
System.out.println("***********************************************************************************");
}
/**
* 桶基本操作
*/
@SneakyThrows
public void bucketBasicOperation() {
System.out.println("***********************桶基本操作*******************************");
System.out.println("===============检查桶是否存在========================");
BucketExistsArgs bucketExistsArgs = BucketExistsArgs.builder().bucket(buckName).build();
boolean bucketExists = minioClient.bucketExists(bucketExistsArgs);
System.out.println(buckName + "存在:" + bucketExists);
// 获取桶列表
System.out.println("=================获取桶列表======================");
List<Bucket> buckets = minioClient.listBuckets();
buckets.forEach(bucket -> System.out.println(bucket.name()));
System.out.println("=================新建桶============================");
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket("test").objectLock(true).build();
// 创建已有bucket会创建失败
minioClient.makeBucket(makeBucketArgs);
buckets = minioClient.listBuckets();
buckets.forEach(bucket -> System.out.println(bucket.name()));
// 删除桶,有文件就会删除失败
System.out.println("=================删除桶======================");
RemoveBucketArgs removeBucketArgs = RemoveBucketArgs.builder().bucket("test").build();
minioClient.removeBucket(removeBucketArgs);
buckets = minioClient.listBuckets();
buckets.forEach(bucket -> System.out.println(bucket.name()));
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void objectLockConfigurationOperation() {
System.out.println("***********************桶对象锁配置*******************************");
GetObjectLockConfigurationArgs getObjectLockConfigurationArgs = GetObjectLockConfigurationArgs.builder().bucket(buckName).build();
ObjectLockConfiguration objectLockConfiguration = minioClient.getObjectLockConfiguration(getObjectLockConfigurationArgs);
System.out.println("获取当前的bucket对象锁配置"+objectLockConfiguration.mode() + " " + objectLockConfiguration.duration());
objectLockConfiguration = new ObjectLockConfiguration(RetentionMode.COMPLIANCE, new RetentionDurationDays(1));
minioClient.setObjectLockConfiguration(SetObjectLockConfigurationArgs.builder().bucket(buckName).config(objectLockConfiguration).build());
objectLockConfiguration = minioClient.getObjectLockConfiguration(getObjectLockConfigurationArgs);
System.out.println("设置后的bucket对象锁配置"+objectLockConfiguration.mode() + " " + objectLockConfiguration.duration());
minioClient.deleteObjectLockConfiguration(DeleteObjectLockConfigurationArgs.builder().bucket(buckName).build());
objectLockConfiguration = minioClient.getObjectLockConfiguration(getObjectLockConfigurationArgs);
System.out.println("删除桶锁对象配置:"+objectLockConfiguration.mode() + " " + objectLockConfiguration.duration());
System.out.println("***********************************************************************************");
}
}
MinioObjectApi
package com.example.template;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.example.util.MinioFileUtils;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.*;
import lombok.SneakyThrows;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* description:
*
* @author DawnStar
* date: 2023/6/16
*/
@Component
public class MinioObjectApi {
@Resource
private MinioClient minioClient;
@Value("${minio.operation-bucket}")
private String buckName;
@SneakyThrows
public void putObject() {
// 上传已知的大小的文件
System.out.println("************************************putObject操作***********************************************");
String fileName = "test.txt";
InputStream inputStream = MinioFileUtils.createFile(fileName);
System.out.println("==================上传已知的大小的文件==========================");
PutObjectArgs firstObjectArgs = PutObjectArgs.builder()
.bucket(buckName)
.object(fileName)
// -1 表示默认分片为 5M
.stream(inputStream, inputStream.available(), -1).build();
ObjectWriteResponse objectWriteResponse = minioClient.putObject(firstObjectArgs);
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
// 上传未知大小的文件
System.out.println("==================上传未知的大小的文件==========================");
PutObjectArgs secondObjectArgs = PutObjectArgs.builder()
.bucket(buckName)
.object(fileName)
.stream(inputStream, -1, PutObjectArgs.MIN_MULTIPART_SIZE)
.build();
objectWriteResponse = minioClient.putObject(secondObjectArgs);
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
// 上传未知大小的文件
System.out.println("==================上传内存中的数据==========================");
String content = "这是一个内存运行的数据";
Map<String, String> headers = new HashMap<>();
headers.put("X-Amz-Storage-Class", "REDUCED_REDUNDANCY");
Map<String, String> userMetadata = new HashMap<>();
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(contentBytes);
PutObjectArgs thirdObjectArgs = PutObjectArgs.builder()
.bucket(buckName)
// 将创建一个名为putMethod文件夹
.object("putMethod/content.txt")
// 两者不能同时为-1
.stream(byteArrayInputStream, -1, PutObjectArgs.MIN_MULTIPART_SIZE)
.headers(headers)
.userMetadata(userMetadata)
.build();
objectWriteResponse = minioClient.putObject(thirdObjectArgs);
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void uploadObject() {
System.out.println("************************************uploadObject操作***********************************************");
String fileName = "uploadObject.txt";
File file = MinioFileUtils.getUploadFile(fileName);
UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
.bucket(buckName)
.object(fileName)
// .filename(file.toPath())
.filename(file.toPath().toString(), UploadObjectArgs.MAX_PART_SIZE)
.build();
ObjectWriteResponse objectWriteResponse = minioClient.uploadObject(uploadObjectArgs);
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void copyObject() {
String fileName = "copy.txt";
putObjectByPartSize("", fileName);
System.out.println("************************************copyObject操作***********************************************");
// object() 与 source()不可相同
CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder()
.bucket(buckName)
.object("copyMethod/" + fileName)
.source(CopySource.builder().bucket(buckName).object(fileName).build())
.build();
ObjectWriteResponse objectWriteResponse = minioClient.copyObject(copyObjectArgs);
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void composeObject() {
System.out.println("************************************composeObject操作***********************************************");
List<ComposeSource> composeSources = new ArrayList<>();
String prefix = "subComposeObject";
for (int i = 0; i < 2; i++) {
String composeFileName = prefix + i + ".txt";
putObjectByPartSize("composeMethod", composeFileName);
ComposeSource composeSource = ComposeSource.builder()
.bucket(buckName)
.object("composeMethod/" + composeFileName)
.build();
composeSources.add(composeSource);
}
String fileName = "composeObject.txt";
ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
.bucket(buckName)
.object("composeMethod/" + fileName)
// 每个文件大小不可以低于5M
.sources(composeSources)
.build();
ObjectWriteResponse objectWriteResponse = minioClient.composeObject(composeObjectArgs);
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void uploadSnowballObjects() {
System.out.println("************************************uploadSnowballObjects操作***********************************************");
String prefix = "uploadSnowballMethod";
List<SnowballObject> objects = new ArrayList<>();
// 参数中size必须与文件大小相等
String content = MinioFileUtils.generateContent(prefix + "my-object-one");
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
InputStream inputStream = MinioFileUtils.createFile(prefix + "my-object-two");
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(contentBytes);
objects.add(
new SnowballObject(
prefix + "/my-object-one",
byteArrayInputStream,
byteArrayInputStream.available(),
null));
objects.add(
new SnowballObject(
prefix + "/my-object-two",
inputStream,
inputStream.available(),
null));
ObjectWriteResponse objectWriteResponse = minioClient.uploadSnowballObjects(
UploadSnowballObjectsArgs.builder().bucket(buckName).objects(objects).build());
System.out.println(objectWriteResponse.versionId() + " " + objectWriteResponse.etag());
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void removeObject() {
System.out.println("************************************removeObject与removeObjects操作***********************************************");
System.out.println("====================删除单个对象================================");
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(buckName).object("test").build();
// 删除不存在的对象也不会有问题
minioClient.removeObject(removeObjectArgs);
DeleteObject deleteObject = new DeleteObject("test");
System.out.println("====================删除多个对象================================");
String prefix = "removeMethod";
DeleteObject deleteObject1 = new DeleteObject(prefix + "/removeObject.txt");
putObjectByPartSize(prefix, "removeObject.txt");
RemoveObjectsArgs removeObjectsArgs = RemoveObjectsArgs.builder().bucket(buckName).objects(new ArrayList<>() {{
this.add(deleteObject);
this.add(deleteObject1);
}}).build();
Iterable<Result<DeleteError>> results = minioClient.removeObjects(removeObjectsArgs);
// 删除不存在的对象也不会有问题
for (Result<DeleteError> deleteErrorResult : results) {
System.out.println(deleteErrorResult.get().toString());
}
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void statObject() {
System.out.println("************************************statObjects操作***********************************************");
uploadObject("statMethod", "statObject.txt");
StatObjectArgs statObjectArgs = StatObjectArgs.builder()
.bucket(buckName)
.object("statMethod/statObject.txt")
.build();
StatObjectResponse statObjectResponse = minioClient.statObject(statObjectArgs);
System.out.println("statObject:" + statObjectResponse.etag() + " " + statObjectResponse.object() + " " + statObjectResponse.versionId());
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void getObject() {
System.out.println("************************************getObjects操作***********************************************");
uploadObject("getMethod", "getObject.txt");
GetObjectArgs getObjectArgs = GetObjectArgs.builder()
.bucket(buckName)
.object("getMethod/getObject.txt")
// 起始位置
.offset(10L)
// 数据长度
.length(100L)
.build();
GetObjectResponse response = minioClient.getObject(getObjectArgs);
byte[] bytes = response.readAllBytes();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(bytes.length);
System.out.println(byteArrayOutputStream);
byteArrayOutputStream.close();
response.close();
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void getPresignedObjectUrl() {
System.out.println("************************************getPresignedObjectUrl操作***********************************************");
GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
.bucket(buckName)
.object("getPresignedMethod/notGetPresignedObjectUrl.txt")
.expiry(24, TimeUnit.HOURS)
.method(Method.GET)
.build();
String presignedObjectUrl = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);
System.out.println("没有该文件的路径" + presignedObjectUrl);
// 获取上传路径
GetPresignedObjectUrlArgs putPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
.bucket(buckName)
.object("getPresignedMethod/getPresignedObjectUrl.txt")
.expiry(24, TimeUnit.HOURS)
.method(Method.PUT)
.build();
presignedObjectUrl = minioClient.getPresignedObjectUrl(putPresignedObjectUrlArgs);
System.out.println("PUT方法上传路径:" + presignedObjectUrl);
// 上传数据
HttpRequest request = HttpUtil.createRequest(cn.hutool.http.Method.PUT, presignedObjectUrl);
byte[] bytes = MinioFileUtils.generateContent("getPresignedObjectUrl.txt").getBytes(StandardCharsets.UTF_8);
HttpResponse execute = request.body(bytes).contentType(ContentType.TEXT_PLAIN.getValue()).execute();
execute.close();
GetPresignedObjectUrlArgs targetPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
.bucket(buckName)
.object("getPresignedMethod/getPresignedObjectUrl.txt")
.expiry(24, TimeUnit.HOURS)
.method(Method.GET)
.build();
presignedObjectUrl = minioClient.getPresignedObjectUrl(targetPresignedObjectUrlArgs);
System.out.println("获取路径:" + presignedObjectUrl);
GetPresignedObjectUrlArgs headPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()
.bucket(buckName)
.object("getPresignedMethod/getPresignedObjectUrl.txt")
.method(Method.HEAD)
.build();
presignedObjectUrl = minioClient.getPresignedObjectUrl(headPresignedObjectUrlArgs);
System.out.println("HEAD方法路径,请通过postman请求该路径:" + presignedObjectUrl);
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void downLoadObject() {
System.out.println("************************************downLoadObject操作***********************************************");
uploadObject("downloadMethod", "downloadObject.txt");
DownloadObjectArgs downloadObjectArgs = DownloadObjectArgs.builder()
.bucket(buckName)
.object("downloadMethod/downloadObject.txt")
.filename("downloadObject.txt")
.build();
// 文件名存在将会下载失败
System.err.println("文件名存在将会下载失败");
minioClient.downloadObject(downloadObjectArgs);
File file = new File("downloadObject.txt");
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
String readLine;
while ((readLine = bufferedReader.readLine()) != null) {
System.out.print(readLine);
}
bufferedReader.close();
System.out.println("***********************************************************************************");
}
@Value("${minio.endpoint}")
private String url;
@SneakyThrows
public void getPresignedPostFormData() {
System.out.println("************************************getPresignedPostFormData操作***********************************************");
// 创建一个时效为7天的策略
PostPolicy policy = new PostPolicy(buckName, ZonedDateTime.now().plusDays(7));
String objectName = "PostFormMethod/postFormData.txt";
// 添加条件,`key`键为对象名称
policy.addEqualsCondition("key", objectName);
// 添加'Content-Type.设置前缀为“application”
policy.addStartsWithCondition("Content-Type", "application/");
// 添加内容长度范围,20Bit - 1MiB,不在这个区间的文件上传失败
policy.addContentLengthRangeCondition(20, 1024*1024);
Map<String, String> formData = minioClient.getPresignedPostFormData(policy);
// 以表单的形式上传文本
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",ContentType.OCTET_STREAM.getValue());
String fileName = objectName.substring(objectName.lastIndexOf("/") + 1);
File uploadFile = MinioFileUtils.getUploadFile(fileName);
// "file" 必须最后添加
multipartBuilder.addFormDataPart(
"file", objectName, RequestBody.create(uploadFile, null));
System.out.println(url+"/"+objectName);
Request request =
new Request.Builder()
// minio服务器路径
.url(url +"/"+buckName)
.post(multipartBuilder.build())
.build();
OkHttpClient httpClient = new OkHttpClient().newBuilder().build();
try (Response response = httpClient.newCall(request).execute();) {
if (response.isSuccessful()) {
System.out.println(fileName + " is uploaded successfully using POST object");
} else {
System.out.println("Failed to upload " + fileName);
}
}
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void listObjects() {
System.out.println("************************************listObjects操作***********************************************");
uploadObject("listMethod", "listObject1.txt");
uploadObject("listMethod", "listObject2.txt");
uploadObject("listMethod", "listObject3.txt");
System.out.println("===================第一种List==================");
ListObjectsArgs listObjectsArgs = ListObjectsArgs.builder().bucket(buckName).build();
Iterable<Result<Item>> listObjects = minioClient.listObjects(listObjectsArgs);
print(listObjects);
System.out.println("===================第二种List==================");
listObjectsArgs = ListObjectsArgs.builder()
.bucket(buckName)
.recursive(true)
.build();
print(minioClient.listObjects(listObjectsArgs));
System.out.println("===================第三种List==================");
listObjectsArgs = ListObjectsArgs.builder()
.bucket(buckName)
.recursive(true)
.prefix("listMethod")
.build();
print(minioClient.listObjects(listObjectsArgs));
System.out.println("===================第四种List==================");
listObjectsArgs = ListObjectsArgs.builder()
.bucket(buckName)
.recursive(true)
.prefix("listMethod")
.startAfter("listMethod/listObject2.txt")
.build();
print(minioClient.listObjects(listObjectsArgs));
// ......
System.out.println("***********************************************************************************");
}
private void print(Iterable<Result<Item>> listObjects) throws Exception {
for (Result<Item> listObject : listObjects) {
System.out.println(listObject.get().objectName());
}
}
@SneakyThrows
public void objectRetentionOperation() {
System.out.println("************************************对象保留策略操作***********************************************");
System.err.println("创建bucket需要设置objectLock为true");
uploadObject("retentionMethod", "objectRetention.txt");
GetObjectRetentionArgs getObjectRetentionArgs = GetObjectRetentionArgs.builder().bucket(buckName).object("retentionMethod/objectRetention.txt")
.build();
Retention objectRetention = minioClient.getObjectRetention(getObjectRetentionArgs);
if (objectRetention == null) {
System.out.println("修改策略前配置: null");
} else {
System.out.println("修改策略前配置: " + objectRetention.mode().toString() + " " + objectRetention.retainUntilDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
Retention retention = new Retention(RetentionMode.COMPLIANCE, ZonedDateTime.now().plusSeconds(40));
SetObjectRetentionArgs setObjectRetentionArgs = SetObjectRetentionArgs.builder()
.bucket(buckName)
.object("retentionMethod/objectRetention.txt")
.config(retention)
// ByPass Mode(略过模式或旁路模式),泛指在一个系统的正常流程中,
// 有一堆检核机制,而“ByPass Mode”就是当检核机制发生异常,
// 无法在短期间内排除时,使系统作业能绕过这些检核机制,
// 使系统能够继续运行的作业模式
.bypassGovernanceMode(true)
.build();
minioClient.setObjectRetention(setObjectRetentionArgs);
objectRetention = minioClient.getObjectRetention(getObjectRetentionArgs);
System.out.println("修改策略后配置:" + objectRetention.mode().toString() + " " + objectRetention.retainUntilDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void objectLegalHoldOperation() {
System.out.println("************************************对象合法保留操作***********************************************");
System.err.println("创建bucket需要设置objectLock为true");
uploadObject("legalHoldMethod", "objectLegalHoldMethod.txt");
IsObjectLegalHoldEnabledArgs isObjectLegalHoldEnabledArgs = IsObjectLegalHoldEnabledArgs.builder()
.bucket(buckName)
.object("legalHoldMethod/objectLegalHoldMethod.txt")
.build();
boolean objectLegalHoldEnabled = minioClient.isObjectLegalHoldEnabled(isObjectLegalHoldEnabledArgs);
System.out.println("执行isObjectLegalHoldEnabledArgs:" + objectLegalHoldEnabled);
EnableObjectLegalHoldArgs enableObjectLegalHoldArgs = EnableObjectLegalHoldArgs.builder()
.bucket(buckName)
.object("legalHoldMethod/objectLegalHoldMethod.txt")
.build();
minioClient.enableObjectLegalHold(enableObjectLegalHoldArgs);
objectLegalHoldEnabled = minioClient.isObjectLegalHoldEnabled(isObjectLegalHoldEnabledArgs);
System.out.println("执行enableObjectLegalHold:" + objectLegalHoldEnabled);
DisableObjectLegalHoldArgs disableObjectLegalHoldArgs = DisableObjectLegalHoldArgs.builder()
.bucket(buckName)
.object("legalHoldMethod/objectLegalHoldMethod.txt")
.build();
minioClient.disableObjectLegalHold(disableObjectLegalHoldArgs);
objectLegalHoldEnabled = minioClient.isObjectLegalHoldEnabled(isObjectLegalHoldEnabledArgs);
System.out.println("执行disableObjectLegalHold:" + objectLegalHoldEnabled);
System.out.println("***********************************************************************************");
}
@SneakyThrows
public void objectTagOperation() {
System.out.println("************************************对象标签操作***********************************************");
uploadObject("tagMethod", "objectTags.txt");
// 与给bucket添加标签的方法是相似的
SetObjectTagsArgs setObjectTagsArgs = SetObjectTagsArgs.builder()
.bucket(buckName)
.object("tagMethod/objectTags.txt")
.tags(new HashMap<String, String>(1) {{
this.put("blessing", "star");
}})
.build();
minioClient.setObjectTags(setObjectTagsArgs);
GetObjectTagsArgs getObjectTagsArgs = GetObjectTagsArgs.builder()
.bucket(buckName)
.object("tagMethod/objectTags.txt")
.build();
Tags objectTags = minioClient.getObjectTags(getObjectTagsArgs);
System.out.println("设置后的标签:" + objectTags.get());
DeleteObjectTagsArgs deleteObjectTagsArgs = DeleteObjectTagsArgs.builder()
.bucket(buckName)
.object("tagMethod/objectTags.txt")
.build();
minioClient.deleteObjectTags(deleteObjectTagsArgs);
objectTags = minioClient.getObjectTags(getObjectTagsArgs);
System.out.println("删除后的标签对象:" + objectTags.get());
System.out.println("***********************************************************************************");
}
// @SneakyThrows
// public void selectObjectContent() {
// uploadObject("selectMethod", "selectObjectContent1.txt");
// uploadObject("selectMethod", "selectObjectContent2.txt");
// uploadObject("selectMethod", "selectObjectContent3.txt");
// String sqlExpression = "select * from S3Object";
// InputSerialization is = new InputSerialization(null, false, null, null, FileHeaderInfo.USE, null, null, null);
// OutputSerialization os = new OutputSerialization(null, null, null, QuoteFields.ASNEEDED, null);
// SelectResponseStream stream =
// minioClient.selectObjectContent(
// SelectObjectContentArgs.builder()
// .bucket(buckName)
// .object("selectMethod/selectObjectContent3.txt")
// .sqlExpression(sqlExpression)
// .inputSerialization(is)
// .outputSerialization(os)
// .requestProgress(true)
// .build());
//
// byte[] buf = new byte[512];
// int bytesRead = stream.read(buf, 0, buf.length);
// System.out.println(new String(buf, 0, bytesRead, StandardCharsets.UTF_8));
//
// Stats stats = stream.stats();
// System.out.println("bytes scanned: " + stats.bytesScanned());
// System.out.println("bytes processed: " + stats.bytesProcessed());
// System.out.println("bytes returned: " + stats.bytesReturned());
//
// stream.close();
// }
/// The operation is not valid for the current state of the object.不知道还需要什么状态
// @SneakyThrows
// public void restoreObject() {
// System.out.println("************************************restoreObject操作***********************************************");
// uploadObject("restoreMethod", "restoreObject.txt");
//// minioClient.removeObject(RemoveObjectArgs.builder().bucket(buckName).object("restoreMethod/restoreObject.txt").build());
//
// RestoreObjectArgs restoreObjectArgs = RestoreObjectArgs.builder()
// .bucket(buckName)
// .object("restoreMethod/restoreObject.txt")
// .request(new RestoreRequest(null, null, null, null, null, null))
// .build();
// minioClient.restoreObject(restoreObjectArgs);
//
// System.out.println("***********************************************************************************");
//
// }
@SneakyThrows
public void uploadObject(String folder, String fileName) {
byte[] contentBytes = MinioFileUtils.generateContent(fileName).getBytes(StandardCharsets.UTF_8);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(contentBytes);
PutObjectArgs thirdObjectArgs = PutObjectArgs.builder()
.bucket(buckName)
// 将创建一个名为putMethod文件夹
.object(folder + "/" + fileName)
// 两者不能同时为-1
.stream(byteArrayInputStream, -1, PutObjectArgs.MIN_MULTIPART_SIZE)
.build();
ObjectWriteResponse objectWriteResponse = minioClient.putObject(thirdObjectArgs);
}
@SneakyThrows
private void putObjectByPartSize(String folder, String fileName) {
String content = MinioFileUtils.generateContentPartSize(fileName, PutObjectArgs.MIN_MULTIPART_SIZE);
byte[] contentBytes = content.getBytes(StandardCharsets.UTF_8);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(contentBytes);
PutObjectArgs thirdObjectArgs = PutObjectArgs.builder()
.bucket(buckName)
// 将创建一个名为putMethod文件夹
.object(folder + "/" + fileName)
// 两者不能同时为-1
.stream(byteArrayInputStream, -1, PutObjectArgs.MIN_MULTIPART_SIZE)
.build();
minioClient.putObject(thirdObjectArgs);
}
}
启动类
package com.example;
import io.minio.*;
import io.minio.messages.*;
import com.example.config.MinioProperties;
import com.example.template.MinioBucketApi;
import com.example.template.MinioObjectApi;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Description:
*
* @author DawnStar
* Date: 2023/6/16 19:24
*/
@SpringBootApplication
public class MinioApplication implements ApplicationRunner {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = new SpringApplicationBuilder(MinioApplication.class).web(WebApplicationType.NONE).run(args);
MinioBucketApi bean = context.getBean(MinioBucketApi.class);
invokeMethod(bean);
// bean.objectLockConfigurationOperation();
MinioObjectApi objectApiBean = context.getBean(MinioObjectApi.class);
objectApiBean.getPresignedPostFormData();
invokeMethod(objectApiBean);
System.out.println("结束");
// objectApiBean.objectLegalHoldOperation();
// SpringApplication.run(MinioApplication.class, args);
}
@Resource
private MinioProperties minioProperties;
@Override
public void run(ApplicationArguments args) throws Exception {
if (minioProperties.getInitBuckets().length == 0) {
return;
}
// 初始化桶
MinioClient minioClient = MinioClient.builder().credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
.endpoint(minioProperties.getEndpoint())
.build();
// 获取所有桶
List<Bucket> buckets = minioClient.listBuckets();
List<String> collect = buckets.stream().map(Bucket::name).collect(Collectors.toList());
List<String> targetBucketNames = Arrays.stream(minioProperties.getInitBuckets()).filter(s -> !collect.contains(s)).collect(Collectors.toList());
for (String targetBucketName : targetBucketNames) {
MakeBucketArgs makeBucketArgs = MakeBucketArgs.builder().bucket(targetBucketName).build();
minioClient.makeBucket(makeBucketArgs);
}
String operationBucket = minioProperties.getOperationBucket();
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(operationBucket).build());
if (!bucketExists) {
// 只能在创建时候才能设置对象锁,之后都无效
minioClient.makeBucket(MakeBucketArgs.builder().bucket(operationBucket).objectLock(true).build());
}
// 防止bucket被设置了对象锁,无法修改minio服务器的对象
minioClient.deleteObjectLockConfiguration(DeleteObjectLockConfigurationArgs.builder().bucket(operationBucket).build());
}
private static void invokeMethod(Object o) {
Method[] declaredMethods = o.getClass().getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
if (declaredMethod.getParameters().length > 0) {
continue;
}
try {
if (declaredMethod.getReturnType().equals(void.class) && !declaredMethod.getName().contains("lambda")) {
declaredMethod.invoke(o);
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("\n\n");
}
}
}
注意事项
关于Nginx代理问题
通过nginx代理,https下,通过getPresignedObjectUrl
方法获取文件URL可能无法访问指定的文件。
以下是官方给出的一种方案,详情请访问Minio Nginx代理
location / {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 300;
# Default is HTTP/1, keepalive is only enabled in HTTP/1.1
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass https://minio:9000/; # This uses the upstream directive definition to load balance
}
location /minio {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
# This is necessary to pass the correct IP to be hashed
real_ip_header X-Real-IP;
proxy_connect_timeout 300;
# To support websockets in MinIO versions released after January 2023
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
chunked_transfer_encoding off;
proxy_pass https://minio:9001/; # This uses the upstream directive definition to load balance and assumes a static Console port of 9001
}
关于秒传,断点续传,分片续传问题
目前(2023/06/17) Minio Java API没有提供断点续传功能,所以如果想实现上述功能,可以借助Redis,和继承MinioAsyncClient
类并将其重写开放部分方法的访问权限来实现相关功能。可以参考这篇博客blog.csdn.net/Alan_ran/ar… 不过他的版本是低的,所以有些方法过时了如
listParts
方法便是过时的。
当然官方API也给予相关的方法是
listPartsAsync
方法,可以看出listParts
方法中也是调用了listPartsAsync
方法。更多具体细节请查看S3Base
类以及其子类MinioAsyncClient
,在8.5.3
中MinioClinet
其实是作为一个工具类,其内部封装了MinioAsyncClient
类。