0、前言
现在网上很多关于MinIO的教程都是过时的,本文以最新版本MinIO(8.2+release)为例,教大家如何快速打造个人云盘,并详细介绍最新版本MinIO-API使用方法。
1、MinIO简介
①速度快、使用广:MinIO是全球领先的对象存储先锋,有数百万的用户。在标准硬件上读/写速度上高达183GB/秒和171GB/秒。阿里巴巴、腾讯、百度、华为、中国移动等9000多家企业都在使用MinIO产品。
②免费、简单:MinIO基于Apache V2 license 100%开放源代码。用户可以自由使用和集成MinIO、在此基础上发布新的版本和软件。极简主义是MinIO的指导性设计原则,几分钟内即可安装和配置使用。
③就我体验下来:这是一个极其简洁好用的分布式存储工具,非常适合打造个人/企业级云盘、私有云、企业主存储层和海量存储基础架构。
2、MinIO部署
①下载:MinIO下载文件为minio.exe,下面是官网下载路径和中国加速镜像下载路径。需根据自身操作系统来选择下载文件,本文以Windows-64位操作系统为例。
http://dl.minio.org.cn/server/minio/release
②部署启动:我们以下载目录E:\javaworkspace\minIO\dev为例,将minio.exe拷贝至该目录下,WIIN+R键打开运行界面,以管理员身份运行CMD。
(1)启动分为三步:进入下载目录>>设置临时登录账号和密码>>启动服务,详细过程如图所示:显示以下界面则启动成功!
(2)说明:
设置临时登录账号命令:set MINIO_ROOT_USER=admin |
设置临时登录密码命令:set MINIO_ROOT_PASSWORD=12345678 |
本地启动服务命令:minio.exe server E:\javaworkspace\minIO\dev --address "127.0.0.1:9000" |
因为我们是本地启动所以IP设置为127.0.0.1,端口默认为9000;
如果不设置临时账号密码则系统默认账号密码是 minioadmin / minioadmin;
也可将账号密码添加到环境变量,变量名MINIO_ROOT_USER和MINIO_ROOT_PASSWORD,变量值就是你的账号和密码,这样以后就可以直接登录了(不推荐)。
Tips:我们也可以在下载目录创建一个本地启动minio.bat,方便我们快速启动,bat代码如下:
set MINIO_ROOT_USER=admin
set MINIO_ROOT_PASSWORD=12345678
minio.exe server E:\javaworkspace\minIO\dev --address "127.0.0.1:9000"
③登录Minio并创建目录(Bucket):并在目录中完成文件上传下载删除等基本操作。
我们输入http://127.0.0.1:9000登录系统,如下图所示:
登陆后,我们创建一个Bucket,Bucket是MinIO的特有名称,我们理解为目录或者顶层文件夹。
我们创建了my-bucketname、mydisc、mydisc1、mydisc2目录。
我们进入mydisc目录,尝试上传、下载、删除文件。
到此为止,一个简单的个人云盘就制作完成了,可以说是相当的方便快捷。
Tips:针对企业级云盘和私有云
(1)目录(Bucket)下可以自由创建文件夹、文件,形成目录>>文件夹>>文件的结构。
(2)目录可以添加Event事件,包括PUT/GET/DELETE;可以设置隐私Private/Public;可以设置是否加密Encryption;可以设置用户权限只读/可写等。
(3)ADMIN用户可以创建Users和Groups,通过用户和分组进行权限管控。
(4)Configuration中可以设置地区(时区)、缓存、地址端口等信息。
(5)TOOLS中可以设置日志、监控、S3云服务及诊断信息。
3、Springboot整合Minio-API实现查询、上传、下载、删除基本功能
MinIO SDKS支持多种语言开发,包括Java、Go、Python、JavaScript、.NET、Haskell等。我们选用Java来进行Minio-API的开发。
①项目架构
JDK11+
Springboot2.5+
②Maven项目依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.2.2</version>
</dependency>
③程序实现:创建两个Service服务类用于API服务;创建两个Controller接口类用于测试,项目架构如下图:
BucketService是目录(Bucket)查询、创建、删除的API服务。 |
BucketController用于BucketService的测试实现。 |
FileService是目录内文件(Object)查询、上传、下载、删除的API服务。 |
FileController用于FileService的测试实现。 |
所有结果我们均在后台输出,前台网页仅用于测试输出。
针对目录(Bucket)的操作:
我们梳理一下目录(Bucket)查询、创建、删除、标签等功能所用到的API服务:
功能 | 实现方法 | 实现过程 |
查询目录列表信息 | listBucket() | 可循环输出所有目录名称(创建时间等) |
创建一个目录 | makeBucket() | 首先通过bucketExists查询目录是否存在,不存在则创建一个 |
删除一个目录 | removeBucket() | 首先通过bucketExists查询目录是否存在,其次通过listObjects判断目录内是否有文件,如有文件则不能删除 |
为目录设置标签 | setBucketTags() | 首先通过bucketExists查询目录是否存在,存在则为目录设置单(多)标签 |
(1)BucketService代码如下:
package com.example.minio.FileService;
import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class BucketService {
private Logger logger= LoggerFactory.getLogger(this.getClass());
//查询bucket列表信息
public void listBucket() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try {
MinioClient minioClient= MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin","12345678")
.build();
//输出bucket列表,包含创建日期和名称
List<Bucket> listBuckets=minioClient.listBuckets();
for (Bucket bucket:listBuckets){
logger.info(bucket.creationDate()+","+bucket.name());
}
}catch (MinioException ex){
logger.info("Error occurred:"+ ex);
logger.info("HTTP trace:"+ ex.httpTrace());
}
}
//创建一个bucket
public void makeBucket(String bucketname)throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try {
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient = MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin", "12345678")
.build();
//判断bucket是否存在
boolean found = minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
//没有即自动创建一个bucket
if (!found) {
minioClient.makeBucket(
MakeBucketArgs.builder()
.bucket(bucketname)
.build());
logger.info("Bucket:" + bucketname + " created successfully");
} else {
logger.info("Bucket:" + bucketname + " already exists");
}
} catch (MinioException ex) {
logger.info("Error occurred:" + ex);
logger.info("HTTP trace:" + ex.httpTrace());
}
}
//删除一个bucket
public void removeBucket(String bucketname)throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try {
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient = MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin", "12345678")
.build();
//判断bucket是否存在
boolean found = minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
//有即删除bucket
if (found) {
//判断bucket是否有文件,有则不能删除
Iterable<Result<Item>> results=minioClient.listObjects(
ListObjectsArgs.builder()
.bucket("my-bucketname")
.maxKeys(10)
.build());
if(results.iterator().hasNext()){
logger.info("Bucket:" + bucketname + " you tried to delete is not empty");
}else{
minioClient.removeBucket(
RemoveBucketArgs
.builder()
.bucket(bucketname)
.build());
logger.info("Bucket:" + bucketname + " removed successfully");
}
} else {
logger.info("Bucket:" + bucketname + " not exists");
}
} catch (MinioException ex) {
logger.info("Error occurred:" + ex);
logger.info("HTTP trace:" + ex.httpTrace());
}
}
//为bucket设置Tags标签
public void setBucketTags(String bucketname)throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try {
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient = MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin", "12345678")
.build();
//判断bucket是否存在
boolean found = minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
//有即为bucket设置Tags标签
if (found) {
//定义多标签
Map<String,String> tagsmap= new HashMap<>();
tagsmap.put("Project"," Project One");
tagsmap.put("Belong to","Lu Lu");
//设置多标签
minioClient.setBucketTags(
SetBucketTagsArgs.builder()
.bucket(bucketname)
.tags(tagsmap)
.build());
logger.info("Bucket:" + bucketname + " set Tags successfully");
} else {
logger.info("Bucket:" + bucketname + " not exists");
}
} catch (MinioException ex) {
logger.info("Error occurred:" + ex);
logger.info("HTTP trace:" + ex.httpTrace());
}
}
}
(2)BucketController代码如下:
package com.example.minio.FileController;
import com.example.minio.FileService.BucketService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@RestController
public class BucketController {
@Autowired
BucketService bucketService;
@GetMapping("/listbucket")
public String listbucket() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
bucketService.listBucket();
return "查询bucket列表信息!";
}
@GetMapping("/makebucket")
public String makebucket() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
bucketService.makeBucket("my-bucketname");
return "创建bucket!";
}
@GetMapping("/removebucket")
public String removebucket() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
bucketService.removeBucket("my-bucketname");
return "删除bucket!";
}
@GetMapping("/setbuckettags")
public String setbuckettags() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
bucketService.setBucketTags("my-bucketname");
return "设置bucket标签!";
}
}
(3)启动项目,在浏览器输入http://localhost:8080/listbucket,查询输出目录列表。结果如下:
我们在前台删除my-bucketname目录,输入http://localhost:8080/makebucket通过API创建一个my-bucketname目录,再输入http://localhost:8080/removebucket再次通过API删除my-bucketname目录,结果如下:
我们再次创建my-bucketname目录,并在目录中添加部分文件,再执行删除目录操作,删除失败,结果如下:
我们输入http://localhost:8080/setbuckettags,可为目录设置多种类型标签。
针对目录内文件(Object)的操作:
我们梳理一下目录内文件(Object)查询、上传、下载、删除、批量删除等功能所用到的API服务:
功能 | 实现方法 | 实现过程 |
查询目录中文件列表信息 | listObjects() | 首选判断目录是否存在,存在则循环输出该目录下所有文件名称(文件大小等) |
文件上传 | uploadObject() | 首选判断目录是否存在,存在则上传相应的文件 |
文件下载 | downloadObject() | 首选判断目录是否存在,其次判断目录下该文件是否存在,存在则下载到项目根目录 |
文件删除 | removeObject() | 首选判断目录是否存在,其次判断目录下该文件是否存在,存在则删除对应文件 |
文件批量删除 | removeObjects() | 首选判断目录是否存在,其次判断目录下该文件集是否存在,存在则删除对应文件集 |
文件下载(流文件形式) | getObject() | 通过InputStream流文件类型获取文件,使用后需要关闭释放网络资源【此类型本文不做介绍,可以参考官方文档】 |
(1)FileService代码如下:
package com.example.minio.FileService;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
@Component
public class FileService {
private Logger logger= LoggerFactory.getLogger(this.getClass());
//查询bucket中文件列表信息
public void listObjects(String bucketname) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try{
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient= MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin","12345678")
.build();
//判断bucket是否存在
boolean found= minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
if(!found){
logger.info("Bucket:" + bucketname + " not exists");
}else {
//获取bucket中文件列表信息
Iterable<Result<Item>> results=minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucketname)
.maxKeys(100)//查询最大数量,默认1000
//.includeVersions(true)//是否带版本号,如果为true,且文件没有版本号则会报错
//.prefix("m")//查询文件名以"m"开头的文件
.build());
int i=0;
for (Result<Item> result:results){
i++;
Item item=result.get();
logger.info("object"+i+":"+item.objectName()+" size:"+item.size()/1024+"KB");
}
}
}catch (MinioException ex){
logger.info("Error occurred:"+ ex);
logger.info("HTTP trace:"+ ex.httpTrace());
}
}
//上传服务
public void uploadObject(String bucketname) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try{
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient= MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin","12345678")
.build();
//判断bucket是否存在
boolean found= minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
//没有即自动创建一个bucket
if(!found){
minioClient.makeBucket(
MakeBucketArgs.builder()
.bucket(bucketname)
.build());
}else {
logger.info("Bucket:"+bucketname+" already exists");
}
//Object上传服务
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket(bucketname)
.object("mailservice.log")//设置上传文件存放的名字
.filename("D:\\temp\\log\\mailservice.log")//设置上传路径及文件名
.build());
logger.info("D:\\temp\\log\\mailservice.log is successfully uploaded as mailservice.log to bucket: "+bucketname);
}catch (MinioException ex){
logger.info("Error occurred:"+ ex);
logger.info("HTTP trace:"+ ex.httpTrace());
}
}
//下载服务
public void downloadObject(String bucketname,String objectname) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try{
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient= MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin","12345678")
.build();
//判断bucket是否存在
boolean found= minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
if(!found){
logger.info("Bucket:" + bucketname + " not exists");
}else {
//Object下载服务,判断Object文件是否存在
Iterable<Result<Item>> results=minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucketname)
.build());
boolean objectfound =false;
for (Result<Item> result:results){
Item item=result.get();
if (Objects.equals(item.objectName(), objectname)){
objectfound =true;
break;
}
}
//当Object文件存在时,则下载到项目根目录
if(objectfound){
minioClient.downloadObject(
DownloadObjectArgs.builder()
.bucket(bucketname)
.object(objectname)
.filename(objectname)
.build());//下载到当前项目目录
logger.info("objectname:" + objectname + " is downloaded in local project folder");
}else {
logger.info("objectname:" + objectname + " does not exist");
}
}
}catch (MinioException ex){
logger.info("Error occurred:"+ ex);
logger.info("HTTP trace:"+ ex.httpTrace());
}
}
//删除服务
public void removeObject(String bucketname,String objectname) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try{
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient= MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin","12345678")
.build();
//判断bucket是否存在
boolean found= minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
if(!found){
logger.info("Bucket:" + bucketname + " not exists");
}else {
//Object删除服务,判断需要删除的Object文件是否存在
Iterable<Result<Item>> results=minioClient.listObjects(
ListObjectsArgs.builder()
.bucket(bucketname)
.build());
boolean objectfound =false;
for (Result<Item> result:results){
Item item=result.get();
if (Objects.equals(item.objectName(), objectname)){
objectfound =true;
break;
}
}
//当Object文件存在时,则删除该文件
if(objectfound){
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketname)
.object(objectname)
.build());
logger.info("objectname:" + objectname + " removed successfully");
}else {
logger.info("objectname:" + objectname + " does not exist");
}
}
}catch (MinioException ex){
logger.info("Error occurred:"+ ex);
logger.info("HTTP trace:"+ ex.httpTrace());
}
}
//批量删除服务
public void removeObjects(String bucketname,String[] objectnames) throws IOException, NoSuchAlgorithmException, InvalidKeyException {
try{
//使用MinIO服务器、其访问密钥和密钥创建minioClient
MinioClient minioClient= MinioClient.builder()
//MinIO服务器访问网址端口
.endpoint("http://127.0.0.1:9000")
//MinIO服务器登录账号和密码
.credentials("admin","12345678")
.build();
//判断bucket是否存在
boolean found= minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketname)
.build());
if(!found){
logger.info("Bucket:" + bucketname + " not exists");
}else {
//存入需要删除的文件名集合
List<DeleteObject> objects=new LinkedList<>();
for(String s:objectnames){
objects.add(new DeleteObject(s));
}
//Object批量删除服务
Iterable<Result<DeleteError>> results=minioClient.removeObjects(
RemoveObjectsArgs.builder()
.bucket(bucketname)
.objects(objects)
.build());
if(results.iterator().hasNext()){
for(Result<DeleteError> result:results){
DeleteError error=result.get();
logger.info("Error in deleting object " + error.objectName() + "; " + error.message());
}
}else{
logger.info("objectnames:" + Arrays.toString(objectnames) + " removed successfully");
}
}
}catch (MinioException ex){
logger.info("Error occurred:"+ ex);
logger.info("HTTP trace:"+ ex.httpTrace());
}
}
}
(2)FileController代码如下:
package com.example.minio.FileController;
import com.example.minio.FileService.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
@RestController
public class FileController {
@Autowired
FileService fileService;
@GetMapping("/listobjects")
public String listobjects() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
fileService.listObjects("mydisc");
return "查询bucket中文件列表信息!";
}
@GetMapping("/uploadobject")
public String uploadobject() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
fileService.uploadObject("mydisc");
return "上传完毕!";
}
@GetMapping("/downloadobject")
public String downloadobject() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
fileService.downloadObject("mydisc","mailservice.log");
return "下载完毕!";
}
@GetMapping("/removeobject")
public String removeobject() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
fileService.removeObject("mydisc","mailservice.log");
return "删除完毕!";
}
@GetMapping("/removeobjects")
public String removeobjects() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
String[] objectnames=new String[]{"mailservice.log","minio-4.jpg","minio-5.jpg"};
fileService.removeObjects("mydisc",objectnames);
return "批量删除完毕!";
}
}
(3)启动项目,在浏览器输入http://localhost:8080/listobjects,查询输出目录下文件列表。结果如下:mydisc目录下有5个文件,可看到名称和文件大小。
输入http://localhost:8080/uploadobject,通过API往mydisc目录中上传一个文件,注意,文件名称相同会被覆盖。
输入http://localhost:8080/downloadobject,通过API将文件下载到项目根目录。
输入http://localhost:8080/removeobject,通过API删除文件,如文件不存在则报相应错误。
输入http://localhost:8080/removeobjects,通过API批量删除文件集,结果如下:
④综上:我们通过Springboot整合Minio-API实现了目录查询、创建、删除和文件查询、上传、下载、删除等基本功能。
通过API接口可以与WEB前端、第三方软件、MES系统进行交互,还可自行开发网盘前端界面。
除此之外,MinIO官方还提供了非常丰富的API接口可供选择。
Bucket operations有28种,包括目录权限、版本、加解密设置等方法。
Object operations有21种,包括流文件形式下载、文件复制拷贝、文件标签设置等方法。
具体API开发指南,请参考https://docs.min.io/docs/java-client-api-reference.html,本文就不再讲述了。
4、总结
①全新MinIO体验下来,部署非常快捷,配置简单,前台操作方便,上传下载速度极快,非常适合打造个人/企业级云盘。
②Minio-API提供查询、上传、下载、删除等各种方法,代码规范,易于使用。官方提供了详细的开发指南,而且支持多种语言开发,在开源项目中是非常用心的了。
③本文提供了最新版本Minio-API目录查询、创建、删除和文件查询、上传、下载、删除等基本操作的源代码,经测试无误,可以直接在项目中使用。其他功能可根据文档进行开发。