文章目录
1. 前置知识
1.1 文件的存储方式
在开发一个 Web 应用时,我们有多种方式存储图片、视频、办公文档等文件,以下是几种主要的存储方式
存储方式 | 优点 | 缺点 |
---|---|---|
服务器磁盘 | 开发便捷,成本低 | 扩展困难 |
分布式文件系统 | 实现扩容非常方便 | 复杂度高 |
第三方存储 | 开发简单,功能强大,免维护 | 收费 |
-
存储在服务器的磁盘上:实现起来非常简单,但是如果文件越来越多,或者在特定的业务场景下单个文件的内容就很大,就算你的硬盘有 2TB 的空间,服务器的磁盘空间迟早会被占满,而且将文件存储在服务器的硬盘,后期扩展将变得十分麻烦
-
存储在分布式文件系统中:实现扩容非常方便,但相对于直接存储在服务器硬盘上来说,实现也更加复杂
-
第三方存储:国内比较优秀的第三方存储服务有阿里云的对象存储服务(对象存储 OSS_云存储服务_企业数据管理_存储-阿里云 (aliyun.com)),缺点当然就是收费
OSS,Object Storage Service,对象存储服务
1.2 主流的分布式文件系统
存储方式 | 优点 | 缺点 |
---|---|---|
FastDFS(国产开源) | 1. 主备服务,高可用 2.支持主从文件,支持自定义扩展名 3.支持动态扩容 | 1. 没有完备官方文档,近几年没有更新 2. 环境搭建较为麻烦 |
MinIO | 1. 性能高,标准硬件条件下 MinIO 能达到55GB/s的读、35GB/s的写速率 2. 部署自带管理界面 3. MinI0.Inc运营的开源项目,社区活跃度高 4. 提供了所有主流开发语言的SDK | 不支持动态增加节点 |
对比下来,MinIO 更适合在项目开发中使用
2. 什么是MinIO
MinlO 是基于 Apache License v2.0 开源协议的的对象存储服务,可以做为云存储的解决方案,保存海量的图片,视频文档
MinIO 的优点:
- GoLang 语言实现,配置简单,单行命令可以运行起来
- MinIO 兼容亚马逊 S3 云存储服务接口,适合于存储大容量非结构化的数据,一个对象文件可以是任意大小,从几 KB 到最大的 5TB
MinIO 的官网文档:MinIO对象存储 Kubernetes(提供了中文开发文档)
3. 安装MinIO
我们通过 docker 来安装 MinIO
3.1 搜索MinIO镜像
sudo docker search minio
我们选择 STARS 数最高的
3.2 下载MinIO镜像
通过以下指令下载 MinIO 镜像
sudo docker pull minio/minio
3.3 将MinIO镜像保存到本地(可跳过)
下载好 MinIO 的镜像后,可以将镜像保存为 tar 文件,下载到 Windows 本地,方便下一次在另一个 Linux 系统上运行
sudo docker save minio/minio:latest -o /tmp/minio-latest.tar
sudo chmod +rx /tmp/minio-latest.tar
保存的 tar 文件,命名中不要有英文的冒号
:
,因为 Windows 环境下文件的命名是不允许出现英文冒号的,如果文件名中有英文冒号,Windows 系统会自动截掉英文冒号以及英文冒号后面的部分
3.4 创建MinIO容器并启动MinIO
- 旧版的 MinIO 可能控制台端口和访问端口都是同一个
- 最新版本的 MinIO 已经将控制台端口和访问端口分离
运行以下指令创建 MinIO 容器并启动 MinIO
sudo docker run \
-p 9000:9000 \
-p 9090:9090 \
--name minio \
--restart=always \
-e "MINIO_ACCESS_KEY=wuyanzu" \
-e "MINIO_SECRET_KEY=bo@DwF1mzr_wF7am" \
-v /home/data:/data \
-v /home/config:/root/.minio \
-d \
minio/minio server /data --address ":9000" --console-address ":9090"
指令的详细解释:
sudo docker run
: 这是 Docker 命令,用于启动一个新的容器实例-p 9000:9000
: 这个参数将宿主机的端口 9000 映射到容器内的端口 9000。MinIO 服务通常使用这个端口来处理 API 请求-p 9090:9090
: 这个参数将宿主机的端口 9090 映射到容器内的端口 9090。MinIO 控制台(Web 界面)通常使用这个端口。--name minio
: 这个参数为容器指定了一个名称,这里命名为 “minio”--restart=always
: 这个参数指定了容器的重启策略。如果容器退出,Docker 将始终尝试重启它-e "MINIO_ACCESS_KEY=wuyanzu"
: 这个参数设置了一个环境变量,MINIO_ACCESS_KEY
是 MinIO 服务的访问密钥,用于身份验证。这里的值被设置为 “wuyanzu”-e "MINIO_SECRET_KEY=bo@DwF1mzr_wF7am"
: 这个参数设置了一个环境变量,MINIO_SECRET_KEY
是 MinIO 服务的秘密密钥,也用于身份验证。这里的值是一个复杂的密码-v /home/data:/data
: 这个参数将宿主机的/home/data
目录挂载到容器内的/data
目录。MinIO 将在这个目录中存储数据-v /home/config:/root/.minio
: 这个参数将宿主机的/home/config
目录挂载到容器内的/root/.minio
目录。MinIO 将在这个目录中存储配置文件-d
: 这个参数指示 Docker 以守护进程模式运行容器,即容器将在后台运行minio/minio
: 这是使用的 Docker 镜像名称,这里使用的是 MinIO 官方镜像server /data
: 这是传递给 MinIO 镜像的命令和参数。server
是 MinIO 的命令,用于启动 MinIO 服务,/data
是 MinIO 存储数据的目录(/data 是容器内的目录)--address ":9000"
: 这个参数指定了 MinIO 服务器监听的地址和端口,这里指定的是容器内的 9000 端口--console-address ":9090"
: 这个参数指定了 MinIO 控制台(Web 界面)监听的地址和端口,这里指定的是容器内的 9090 端口
3.5 开放防火墙的端口
为了能够从外界访问 MinIO,需要为 MinIO 开放防火墙的 9000 端口 和 9090 端口
- 如果你使用的是云服务器,在安全组中放行 9000 端口 和 9090 端口
- 如果你安装了宝塔,除了在安全组中放行 9000 端口 和 9090 端口,还要在宝塔中放行 9000 端口 和 9090 端口
完成以上两个操作后,输入以下指令开放 9000 端口 和 9090 端口
Ubuntu
sudo ufw allow 9000
sudo ufw allow 9090
sudo ufw reload
CentOS
sudo firewall-cmd --zone=public --add-port=9000 /tcp --permanent
sudo firewall-cmd --zone=public --add-port=9090 /tcp --permanent
sudo firewall-cmd --reload
3.6 访问MinIO控制台
在浏览器输入以下内容,访问 MinIO 的控制台(注意将 IP 地址改为你的 IP 地址)
http://127.0.0.1:9090
登录页如下
输入用户名和密码后进入 MinIO 的控制台(用户名和密码在启动 MinIO 的指令中)
登录成功后的页面
4. MinIO中的基本概念
4.1 Bucket(桶)
MinIO 中的 Bucket 可以理解为 Windows 中的盘符( 如 C 盘、D 盘)
我们在 MinIO 的控制台中新建一个桶
4.2 Path / Prefix(路径)
MinIO 中的 Path / Prefix 可以理解为 Windows 中的文件夹
我们在刚创建的桶中新建一个 Path (Path 的名称中最好不要有中文)
4.3 文件
MinIO 中的文件跟 Windows 系统中的文件概念相似
4.3.1 在控制台中上传文件
我们新建一个名为 index.html 的 HTML 文件,并将这个 HTML 文件上传到 MinIO 中
HTML 文件的内容如下
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello, MinIO</title>
<style>
body {
background-color: black;
font-family: 'Arial', sans-serif;
text-align: center;
color: white;
margin-top: 100px;
}
h1 {
font-size: 48px;
margin: 0;
}
</style>
</head>
<body>
<h1>Hello, MinIO</h1>
</body>
</html>
可以上传文件,也可以上传文件夹,我们选择上传文件
上传成功后的页面
4.3.2 在控制台中下载文件
点击文件名,再点击 Download
4.3.3 在控制台中分享文件
我们也可以将文件分享给我们的小伙伴
点击 Share 后会生成一个链接,在浏览器中访问这个链接就能够下载这个文件
5. MinIO快速入门(在 Java 代码中操作 MinIO)
5.1 引入依赖
要在 Java 代码中操作 MinIO,需要先提供 MinIO 的 Maven 依赖
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.10</version>
</dependency>
同时为了更方便地记录日志,我们引入 lombok 依赖(主要是为了使用 @Slf4j 注解)
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
5.2 编写测试类,获取MinIO中的文件
注意:在 Java 代码中连接 MinIO 使用的是 9000 端口,9090 端口是 MinIO 控制台的访问地址,而 9000 才是 MinIO 对外暴露的端口
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MinioClient;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@Slf4j
public class MinioTests {
private MinioClient minioClient;
@Test
public void testGetObject() throws Exception {
String path = "niekeyi/";
GetObjectArgs getObjectArgs = GetObjectArgs.builder()
.bucket("blog")
.object(path + "index.html")
.build();
GetObjectResponse getObjectResponse = minioClient.getObject(getObjectArgs);
getObjectResponse.transferTo(System.out);
}
@BeforeEach
public void init() {
minioClient = MinioClient.builder().
credentials("wuyanzu", "bo@DwF1mzr_wF7am").
endpoint("http://127.0.0.1:9000").
build();
}
@AfterEach
public void shutdown() throws Exception {
// 关闭连接,释放资源。
minioClient.close();
}
}
在控制台中就可以获取到刚上传的 HTML 文件
5.3 上传文件到MinIO
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
@Slf4j
public class MinioTests {
private MinioClient minioClient;
@Test
public void testGetObject() throws Exception {
String path = "niekeyi/";
GetObjectArgs getObjectArgs = GetObjectArgs.builder()
.bucket("blog")
.object(path + "index.html")
.build();
GetObjectResponse getObjectResponse = minioClient.getObject(getObjectArgs);
getObjectResponse.transferTo(System.out);
}
@Test
public void testPutObject() {
InputStream inputStream;
String filePath = "F://MinIO/一个真正的MAN.mp4";
try {
inputStream = new FileInputStream(filePath);
} catch (IOException e) {
log.error("文件不存在:{}", e.getMessage());
return;
}
try {
PutObjectArgs putObjectArgs;
String bucketName = "blog";
String path = "niekeyi/";
String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);
putObjectArgs = PutObjectArgs.builder()
.contentType("video/mp4") // 文件类型
.bucket(bucketName) // 桶名称
.object(path + fileName) // 文件名
.stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
System.err.println("http://127.0.0.1:9000/" + bucketName + "/" + fileName);
} catch (Exception e) {
log.error("上传失败:{}", e.getMessage());
}
}
@BeforeEach
public void init() {
minioClient = MinioClient.builder().
credentials("wuyanzu", "bo@DwF1mzr_wF7am").
endpoint("http://127.0.0.1:9000").
build();
}
@AfterEach
public void shutdown() throws Exception {
// 关闭连接,释放资源。
minioClient.close();
}
}
打开 MinIO 的控制台,发现文件已经成功上传了
6. 将操作MinIO的Java代码封装成一个starter
如果你想让更多的人能够使用你的 starter,可以使用较低版本的 JDK 和较低版本的 SpringBoot 开发
由于 MinIO 官方并没有提供操作 MinIO 的 starter,而 MinIO 的主要功能是存储文件,我们在其它项目中也有很大的概率会用到 MinIO,所以我们可以将操作 MinIO 的 Java 代码封装成一个 starter,以后需要通过 Java 代码操作 MinIO 时,直接引入 starter,做少量的配置就能使用了
不知道如何自定义 starter 的,可以参考我的另一篇博文:SpringBoot自定义starter(starter的命名规范、starter的结构、自定义starter、为配置属性添加描述信息、检验配置属性
当然,市面上也有一些第三方的基于 MinioClient 的 starter ,可以在 GitHub 或 Gitee 上搜索
6.1 创建父工程
创建工程后先修改 Maven 仓库路径和 Maven 的配置文件
6.2 初始化父工程
父工程不需要 src 目录,我们删除父工程的 src 目录
接着在父工程的 pom.xml 文件的 properties 标签中添加以下内容,表明父工程是一个聚合工程
<packaging>pom</packaging>
引入 MinIO 的 Maven 依赖
<!-- https://mvnrepository.com/artifact/io.minio/minio -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.10</version>
</dependency>
最后把 <build></build>
标签也删掉,因为父工程并不需要打包插件
6.3 创建 autoconfigure 模块
右键父工程
选择普通的 Maven 项目
minio-spring-boot-starter
6.4 创建 starter 模块
6.5 在 starter 模块中引入 autoconfigure 模块的依赖
在 starter 模块的 pom.xml 文件中引入 autoconfigure 模块的依赖
<dependencies>
<dependency>
<groupId>cn.edu.scau</groupId>
<artifactId>minio-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--如果当前starter需要依赖其它的starter或jar包,在这里导入-->
</dependencies>
6.6 新建配置类
在 autoconfigure 模块下新建一个配置类,配置类命名为 MinioAutoConfiguration
package cn.edu.scau;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MinioAutoConfiguration {
}
6.7 新建配置属性类
在 autoconfigure 模块下新建一个 MinioConfigurationProperties 配置属性类
package cn.edu.scau;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "minio")
public class MinioConfigurationProperties {
}
prefix = “minio” 指的是在配置文件中填写配置属性时的前缀
6.8 让用户在配置文件中填写配置属性时有提示
为了在 application.yml 文件或 application.properties 文件中填写配置属性时有提示,我们需要完成两个步骤
第一步:在 autoconfigure 模块中添加文件处理器的依赖
<!--导入配置文件处理器,配置文件进行绑定就会有提示-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
第二步:在 autoconfigure 模块的 resources/META-INF/spring
目录下创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件
org.springframework.boot.autoconfigure.AutoConfiguration.imports
文件内容如下
cn.edu.scau.MinioAutoConfiguration
完成上述两个步骤后,MinioConfigurationProperties 类的 @ConfigurationProperties 可能会给出警告信息,因为 MinioConfigurationProperties 是一个配置属性类,但它并没有没有与任何一个由 Spring 管理的配置类绑定在一起
我们只需要将 MinioAutoConfiguration 类与 ConfigurationProperties 类绑定在一起就可以了,可以通过 EnableConfigurationProperties 注解绑定
@EnableConfigurationProperties(MinioConfigurationProperties.class)
6.9 编写 MinioConfigurationProperties 类
MinioConfigurationProperties 类主要关心连接 MinIO 所需要的几个参数
-
endpoint
-
accessKey
-
secretKey
-
bucket
package cn.edu.scau;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "minio")
public class MinioConfigurationProperties {
private String endpoint;
private String accessKey;
private String secretKey;
private String bucket;
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getAccessKey() {
return accessKey;
}
public void setAccessKey(String accessKey) {
this.accessKey = accessKey;
}
public String getSecretKey() {
return secretKey;
}
public void setSecretKey(String secretKey) {
this.secretKey = secretKey;
}
public String getBucket() {
return bucket;
}
public void setBucket(String bucket) {
this.bucket = bucket;
}
@Override
public String toString() {
return "MinioConfigurationProperties{" +
"endpoint='" + endpoint + '\'' +
", accessKey='" + accessKey + '\'' +
", secretKey='" + secretKey + '\'' +
", bucket='" + bucket + '\'' +
'}';
}
}
6.10 将 MinioClient 对象注入容器中
package cn.edu.scau;
import io.minio.MinioClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(MinioConfigurationProperties.class)
public class MinioAutoConfiguration {
private final MinioConfigurationProperties minioConfigurationProperties;
public MinioAutoConfiguration(MinioConfigurationProperties minioConfigurationProperties) {
this.minioConfigurationProperties = minioConfigurationProperties;
}
@Bean
public MinioClient minioClient() {
return MinioClient
.builder()
.credentials(minioConfigurationProperties.getAccessKey(), minioConfigurationProperties.getSecretKey())
.endpoint(minioConfigurationProperties.getEndpoint())
.build();
}
}
6.11 创建 MinioFileStorageUtil 类
MinioFileStorageUtil 类是 starter 的核心类,封装了大量的操作 MinIO 的代码
package cn.edu.scau;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Import;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
@Slf4j
@EnableConfigurationProperties(MinioConfigurationProperties.class)
@Import(MinioAutoConfiguration.class)
public class MinioFileStorageUtil {
private final MinioClient minioClient;
private final MinioConfigurationProperties minioConfigurationProperties;
public MinioFileStorageUtil(MinioClient minioClient, MinioConfigurationProperties minioConfigurationProperties) {
this.minioClient = minioClient;
this.minioConfigurationProperties = minioConfigurationProperties;
}
private final static String separator = "/";
/**
* @param path 文件路径
* @param filename yyyy/MM/dd/filename.jpg
* @return String
*/
public String buildFilePath(String path, String filename) {
StringBuilder stringBuilder = new StringBuilder(50);
if (path != null && !path.isEmpty()) {
stringBuilder.append(path).append(separator);
}
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
String todayString = simpleDateFormat.format(new Date());
stringBuilder.append(todayString)
.append(separator)
.append(filename);
return stringBuilder.toString();
}
/**
* 上传图片文件-jpg
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
public String uploadImageFile(String prefix, String filename, InputStream inputStream) {
String filePath = buildFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("image/jpg")
.bucket(minioConfigurationProperties.getBucket()).stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
return minioConfigurationProperties.getEndpoint() + separator + minioConfigurationProperties.getBucket() + separator + filePath;
} catch (Exception e) {
log.error("Minio put file error.", e);
throw new RuntimeException("Minio put file error.");
}
}
/**
* 上传html文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
public String uploadHtmlFile(String prefix, String filename, InputStream inputStream) {
String filePath = buildFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("text/html")
.bucket(minioConfigurationProperties.getBucket()).stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
return minioConfigurationProperties.getEndpoint() + separator + minioConfigurationProperties.getBucket() + separator + filePath;
} catch (Exception e) {
log.error("Minio put file error.", e);
throw new RuntimeException("Minio put file error.");
}
}
/**
* 上传mp4文件
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
public String uploadMp4File(String prefix, String filename, InputStream inputStream) {
String filePath = buildFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.contentType("video/mp4")
.bucket(minioConfigurationProperties.getBucket()).stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
return minioConfigurationProperties.getEndpoint() + separator + minioConfigurationProperties.getBucket() + separator + filePath;
} catch (Exception e) {
log.error("Minio put file error.", e);
throw new RuntimeException("Minio put file error.");
}
}
/**
* 上传文件,由 MinIO 自动推断文件类型
*
* @param prefix 文件前缀
* @param filename 文件名
* @param inputStream 文件流
* @return 文件全路径
*/
public String uploadFile(String prefix, String filename, InputStream inputStream) {
String filePath = buildFilePath(prefix, filename);
try {
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object(filePath)
.bucket(minioConfigurationProperties.getBucket()).stream(inputStream, inputStream.available(), -1)
.build();
minioClient.putObject(putObjectArgs);
return minioConfigurationProperties.getEndpoint() + separator + minioConfigurationProperties.getBucket() + separator + filePath;
} catch (Exception e) {
log.error("Minio put file error.", e);
throw new RuntimeException("Minio put file error.");
}
}
/**
* 删除文件
*
* @param pathUrl 文件全路径
*/
public void deleteFile(String pathUrl) {
String key = pathUrl.replace(minioConfigurationProperties.getEndpoint() + separator, "");
int index = key.indexOf(separator);
String bucket = key.substring(0, index);
String filePath = key.substring(index + 1);
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
.bucket(bucket)
.object(filePath)
.build();
try {
minioClient.removeObject(removeObjectArgs);
} catch (Exception e) {
log.error("minio remove file error. pathUrl:{}", pathUrl);
throw new RuntimeException("Minio remove file error. pathUrl:" + pathUrl);
}
}
/**
* 下载文件
*
* @param pathUrl 文件全路径
* @return 字节数组
*/
public byte[] downloadFile(String pathUrl) {
String key = pathUrl.replace(minioConfigurationProperties.getEndpoint() + separator, "");
int index = key.indexOf(separator);
String filePath = key.substring(index + 1);
InputStream inputStream;
try {
GetObjectArgs getObjectArgs = GetObjectArgs.builder()
.bucket(minioConfigurationProperties.getBucket())
.object(filePath)
.build();
inputStream = minioClient.getObject(getObjectArgs);
} catch (Exception e) {
log.error("Minio down file error. pathUrl:{}", pathUrl);
throw new RuntimeException("Minio down file error. pathUrl:" + pathUrl);
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int returnCode = 0;
while (true) {
try {
if (inputStream != null && !((returnCode = inputStream.read(buffer, 0, 1024)) > 0)) {
break;
}
} catch (IOException e) {
throw new RuntimeException("Minio transfer file to byte array failed.");
}
byteArrayOutputStream.write(buffer, 0, returnCode);
}
return byteArrayOutputStream.toByteArray();
}
}
6.12 将 MinioFileStorageUtil 类注入容器中
package cn.edu.scau;
import io.minio.MinioClient;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(MinioConfigurationProperties.class)
public class MinioAutoConfiguration {
private final MinioConfigurationProperties minioConfigurationProperties;
public MinioAutoConfiguration(MinioConfigurationProperties minioConfigurationProperties) {
this.minioConfigurationProperties = minioConfigurationProperties;
}
@Bean
public MinioClient minioClient() {
return MinioClient
.builder()
.credentials(minioConfigurationProperties.getAccessKey(), minioConfigurationProperties.getSecretKey())
.endpoint(minioConfigurationProperties.getEndpoint())
.build();
}
@Bean
public MinioFileStorageUtil minioFileStorageService(MinioClient minioClient, MinioConfigurationProperties minioConfigurationProperties) {
return new MinioFileStorageUtil(minioClient, minioConfigurationProperties);
}
}
6.13 打包
在父工程中打包
7. 测试封装好的 starter
新建一个 SpringBoot 工程,测试封装好的 starter
7.1 引入依赖
在测试工程中引入封装好的 starter
<dependency>
<groupId>cn.edu.scau</groupId>
<artifactId>minio-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
starter 的名字要与你在 starter 模块中对应的 artifactId 对应
7.2 编写配置文件
编写测试工程的配置文件(application.yml)
可以看到,编写配置文件时有提示了
minio:
endpoint: http://127.0.0.1:9000
accessKey: wuyanzu
secretKey: bo@DwF1mzr_wF7am
bucket: blog
7.3 编写测试类
7.3.1 测试下载文件
package cn.edu.scau;
import cn.edu.scau.MinioConfigurationProperties;
import cn.edu.scau.MinioFileStorageUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@Slf4j
@SpringBootTest
public class MinioStarterTests {
@Autowired
private MinioFileStorageUtil minioFileStorageUtil;
@Autowired
private MinioConfigurationProperties minioConfigurationProperties;
@Test
public void testDownLoadFile() throws FileNotFoundException {
String endpoint = minioConfigurationProperties.getEndpoint();
String bucket = minioConfigurationProperties.getBucket();
String transferToPath = "F://MinIO/index.html";
byte[] bytes = minioFileStorageUtil.downloadFile(endpoint + "/" + bucket + "/" + "niekeyi/index.html");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(transferToPath));
int bufferSize = 1024 * 1024; // 1MB
try {
int offset = 0;
while (offset < bytes.length) {
int len = bytes.length - offset;
if (len > bufferSize) {
len = bufferSize;
}
bufferedOutputStream.write(bytes, offset, len);
offset += len;
}
bufferedOutputStream.flush();
bufferedOutputStream.close();
log.info("文件转存成功:{}", transferToPath);
} catch (Exception e) {
log.error("文件转存失败:{}", e.getMessage());
throw new RuntimeException("文件转存失败");
}
}
}
7.3.2 测试上传文件
上传文件后会返回访问文件的 URL
package cn.edu.scau;
import cn.edu.scau.MinioConfigurationProperties;
import cn.edu.scau.MinioFileStorageUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@Slf4j
@SpringBootTest
public class MinioStarterTests {
@Autowired
private MinioFileStorageUtil minioFileStorageUtil;
@Autowired
private MinioConfigurationProperties minioConfigurationProperties;
@Test
public void testDownLoadFile() throws FileNotFoundException {
String endpoint = minioConfigurationProperties.getEndpoint();
String bucket = minioConfigurationProperties.getBucket();
String transferToPath = "F://MinIO/index.html";
byte[] bytes = minioFileStorageUtil.downloadFile(endpoint + "/" + bucket + "/" + "niekeyi/index.html");
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(transferToPath));
int bufferSize = 1024 * 1024; // 1MB
try {
int offset = 0;
while (offset < bytes.length) {
int len = bytes.length - offset;
if (len > bufferSize) {
len = bufferSize;
}
bufferedOutputStream.write(bytes, offset, len);
offset += len;
}
bufferedOutputStream.flush();
bufferedOutputStream.close();
log.info("文件转存成功:{}", transferToPath);
} catch (Exception e) {
log.error("文件转存失败:{}", e.getMessage());
throw new RuntimeException("文件转存失败");
}
}
@Test
public void uploadHtmlFile() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("F://MinIO/index.html");
String filePath = minioFileStorageUtil.uploadHtmlFile("niekeyi", "index.html", fileInputStream);
log.info("文件上传成功:{}", filePath);
}
@Test
public void uploadMp4File() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("F://MinIO/一个真正的MAN.mp4");
String filePath = minioFileStorageUtil.uploadMp4File("niekeyi", "一个真正的MAN.mp4", fileInputStream);
log.info("文件上传成功:{}", filePath);
}
@Test
public void uploadFile() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream("F://MinIO/2024年9月5日.txt");
String filePath = minioFileStorageUtil.uploadFile("niekeyi", "2024年9月5日.txt", fileInputStream);
log.info("文件上传成功:{}", filePath);
}
}
8. 控制匿名用户对桶对的访问权限
利用 MinioClient 上传文件到 MinIO 后,利用返回的 URL 直接在浏览器访问,会出现以下错误
This XML file does not appear to have any style information associated with it. The document tree is shown below.
AccessDenied
Access Denied.
niekeyi/2024/09/05/一个真正的MAN.mp4
blog
/blog/niekeyi/2024/09/05/一个真正的MAN.mp4
17F267748F33E80C
dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
因为在浏览器中访问 MinIO 是以匿名用户的身份访问的,MinIO 的桶默认不对匿名用户开放
解决方法:为匿名用户开启桶的访问权限
- / 表示可以访问桶中的任意路径
- read only 表示只读
9. 源代码
自定义 minio-spring-boot-starter 的源代码已经放到了 Gitee 上:minio-starter