一、引入POM依赖
<!-- Minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
注意:8.4.3需要排除okhttp在引入版本高的okhttp否者启动会报错。
二、配置类
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.SetBucketPolicyArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Minio 配置信息
*
* @author zero
*/
@Component
@Slf4j
public class MinioConfig {
@Value("${zero.minio.bucketName}")
private String bucketName;
@Bean
public MinioClient getMinioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(minio.getEndpoint())
.credentials(minio.getAccessKey(), minio.getSecretKey())
.build();
try {
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!bucketExists) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
// Define the policy string
String policy = "{\n" +
" \"Version\": \"2012-10-17\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Action\": [\n" +
" \"s3:GetObject\"\n" +
" ],\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": {\n" +
" \"AWS\": [\n" +
" \"*\"\n" +
" ]\n" +
" },\n" +
" \"Resource\": [\n" +
" \"arn:aws:s3:::" + bucketName + "/*\"\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}";
// Set the bucket policy
minioClient.setBucketPolicy(
SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(policy)
.build());
}
} catch (Exception e) {
log.error("创建桶失败!", e);
throw new RuntimeException("创建桶失败!");
}
return minioClient;
}
}
三、YAML配置文件
spring:
resources:
static-locations: ${zero.minio.endpoint}/${zero.minio.bucketName}
servlet:
multipart:
enabled: true #开启文件上传
max-file-size: 100MB #单个文件大小
max-request-size: 100MB #总上传的数据大小
jackson:
# json 序列化排除值为 null 的属性
default-property-inclusion: non_null
# 配置 Date 类的时间格式
date-format: yyyy-MM-dd HH:mm:ss
# 配置 Date 类的时区
time-zone: GMT+8
zero:
minio:
endpoint: http://127.0.0.1:9000
accessKey: admin
secretKey: admin
bucketName: test
四、工具类MinioUtils
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.ymf.common.core.utils.UploadFileResult;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
/**
* Minio工具类
*
* @author zero
*/
@Component
@Slf4j
public class MinioUtils {
@Resource
private MinioClient minioClient;
/**
* 判断桶是否存在
*
* @param bucketName bucket名称
* @return true存在,false不存在
*/
public boolean bucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("检查桶是否存在失败!", e);
throw new RuntimeException("检查桶是否存在失败!");
}
}
/**
* 创建bucket
*
* @param bucketName bucket名称
*/
public void createBucket(String bucketName) {
if (!this.bucketExists(bucketName)) {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("创建桶失败!", e);
throw new RuntimeException("创建桶失败!");
}
}
}
/**
* 上传MultipartFile文件到全局默认文件桶中
*
* @param bucketName 桶
* @param catalog 文件夹
* @param file 文件
* @return 文件对应的URL
*/
public UploadFileResult putObject(String bucketName, String catalog, MultipartFile file) {
// 给文件名添加时间戳防止重复
String originalFilename = file.getOriginalFilename();
String fileName = getFileName(Objects.requireNonNull(originalFilename));
// 开始上传
String objectName = this.buildObject(catalog, fileName);
try (InputStream stream = file.getInputStream();) {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.contentType(file.getContentType())
.stream(stream, stream.available(), -1)
.build()
);
} catch (Exception e) {
log.error("文件流上传错误!", e);
throw new RuntimeException("文件上传失败!");
}
return UploadFileResult.builder()
.imageAddress(objectName)
.sourceFileName(originalFilename)
.build();
}
/**
* 判断文件是否存在
*
* @param bucketName 桶名称
* @param catalog 文件夹
* @param fileName 文件名称
* @return true存在, 反之
*/
public boolean checkFileIsExist(String bucketName, String catalog, String fileName) {
try {
String objectName = this.buildObject(catalog, fileName);
minioClient.statObject(
StatObjectArgs.builder().bucket(bucketName).object(objectName).build()
);
} catch (Exception e) {
return false;
}
return true;
}
/**
* 判断文件夹是否存在
*
* @param bucketName 桶名称
* @param catalog 文件夹名称
* @return true存在, 反之
*/
public Boolean checkFolderIsExist(String bucketName, String catalog) {
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs
.builder()
.bucket(bucketName)
.prefix(catalog)
.recursive(false)
.build());
for (Result<Item> result : results) {
Item item = result.get();
if (item.isDir() && Objects.equals(catalog, item.objectName())) {
return true;
}
}
} catch (Exception e) {
return false;
}
return true;
}
/**
* 获取全部bucket
*
* @return 所有桶信息
*/
public List<Bucket> getAllBuckets() {
try {
return minioClient.listBuckets();
} catch (Exception e) {
log.error("获取全部存储桶失败!", e);
throw new RuntimeException("获取全部存储桶失败!");
}
}
/**
* 根据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) {
log.error("根据存储桶名称获取信息失败!", e);
throw new RuntimeException("根据存储桶名称获取信息失败!");
}
}
/**
* 根据bucketName删除信息
*
* @param bucketName bucket名称
*/
public void removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
log.error("根据存储桶名称删除桶失败!", e);
throw new RuntimeException("根据存储桶名称删除桶失败!");
}
}
/**
* 删除文件
*
* @param bucketName bucket名称
* @param catalog 文件夹
* @param fileName 文件名称
*/
public boolean removeObject(String bucketName, String catalog, String fileName) {
try {
String objectName = this.buildObject(catalog, fileName);
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
return false;
}
return true;
}
/**
* @param catalog 文件夹
* @param fileName 文件名称 index.html
* @return
*/
private String buildObject(String catalog, String fileName) {
return StrUtil.format("/{}/{}", catalog, fileName);
}
/**
* 生成唯一ID
*
* @param objectName 文件名
* @return 唯一ID
*/
private static String getFileName(String objectName) {
//判断文件最后一个点所在的位置
int lastIndexOf = objectName.lastIndexOf(".");
if (lastIndexOf == -1) {
return StrUtil.format("{}_{}", objectName, System.currentTimeMillis());
} else {
// 获取文件前缀,已最后一个点进行分割
String filePrefix = objectName.substring(0, objectName.lastIndexOf("."));
// 获取文件后缀,已最后一个点进行分割
String fileSuffix = objectName.substring(objectName.lastIndexOf(".") + 1);
// 组成唯一文件名
return StrUtil.format("{}_{}.{}", filePrefix, System.currentTimeMillis(), fileSuffix);
}
}
/**
* 获取文件信息, 如果抛出异常则说明文件不存在
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
*/
public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
}
/**
* 获取文件外链
*
* @param bucketName bucket名称
* @param objectName 文件名称
* @return url
*/
@SneakyThrows
public String getObjectUrl(String bucketName, String objectName) {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.build());
}
/**
* 根据文件前置查询文件
*
* @param bucketName bucket名称
* @param prefix 前缀
* @param recursive 是否递归查询
* @return
*/
@SneakyThrows
public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
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> result : objectsIterator) {
Item item = result.get();
list.add(item);
}
}
return list;
}
public void download(String bucketName, String objectName, HttpServletResponse response) {
try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
OutputStream outputStream = response.getOutputStream()) {
response.reset();
response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(objectName, "UTF-8"));
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.addHeader("Access-Control-Allow-Headers", "Content-Type");
response.setContentType("application/octet-stream; charset=UTF-8");
IoUtil.copy(inputStream, outputStream);
} catch (Exception e) {
log.error("文件下载异常!", e);
throw new RuntimeException("文件下载异常!");
}
}
}
五、UploadFileResult类
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*;
/**
* @author zero
*/
@Getter
@Setter
@ApiModel("文件上传--返回对象")
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UploadFileResult {
@ApiModelProperty("图片地址")
private String imageAddress;
@ApiModelProperty("文件MD5值")
private String md5;
@ApiModelProperty("原文件名称")
private String sourceFileName;
}