官方文档:Kubernetes 的 MinIO 对象存储 — MinIO Object Storage for Kubernetes
一、简介
Minio 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。Minio是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
io.minio minio8.2.2 cn.hutool hutool-all5.8.18
Tips:MinIO依赖冲突问题 SpringBoot集成MinIO依赖冲突问题_Fly_Camel_Yu的博客-CSDN博客场景一: 使用minio8.3.0版本的依赖,报下列异常:An attempt was made to call a method that does not exist. The attempt was made from the following location: io.minio.S3Base.(S3Base.java:105)The following method did not exist: okhttp3.Requ...https://blog.csdn.net/qq_39974348/article/details/121742672
import io.minio.MinioClient; import lombok.SneakyThrows; import org.atm.dc.app.oss.props.MinioProperties; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.annotation.Resource; /** * Minio配置类 * * @author meng */ @Configuration @EnableConfigurationProperties(MinioProperties.class) @ConditionalOnProperty(value = "oss.name", havingValue = "minio") public class MinioConfiguration { @Resource private MinioProperties ossProperties; @Bean @SneakyThrows public MinioClient minioClient() { return MinioClient.builder() .endpoint(ossProperties.getEndpoint()) .credentials(ossProperties.getAccessKey(), ossProperties.getSecretKey()) .build(); } }
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.util.List; /** * Minio参数配置类 * * @author meng */ @Data @ConfigurationProperties(prefix = MinioProperties.PREFIX) public class MinioProperties { /** * 配置前缀 */ public static final String PREFIX = "oss"; /** * 对象存储名称 */ private String name; /** * 对象存储服务的URL */ private String endpoint; /** * Access key 账户ID */ private String accessKey; /** * Secret key 密码 */ private String secretKey; /** * 默认的存储桶名称 */ private String bucketName = "meng"; /** * 可上传的文件后缀名 */ private ListfileExt; }
yml文件:
#MINIO oss: name: minio accessKey: 123456 secretKey: 123456 bucketName: test endpoint: http://ip:端口号 fileExt: png,jpg,jpeg,pdf
import lombok.Data; import java.util.Date; /** * OssFile * * @author meng */ @Data public class OssFile { /** * 文件地址 */ private String filePath; /** * 域名地址 */ private String domain; /** * 文件名 */ private String name; /** * 原始文件名 */ private String originalName; /** * 文件hash值 */ public String hash; /** * 文件大小 */ private long size; /** * 文件上传时间 */ private Date putTime; /** * 文件contentType */ private String contentType; }
import org.atm.dc.app.oss.model.OssFile; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.util.List; /** * OssTemplate抽象API * * @author meng */ public interface OssTemplate { /** * 存储桶是否存在 * * @param bucketName 存储桶名称 * @return boolean */ boolean bucketExists(String bucketName); /** * 获取文件信息 * * @param fileName 存储桶文件名称 * @return InputStream */ OssFile getOssInfo(String fileName); /** * 上传文件 * * @param folderName 上传的文件夹名称 * @param fileName 上传文件名 * @param file 上传文件类 * @return BladeFile */ OssFile upLoadFile(String folderName, String fileName, MultipartFile file); /** * 上传文件 * * @param folderName 上传的文件夹名称 * @param fileName 存储桶对象名称 * @param suffix 文件后缀名 * @param stream 文件流 * @return BladeFile */ OssFile upLoadFile(String folderName, String fileName, String suffix, InputStream stream); /** * 删除文件 * * @param fileName 存储桶对象名称 */ boolean removeFile(String fileName); /** * 批量删除文件 * * @param fileNames 存储桶对象名称集合 */ boolean removeFiles(ListfileNames); /** * @Description: 下载文件 * @Param response: 响应 * @Param fileName: 文件名 * @Param filePath: 文件路径 * @return: void */ void downloadFile(HttpServletResponse response, String fileName, String filePath); }
MinIOTemplate:
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.text.StrPool; import cn.hutool.core.util.ObjectUtil; import io.minio.*; import io.minio.http.Method; import io.minio.messages.DeleteObject; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.atm.dc.app.common.Constants; import org.atm.dc.app.oss.model.OssFile; import org.atm.dc.app.oss.props.MinioProperties; import org.atm.dc.app.oss.template.OssTemplate; import org.atm.dc.app.util.FileInfoUtil; import org.atm.dc.exception.BaseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.InputStream; import java.net.URLEncoder; import java.util.List; import java.util.stream.Stream; /** * MinIOTemplate * * @author meng */ @Slf4j @Service public class MinioTemplate implements OssTemplate { private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * MinIO客户端 */ @Resource private MinioClient client; /** * 配置类 */ @Resource private MinioProperties ossProperties; /** * 格式化时间 */ private static final String DATE_FORMAT = "yyyyMMdd"; /** * 字符集 */ private static final String ENCODING = "UTF-8"; /** * 存储桶是否存在 * * @param bucketName 存储桶名称 * @return boolean */ @Override public boolean bucketExists(String bucketName) { try { return client.bucketExists(BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build()); } catch (Exception e) { logger.error("minio bucketExists Exception:{}", e); } return false; } /** * @Description: 创建 存储桶 * @Param bucketName: 存储桶名称 * @return: void * @Date: 2023/8/2 11:28 */ public void makeBucket(String bucketName) { try { if (!client.bucketExists(BucketExistsArgs.builder().bucket(getBucketName(bucketName)).build())) { client.makeBucket(MakeBucketArgs.builder().bucket(getBucketName(bucketName)).build()); logger.info("minio makeBucket success bucketName:{}", bucketName); } } catch (Exception e) { logger.error("minio makeBucket Exception:{}", e); } } /** * 获取文件信息 * * @param fileName 存储桶文件名称 * @return InputStream */ @Override public OssFile getOssInfo(String fileName) { try { StatObjectResponse stat = client.statObject( StatObjectArgs.builder().bucket(getBucketName(ossProperties.getBucketName())).object(fileName) .build()); OssFile ossFile = new OssFile(); ossFile.setName(ObjectUtil.isEmpty(stat.object()) ? fileName : stat.object()); ossFile.setFilePath(ossFile.getName()); ossFile.setDomain(getOssHost(ossProperties.getBucketName())); ossFile.setHash(String.valueOf(stat.hashCode())); ossFile.setSize(stat.size()); ossFile.setPutTime(DateUtil.date(stat.lastModified().toLocalDateTime())); ossFile.setContentType(stat.contentType()); return ossFile; } catch (Exception e) { logger.error("minio getOssInfo Exception:{}", e); } return null; } /** * 上传文件 * * @param folderName 上传的文件夹名称 * @param fileName 上传文件名 * @param file 上传文件类 * @return BladeFile */ @Override @SneakyThrows public OssFile upLoadFile(String folderName, String fileName, MultipartFile file) { if (file == null || file.isEmpty()) { throw new BaseException("400", Constants.FILE_EMPTY); } // 文件大小 if (file.getSize() > 5 * 1024 * 1024) { throw new BaseException("400", "文件大小不能超过5M"); } String suffix = FileInfoUtil.getFileExtension(file.getOriginalFilename()); // 文件后缀判断 if (!CollUtil.contains(ossProperties.getFileExt(), suffix)) { String error = String.format("文件类型错误,目前支持[%s]等文件类型", String.join(",", ossProperties.getFileExt())); throw new BaseException("400", error); } try { return upLoadFile(folderName, fileName, suffix, file.getInputStream()); } catch (Exception e) { logger.error("minio upLoadFile Exception:{}", e); throw new BaseException("400", "文件上传失败,请重新上传或联系管理员"); } } /** * 上传文件 * * @param folderName 上传的文件夹名称 * @param fileName 存储桶对象名称 * @param suffix 文件后缀名 * @param stream 文件流 * @return BladeFile */ @Override public OssFile upLoadFile(String folderName, String fileName, String suffix, InputStream stream) { try { return upLoadFile(ossProperties.getBucketName(), folderName, fileName, suffix, stream, "application/octet" + "-stream"); } catch (Exception e) { logger.error("minio upLoadFile Exception:{}", e); } return null; } /** * @Description: 上传文件 * @Param bucketName: 存储桶名称 * @Param folderName: 上传的文件夹名称 * @Param fileName: 上传文件名 * @Param suffix: 文件后缀名 * @Param stream: 文件流 * @Param contentType: 文件类型 * @Author: wmh * @Date: 2023/8/1 19:59 */ @SneakyThrows public OssFile upLoadFile(String bucketName, String folderName, String fileName, String suffix, InputStream stream, String contentType) { if (!bucketExists(bucketName)) { logger.info("minio bucketName is not creat"); makeBucket(bucketName); } OssFile file = new OssFile(); String originalName = fileName; String filePath = getFilePath(folderName, fileName, suffix); client.putObject(PutObjectArgs.builder().bucket(getBucketName(bucketName)).object(filePath) .stream(stream, stream.available(), -1).contentType(contentType).build()); file.setOriginalName(originalName); file.setName(filePath); file.setDomain(getOssHost(bucketName)); file.setFilePath(filePath); stream.close(); logger.info("minio upLoadFile success, filePath:{}", filePath); return file; } /** * 删除文件 * * @param fileName 存储桶对象名称 */ @Override public boolean removeFile(String fileName) { try { client.removeObject( RemoveObjectArgs.builder().bucket(getBucketName(ossProperties.getBucketName())).object(fileName) .build()); logger.info("minio removeFile success, fileName:{}", fileName); return true; } catch (Exception e) { logger.error("minio removeFile fail, fileName:{}, Exception:{}", fileName, e); } return false; } /** * 批量删除文件 * * @param fileNames 存储桶对象名称集合 */ @Override public boolean removeFiles(ListfileNames) { try { Stream stream = fileNames.stream().map(DeleteObject::new); client.removeObjects(RemoveObjectsArgs.builder().bucket(getBucketName(ossProperties.getBucketName())) .objects(stream::iterator).build()); logger.info("minio removeFiles success, fileNames:{}", fileNames); return true; } catch (Exception e) { logger.error("minio removeFiles fail, fileNames:{}, Exception:{}", fileNames, e); } return false; } /** * @Description: 下载文件 * @Param response: 响应 * @Param fileName: 文件名 * @Param filePath: 文件路径 * @return: void * @Author: wmh * @Date: 2023/8/2 14:08 */ @Override public void downloadFile(HttpServletResponse response, String fileName, String filePath) { GetObjectResponse is = null; try { GetObjectArgs getObjectArgs = GetObjectArgs.builder().bucket(ossProperties.getBucketName()).object(filePath) .build(); is = client.getObject(getObjectArgs); // 设置文件ContentType类型,这样设置,会自动判断下载文件类型 response.setContentType("application/x-msdownload"); response.setCharacterEncoding(ENCODING); // 设置文件头:最后一个参数是设置下载的文件名并编码为UTF-8 response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(fileName, ENCODING)); IoUtil.copy(is, response.getOutputStream()); logger.info("minio downloadFile success, filePath:{}", filePath); } catch (Exception e) { logger.error("minio downloadFile Exception:{}", e); } finally { IoUtil.close(is); } } /** * 获取文件外链 * * @param bucketName bucket名称 * @param fileName 文件名称 * @param expires 过期时间 * @return url */ public String getPresignedObjectUrl(String bucketName, String fileName, Integer expires) { String link = ""; try { link = client.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(getBucketName(bucketName)) .object(fileName).expiry(expires).build()); } catch (Exception e) { logger.error("minio getPresignedObjectUrl is fail, fileName:{}", fileName); } return link; } /** * 根据规则生成存储桶名称规则 * * @param bucketName 存储桶名称 * @return String */ private String getBucketName(String bucketName) { return bucketName; } /** * 根据规则生成文件路径 * * @param folderName 上传的文件夹名称 * @param originalFilename 原始文件名 * @param suffix 文件后缀名 * @return string 上传的文件夹名称/yyyyMMdd/原始文件名_时间戳.文件后缀名 */ private String getFilePath(String folderName, String originalFilename, String suffix) { return StrPool.SLASH + String.join(StrPool.SLASH, folderName, DateUtil.date().toString(DATE_FORMAT), originalFilename) + StrPool.C_UNDERLINE + DateUtil.current() + StrPool.DOT + suffix; } /** * 获取域名 * * @param bucketName 存储桶名称 * @return String */ public String getOssHost(String bucketName) { return ossProperties.getEndpoint() + StrPool.SLASH + getBucketName(bucketName); } }