1、MinIO是什么
MinIO 是一个基于 Apache License v2.0 开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
2、官网
3、优点
- 部署简单,支持各种平台
- 海量存储,支持单个对象最大5TB
- 兼容 Amazon S3接口
- 低冗余,磁盘损坏高容忍
- 读写性能优异
4、通过docker部署MinIO
创建Access Key
5、SpringBoot整合MinIO
pom依赖引入
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.5.5</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
yml文件配置
spring:
application:
name: springboot-demo
# 配置文件上传大小限制
servlet:
multipart:
max-file-size: 200MB
max-request-size: 200MB
minio:
endpoint: http://ip:9000
access-key: 你的access-key
secret-key: 你的secret-key
bucket: 桶名
创建minio配置类
package com.example.springbootdemo.config;
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>
* minio配置类
* </p>
*
* @author yurenwei
* @since 2023/9/22
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
/**
* 访问地址
*/
private String endpoint;
/**
* 账户
*/
private String accessKey;
/**
* 秘钥
*/
private String secretKey;
/**
* 桶
*/
private String bucket;
/**
* 创建MinioClient客户端
*
* @return
*/
@Bean
public MinioClient getMinioClient() {
return MinioClient.builder()
.endpoint(getEndpoint())
.credentials(getAccessKey(),getSecretKey())
.build();
}
}
创建minio工具类
package com.example.springbootdemo.util;
import cn.hutool.core.date.DatePattern;
import com.example.springbootdemo.vo.FileVO;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* <p>
* minio工具类
* </p>
*
* @author yurenwei
* @since 2023/9/22
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtil {
private final MinioClient minioClient;
/**
* 创建存储桶
* 如果没有存储桶则创建
*
* @param bucketName 存储桶
*/
@SneakyThrows(Exception.class)
private void createBucket(String bucketName) {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}
/**
* 判断存储桶是否存在
*
* @param bucketName 存储桶
* @return boolean
*/
@SneakyThrows(Exception.class)
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}
/**
* 获得所有存储桶列表
*
* @return List<Bucket>
*/
@SneakyThrows(Exception.class)
public List<Bucket> getAllBuckets() {
return minioClient.listBuckets();
}
/**
* 根据存储桶获取其相关信息
*
* @param bucketName 存储桶
* @return Bucket
*/
@SneakyThrows(Exception.class)
public Bucket getBucket(String bucketName) {
return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst().orElse(null);
}
/**
* 根据bucketName删除存储桶
*
* @param bucketName 存储桶
*/
@SneakyThrows(Exception.class)
public void removeBucket(String bucketName) {
minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
}
/**
* 上传文件
*
* @param fileList 文件集合
* @param bucketName 存储桶
* @return List<FileVO>
*/
public List<FileVO> uploadFile(MultipartFile[] fileList, String bucketName) {
try {
List<FileVO> list = new ArrayList<>();
createBucket(bucketName);
for (MultipartFile file : fileList) {
String oldName = file.getOriginalFilename();
assert oldName != null;
String fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN))+oldName.substring(oldName.lastIndexOf("."));
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(file.getInputStream(), file.getSize(), 0)
.contentType(file.getContentType())
.build()
);
String url = this.getObjUrl(bucketName, fileName);
list.add(new FileVO().setOldFileName(oldName)
.setNewFileName(fileName)
.setFileUrl(url.substring(0, url.indexOf("?")))
);
}
return list;
} catch (Exception e) {
log.error("上传文件出错:{}", e.getMessage());
return null;
}
}
/**
* 获取文件链接
*
* @param bucketName 存储桶
* @param fileName 文件名称
* @return String
*/
@SneakyThrows(Exception.class)
public String getObjUrl(String bucketName, String fileName) {
return minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.bucket(bucketName)
.object(fileName)
.method(Method.GET)
.expiry(30, TimeUnit.SECONDS)
.build()
);
}
/**
* 下载文件
*
* @param bucketName 存储桶
* @param fileName 文件名称
*/
@SneakyThrows(Exception.class)
public InputStream download(String bucketName, String fileName) {
return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
}
/**
* 删除文件
*
* @param bucketName 存储桶
* @param fileName 文件名称
*/
@SneakyThrows(Exception.class)
public void removeFile(String bucketName, String fileName) {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
}
}
创建文件对象
package com.example.springbootdemo.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* <p>
* 文件对象
* </p>
*
* @author yurenwei
* @since 2023/9/22
*/
@ApiModel(value = "不同状态数量视图对象", description = "不同状态数量")
@Accessors(chain = true)
@Data
public class FileVO implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "原文件名")
private String oldFileName;
@ApiModelProperty(value = "新文件名")
private String newFileName;
@ApiModelProperty(value = "文件地址")
private String fileUrl;
}
创建controller
package com.example.springbootdemo.controller;
import com.example.springbootdemo.config.MinioConfig;
import com.example.springbootdemo.util.MinioUtil;
import com.example.springbootdemo.util.Result;
import com.example.springbootdemo.vo.FileVO;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;
/**
* <p>
* minio控制器
* </p>
*
* @author yurenwei
* @since 2023/9/22
*/
@Slf4j
@Api(tags = "minio接口")
@RestController
@RequestMapping("/api/minio")
public class MinioController {
@Autowired
private MinioUtil minioUtil;
@Autowired
private MinioConfig minioConfig;
/**
* 文件上传
*
* @param file
*/
@PostMapping("/upload")
public Result<List<FileVO>> upload(@RequestParam("file") MultipartFile file) {
List<FileVO> fileList = minioUtil.uploadFile(new MultipartFile[]{file}, minioConfig.getBucket());
return Result.ok(fileList);
}
/**
* 删除
*
* @param fileName 文件名
*/
@DeleteMapping("/delete")
public Result delete(@RequestParam("fileName") String fileName) {
minioUtil.removeFile(minioConfig.getBucket(), fileName);
return Result.ok();
}
/**
* 文件下载
*
* @param fileName 文件名
* @param response 响应
*/
@GetMapping("/download")
public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) {
try {
InputStream stream = minioUtil.download(minioConfig.getBucket(), fileName);
ServletOutputStream output = response.getOutputStream();
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName.substring(fileName.lastIndexOf("/") + 1), "UTF-8"));
response.setContentType("application/octet-stream");
response.setCharacterEncoding("UTF-8");
IOUtils.copy(stream, output);
} catch (IOException e) {
e.printStackTrace();
}
}
}
6、测试
测试上传文件
查看minio客户端
发现已成功上传值minio
测试下载文件
打开文件是我上传的excel文件的内容数据
测试删除文件
查看minio
发现刚才的文件已被删除了
至此,SpringBoot整合MinIO完成结束了。