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: minioadminsecretKey: minioadminbucketName: 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;@Beanpublic 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;@Autowiredprivate MinioClient minioClient;@Autowiredprivate 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 "";}}
}