Minio
官方文档: https://www.minio.org.cn/docs/minio/container/index.html
MinIO是一个对象存储解决方案,它提供了与Amazon Web Services S3兼容的API,并支持所有核心S3功能。 MinIO有能力在任何地方部署 - 公有云或私有云,裸金属基础设施,编排环境,以及边缘基础设施。
项目集成
1. 引入依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.17</version>
</dependency>
2. Yaml配置
minio:
endpoint: http://127.0.0.1:9090 # 访问地址,取决于部署的ip和端口
accessKey: minioadmin
secretKey: minioadmin
bucketName: xxxx # Minio创建的桶名称
3. Minio配置
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
/**
* @author 98
* @date 2025-03-19 09:28
*/
@Data
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioConfiguration {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucketName;
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
4. 工具类
/**
* @author 98
* @date 2025-03-19 09:30
*/
@Component
public class MinioUtils {
private static final Log log = LogFactory.get();
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;
@Autowired
private MinioClient minioClient;
@Autowired
private MinioConfiguration minioConfiguration;
/**
* 判断bucket是否存在,不存在则创建
* @param bucketName
*/
public void existBucket(String bucketName) {
try {
BucketExistsArgs build = BucketExistsArgs.builder().bucket(bucketName).build();
boolean exists = minioClient.bucketExists(build);
if (!exists) {
this.makeBucket(bucketName);
}
} catch (Exception e) {
log.error("bucket判断失败", e);
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, e.toString());
}
}
/**
* 创建存储bucket
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
log.error("bucket创建失败", e);
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, e.toString());
}
return true;
}
/**
* 删除存储bucket
* @param bucketName 存储bucket名称
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
log.error("bucket删除失败", e);
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, e.toString());
}
return true;
}
/**
* 单个上传文件
* @param multipartFile
* @return
*/
public String upload(MultipartFile multipartFile) throws Exception {
// 校验文件
assertAllowed(multipartFile, MimeTypeConstant.DEFAULT_ALLOWED_EXTENSION);
// 判断桶是否存在
this.existBucket(minioConfiguration.getBucketName());
String fileName = extractFilename(multipartFile);
InputStream inputStream = null;
try {
inputStream = multipartFile.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioConfiguration.getBucketName())
.object(fileName)
.stream(inputStream, inputStream.available(), -1)
.contentType(multipartFile.getContentType())
.build()
);
} catch (IOException e) {
log.error("上传文件失败", e.toString());
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, e.toString());
} finally {
if (Objects.nonNull(inputStream)) {
inputStream.close();
}
}
return fileName;
}
/**
* 获取Minio浏览地址
* @param fileName
* @return
*/
public String getReviewUrl(String fileName) {
String url = null;
try {
url = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(minioConfiguration.getBucketName())
.object(fileName)
.expiry(7, TimeUnit.DAYS)
.build()
);
} catch (Exception e) {
log.error("获取浏览地址失败", e.toString());
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, e.toString());
}
return url;
}
/**
* 文件校验
* @param file
* @param allowedExtension
*/
private static void assertAllowed(MultipartFile file, String[] allowedExtension) throws InvalidExtensionException {
if (file.getSize() > DEFAULT_MAX_SIZE) {
long size = DEFAULT_MAX_SIZE / 1024 / 1024;
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, "文件大小超过限制(MB):" + size);
}
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (Objects.nonNull(allowedExtension) && !isAllowedExtension(extension, allowedExtension)) {
if (allowedExtension == MimeTypeConstant.IMAGE_EXTENSION) {
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
} else if (allowedExtension == MimeTypeConstant.FLASH_EXTENSION) {
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
} else if (allowedExtension == MimeTypeConstant.MEDIA_EXTENSION) {
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
} else if (allowedExtension == MimeTypeConstant.VIDEO_EXTENSION) {
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
} else {
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
/**
* 获取文件后缀
* @param file
* @return
*/
private static String getExtension(MultipartFile file) {
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StrUtil.isBlank(extension)) {
String contentType = Optional.ofNullable(file.getContentType()).orElse(StrUtil.EMPTY);
extension = MimeTypeConstant.getExtension(contentType);
}
return extension;
}
/**
* 判断MIME类型是否是允许的MIME类型
* @param extension
* @param allowedExtension
* @return
*/
private static boolean isAllowedExtension(String extension, String[] allowedExtension) {
for (String str : allowedExtension) {
if (str.equalsIgnoreCase(extension)) {
return true;
}
}
return false;
}
/**
* 自定义文件名
* @param file
* @return
*/
private static String extractFilename(MultipartFile file) {
String extension = getExtension(file);
return DateUtil.today() + "/" + UUID.fastUUID() + "." + extension;
}
/**
* description: 下载文件
*
* @param fileName
* @return: org.springframework.http.ResponseEntity<byte[]>
*/
public ResponseEntity<byte[]> download(String fileName) {
ResponseEntity<byte[]> responseEntity = null;
GetObjectArgs build = GetObjectArgs.builder().bucket(minioConfiguration.getBucketName()).object(fileName).build();
try (InputStream in = minioClient.getObject(build); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
IOUtils.copy(in, out);
//封装返回值
byte[] bytes = out.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
headers.setContentLength(bytes.length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setAccessControlExposeHeaders(Collections.singletonList("*"));
responseEntity = new ResponseEntity<>(bytes, headers, HttpStatus.HTTP_OK);
} catch (Exception e) {
log.error("下载失败", e.toString());
throw ExceptionFactory.getBusinessException(HttpStatus.HTTP_INTERNAL_ERROR, e.toString());
}
return responseEntity;
}
}
import org.apache.tomcat.util.http.fileupload.FileUploadException;
import java.util.Arrays;
/**
* @author 98
* @date 2025-03-19 09:40
*/
public class InvalidExtensionException extends FileUploadException {
private static final long serialVersionUID = 1L;
private String[] allowedExtension;
private String extension;
private String filename;
public InvalidExtensionException(String[] allowedExtension, String extension, String filename) {
super("filename : [" + filename + "], extension : [" + extension + "], allowed extension : [" + Arrays.toString(allowedExtension) + "]");
this.allowedExtension = allowedExtension;
this.extension = extension;
this.filename = filename;
}
public String[] getAllowedExtension() {
return allowedExtension;
}
public String getExtension() {
return extension;
}
public String getFilename() {
return filename;
}
public static class InvalidImageExtensionException extends InvalidExtensionException {
private static final long serialVersionUID = 1L;
public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) {
super(allowedExtension, extension, filename);
}
}
public static class InvalidFlashExtensionException extends InvalidExtensionException {
private static final long serialVersionUID = 1L;
public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) {
super(allowedExtension, extension, filename);
}
}
public static class InvalidMediaExtensionException extends InvalidExtensionException {
private static final long serialVersionUID = 1L;
public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) {
super(allowedExtension, extension, filename);
}
}
public static class InvalidVideoExtensionException extends InvalidExtensionException {
private static final long serialVersionUID = 1L;
public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) {
super(allowedExtension, extension, filename);
}
}
}
/**
* @author 98
* @date 2025-03-19 09:37
*/
public class MimeTypeConstant {
public static final String IMAGE_PNG = "image/png";
public static final String IMAGE_JPG = "image/jpg";
public static final String IMAGE_JPEG = "image/jpeg";
public static final String IMAGE_BMP = "image/bmp";
public static final String IMAGE_GIF = "image/gif";
public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
public static final String[] FLASH_EXTENSION = { "swf", "flv" };
public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
"asf", "rm", "rmvb" };
public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
public static final String[] DEFAULT_ALLOWED_EXTENSION = {
// 图片
"bmp", "gif", "jpg", "jpeg", "png",
// word excel powerpoint
"doc", "docx", "xls", "xlsx", "ppt", "pptx", "html", "htm", "txt",
// 压缩文件
"rar", "zip", "gz", "bz2",
// 视频格式
"mp4", "avi", "rmvb",
// pdf
"pdf" };
public static String getExtension(String prefix) {
switch (prefix) {
case IMAGE_PNG:
return "png";
case IMAGE_JPG:
return "jpg";
case IMAGE_JPEG:
return "jpeg";
case IMAGE_BMP:
return "bmp";
case IMAGE_GIF:
return "gif";
default:
return "";
}
}
}