MinIO 是一个基于 Apache License v2.0 开源协议使用 Go 语言开发的对象存储服务。它兼容亚马逊 S3 云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几 kb 到最大 5T 不等。MinIO 是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
MinIO 包含 MinIO Server, MinIO Client 以及方便开发基于不同编程语言使用的 MinIO SDK,这三部分组成,使用步骤也很简单,在服务器上安装 MinIO Server 应用,在项目中集成对应的 MinIO SDK,然后按照你的业务情况编写相应的实现即可,在开始前,我们先看看为什么我选择 MiniIO 作为自建的 OSS 服务
- MinIO 由良好的存储机制
- 兼容 Amason 的 S3 分布式存储
- 天然的支持云原生
- 支持私有部署,可分布式,可单机,100%开源
- 友好简单的部署方式,提供管理页面
- 还可以配合其他的健康管理工具进行监控,比如 Prometheus
安装
由于 MinIO Server 已经提供了 Docker 的安装镜像,那我们就以 Docker 安装为例,其他安装方式可参考官方教程 MinIO Quickstart Guide
关于 Docker 的安装这里不再赘述,Docker 相关详细的使用等知识,可参考我之前的文章 Docker(一)
1 2 3 4 5 6 7 8
| docker pull minio/minio
docker run -p 9000:9000 --name minio \ -v /opt/docker/minio/data:/data \ -v /opt/docker/minio/config:/root/.minio \ -d --restart=always \ -d minio/minio server /data
|
这里简单说一下命令的含义,应用命名为 minio ,运行服务在 9000 端口,同时将容器的相关路径文件映射到宿主机的 /opt/docker/minio
路径,开机自启
成功运行服务,可查看日志
1 2 3 4 5 6 7 8 9 10
| Endpoint: http://172.17.0.2:9000 http://127.0.0.1:9000 Browser Access: http://172.17.0.2:9000 http://127.0.0.1:9000 Object API (Amazon S3 compatible): Go: https://docs.min.io/docs/golang-client-quickstart-guide Java: https://docs.min.io/docs/java-client-quickstart-guide Python: https://docs.min.io/docs/python-client-quickstart-guide JavaScript: https://docs.min.io/docs/javascript-client-quickstart-guide .NET: https://docs.min.io/docs/dotnet-client-quickstart-guide Detected default credentials 'minioadmin:minioadmin', please change the credentials immediately using 'MINIO_ROOT_USER' and 'MINIO_ROOT_PASSWORD'
|
安装完成后,我们就可以通过 http://localhost:9000 访问 MinIO 服务,默认用户名和密码分别为: minioadmin, minioadmin
使用
页面操作
我们直接看图,输入账号密码后,可以看到 MinIO 的管理页面,我们就可以上传文件,是不是很方便。第一次上传必须先要创建一个 bucket 后,才可以上传,如下图操作结果
Client 操作
SDK 操作
这里以 Java 语言为例,查看官方文档时,一定要查看英文文档,中文文档已年久失修落后很多,其他的语言实现请参考官方文档
导入依赖
1 2 3
| dependencies { implementation "io.minio:minio:8.1.0" }
|
功能实现
由于我这里是 SpringBoot 项目,为了方便在应用的 application.yml
文件中配置了 MinIO 相关的参数
配置文件
1 2 3 4 5 6 7 8 9 10 11
| minio: endpoint: http://127.0.0.1 port: 9000 accessKey: minioadmin secretKey: minioadmin bucketName: cpe-manager-test
|
工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
@Slf4j @Component public class MinioUtils {
@Value("${minio.endpoint}") private static final String ENDPOINT = "http://192.168.1.163"; @Value("${minio.port}") private static final Integer PORT = 19000; @Value("${minio.accessKey}") private static final String ACCESS_KEY = "minioadmin"; @Value("${minio.secretKey}") private static final String SECRET_KEY = "minioadmin"; @Value("${minio.bucketName}") private static final String BUCKET_NAME = "cpe-manager-test";
private static MinioClient minioClient;
public static MinioClient getInstance() { if (minioClient == null) { minioClient = MinioClient.builder().endpoint(ENDPOINT, PORT, false).credentials(ACCESS_KEY, SECRET_KEY).build(); } return minioClient; }
public static List<Bucket> getAllBucket() throws Exception { List<Bucket> buckets = getInstance().listBuckets(); for (Bucket bucket : buckets) { log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate()); } return buckets; }
public static String uploadToMinio(InputStream inputStream, String objectName, String bucketName) { try { String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf(".")); String contentType = FileType.getContentType(fileSuffix);
long size = inputStream.available(); PutObjectArgs putObjectArgs = PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(inputStream, size, -1) .contentType(contentType) .build(); ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs); inputStream.close(); if (!StringUtils.isEmpty(objectWriteResponse.etag())) { return getUrlByObjectName(objectName); } } catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); } return null; }
public static String uploadToMinio(InputStream inputStream, String objectName) { try { String fileSuffix = Objects.requireNonNull(objectName).substring(objectName.lastIndexOf(".")); String contentType = FileType.getContentType(fileSuffix);
long size = inputStream.available(); PutObjectArgs putObjectArgs = PutObjectArgs.builder() .bucket(BUCKET_NAME) .object(objectName) .stream(inputStream, size, -1) .contentType(contentType) .build(); ObjectWriteResponse objectWriteResponse = getInstance().putObject(putObjectArgs); inputStream.close(); if (!StringUtils.isEmpty(objectWriteResponse.etag())) { return getUrlByObjectName(objectName); } } catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); } return null; }
public static String getUrlByObjectName(String objectName) { try { return getInstance().getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(BUCKET_NAME) .object(objectName)
.build()); } catch (Exception e) { log.error(e.getMessage()); e.printStackTrace(); } return null; }
public static void downloadFromMinioToFile(String objectName, String fileName, String dir) throws Exception { GetObjectArgs objectArgs = GetObjectArgs.builder() .bucket(BUCKET_NAME) .object(objectName) .build(); File file = new File(dir); if (!file.exists()) { if (file.mkdirs()) { log.error("创建失败"); } } InputStream inputStream = getInstance().getObject(objectArgs); FileOutputStream outputStream = new FileOutputStream(new File(dir, fileName.substring(fileName.lastIndexOf("/") + 1))); int length; byte[] buffer = new byte[1024]; while ((length = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, length); } outputStream.close(); inputStream.close(); }
@SneakyThrows public static Map<String, String> removeObjects(List<String> listFile) { List<DeleteObject> objects = new LinkedList<>(); Map<String, String> resultMap = new HashMap<>(); listFile.forEach(t -> objects.add(new DeleteObject(t))); Iterable<Result<DeleteError>> results = getInstance().removeObjects( RemoveObjectsArgs.builder() .bucket(BUCKET_NAME) .objects(objects) .build()); for (Result<DeleteError> result : results) { DeleteError error = result.get(); resultMap.put(error.objectName(), error.message()); log.error("Error in deleting:{}, message{}", error.objectName(), error.message()); } return resultMap; }
}
|
上传接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @ApiOperation(value = "文件上传", notes = "支持多文件上传") public List<String> uploadTest(@ApiParam(value = "文件") @RequestParam("file") List<MultipartFile> file) { List<String> successFile = new ArrayList<>(file.size()); file.forEach(t -> { try { String url = MinioUtils.uploadToMinio(t.getInputStream(), t.getOriginalFilename()); log.info("图片地址{}", url); successFile.add(url); } catch (IOException e) { e.printStackTrace(); } }); return successFile; }
|
测试
问题
bucket命名
创建 bucket 时,命名不可以使用下划线符号 “_”
账号密码修改
通过网页管理页面修改登录账号及密码,提示 “Credentials of this user cannot be updated through MinIO Browser.” ,原因是安装应用时,并未显示的指定用户名和密码,可在运行启动时添加如下配置
1 2
| -e "MINIO_ROOT_USER=admin" \ -e "MINIO_ROOT_PASSWORD=admin123456" \
|
最长7天有效
通过网页管理页面共享图片或者是使用 SDK 上传图片得到的图片 URL 地址,有效期最长为7天
1 2 3 4 5 6
| mc config host add minio http://192.168.1.163:19000 minioadmin minioadmin --api S3v4 mc policy set public minio/cpe-manager-test
mc config host add minio http://127.0.0.1:9000 minioadmin minioadmin --api S3v4 mc policy set public minio/bucket (bucket修改成你自己的名字)
|
图片无法查看
- 使用 SDK 上传时,需要注意设置content-type信息
- 无权限查看
小结
关于 MinIO 还有很多知识点,本片只是站在使用者角度,把一些使用过程和问题进行了汇总,谈不上深度
参考
- Minio 手册
- Minio 示例
- Minio 修改密码
- Minio
- Minio 安装以及使用
- Minio 设置文件链接永久有效