文章目录
1.简介
是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等,相对于FastDFS部署起来要轻量很多。
中文文档: http://docs.minio.org.cn/docs/master/distributed-minio-quickstart-guide
2.安装使用
2.1.下载二进制文件安装
2.1.1 单机部署
# 下载 minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
# 添加可执行权限
chmod +x minio
# 设置登录minio的 access key
export MINIO_ACCESS_KEY=minioadmin
# 设置登录minio的 secret key
export MINIO_SECRET_KEY=minioadmin
# 启动 minio 并设置文件存储的路径为/data目录,如果需要退出命令行程序还在需要在最前面加上 nohup 命令
./minio server /data
启动后可以看到如下信息
- 控制台(页面)访问地址: http://192.168.94.128:37788/
- API(SDK)访问地址: http://192.168.94.128:9000/
2.1.2.集群安装
参考:http://docs.minio.org.cn/docs/master/distributed-minio-quickstart-guide
2.1.2.1 创建存储路径
创建minio文件存储路径
mkdir -p /data/minio
注意事项
- 新创建的目录需要挂载到磁盘下要不然启动会报错
- 分布式Minio里所有的节点需要有同样的access秘钥和secret秘钥,这样这些节点才能建立联接。为了实现这个,你需要在执行minio server命令之前,先将access秘钥和secret秘钥export成环境变量。
- 分布式Minio使用的磁盘里必须是干净的,里面没有数据。
- 分布式Minio里的节点时间差不能超过3秒,你可以使用NTP 来保证时间一致。
注意:需要将新建的目录挂在到对应的磁盘下,磁盘不挂载好,集群启动会报错 Waiting for a minimum of 2 disks to come online :找不到磁盘:
mount /dev/sda1 /data
查看是否挂载成功
lsblk
2.1.2.2.创建集群启动脚本
- 集群环境建议把MINIO_ACCESS_KEY和MINIO_SECRET_KEY换成MINIO_ROOT_USER和MINIO_ROOT_PASSWORD
- 需要把下面的ip地址换成你部署节点的
vi minio-run.sh
#!/bin/bash
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=minioadmin
/usr/local/minio/minio server \
http://192.168.94.128/data/minio \
http://192.168.94.129/data/minio \
http://192.168.94.131/data/minio \
http://192.168.94.132/data/minio
2.1.2.3.分别启动4个节点
./minio-run.sh
访问任意一个节点的控制台可以查看到集群信息,表示集群环境搭建成功了。
2.2.使用docker安装
创建挂载目录
mkdir -p /docker/minio/data /docker/minio/config
配置ip4转发
vim /etc/sysctl.conf
#配置转发
net.ipv4.ip_forward=1
#重启服务,让配置生效
systemctl restart network
#查看是否成功,如果返回为“net.ipv4.ip_forward = 1”则表示成功
sysctl net.ipv4.ip_forward
构建并运行容器
docker run -p 9000:9000 -p 9001:9001 --name minio \
-it -d --restart=always \
-e "MINIO_ACCESS_KEY=minioadmin" \
-e "MINIO_SECRET_KEY=minioadmin" \
-v /docker/minio/data:/data \
-v /docker/minio/config:/root/.minio \
minio/minio server /data \
--console-address ":9001" --address ":9090"
控制台用9001端口, API用9000端口
3.实战
3.1.引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.3.4</version>
<exclusions>
<exclusion>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--内置的okhttp版本太低会在上传文件的时候报错,所以需要导入高版本的okhttp-->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>2.8.0</version>
</dependency>
3.2.配置Minio连接信息
server:
port: 8025
minio:
#minio部署的机器ip地址
endpoint: 192.168.94.128
#minio使用的端口
port: 9000
#唯一标识的账户
accessKey: minioadmin
#账户的密码
secretKey: minioadmin
#是否使用https
secure: false
#测试使用的桶名称
defaultBucketName: test
3.3.编辑Minio配置类
@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig {
/**
* minio部署的机器ip地址
*/
private String endpoint;
/**
* minio使用的端口
*/
private Integer port;
/**
*唯一标识的账户
*/
private String accessKey;
/**
* 账户的密码
*/
private String secretKey;
/**
* 如果是true,则用的是https而不是http,默认值是true
*/
private Boolean secure;
/**
* 默认使用的桶名称
*/
private String defaultBucketName;
/**
* 对象交给spring管理
*/
@Bean
public MinioClient getMinioClient() {
MinioClient minioClient = MinioClient.builder()
.endpoint(endpoint , port , secure)
.credentials(accessKey, secretKey)
.build();
return minioClient;
}
}
3.4 编写Mino工具类
@Component
@Slf4j
public class MinioUtil {
@Autowired
private MinioClient minioClient;
/**
* 获取当前日期字符串格式
* @return 2021/12/5
*/
public String getDatePath() {
LocalDateTime now = LocalDateTime.now();
return String.format("/%s/%s/%s/", now.getYear(), now.getMonthValue(), now.getDayOfMonth());
}
/**
* 判断桶是否存
* @param bucketName 桶名称
* @return
*/
public boolean bucketExists(String bucketName) throws Exception {
boolean flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (flag) {
return true;
}
return false;
}
/**
* 创建桶
*/
public boolean createBucket(String bucketName) {
try {
//判断文件存储的桶对象是否存在
boolean isExist = bucketExists(bucketName);
if (isExist) {
log.info("Bucket asiatrip already exists.");
return false;
} else {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
return true;
}
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 列出桶里的所有对象
* @param bucketName 桶名称
*/
public Iterable<Result<Item>> listObjects(String bucketName) {
return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
}
/**
* 删除桶
* @param bucketName 桶名称
* @return 是否删除成功
*/
public boolean removeBucket(String bucketName) {
try {
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(RemoveBucketArgs.builder().bucket(bucketName).build());
flag = bucketExists(bucketName);
if (!flag) {
return true;
}
}
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
return false;
}
/**
* 获取所有桶信息
*/
public List<Bucket> getAllBucket() {
try {
// 获取minio中所以的bucket
List<Bucket> buckets = minioClient.listBuckets();
for (Bucket bucket : buckets) {
log.info("bucket 名称: {} bucket 创建时间: {}", bucket.name(), bucket.creationDate());
}
return buckets;
} catch (Exception e) {
log.error("errorMsg={}",e);
return Collections.emptyList();
}
}
/**
* 上传本地文件到指定桶下
* @param bucketName 桶名称
* @param objectName 对象名称
* @param localFileName 要上传的文件路径
* @return
*/
public boolean upload(String bucketName, String objectName, String localFileName) {
try {
File file = new File(localFileName);
FileInputStream fileInputStream = new FileInputStream(file);
minioClient.putObject(PutObjectArgs.builder()
.stream(fileInputStream, file.length(), PutObjectArgs.MIN_MULTIPART_SIZE)
.bucket(bucketName)
.object(objectName)
.build());
return true;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 上传MultipartFile到指定桶下
* @param bucketName 桶名称
* @param objectName 对象名称
* @param file 文件流
*/
public boolean upload(String bucketName, String objectName, MultipartFile file) {
try {
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.stream(file.getInputStream(), file.getSize(), PutObjectArgs.MIN_MULTIPART_SIZE)
.object(objectName)
.build());
return true;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 下载文件到本地
* @param bucketName 桶名称
* @param objectName 对象名称
* @param localFileName 本地文件存储路径
*/
public boolean downLocal(String bucketName, String objectName, String localFileName) {
try {
minioClient.downloadObject(DownloadObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.filename(localFileName)
.build());
return true;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 下载文件写入到HttpServletResponse
* @param bucketName 桶名称
* @param objectName 对象名称
* @param response HttpServletResponse对象
*/
@SneakyThrows
public void downResponse(String bucketName, String objectName, HttpServletResponse response) {
GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
response.setHeader("Content-Disposition", "attachment;filename=" + objectName.substring(objectName.lastIndexOf("/") + 1));
response.setContentType("application/force-download");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(object, response.getOutputStream());
}
/**
* 删除指定桶的指定文件对象
* @param bucketName 桶名称
* @param objectName 对象名称
*/
public boolean delete(String bucketName, String objectName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
return true;
}catch (Exception e){
log.error("errorMsg={}",e);
return false;
}
}
/**
* 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
* @param bucketName 存储桶名称
* @param objectNames 含有要删除的多个object名称的迭代器对象
* @return
*/
public boolean deletes(String bucketName, List<String> objectNames) {
try {
List<String> deleteErrorNames = new ArrayList<>();
List<DeleteObject> list = new LinkedList<>();
objectNames.forEach(item -> list.add(new DeleteObject(item)));
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
deleteErrorNames.add(error.objectName());
}
return deleteErrorNames.size() == 0 ? true : false;
} catch (Exception e) {
log.error("errorMsg={}",e);
return false;
}
}
/**
* 获取文件带时效的访问链接 失效时间(以秒为单位),默认是7天不得大于七天
* @param bucketName 桶名称
* @param remoteFileName 对象名称
* @param timeout 时间
* @param unit 单位
* @return 文件访问链接
*/
public String getPresignedObjectUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit) {
try {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(remoteFileName)
.expiry((int) unit.toSeconds(timeout))
.build());
} catch (Exception e) {
log.error("errorMsg={}",e);
return null;
}
}
}
重源码中能看到getPresignedObjectUrl函数可以设置的外链的时效范围为 7天>= expiry >=1,默认值为7天
3.5.测试接口
@RestController
@RequestMapping("/file")
public class FileController {
@Resource
private MinioConfig minioConfig;
@Autowired
private MinioUtil minioUtil;
/**
* 上传文件
* @param file
* @param bucketName 桶名称
* @return 返回对象名称和外链地址
*/
@PostMapping(value = "/")
public ResponseEntity<HashMap<String, String>> uploadFile(MultipartFile file, @RequestParam(required = false) String bucketName) {
bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioConfig.getDefaultBucketName();
String objectName = minioUtil.getDatePath() + file.getOriginalFilename();
minioUtil.upload(bucketName, objectName, file);
String viewPath = minioUtil.getPresignedObjectUrl(bucketName, objectName, 60, TimeUnit.SECONDS);
HashMap<String, String> objectInfo = new HashMap<>();
objectInfo.put("objectName", objectName);
//只能预览图片、txt等部分文件
objectInfo.put("viewPath", viewPath);
return ResponseEntity.ok(objectInfo);
}
/**
* 删除指定桶里的某个对象
* @param bucketName 桶名称
* @param objectName 对象名称
* @return
*/
@DeleteMapping(value = "/")
public ResponseEntity<String> deleteByPath(@RequestParam(required = false) String bucketName, String objectName) {
bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioConfig.getDefaultBucketName();
minioUtil.delete(bucketName, objectName);
return ResponseEntity.ok("删除成功");
}
/**
* 下载文件
* @param bucketName 桶名称
* @param objectName 对象名称
* @param response 相应结果
*/
@GetMapping("/")
public void downLoad(@RequestParam(required = false) String bucketName, String objectName,HttpServletResponse response) {
// 获取文件
minioUtil.downResponse(bucketName,objectName,response);
}
}
4.测试
4.1.上传文件
通过postman上传文件到minio上面,会返回一个对象名称和一个带时效的外链名称
可以复制外链地址到浏览器上面进行下载该文件
因为代码里配置了链接的有效期为1分钟,超时后再访问就失效了
4.2.下载文件
浏览器访问接口参数为对象的名称可以下载对应的文件
http://localhost:8025/file/?objectName=/test/2021/12/5/booklist.txt
4.3.删除文件
通过postman设置DELETE请求类型调用删除文件接口
localhost:8025/file/?objectName=2021/12/5/booklist.txt
5.设置桶里的文件访问权限为公开的
设置完就可以通过浏览器直接访问文件了。
访问方式为http://192.168.94.128:API端口/桶名称/对象名称
例如: http://192.168.94.128:9000/test/2021/12/5/booklist.txt
6.项目配套代码
创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star
【SpringBoot框架篇】其它文章如下,后续会继续更新。
- 1.搭建第一个springboot项目
- 2.Thymeleaf模板引擎实战
- 3.优化代码,让代码更简洁高效
- 4.集成jta-atomikos实现分布式事务
- 5.分布式锁的实现方式
- 6.docker部署,并挂载配置文件到宿主机上面
- 7.项目发布到生产环境
- 8.搭建自己的spring-boot-starter
- 9.dubbo入门实战
- 10.API接口限流实战
- 11.Spring Data Jpa实战
- 12.使用druid的monitor工具查看sql执行性能
- 13.使用springboot admin对springboot应用进行监控
- 14.mybatis-plus实战
- 15.使用shiro对web应用进行权限认证
- 16.security整合jwt实现对前后端分离的项目进行权限认证
- 17.使用swagger2生成RESTful风格的接口文档
- 18.使用Netty加websocket实现在线聊天功能
- 19.使用spring-session加redis来实现session共享
- 20.自定义@Configuration配置类启用开关
- 21.对springboot框架编译后的jar文件瘦身
- 22.集成RocketMQ实现消息发布和订阅
- 23.集成smart-doc插件零侵入自动生成RESTful格式API文档
- 24.集成FastDFS实现文件的分布式存储
- 25.集成Minio实现文件的私有化对象存储
- 26.集成spring-boot-starter-validation对接口参数校验
- 27.集成mail实现邮件推送带网页样式的消息
- 28.使用JdbcTemplate操作数据库
- 29.Jpa+vue实现单模型的低代码平台
- 30.使用sharding-jdbc实现读写分离和分库分表
- 31.基于分布式锁或xxx-job实现分布式任务调度
- 32.基于注解+redis实现表单防重复提交
- 33.优雅集成i18n实现国际化信息返回
- 34.使用Spring Retry完成任务的重试