关于Minio分布式文件存储系统是什么,后期做一个知识分享。另外,Linux系统自己也不是很熟,所以对于用到的一些小白知识也会做一个简单的介绍。
Minio支持Windows、MacOs以及Linux常用系统的部署,为了更加贴合生产环境,这里我以Linux系统为例,介绍一下Minio的部署方式以及如何集成到我们的项目当中。
minio 初探
作为入门,我们先简单搭建一个单机环境,并且在项目中进行使用。
单机服务部署
参照官方文档 docs.minio.org.cn/docs/ ,将minio可执行文件下载下来,放到/opt目录下(版本为20201028,minio的更新非常快)。执行以下命令:
#赋予文件执行权限
chmod +x minio
#启动服务
./minio server /data
官网下载有点慢,可以到国内镜像下载 http://dl.minio.org.cn/
出现下图即说明部署成功,minio提供浏览器端的使用方式,你可以访问http://127.0.0.1:9000,输入accesskey和secretkey(默认是minioadmin,也可以自己配置)登陆minio的浏览器管理界面,来使用minio。9000是minio的默认端口,也可以自己指定端口。
#设置minio accesskey(关于export 命令可以参考[主要用来设置环境变量](https://www.runoob.com/linux/linux-comm-export.html))
export MINIO_ACCESS_KEY=yourAccesskey
#设置minio secretKey
export MINIO_SECRET_KEY=yourSecretKey
#指定端口
./minio server /data --address :9001
浏览器端食用
minio浏览器端使用界面,minio提供的管理功能如图所示,主要包括创建存储桶和文件上传、下载、分享、删除操作。值得一提的是minio的文件分享和百度云的分享类似,可以设置分享链接的有效期(视自己的应用场景使用)。
OK,至此一个单机的minio服务已经成功部署,接下来,让我们在自己的项目中使用minio来进行文件的一些常规操作。
Springboot 食用
为什么使用springboot来介绍minio在项目中的使用呢?因为它方便、快、简单!
我使用的是开发工具是idea,新建springboot项目,在pom文件添加如下依赖(springboot、minio、lombok)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
minio配置类
主要是装载MinioClient对象,这是Java使用minio的客户端对象,所有涉及minio的操作都要借助它来完成。
首先,将minio的相关参数写入到配置文件application.yml
#minio 参数配置(accessKey、secretKey、访问地址、默认存储桶)
minio:
endpoint: http://127.0.0.1:9000
accesskey: minioadmin
secretKey: minioadmin
bucketName: bucket1
Minio配置类如下:
@Configuration
@Data
@AllArgsConstructor
@NoArgsConstructor
public class MinioConfig {
@Value("${minio.endpoint}")
String endpoint;
@Value("${minio.accessKey}")
String accessKey;
@Value("${minio.secretKey}")
String secretKey;
@Value("${minio.bucketName}")
String bucketName;
@Bean
public MinioClient minioClient() {
//新建minioClient,之后的所有minio有关操作都需使用该对象
MinioClient minioClient =
MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
//判断默认存储桶是否存在,若不存在,调用接口进行创建
try {
//调用bucketExists接口,判断存储桶是否已经存在
if (minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()))
return minioClient;
//创建默认的存储桶
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (ErrorResponseException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
return minioClient;
minio工具类
这里只简单介绍一下常用的几个功能,包括存储桶的创建、文件上传、下载等。完整工具类见github。
判断存储桶是否存在和创建存储桶,上面已经使用过了,这里就不再说了。文件上传方法如下,当文件上传成功之后,我们需要记得文件上传到哪个存储桶以及文件的上传路径,这样才可以下载文件。
public static String uploadFile(String bucketName, String fileName, InputStream inputStream) {
//如果bucketName 为空,则使用默认的存储桶
if (StringUtils.isEmpty(bucketName)) {
bucketName = defaultBucketName;
String filePath = null;
try {
// 当文件名重复时,minio默认会覆盖原文件,所以这里为文件名添加一个随机生成的前缀(如果想不被覆盖,可以开启存储桶的版本设置)
fileName= RandomStringUtils.randomNumeric(8)+"/"+fileName;
// 调用minio的上传接口,上传文件,并指定上传以后的文件路径(也就是文件名)
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).
object(fileName).
stream(inputStream, -1, 50 * 1024 * 1024).
build());
filePath=fileName;
} catch (Exception e) {
e.printStackTrace();
//返回文件在存储桶的存储路径
return filePath;
文件下载方法如下,传入文件所在的存储桶和文件的存储路径,即可下载文件。
public static InputStream downLoadFile(String bucketName,String filePath){
//如果bucketName 为空,则使用默认的存储桶
if (StringUtils.isEmpty(bucketName)) {
bucketName = defaultBucketName;
InputStream inputStream=null;
try {
// 调用minio的下载接口,传入要下载文件路径,获取文件流
inputStream=
minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(filePath).build());
}catch (Exception e) {
e.printStackTrace();
return inputStream;
minio 测试使用
新建springboot工程
工程目录结构如下:
pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.crossnote</groupId>
<artifactId>miniodemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>miniodemo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.4.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.0.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.4.RELEASE</version>
<configuration>
<mainClass>com.crossnote.miniodemo.MiniodemoApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
application.yml内容如下:
server:
port: 8081
servlet:
# 服务访问路径
context-path: /minioserver
spring:
application:
name: minio-spring
servlet:
multipart:
#最大上传文件设置
max-file-size: 5120MB
max-request-size: 5120MB
#minio 参数配置(accessKey、secretKey、访问地址、默认存储桶)
minio:
endpoint: http://127.0.0.1:9000
accessKey: minioadmin
secretKey: minioadmin
bucketName: bucket1
编写一个简单的controller,包含文件上传和下载两个接口:
@RestController
public class MinioController {
* 文件上传,返回文件在存储桶中的存储路径
* @param file
* @return 存储路径
@PostMapping("/uploadFile")
public String uploadFile(MultipartFile file) {
String fileName = file.getOriginalFilename();
InputStream inputStream = null;
try {
inputStream = file.getInputStream();
} catch (IOException e) {
e.printStackTrace();
return MinioUtil.uploadFile(null, fileName, inputStream);
* 文件下载接口,返回文件流
* @param filePath
* @param response
@GetMapping("/downloadFile")
public void downLoadFile(String filePath, HttpServletResponse response) {
InputStream fileInputStream = MinioUtil.downLoadFile(null, filePath);
OutputStream outputStream = null;
try {
outputStream = response.getOutputStream();
IOUtils.copyLarge(fileInputStream, outputStream);
} catch (IOException e) {
e.printStackTrace();
记录一下写MinioUtil类遇到的知识点:
一开始,这个工具类我是这么写的,看着没啥问题,MinioUtil是个工具类,方法都用static描述,直接在Controller中调用,但是调用上传接口时报 59行 bucketName为null。 why?检查一下@Value注入参数确实是存在的,想起来static对象和普通对象初始化的顺序不同(不清楚底层是不是这个原因),参照这个进行改正
public class MinioUtil {
//注入minioClient对象
@Autowired
private static MinioClient minioClient;
@Value("${minio.bucketName}")
private static String defaultBucketName;
* 判断存储桶是否存在
* @param bucketName 存储桶名称
* @return
public static boolean isBucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
* 创建存储桶
* @param bucketName 存储桶名称
* @return
public static boolean createBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
return true;
* 上传文件,并返回文件的存储路径
* @param bucketName 存储桶名称
* @param fileName 文件名
* @param inputStream 文件流
* @return
public static String uploadFile(String bucketName, String fileName, InputStream inputStream) {
//如果bucketName 为空,则使用默认的存储桶
if (StringUtils.isEmpty(bucketName)) {
bucketName = defaultBucketName;
String filePath = null;
try {
// 当文件名重复时,minio默认会覆盖原文件,所以这里为文件名添加一个随机生成的前缀(如果想不被覆盖,可以开启存储桶的版本设置)
fileName = RandomStringUtils.randomNumeric(8) + "/" + fileName;
// 调用minio的上传接口,上传文件,并指定上传以后的文件路径(也就是文件名)
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).
object(fileName).
stream(inputStream, -1, 50 * 1024 * 1024).
build());
filePath = fileName;
} catch (Exception e) {
e.printStackTrace();
//返回文件在存储桶的存储路径
return filePath;
* 下载文件,返回文件流
* @param bucketName 存储桶名称
* @param filePath 文件路径(对minio来说,就是文件名)
* @return
public static InputStream downLoadFile(String bucketName, String filePath) {
//如果bucketName 为空,则使用默认的存储桶
if (StringUtils.isEmpty(bucketName)) {
bucketName = defaultBucketName;
InputStream inputStream = null;
try {
// 调用minio的下载接口,传入要下载文件路径,获取文件流
inputStream =
minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(filePath).build());
} catch (Exception e) {
e.printStackTrace();
return inputStream;
改正后的写法如下:
@Component
public class MinioUtil {
private static MinioClient minioClient;
private static String defaultBucketName;
* https://blog.csdn.net/mononoke111/article/details/81088472
* 若要给静态变量赋值,可以使用set()方法,其中需要在类上加入@Component注解
@Value("${minio.bucketName}")
public void setDefaultBucketName(String defaultBucketName) {
MinioUtil.defaultBucketName = defaultBucketName;
@Autowired
public void setMinioClient(MinioClient minioClient) {
MinioUtil.minioClient = minioClient;
* 判断存储桶是否存在
* @param bucketName 存储桶名称
* @return
public static boolean isBucketExists(String bucketName) {
try {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
* 创建存储桶
* @param bucketName 存储桶名称
* @return
public static boolean createBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
} catch (Exception e) {
e.printStackTrace();
return false;
return true;
* 上传文件,并返回文件的存储路径
* @param bucketName 存储桶名称
* @param fileName 文件名
* @param inputStream 文件流
* @return
public static String uploadFile(String bucketName, String fileName, InputStream inputStream) {
//如果bucketName 为空,则使用默认的存储桶
if (StringUtils.isEmpty(bucketName)) {
bucketName = defaultBucketName;
String filePath = null;
try {
// 当文件名重复时,minio默认会覆盖原文件,所以这里为文件名添加一个随机生成的前缀(如果想不被覆盖,可以开启存储桶的版本设置)
fileName = RandomStringUtils.randomNumeric(8) + "/" + fileName;
// 调用minio的上传接口,上传文件,并指定上传以后的文件路径(也就是文件名)
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).
object(fileName).
stream(inputStream, -1, 50 * 1024 * 1024).
build());
filePath = fileName;
} catch (Exception e) {
e.printStackTrace();
//返回文件在存储桶的存储路径
return filePath;
* 下载文件,返回文件流
* @param bucketName 存储桶名称
* @param filePath 文件路径(对minio来说,就是文件名)
* @return
public static InputStream downLoadFile(String bucketName, String filePath) {
//如果bucketName 为空,则使用默认的存储桶
if (StringUtils.isEmpty(bucketName)) {
bucketName = defaultBucketName;
InputStream inputStream = null;
try {
// 调用minio的下载接口,传入要下载文件路径,获取文件流
inputStream =
minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(filePath).build());
} catch (Exception e) {
e.printStackTrace();
return inputStream;
开启服务之后,调用上传接口,返回文件的存储路径:
使用上传借口返回的上传路径,下载文件(直接从浏览器打开):
http://localhost:8081/minioserver/downloadFile?filePath=15089295/SpringBoot 从入门到进阶系列官方小册.pdf
注意事项:
与Minio服务器连接的机器,和minio服务器时间相差不能超过15分钟,否则会报如下错误:
Error occurred: error occurred
ErrorResponse(code=AccessDenied, message=Access denied, bucketName=f3e0a479-60da-45c8-9ac2-5b744a92d419, objectName=null, resource=/f3e0a479-60da-45c8-9ac2-5b744a92d419, requestId=15E37CD9D06980DF, hostId=null)
request={method=HEAD, url=http://10.10.10.1:9000/f3e0a479-60da-45c8-9ac2-5b744a92d419, headers=Host: 125.220.157.228:83
User-Agent: MinIO (amd64; amd64) minio-java/dev
x-amz-date: 20191225T023929Z
response={code=403, headers=Server: nginx/1.13.6
Date: Wed, 25 Dec 2019 02:39:29 GMT
Content-Length: 0
Connection: keep-alive
Accept-Ranges: bytes
Content-Security-Policy: block-all-mixed-content
Vary: Origin
X-Amz-Request-Id: 15E37CD9D06980DF
X-Xss-Protection: 1; mode=block
复制代码