minio官网地址:https://min.io/
minio官方下载地址:https://min.io/download#/linux
minio官方文档地址:https://min.io/docs/minio
linux minio单机安装
参考官方教程:
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin ./minio server /mnt/minio/data --console-address ":9001"
执行上方命令后,用浏览器访问本地9001端口即可看到控制台界面,用默认用户名密码登录即可,文件存储目录是上面指定的/mnt/minio/data目录
登录之后,可以在界面上创建自己的Bucket,生成accesskey,新建用户等
比如:可以新建test用户密码为test12345678A,配置好readwrite和diagnostics策略,新建testbucket桶,Access Policy设置为public。
minio默认的访问端口是9000,在界面上上传一张图片:2022-01-22_18-40.png
那么用本机ip端口9000就可以访问这个图片:
比如:http://127.0.0.1:9000/testbucket/2022-01-22_18-40.png
windows minio单机安装
参考官方教程:
PS> Invoke-WebRequest -Uri "https://dl.min.io/server/minio/release/windows-amd64/minio.exe" -OutFile "C:\minio.exe"
PS> setx MINIO_ROOT_USER admin
PS> setx MINIO_ROOT_PASSWORD password
PS> C:\minio.exe server F:\Data --console-address ":9001"
springboot集成minio
maven 引入(截止发文最新版本是8.5.6):
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.6</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
配置application.properties
minio.endpoint=http://192.168.0.118:9000
minio.accessKey=test
minio.secretKey=test12345678A
minio.bucket=testbucket
minio.parentPath=project1
新建MinioProperties
package com.zhaohy.app.minio;
import org.hibernate.validator.constraints.URL;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import jakarta.validation.constraints.NotEmpty;
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
/**
* 服务地址
*/
@NotEmpty(message = "minio服务地址不可为空")
@URL(message = "minio服务地址格式错误")
private String endpoint;
/**
* 认证账户
*/
@NotEmpty(message = "minio认证账户不可为空")
private String accessKey;
/**
* 认证密码
*/
@NotEmpty(message = "minio认证密码不可为空")
private String secretKey;
/**
* 桶名称, 优先级最低
*/
private String bucket;
/**
* bucket下面的第一个文件夹,用来区分不同子项目
*/
private String parentPath;
/**
* 桶不在的时候是否新建桶
*/
private boolean createBucket = true;
/**
* 启动的时候检查桶是否存在
*/
private boolean checkBucket = true;
/**
* 设置HTTP连接、写入和读取超时。值为0意味着没有超时
* HTTP连接超时,以毫秒为单位。
*/
private long connectTimeout;
/**
* 设置HTTP连接、写入和读取超时。值为0意味着没有超时
* HTTP写超时,以毫秒为单位。
*/
private long writeTimeout;
/**
* 设置HTTP连接、写入和读取超时。值为0意味着没有超时
* HTTP读取超时,以毫秒为单位。
*/
private long readTimeout;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
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 getBucket() {
return bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
public boolean isCreateBucket() {
return createBucket;
}
public void setCreateBucket(boolean createBucket) {
this.createBucket = createBucket;
}
public boolean isCheckBucket() {
return checkBucket;
}
public void setCheckBucket(boolean checkBucket) {
this.checkBucket = checkBucket;
}
public long getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(long connectTimeout) {
this.connectTimeout = connectTimeout;
}
public long getWriteTimeout() {
return writeTimeout;
}
public void setWriteTimeout(long writeTimeout) {
this.writeTimeout = writeTimeout;
}
public long getReadTimeout() {
return readTimeout;
}
public void setReadTimeout(long readTimeout) {
this.readTimeout = readTimeout;
}
public String getParentPath() {
return parentPath;
}
public void setParentPath(String parentPath) {
this.parentPath = parentPath;
}
}
新建MinioConfiguration
package com.zhaohy.app.minio;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.zhaohy.app.utils.LogUtils;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
@Configuration
public class MinioConfiguration {
@Resource
private MinioProperties minioAutoProperties;
@Bean
public MinioClient minioClient() {
LogUtils.info("开始初始化MinioClient, url为{}, accessKey为:{}", minioAutoProperties.getEndpoint(), minioAutoProperties.getAccessKey());
MinioClient minioClient = MinioClient
.builder()
.endpoint(minioAutoProperties.getEndpoint())
.credentials(minioAutoProperties.getAccessKey(), minioAutoProperties.getSecretKey())
.build();
minioClient.setTimeout(
minioAutoProperties.getConnectTimeout(),
minioAutoProperties.getWriteTimeout(),
minioAutoProperties.getReadTimeout()
);
// Start detection
if (minioAutoProperties.isCheckBucket()) {
LogUtils.info("checkBucket为{}, 开始检测桶是否存在", minioAutoProperties.isCheckBucket());
String bucketName = minioAutoProperties.getBucket();
if (!checkBucket(bucketName, minioClient)) {
LogUtils.info("文件桶[{}]不存在, 开始检查是否可以新建桶", bucketName);
if (minioAutoProperties.isCreateBucket()) {
LogUtils.info("createBucket为{},开始新建文件桶", minioAutoProperties.isCreateBucket());
createBucket(bucketName, minioClient);
}
}
LogUtils.info("文件桶[{}]已存在, minio客户端连接成功!", bucketName);
} else {
throw new RuntimeException("桶不存在, 请检查桶名称是否正确或者将checkBucket属性改为false");
}
return minioClient;
}
private boolean checkBucket(String bucketName, MinioClient minioClient) {
boolean isExists = false;
try {
isExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("failed to check if the bucket exists", e);
}
return isExists;
}
private void createBucket(String bucketName, MinioClient minioClient) {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
LogUtils.info("文件桶[{}]新建成功, minio客户端已连接", bucketName);
} catch (Exception e) {
throw new RuntimeException("failed to create default bucket", e);
}
}
}
新建MinioPathUtils
package com.zhaohy.app.minio;
import java.util.UUID;
import com.zhaohy.app.utils.DateUtil;
public class MinioPathUtils {
public static final String minioPath = "project1";
public static final String extranetAddr = "http://192.168.0.118:9000";
public static final String publicBucket = "testbucket";
public static String getMinioTmpDir() {
return minioPath + "/tmp/" + DateUtil.getSystemDate("yyyyMM") + "/" + UUID.randomUUID().toString().replace("-", "") + "/";
}
public static String getMinioDefaultDir() {
return minioPath + "/" + DateUtil.getSystemDate("yyyyMM") + "/" + UUID.randomUUID().toString().replace("-", "") + "/";
}
public static String getMinioCustomDir(String path) {
return minioPath + path + DateUtil.getSystemDate("yyyyMM") + "/" + UUID.randomUUID().toString().replace("-", "") + "/";
}
public static String getBaseUrl() {
return extranetAddr + "/" + publicBucket + "/";
}
}
新建MinioTemplate
package com.zhaohy.app.minio;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.IntFunction;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import io.minio.BucketExistsArgs;
import io.minio.CopyObjectArgs;
import io.minio.CopySource;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.ListObjectsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.Result;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.errors.ErrorResponseException;
import io.minio.errors.InsufficientDataException;
import io.minio.errors.InternalException;
import io.minio.errors.InvalidResponseException;
import io.minio.errors.ServerException;
import io.minio.errors.XmlParserException;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
@Component
public class MinioTemplate {
@Resource
private MinioClient minioClient;
@Resource
private MinioProperties minioProperties;
/**
* 判断文件是否存在
*
* @param bucketName 存储桶
* @param objectName 对象
* @return true:存在
*/
public 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 boolean doesFolderExist(String objectName) {
boolean exist = false;
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(minioProperties.getBucket()).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名称
* @return true存在,false不存在
*/
public Boolean bucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("检查桶是否存在失败!", e);
}
}
/**
* 创建bucket
*
* @param bucketName bucket名称
*/
public void createBucket(String bucketName) {
if (!this.bucketExists(bucketName)) {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("创建桶失败!", e);
}
}
}
/**
* 上传bytes文件
*
* @param objectName 文件名
* @param bytes 字节
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 详细可看: https://www.runoob.com/http/http-content-type.html
* @return 文件url
*/
public String putObject(String objectName, byte[] bytes, String contentType) {
// 开始上传
this.putBytes(minioProperties.getBucket(), objectName, bytes, contentType);
return MinioPathUtils.getBaseUrl() + objectName;
}
/**
* 上传MultipartFile文件到全局默认文件桶下的folder文件夹下
*
* @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @param file 文件
* @return 文件对应的URL
*/
public String putObject(String objectName, MultipartFile file) {
// 开始上传
this.putMultipartFile(minioProperties.getBucket(), objectName, file);
return MinioPathUtils.getBaseUrl() + objectName;
}
/**
* 上传流到指定的文件桶下
*
* @param bucketName 桶名称
* @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @param stream 文件流
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 详细可看: https://www.runoob.com/http/http-content-type.html
* @return 文件对应的URL
*/
public String putObject(String objectName, InputStream stream, String contentType) {
// 开始上传
this.putInputStream(minioProperties.getBucket(), objectName, stream, contentType);
return MinioPathUtils.getBaseUrl() + objectName;
}
/**
* 上传File文件到默认桶下
*
* @param objectName 文件名
* @param file 文件
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 详细可看: https://www.runoob.com/http/http-content-type.html
* @return 文件对应的URL
*/
public String putObject(String objectName, File file, String contentType) {
// 开始上传
this.putFile(minioProperties.getBucket(), objectName, file, contentType);
return MinioPathUtils.getBaseUrl() + objectName;
}
/**
* 判断文件是否存在
*
* @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @return true存在, 反之
*/
public Boolean checkFileIsExist(String objectName) {
return this.checkFileIsExist(minioProperties.getBucket(), objectName);
}
/**
* 判断文件夹是否存在
*
* @param folderName 文件夹名称
* @return true存在, 反之
*/
public Boolean checkFolderIsExist(String folderName) {
return this.checkFolderIsExist(minioProperties.getBucket(), folderName);
}
/**
* 判断文件是否存在
*
* @param bucketName 桶名称
* @param objectName 文件名称, 如果要带文件夹请用 / 分割, 例如 /help/index.html
* @return true存在, 反之
*/
public Boolean checkFileIsExist(String bucketName, String objectName) {
try {
minioClient.statObject(
StatObjectArgs.builder().bucket(bucketName).object(objectName).build()
);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 判断文件夹是否存在
*
* @param bucketName 桶名称
* @param folderName 文件夹名称
* @return true存在, 反之
*/
public Boolean checkFolderIsExist(String bucketName, String folderName) {
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs
.builder()
.bucket(bucketName)
.prefix(folderName)
.recursive(false)
.build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir() && folderName.equals(item.objectName())) {
return true;
}
}
} catch (Exception e) {
return false;
}
return true;
}
/**
* 根据文件全路径获取文件流
*
* @param objectName 文件名称
* @return 文件流
*/
public InputStream getObject(String objectName) {
return this.getObject(minioProperties.getBucket(), objectName);
}
/**
* 根据文件桶和文件全路径获取文件流
*
* @param bucketName 桶名称
* @param objectName 文件名
* @return 文件流
*/
public InputStream getObject(String bucketName, String objectName) {
try {
return minioClient
.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
throw new RuntimeException("根据文件名获取流失败!", e);
}
}
/**
* 断点下载
*
* @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, ErrorResponseException {
return minioClient.getObject(
GetObjectArgs.builder().bucket(bucketName).object(objectName).offset(offset).length(length)
.build());
}
/**
* 获取全部bucket
*
* @return 所有桶信息
*/
public List<Bucket> getAllBuckets() {
try {
return minioClient.listBuckets();
} catch (Exception e) {
throw new RuntimeException("获取全部存储桶失败!", e);
}
}
/**
* 根据bucketName获取信息
*
* @param bucketName bucket名称
* @return 单个桶信息
*/
public Optional<Bucket> getBucket(String bucketName) {
try {
return minioClient.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
} catch (Exception e) {
throw new RuntimeException("根据存储桶名称获取信息失败!", e);
}
}
/**
* 根据bucketName删除信息
*
* @param bucketName bucket名称
*/
public void removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
throw new RuntimeException("根据存储桶名称删除桶失败!", e);
}
}
/**
* 删除文件
*
* @param objectName 文件名称
*/
public boolean removeObject(String objectName) {
try {
this.removeObject(minioProperties.getBucket(), objectName);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 删除文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
*/
public boolean removeObject(String bucketName, String objectName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
return false;
}
return true;
}
/**
* 上传MultipartFile通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param file 文件
*/
private void putMultipartFile(String bucketName, String objectName, MultipartFile file) {
InputStream stream = null;
try {
stream = file.getInputStream();
} catch (IOException e) {
throw new RuntimeException("文件流获取错误", e);
}
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(file.getContentType())
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件流上传错误", e);
}
}
/**
* 上传InputStream通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param stream 文件流
*/
private void putInputStream(String bucketName, String objectName, InputStream stream, String contentType) {
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件流上传错误", e);
}
}
/**
* 上传 bytes 通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param bytes 字节编码
*/
private void putBytes(String bucketName, String objectName, byte[] bytes, String contentType) {
// 字节转文件流
InputStream stream = new ByteArrayInputStream(bytes);
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件流上传错误", e);
}
}
/**
* 上传 file 通用方法
*
* @param bucketName 桶名称
* @param objectName 文件名
* @param file 文件
* @param contentType 文件类型, 例如 image/jpeg: jpg图片格式, 详细可看: https://www.runoob.com/http/http-content-type.html
*/
private void putFile(String bucketName, String objectName, File file, String contentType) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(contentType)
.stream(fileInputStream, fileInputStream.available(), -1)
.build()
);
} catch (Exception e) {
throw new RuntimeException("文件上传错误", e);
}
}
/**
* 获取文件信息, 如果抛出异常则说明文件不存在
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
*/
public StatObjectResponse getObjectInfo(String objectName) throws Exception {
return minioClient.statObject(StatObjectArgs.builder().bucket(minioProperties.getBucket()).object(objectName).build());
}
/**
* 获取文件外链
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param expires 过期时间 <=7
* @return url
* @throws IOException
* @throws IllegalArgumentException
* @throws ServerException
* @throws XmlParserException
* @throws NoSuchAlgorithmException
* @throws InvalidResponseException
* @throws InternalException
* @throws InsufficientDataException
* @throws ErrorResponseException
* @throws InvalidKeyException
*/
public String getObjectURL(String bucketName, String objectName, Integer expires) throws InvalidKeyException, ErrorResponseException, InsufficientDataException, InternalException, InvalidResponseException, NoSuchAlgorithmException, XmlParserException, ServerException, IllegalArgumentException, IOException {
IntFunction<Integer> integerIntFunction = (int i) -> {
return Math.min(i, 7);
};
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(objectName)
.expiry(integerIntFunction.apply(expires))
.build());
}
/**
* 根据文件前置查询文件
*
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return MinioItem 列表
* @throws IOException
* @throws XmlParserException
* @throws ServerException
* @throws NoSuchAlgorithmException
* @throws InvalidResponseException
* @throws InternalException
* @throws InsufficientDataException
* @throws IllegalArgumentException
* @throws ErrorResponseException
* @throws JsonMappingException
* @throws InvalidKeyException
* @throws JsonParseException
*/
public List<Item> getAllObjectsByPrefix(String prefix, boolean recursive) throws JsonParseException, InvalidKeyException, JsonMappingException, ErrorResponseException, IllegalArgumentException, InsufficientDataException, InternalException, InvalidResponseException, NoSuchAlgorithmException, ServerException, XmlParserException, IOException {
List<Item> list = new ArrayList<>();
Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
ListObjectsArgs.builder().bucket(minioProperties.getBucket()).prefix(prefix)
.recursive(recursive).build()
);
if (objectsIterator != null) {
for (Result<Item> result : objectsIterator) {
Item item = result.get();
list.add(item);
}
}
return list;
}
/**
* 拷贝文件
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @param srcBucketName 目标bucket名称
* @param srcObjectName 目标文件名称
*/
public ObjectWriteResponse copyObject(String bucketName, String objectName,
String srcBucketName, String srcObjectName)
throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {
return minioClient.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder().bucket(bucketName).object(objectName).build())
.bucket(srcBucketName)
.object(srcObjectName)
.build());
}
}
这样就可以在业务代码里注入使用MinioTemplate了,这里面只是封装了一些常用的方法,不够的可以根据官方api文档再进行封装
文中的对外访问链接可以在nginx上配置域名,指向minio的9000端口就可以了。