目录
在很多互联网产品应用中,都涉及到各种与文件存储相关的业务,随着技术的发展,关于如何解决分布式文件存储也有了比较成熟的方案,比如私有云部署下可以考虑fastdfs,阿里云对象存储oss,七牛云等,本篇将为你介绍另一种文件存储方式,即MinIO 。
MinIO基于Apache License v2.0开源协议的对象存储服务,可以做为云存储的解决方案用来保存海量的图片,视频,文档,是一款高性能、分布式的对象存储系统,?可以100%的运行在标准硬件,即X86等低成本机器也能够很好的运行MinIO。
传统的存储和其他的对象存储不同的是:
它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,它不会为了更多的业务功能而妥协,失去MinIO的易用性、高效性。 这样的结果所带来的好处是:它能够更简单的实现局有弹性伸缩能力的原生对象存储服务。
Minio具有如下特点
本文采用docker的方式快速搭建起Minio的环境,也可以通过官网下载安装包部署,官网安装包下载地址
docker pull minio/minio
docker run -d -p 9000:9000 -p 9090:9090 \
--name minio \
-e "MINIO_ACCESS_KEY=minio" \
-e "MINIO_SECRET_KEY=minio" \
-v /home/minio/data:/data \
-v /home/minio/config:/root/.minio \
minio/minio server \
/data --console-address ":9090" -address ":9000"
容器启动成功后,注意开发相关的防火墙端口即可,然后访问地址:IP:9000,即可访问Minio的web界面
输入账户和密码,登录进去之后,看到下面的效果说明Minio环境搭建完成
在正式开始使用Minio之前,有必要先了解下几个相关的概念
如下,点击创建一个新的bucket,创建完成后就可以在列表上看到这个bucket;
给当前这个bucket上传一个文件
点击文件夹图标
上传一张本地文件,上传完成后就可以看到这个文件了,也可以浏览上传的文件
针对客户端的操作,经常需要维护相关的账号来管理,比如账户的操作权限,访问控制等;
点击,创建用户
填写用户信息,勾选权限保存即可
然后在用户列表就可以看到这个用户了
通过上面的环境搭建和操作,演示并了解了如何快速使用Minio,更多的功能可以参阅相关资料进一步学习了解,下面我们编写java代码完成文件的上传。
在当前的maven工程中导入minio的依赖,客户端具体版本可以根据你的实际需要选择
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
通过下面这段代码将本地的一张图片文件上传到minio的test这个bucket目录下
public static void main(String[] args) {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream("F:\\网盘\\Title-logo.png");
MinioClient client = MinioClient.builder().credentials("minio", "minio").endpoint("http://IP:9000").build();
PutObjectArgs putObjectArgs = PutObjectArgs.builder()
.object("logo.png")
.contentType("image/png")
.bucket("test")
.stream(inputStream, inputStream.available(), -1)
.build();
client.putObject(putObjectArgs);
System.out.println("上传成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行这段代码,上传成功后,去到test的目录下刷新之后可以看到文件已经上传
接下来让我们看看如何在springboot中集成Minio,参考下面的操作步骤
创建一个maven工程,引入如下相关的依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
在配置文件中添加如下内容
server:
port: 8087
logging:
level:
root: info
com.congge.model: debug
minio:
username: minio
pwd: minio
bucket: test
endpoint: http://IP:9000
readPath: http://IP:9000
通过这种方式将配置文件中以minio开头的那些配置加载到spring容器中管理,其他位置使用的时候直接注入即可
@Data
@ConfigurationProperties(prefix = "minio")
@Component
public class MinIOConfigProperties implements Serializable {
private String username;
private String pwd;
private String bucket;
private String endpoint;
private String readPath;
}
通过这个全局的配置类,其他需要上传文件的类中直接注入MinioClient即可
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
public class MinIOConfig {
@Autowired
private MinIOConfigProperties minIOConfigProperties;
@Bean
public MinioClient buildMinioClient() {
return MinioClient
.builder()
.credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
.endpoint(minIOConfigProperties.getEndpoint())
.build();
}
}
在日常开发中,可以自定义一个minio的工具类,使用的时候就比较方便了,下面的代码中列举了常用的一些API操作方法,可以结合实际需要自定义更多的工具方法
import com.congge.config.MinIOConfig;
import com.congge.config.MinIOConfigProperties;
import com.congge.service.MinioFileService;
import io.minio.*;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Import;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Slf4j
@Import(MinIOConfig.class)
@Service
public class MinioFileServiceImpl implements MinioFileService {
@Autowired
private MinioClient minioClient;
@Autowired
private MinIOConfigProperties minIOConfigProperties;
private final static String separator = "/";
/**
* 下载文件
*
* @param pathUrl 文件全路径
* @return 文件流
*/
@Override
public void downLoadFile(String pathUrl, HttpServletResponse response) {
String[] pathItems = pathUrl.split("/");
String originFileName = pathItems[pathItems.length - 1];
String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + "/", "");
int index = key.indexOf(separator);
//String bucket = key.substring(0,index);
String filePath = key.substring(index + 1);
InputStream inputStream = null;
try {
inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
response.setHeader("Content-Disposition", "attachment;filename=" + originFileName);
response.setContentType("application/force-download");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(inputStream, response.getOutputStream());
System.out.println("下载成功");
} catch (Exception e) {
log.error("minio down file error. pathUrl:{}", pathUrl);
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public String uploadFile(MultipartFile file) throws Exception {
String bucketName = minIOConfigProperties.getBucket();
String endpoint = minIOConfigProperties.getEndpoint();
// 检查存储桶是否已经存在
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (isExist) {
System.out.println("Bucket already exists.");
} else {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
String originalFilename = file.getOriginalFilename();
//拼接生成新的UUID形式的文件名
String objectName = new SimpleDateFormat("yyyy/MM/dd/").format(new Date()) +
UUID.randomUUID().toString().replaceAll("-", "")
+ originalFilename.substring(originalFilename.lastIndexOf("."));
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(bucketName)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), -1).build();
minioClient.putObject(objectArgs);
//组装桶中文件的访问url
String resUrl = endpoint + "/" + bucketName + "/" + objectName;
return resUrl;
}
/**
* 删除文件
*
* @param pathUrl 文件全路径
*/
@Override
public void delete(String pathUrl) {
String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + "/", "");
int index = key.indexOf(separator);
String bucket = key.substring(0, index);
String filePath = key.substring(index + 1);
// 删除Objects
RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
try {
minioClient.removeObject(removeObjectArgs);
} catch (Exception e) {
log.error("minio remove file error. pathUrl:{}", pathUrl);
e.printStackTrace();
}
}
public List<Bucket> listBuckets()
throws Exception {
return minioClient.listBuckets();
}
public boolean bucketExists(String bucketName) throws Exception {
boolean flag = minioClient.bucketExists(bucketName);
if (flag) {
return true;
}
return false;
}
@Override
public List<String> listBucketNames() throws Exception{
List<Bucket> bucketList = listBuckets();
List<String> bucketListName = new ArrayList<>();
for (Bucket bucket : bucketList) {
bucketListName.add(bucket.name());
}
return bucketListName;
}
@Override
public boolean makeBucket(String bucketName) throws Exception{
boolean flag = bucketExists(bucketName);
if (!flag) {
minioClient.makeBucket(bucketName);
return true;
} else {
return false;
}
}
@Override
public boolean removeBucket(String bucketName) throws Exception{
boolean flag = bucketExists(bucketName);
if (flag) {
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects) {
Item item = result.get();
// 有对象文件,则删除失败
if (item.size() > 0) {
return false;
}
}
// 删除存储桶,注意,只有存储桶为空时才能删除成功。
minioClient.removeBucket(bucketName);
flag = bucketExists(bucketName);
if (!flag) {
return true;
}
}
return false;
}
@Override
public List<String> listObjectNames(String bucketName) throws Exception{
List<String> listObjectNames = new ArrayList<>();
boolean flag = bucketExists(bucketName);
if (flag) {
Iterable<Result<Item>> myObjects = listObjects(bucketName);
for (Result<Item> result : myObjects) {
Item item = result.get();
listObjectNames.add(item.objectName());
}
}
return listObjectNames;
}
@Override
public boolean removeObject(String bucketName, String objectName) throws Exception{
boolean flag = bucketExists(bucketName);
if (flag) {
List<String> objectList = listObjectNames(bucketName);
for (String s : objectList) {
if(s.equals(objectName)){
minioClient.removeObject(bucketName, objectName);
return true;
}
}
}
return false;
}
@Override
public String getObjectUrl(String bucketName, String objectName) throws Exception{
boolean flag = bucketExists(bucketName);
String url = "";
if (flag) {
url = minioClient.getObjectUrl(bucketName, objectName);
}
return url;
}
public Iterable<Result<Item>> listObjects(String bucketName) throws Exception {
boolean flag = bucketExists(bucketName);
if (flag) {
return minioClient.listObjects(bucketName);
}
return null;
}
}
为了方便测试,下面定义了一个测试接口
import com.congge.service.MinioFileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@Slf4j
public class FileController {
@Autowired
private MinioFileService minioFileService;
/**
* 上传文件
* @param file
* @return
* @throws Exception
*/
@PostMapping("/upload")
public String upload(@RequestBody MultipartFile file) throws Exception {
String url = minioFileService.uploadFile(file);
return "文件上传成功,文件路径:" + url;
}
/**
* 下载文件
* @param pathUrl
* @param response
*/
@GetMapping("/download")
public void download(@RequestParam("pathUrl") String pathUrl, HttpServletResponse response) {
minioFileService.downLoadFile(pathUrl,response);
}
/**
* 列出所有bucket名称
* @return
* @throws Exception
*/
@PostMapping("/list/bucket")
public List<String> list() throws Exception {
return minioFileService.listBucketNames();
}
/**
* 创建bucket
* @param bucketName
* @return
* @throws Exception
*/
@PostMapping("/create/bucket")
public boolean createBucket(@RequestParam("bucketName")String bucketName) throws Exception {
return minioFileService.makeBucket(bucketName);
}
/**
* 删除bucket
* @param bucketName
* @return
* @throws Exception
*/
@PostMapping("/delete/bucket")
public boolean deleteBucket(@RequestParam("bucketName")String bucketName) throws Exception {
return minioFileService.removeBucket(bucketName);
}
/**
* 列出bucket的所有对象名称
* @param bucketName
* @return
* @throws Exception
*/
@PostMapping("/list/object_names")
public List<String> listObjectNames(@RequestParam("bucketName")String bucketName) throws Exception {
return minioFileService.listObjectNames(bucketName);
}
/**
* 删除bucket中的某个对象
* @param bucketName
* @param objectName
* @return
* @throws Exception
*/
@PostMapping("/remove/object")
public boolean removeObject(@RequestParam("bucketName")String bucketName,@RequestParam("objectName") String objectName) throws Exception {
return minioFileService.removeObject(bucketName, objectName);
}
/**
* 获取文件访问路径
* @param bucketName
* @param objectName
* @return
* @throws Exception
*/
@PostMapping("/get/object/url")
public String getObjectUrl(@RequestParam("bucketName")String bucketName, @RequestParam("objectName")String objectName) throws Exception {
return minioFileService.getObjectUrl(bucketName, objectName);
}
}
上传文件测试
上传成功后,返回了文件的完整路径,方便后续使用
然后在控制台的test这个bucket中检查是否上传上去了
下载文件测试
使用上一步返回的url,直接调用下载接口,下载完成后可以直接预览
更多的接口有兴趣的同学可以一一尝试下,就不再赘述了。
本文详细总结了Minio的搭建使用,以及与springboot整合的完整步骤,作为一款适用且轻量级的文件存储服务器,可以私有化部署,也可以很方便进行云上部署、容器化部署,为今后在实际项目中的技术选型提供一个参考,本篇到此结束,感谢观看。