什么是MinIO?
Minio
是个基于Golang
编写的开源对象存储套件,基于Apache License v2.0
开源协议,虽然轻量,却拥有着不错的性能。它兼容亚马逊S3云存储服务接口。可以很简单的和其他应用结合使用,例如NodeJS
、Redis
、MySQL
等。
应用场景
MinIO
的应用场景除了可以作为私有云的对象存储服务来使用,也可以作为云对象存储的网关层,无缝对接Amazon S3
或者MicroSoft Azure
。
特点
高性能
:作为一款高性能存储,在标准硬件条件下,其读写速率分别可以达到55Gb/s
和35Gb/s
。并且MinIO支持一个对象文件可以是任意大小,从几kb到最大5T不等。
可扩展
:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并且支持跨越多个数据中心。
云原生
:容器化、基于K8S的编排、多租户支持。
Amazon S3兼容
:使用 Amazon S3 v2 / v4 API。可以使用Minio SDK,Minio Client,AWS SDK 和 AWS CLI 访问Minio服务器。
SDK支持
:
- GO SDK:https://github.com/minio/minio-go
- JavaSDK:https://github.com/minio/minio-java
- PythonSDK:https://github.com/minio/minio-py
功能很强大,本文只是抛砖引玉,有兴趣的朋友自己去探索吧~
安装MinIO
这里使用docker安装,步骤如下:
获取镜像
执行命令如下:
启动镜像
执行命令如下:
docker run -p 9000:9000 -p 9001:9001 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=admin" -e "MINIO_SECRET_KEY=admin" -v /home/data:/data -v /home/config:/root/.minio minio/minio server --console-address ":9000" --address ":9001" /data
|
命令解释如下:
-p
:9000是图形界面的端口,9001是API的端口,在使用SDK连接需要用到
MINIO_ACCESS_KEY
:指定图形界面的用户名
MINIO_SECRET_KEY
:指定图形界面的密码
按照上述两个步骤启动成功即可。
图形界面操作
安装成功后直接访问地址:http:/ip:9000/login
,如下:
输入用户名和密码登录成功后,如下:
菜单很多,这里就不再详细介绍了,笔者这里直接在Buckets菜单中创建一个桶为test,如下图:
并且设置这个桶的隐私规则为public,如下:
MinIO到此已经安装设置成功了
SpringBoot整合MinIO
虽然MinIO在图形界面提供了手动上传的操作,但是也可以通过SDK的方式去上传,下面介绍一下Spring Boot 整合MinIO上传文件。
获取accessKey和secretKey
这里的accessKey
和secretKey
并不是图形界面登录名和密码,获取很简单,直接在图形界面中操作,如下图:
添加依赖
添加MinIO的依赖,如下:
<dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.2.1</version> </dependency>
|
添加配置
在aplication.yml
配置中添加MInIO
相关的配置,如下:
minio: endpoint: http://192.168.47.148 port: 9001 accessKey: 账户 secretKey: 密码 secure: false bucket-name: test image-size: 10485760 file-size: 1073741824
|
配置类
import io.minio.MinioClient; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Data @Configuration @ConfigurationProperties(prefix = "minio") public class MinioProperties {
private String endpoint;
private Integer port;
private String accessKey;
private String secretKey;
private boolean secure;
private String bucketName;
private long imageSize;
private long fileSize;
@Bean public MinioClient minioClient() { MinioClient minioClient = MinioClient.builder() .credentials(accessKey, secretKey) .endpoint(endpoint,port,secure) .build(); return minioClient; } }
|
Utils
文件类型
import cn.hutool.core.io.FileTypeUtil; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.InputStream;
public class FileTypeUtils {
private final static String IMAGE_TYPE = "image/"; private final static String AUDIO_TYPE = "audio/"; private final static String VIDEO_TYPE = "video/"; private final static String APPLICATION_TYPE = "application/"; private final static String TXT_TYPE = "text/";
public static String getFileType(MultipartFile multipartFile) { InputStream inputStream = null; String type = null; try { inputStream = multipartFile.getInputStream(); type = FileTypeUtil.getType(inputStream); System.out.println(type); if (type.equalsIgnoreCase("JPG") || type.equalsIgnoreCase("JPEG") || type.equalsIgnoreCase("GIF") || type.equalsIgnoreCase("PNG") || type.equalsIgnoreCase("BMP") || type.equalsIgnoreCase("PCX") || type.equalsIgnoreCase("TGA") || type.equalsIgnoreCase("PSD") || type.equalsIgnoreCase("TIFF")) { return IMAGE_TYPE+type; } if (type.equalsIgnoreCase("mp3") || type.equalsIgnoreCase("OGG") || type.equalsIgnoreCase("WAV") || type.equalsIgnoreCase("REAL") || type.equalsIgnoreCase("APE") || type.equalsIgnoreCase("MODULE") || type.equalsIgnoreCase("MIDI") || type.equalsIgnoreCase("VQF") || type.equalsIgnoreCase("CD")) { return AUDIO_TYPE+type; } if (type.equalsIgnoreCase("mp4") || type.equalsIgnoreCase("avi") || type.equalsIgnoreCase("MPEG-1") || type.equalsIgnoreCase("RM") || type.equalsIgnoreCase("ASF") || type.equalsIgnoreCase("WMV") || type.equalsIgnoreCase("qlv") || type.equalsIgnoreCase("MPEG-2") || type.equalsIgnoreCase("MPEG4") || type.equalsIgnoreCase("mov") || type.equalsIgnoreCase("3gp")) { return VIDEO_TYPE+type; } if (type.equalsIgnoreCase("doc") || type.equalsIgnoreCase("docx") || type.equalsIgnoreCase("ppt") || type.equalsIgnoreCase("pptx") || type.equalsIgnoreCase("xls") || type.equalsIgnoreCase("xlsx") || type.equalsIgnoreCase("zip")||type.equalsIgnoreCase("jar")) { return APPLICATION_TYPE+type; } if (type.equalsIgnoreCase("txt")) { return TXT_TYPE+type; } } catch (IOException e) { e.printStackTrace(); } return null; } }
|
操作Minio
import com.java.family.minio.config.MinioProperties; import io.minio.*; import io.minio.http.Method; import io.minio.messages.Bucket; import io.minio.messages.DeleteError; import io.minio.messages.DeleteObject; import io.minio.messages.Item; import lombok.SneakyThrows; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit;
@Component public class MinioUtil {
private final MinioClient minioClient;
private final MinioProperties minioProperties;
public MinioUtil(MinioClient minioClient, MinioProperties minioProperties) { this.minioClient = minioClient; this.minioProperties = minioProperties; }
@SneakyThrows public boolean bucketExists(String bucketName) { boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (found) { System.out.println(bucketName + " exists"); } else { System.out.println(bucketName + " does not exist"); } return found; }
@SneakyThrows public boolean makeBucket(String bucketName) { boolean flag = bucketExists(bucketName); if (!flag) { minioClient.makeBucket( MakeBucketArgs.builder() .bucket(bucketName) .build());
return true; } else { return false; } }
@SneakyThrows public List<String> listBucketNames() { List<Bucket> bucketList = listBuckets(); List<String> bucketListName = new ArrayList<>(); for (Bucket bucket : bucketList) { bucketListName.add(bucket.name()); } return bucketListName; }
@SneakyThrows public List<Bucket> listBuckets() { return minioClient.listBuckets(); }
@SneakyThrows public boolean removeBucket(String bucketName) { boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<Item>> myObjects = listObjects(bucketName); for (Result<Item> result : myObjects) { Item item = result.get(); if (item.size() > 0) { return false; } } minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); flag = bucketExists(bucketName); if (!flag) { return true; } } return false; }
@SneakyThrows public List<String> listObjectNames(String bucketName) { List<String> listObjectNames = new ArrayList<>(); boolean flag = bucketExists(bucketName); if (flag) { Iterable<Result<Item>> myObjects = listObjects(bucketName); for (Result<Item> result : myObjects) { Item item = result.get(); listObjectNames.add(item.objectName()); } }else{ listObjectNames.add("存储桶不存在"); } return listObjectNames; }
@SneakyThrows public Iterable<Result<Item>> listObjects(String bucketName) { boolean flag = bucketExists(bucketName); if (flag) { return minioClient.listObjects( ListObjectsArgs.builder().bucket(bucketName).build()); } return null; }
@SneakyThrows public void putObject(String bucketName, MultipartFile multipartFile, String filename, String fileType) { InputStream inputStream = new ByteArrayInputStream(multipartFile.getBytes()); minioClient.putObject( PutObjectArgs.builder().bucket(bucketName).object(filename).stream( inputStream, -1, minioProperties.getFileSize()) .contentType(fileType) .build()); }
@SneakyThrows public String getObjectUrl(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); String url = ""; if (flag) { url = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(objectName) .expiry(2, TimeUnit.MINUTES) .build()); System.out.println(url); } return url; }
@SneakyThrows public boolean removeObject(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); if (flag) { minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build()); return true; } return false; }
@SneakyThrows public InputStream getObject(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); if (flag) { StatObjectResponse statObject = statObject(bucketName, objectName); if (statObject != null && statObject.size() > 0) { InputStream stream = minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .build()); return stream; } } return null; }
@SneakyThrows public StatObjectResponse statObject(String bucketName, String objectName) { boolean flag = bucketExists(bucketName); if (flag) { StatObjectResponse stat = minioClient.statObject( StatObjectArgs.builder().bucket(bucketName).object(objectName).build()); return stat; } return null; }
@SneakyThrows public boolean removeObject(String bucketName, List<String> objectNames) { boolean flag = bucketExists(bucketName); if (flag) { List<DeleteObject> objects = new LinkedList<>(); for (int i = 0; i < objectNames.size(); i++) { objects.add(new DeleteObject(objectNames.get(i))); } Iterable<Result<DeleteError>> results = minioClient.removeObjects( RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build()); for (Result<DeleteError> result : results) { DeleteError error = result.get(); System.out.println( "Error in deleting object " + error.objectName() + "; " + error.message()); return false; } } return true; }
@SneakyThrows public InputStream getObject(String bucketName, String objectName, long offset, Long length) { boolean flag = bucketExists(bucketName); if (flag) { StatObjectResponse statObject = statObject(bucketName, objectName); if (statObject != null && statObject.size() > 0) { InputStream stream = minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .offset(offset) .length(length) .build()); return stream; } } return null; }
@SneakyThrows public boolean putObject(String bucketName, String objectName, InputStream inputStream,String contentType) { boolean flag = bucketExists(bucketName); if (flag) { minioClient.putObject( PutObjectArgs.builder().bucket(bucketName).object(objectName).stream( inputStream, -1, minioProperties.getFileSize()) .contentType(contentType) .build()); StatObjectResponse statObject = statObject(bucketName, objectName); if (statObject != null && statObject.size() > 0) { return true; } } return false; } }
|
Service
import com.java.family.minio.config.MinioProperties; import com.java.family.minio.service.MinioService; import com.java.family.minio.utils.MinioUtil; import io.minio.MinioClient; import io.minio.messages.Bucket; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream; import java.util.List; import java.util.UUID;
@Service public class MinioServiceImpl implements MinioService {
@Autowired private MinioUtil minioUtil; @Autowired private MinioProperties minioProperties;
@Override public boolean bucketExists(String bucketName) { return minioUtil.bucketExists(bucketName); }
@Override public void makeBucket(String bucketName) { minioUtil.makeBucket(bucketName); }
@Override public List<String> listBucketName() { return minioUtil.listBucketNames(); }
@Override public List<Bucket> listBuckets() { return minioUtil.listBuckets(); }
@Override public boolean removeBucket(String bucketName) { return minioUtil.removeBucket(bucketName); }
@Override public List<String> listObjectNames(String bucketName) { return minioUtil.listObjectNames(bucketName); }
@Override public String putObject(MultipartFile file, String bucketName,String fileType) { try { bucketName = StringUtils.isNotBlank(bucketName) ? bucketName : minioProperties.getBucketName(); if (!this.bucketExists(bucketName)) { this.makeBucket(bucketName); } String fileName = file.getOriginalFilename();
String objectName = UUID.randomUUID().toString().replaceAll("-", "") + fileName.substring(fileName.lastIndexOf(".")); minioUtil.putObject(bucketName, file, objectName,fileType); return minioProperties.getEndpoint() + ":" + minioProperties.getPort() + "/" + bucketName + "/" + objectName; } catch (Exception e) { e.printStackTrace(); return "上传失败"; } }
@Override public InputStream downloadObject(String bucketName, String objectName) { return minioUtil.getObject(bucketName,objectName); }
@Override public boolean removeObject(String bucketName, String objectName) { return minioUtil.removeObject(bucketName, objectName); }
@Override public boolean removeListObject(String bucketName, List<String> objectNameList) { return minioUtil.removeObject(bucketName,objectNameList); }
@Override public String getObjectUrl(String bucketName,String objectName) { return minioUtil.getObjectUrl(bucketName, objectName); } }
|
Controller
import com.java.family.minio.service.MinioService; import com.java.family.minio.utils.FileTypeUtils; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.HashMap; import java.util.List; import java.util.Map;
@RequestMapping("/minio") @RestController public class MinioController {
@Autowired private MinioService minioService;
@PostMapping("/upload") public String uploadFile(MultipartFile file, String bucketName) { String fileType = FileTypeUtils.getFileType(file); if (fileType != null) { return minioService.putObject(file, bucketName, fileType); } return "不支持的文件格式。请确认格式,重新上传!!!"; }
@PostMapping("/addBucket/{bucketName}") public String addBucket(@PathVariable String bucketName) { minioService.makeBucket(bucketName); return "创建成功!!!"; }
@GetMapping("/show/{bucketName}") public List<String> show(@PathVariable String bucketName) { return minioService.listObjectNames(bucketName); }
@GetMapping("/showBucketName") public List<String> showBucketName() { return minioService.listBucketName(); }
@GetMapping("/showListObjectNameAndDownloadUrl/{bucketName}") public Map<String, String> showListObjectNameAndDownloadUrl(@PathVariable String bucketName) { Map<String, String> map = new HashMap<>(); List<String> listObjectNames = minioService.listObjectNames(bucketName); String url = "localhost:8085/minio/download/" + bucketName + "/"; listObjectNames.forEach(System.out::println); for (int i = 0; i <listObjectNames.size() ; i++) { map.put(listObjectNames.get(i),url+listObjectNames.get(i)); } return map; }
@DeleteMapping("/removeBucket/{bucketName}") public String delBucketName(@PathVariable String bucketName) { return minioService.removeBucket(bucketName) == true ? "删除成功" : "删除失败"; }
@DeleteMapping("/removeObject/{bucketName}/{objectName}") public String delObject(@PathVariable("bucketName") String bucketName, @PathVariable("objectName") String objectName) { return minioService.removeObject(bucketName, objectName) == true ? "删除成功" : "删除失败"; }
@DeleteMapping("/removeListObject/{bucketName}") public String delListObject(@PathVariable("bucketName") String bucketName, @RequestBody List<String> objectNameList) { return minioService.removeListObject(bucketName, objectNameList) == true ? "删除成功" : "删除失败"; }
@RequestMapping("/download/{bucketName}/{objectName}") public void download(HttpServletResponse response, @PathVariable("bucketName") String bucketName, @PathVariable("objectName") String objectName) { InputStream in = null; try { in = minioService.downloadObject(bucketName, objectName); response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(objectName, "UTF-8")); response.setCharacterEncoding("UTF-8"); IOUtils.copy(in, response.getOutputStream()); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } }
} }
|
测试
直接调用接口上传一张图片试一下,如下:
接口返回的URL就是文件的访问地址,直接输入浏览器访问即可。
在MInIO中也可以看到存储的文件,如下图:
如果你需要分享给别人,也可以手动分享,有效期是7天,一旦过了这个有效期将会失效
,如下:
总结
MInIO虽然是个开源项目,但是功能非常强大,小型项目中完全可以用它实现对象存储,也可以使用MinIO搭建一个免费的图床。
原文:码猿技术专栏