本文,将带你使用 MinIO + Docker 来从零搭建一个文件存储服务,并在 SpringBoot 项目中上传图片到 MinIO 中。
一.为什么要自己搭建?
对于个人来说,当然是攻击风险。第三方对象存储服务通常会收取费用,尤其随着数据量的增加,费用也会相应增加,而通过自己搭建文件存储服务器,可以有效控制成本。其实费用还好,个人小项目访问量不大花不了多少钱,最重要的是被攻击刷流量就GG了。例如:
个人小博客被攻击:
某知名up主被刷爆:
虽然个人小项目一般不会被攻击,但就问你看到-1.5w慌不慌?接下来让我们来学习一下如何使用MinIO搭建一个自己的文件存储服务器吧~
二.为什么使用MinIO?
MinIO 是一个高性能的轻量级对象存储服务器。它具有分布式,高可用性和水平扩展的特点,它非常适合用于大规模数据存储和分析。其优点包括低延迟、高吞吐量、易于部署和管理。
怕很拉跨不敢用?
截止目前,MinIO 在 Github 上有 43.7k Star。
国内阿里巴巴、腾讯、百度、华为、中国移动、中国联通等企业都有在使用 MinIO,甚至不少商业公司二次开发 MinIO 来提供商业化的云存储产品。
三.搭建MinIO
(1) 引言
MinIO 的官方文档上介绍了多种下载方式:
本文我们只介绍如何使用 Docker 基于CentOS 服务器来进行服务的搭建,如果未安装Docker可以通过本文(点击跳转)来了解和安装Docker,或者在官方文档挑选自己擅长的方式进行搭建~
(2) 安装
- 首先我们要创建两个文件目录:一个用来存放 MinIO 的配置文件,一个用来存储我们上传文件数据。
mkdir -p /home/minio/config
mkdir -p /home/minio/data
/home/minio/config
用于存放 MinIO 的配置文件/home/minio/data
用于存储上传的文件数据
- 接下来我们可以通过如下命令拉取最新版镜像并创建 MinIO 容器运行。如果需要下载指定版本可以点击前往DockerHub仓库选择下载。
docker run -p 9000:9000 -p 9001:9001 \
-d --restart=always \
-e "MINIO_ACCESS_KEY=admin" \
-e "MINIO_SECRET_KEY=password" \
-v /home/minio/data:/data \
-v /home/minio/config:/root/.minio \
minio/minio server \
/data \
--console-address ":9001"
运行效果:
命令行解释:
MINIO_ACCESS_KEY
和MINIO_SECRET_KEY
为UI界面登录账号密码-d
将容器以后台(守护进程)模式运行,并与终端分离。--restart=always
选项指定容器在停止后总是自动重启。/home/minio/data
挂载的存储上传文件的目录/home/minio/config
挂载的配置文件minio/minio server
使用MinIO 镜像并启动/data
要使用的数据目录--console-address
指定UI 界面的端口9000:9000
映射服务器端口9001:9001
映射UI界面端口
补充说明:
Docker的run指令会首先在本地查找指定的镜像,如果本地没有找到对应的镜像,则会自动去远程镜像仓库拉取该镜像并在本地运行。
当不带有标签(tag)的镜像名称时,Docker默认会使用latest标签来拉取最新版本的镜像。例如,如果运行
docker run ubuntu
,Docker会首先在本地查找名为ubuntu:latest的镜像是否存在,如果不存在,则会从默认的远程镜像仓库(如Docker Hub)拉取最新版本的ubuntu镜像,并在本地运行。如果指定了具体的标签或版本号,例如
docker run ubuntu:18.04
,Docker会尝试在本地查找名为ubuntu:18.04的镜像,如果本地没有找到,则会从远程镜像仓库拉取对应的镜像。需要注意的是,如果在远程镜像仓库中找不到指定的镜像,或者无法连接到远程镜像仓库,Docker的run指令将无法成功运行,并会报错提示找不到镜像。
- 最后在浏览器中访问 http://服务器IP:9001,即可访问到MinIO的控制台。
可以输入账号 admin,密码 password 进行登录,进入首页。
可以发现它的界面和我们使用的一些第三方对象存储服务非常相似,接下来我们也要进行一系列的配置~
(3) 配置
- 创建一个 Bucket 存储桶,用于稍后文件的上传操作。
- 前往创建用于远程操作的AccessKey 和 SecretKey.
- 创建并保存好生成Access Key 和 Secret Key.
- 默认配置下,访问存储桶是需要请求授权的。但是在实际场景下,我们往往希望允许直接访问,此时就需要添加一条 readonly 访问规则。
进入配置存储桶界面
进行配置,添加只读规则
至此,我们已经基本完成了MinIO的简单配置,当然如果想要更安全,同样是可以像使用第三方对象存储服务一样创建用户组,分配权限等诸如此类的操作。
(4) 测试
- 我们上传一张图片并访问来测试一下是否搭建成功。
- 可以看到成功上传了图片
- 接下来我们在浏览器访问
http://服务器IP:9000/{bucket存储桶名字}/{name图片后缀名}
来进行访问。例如我刚上传的文件{bucket}
是 guanzhi,{name}
是 csdn.png,所以最终的访问路径是http://服务器IP:9000/guanzhi/csdn.png
。
测试成功,能够正常使用,接下来我们来学习一下如何在Java项目中使用吧~
四.在项目中使用
(1) 引言
官方文档上有介绍多种语言的SDK使用指南,本篇我们只学习如何在Java的Maven项目中进行使用,其他语言的同学可以跟着官方文档尝试一下~
(2) 初步尝试
- 首先在我们创建的Maven项目中引入如下依赖
<!-- MinIO 客户端 -->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.9</version>
</dependency>
- 接下来我们尝试一下官方给出的demo(已经添加详细注释,可以查看下述示例代码)
@SpringBootTest
class DemoApplicationTests {
@Test
void demo() {
try {
// 1.创建MinIO客户端,与其建立连接,用于我们上传文件操作
// Create a minioClient with the MinIO server playground, its access key and secret key.
MinioClient minioClient = MinioClient.builder()
.endpoint("http://服务器ip:9000") // 地址为服务器ip+端口号
.credentials("1AYD2DY8qPz5vp0WmQtJ",
"BQeSptD5wO9vPIwtthNg5BbFswPx7WhsPh1Slp0M") // 我们配置并保存的 AccessKey 和 SecretKey
.build();
// 2.创建存储桶,这一步我们可以直接在界面手动创建一次即可,因此可以省略
// Make 'asiatrip' bucket if not exist.
boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket("guanzhi").build());
if (!found) {
// Make a new bucket called 'asiatrip'.
minioClient.makeBucket(MakeBucketArgs.builder().bucket("guanzhi").build());
} else {
System.out.println("Bucket 'guanzhi' already exists.");
}
// 3. 上传文件
// Upload '/home/user/Photos/asiaphotos.zip' as object name 'asiaphotos-2015.zip' to bucket 'asiatrip'.
minioClient.uploadObject(
UploadObjectArgs.builder()
.bucket("guanzhi") // 上传到哪个存储桶?
.object("pom.xml") // 上传的文件命名为什么?
.filename("C:\\code\\demo\\pom.xml") // 上传文件的路径?
.build());
System.out.println(
"'C:\\code\\demo\\pom.xml' is successfully uploaded as "
+ "object 'pom.xml\"' to bucket 'guanzhi'.");
} catch (MinioException e) {
System.out.println("Error occurred: " + e);
System.out.println("HTTP trace: " + e.httpTrace());
} catch (IOException | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}
- 可以发现运行成功。
查看控制台,可以看成功上传了文件。
(3) 在SpringBoot中使用
我们来简单写一个文件上传功能。
- 项目中除了上述依赖,我们还需要引入如下依赖
<!-- 实现对 Spring MVC 的自动化配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependency>
<!-- lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
- 新建 MinIO 配置类,创建 MinioClient Bean
@Configuration
@ConfigurationProperties(prefix = "file.minio")
@Data
public class MinioConfiguration {
private String accessKey;
private String secretKey;
private String endpoint;
private String bucket;
@Bean
public MinioClient minioClient() {
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
}
}
- 在application.yml中编写配置
# 对象存储服务配置
file:
# MinIO自搭建对象存储服务
minio:
endpoint: http://服务器ip:9000 #存储服务域名
accessKey: 1AYD2DY8qPz5vp0WmQtJ
secretKey: BQeSptD5wO9vPIwtthNg5BbFswPx7WhsPh1Slp0M
bucket: guanzhi #存储桶名称
- 编写controller
@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private MinioClient minioClient;
@Resource
private MinioConfiguration minioConfiguration;
/**
* 上传文件
*/
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) throws Exception {
// 上传
String path = UUID.randomUUID() + file.getOriginalFilename(); // 文件名,使用 UUID 随机
minioClient.putObject(PutObjectArgs.builder()
.bucket(minioConfiguration.getBucket()) // 存储桶
.object(path) // 文件名
.stream(file.getInputStream(), file.getSize(), -1) // 文件内容
.contentType(file.getContentType()) // 文件类型
.build());
// 拼接路径
return String.format("%s/%s/%s", minioConfiguration.getEndpoint(), minioConfiguration.getBucket(), path);
}
}
- 启动SpringBoot项目,测试接口,可以看到后端正确响应了文件Url.
- 可以看到控制台存储桶中也有文件
- 在浏览器中我们也成功的通过返回的url访问到了图片
至此,我们已经完成了服务器的搭建,赶紧用起来吧~
五.拓展:在Java中进行https访问
官方文档地址:网络加密(TLS)
(1) 选择阿里云、腾讯云、华为云等一些厂商获取ssl证书
此处我们选择NGINX目录下的证书演示
(2)按照官方文档的要求将私钥命名为 private.key
, 公证书命名为 public.crt
(3)上处我们在启动minio时将本机的/home/minio/config
目录映射到了容器的/root/.minio
目录,因此根据文档的要求,我可以将ssl证书放在其中的/certs
目录下。
(4)上传好后通过docker restart 容器Id
重启minio容器配置即可生效(成功开启https访问)
(5)开启https访问后直接使用上述配置将报如下错误:
javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alerts.getSSLException(Unknown Source)
at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
比较简单的做法是将上述minioClient
的bean改为下述形式(通过java代码取消ssl认证)
@Bean
public MinioClient minioClient() throws NoSuchAlgorithmException, KeyManagementException {
//取消ssl认证
final TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public void checkServerTrusted(X509Certificate[] x509Certificates, String s) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
};
X509TrustManager x509TrustManager = (X509TrustManager) trustAllCerts[0];
final SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, trustAllCerts, new SecureRandom());
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslSocketFactory, x509TrustManager);
builder.hostnameVerifier((s, sslSession) -> true);
OkHttpClient okHttpClient = builder.build();
// 创建 MinioClient 客户端
return MinioClient.builder()
.httpClient(okHttpClient)
.endpoint(endpoint)
.credentials(accessKey,secretKey)
.build();
}