Minio
Minio
1什么是minio?
MinIO 是一款高性能、分布式的对象存储系统. 它是一款软件产品, 可以100%的运行在标准硬件。即X86等低成本机器也能够很好的运行MinIO。
MinIO提供高性能、S3兼容的对象存储。Minio 是一个基于Go语言的对象存储服务。它实现了大部分亚马逊S3云存储服务接口,可以看做是是S3的开源版本,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。区别于分布式存储系统,minio的特色在于简单、轻量级,对开发者友好,认为存储应该是一个开发问题而不是一个运维问题。
MinIO是Kubernetes的原生产品,是唯一一个可在每个公共云、每个Kubernetes发行版、私有云和边缘上使用的对象存储套件。MinIO是软件定义的,在GNU AGPL v3下是100%开源的。
MinIO与传统的存储和其他的对象存储不同的是:它一开始就针对性能要求更高的私有云标准进行软件架构设计。因为MinIO一开始就只为对象存储而设计。所以他采用了更易用的方式进行设计,它能实现对象存储所需要的全部功能,在性能上也更加强劲,它不会为了更多的业务功能而妥协,失去MinIO的易用性、高效性。 这样的结果所带来的好处是:它能够更简单的实现局有弹性伸缩能力的原生对象存储服务。
MinIO 在最大数量的环境中支持最广泛的用例。自推出云原生以来,MinIO 的软件定义套件在公共云、私有云和 边缘无缝运行- 使其成为混合云和多云对象存储的领导者。凭借行业领先的性能 和可扩展性,MinIO 可以提供一系列用例,包括 AI/ML、分析、备份/恢复以及现代 Web 和移动应用程序。
MinIO在传统对象存储用例(例如辅助存储,灾难恢复和归档)方面表现出色。同时,它在机器学习、大数据、私有云、混合云等方面的存储技术上也独树一帜。当然,也不排除数据分析、高性能应用负载、原生云的支持。
在中国:阿里巴巴、腾讯、百度、中国联通、华为、中国移动等等9000多家企业也都在使用MinIO产品
- 社区地址 - https://slack.min.io
- 文档地址(文档推荐)- https://docs.min.io
- 中文文档-http://docs.minio.org.cn/docs/
- 博客地址- https://blog.min.io
- 官网地址- https://min.io
2、集成
Windows
官网
MinIO | 高性能, Kubernetes 原生对象存储
启动
这里以我的为例:
将minio.exe放在了D盘
D:\minio\minio.exe
然后在D:\minio下新建data文件夹用来存放上传的文件
D:\minio\data
最后,在D:\minio路径上输入cmd进入命令行
输入:
**minio.exe server D:\minio\data **
minio默认启动端口号为9000
直接访问http://localhost:9000即可

箭头所指的就是登录密码和账户
Linux
下载安装
https://dl.min.io/server/minio/release/linux-amd64/minio
创建 minio 文件夹以及 data文件夹
mkdir /home/minio
mkdir /home/minio/data
将下载好的文件放入/home/minio中
先进入/home/minio中赋值权限
chmod +x minio
尝试启动
./minio server /home/minio/data

出现这个提示则添加映射路径
./minio server --console-address '0.0.0.0:9999' /home/minio/data
nohup ./minio server --console-address '0.0.0.0:9999' /home/minio/data & #后台启动
说明:9999端口号是控制台端口号,整合文件上传时端口号不能写它。

警告说的是建议修改账号密码 默认账号密码为minioadmin端口为9000
export MINIO_ACCESS_KEY=XXXXXX
export MINIO_SECRET_KEY=XXXXXX
说明:9000端口号是控制台端口号,整合文件上传时端口号写它。
现在直接访问,若出现进不去页面大概是没开放端口,因为映射了端口9999所以需要开放9000和9999
开放端口命令
firewall-cmd --zone=public --add-port=9000/tcp --permanent
firewall-cmd --zone=public --add-port=9999/tcp --permanent
firewall-cmd --reload
访问路径
http://ip/9000
注意:密码不对也不会启动成功
java操作
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--minio依赖-->
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>8.4.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!---->
<!-- knife4j -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- swagger3 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
配置文件
server:
port: 8081
spring:
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
minio:
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin
bucketName: test
Minio客户端配置类
@Data
@Configuration
public class MinioClientConfig {
@Value("${minio.endpoint}")
private String endpoint;
@Value("${minio.accessKey}")
private String accessKey;
@Value("${minio.secretKey}")
private String secretKey;
@Value("${minio.bucketName}")
private String bucketName;
//注入MinioClient客户端
@Bean
public MinioClient minioClient(){
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey,secretKey)
.build();
}
}
Minio常用方法
@Slf4j
@Component
public class MinioUtil {
@Autowired
private MinioClient minioClient;
@Value("${minio.bucketName}")
private String bucketName;
@Value("${minio.endpoint}")
private String endpoint;
/**
* 判断bucket是否存在
* @param name
*/
public boolean existBucket(String name){
//根据bucketName查看是否存在
try {
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder()
.bucket(name)
.build());
if(!exists){
return false;
}
}catch (Exception e){
e.printStackTrace();
}
return true;
}
/**
* 创建存储bucketName
* @param name
* @return
*/
public boolean makeBucket(String name){
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(name)
.build());
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* 删除存储bucketName
* @param name
* @return
*/
public boolean removeBucket(String name){
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(name)
.build());
}catch (Exception e){
e.printStackTrace();
return false;
}
return true;
}
/**
* description: 上传文件
*
* @param multipartFile
* @return: java.lang.String
*/
public List<String> upload(MultipartFile[] multipartFile) {
List<String> names = new ArrayList<>(multipartFile.length);
for (MultipartFile file : multipartFile) {
//获取文件原始名:2.jpg
String fileName = file.getOriginalFilename();
//必须写出\\.这是转义,将文件名从.开始分割成 2 和 jpg
String[] split = fileName.split("\\.");
if (split.length > 1) {
//防止文件重名:添加系统时间:文件名为:2_37598375837.jpg
//方式1:
//fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1];
//方式2:
// 完善1、 --> 在文件名中添加唯一值
String uuid = UUID.randomUUID().toString().replace("-", "");
fileName = uuid + split[0] + "." + split[1];
// 完善2、 --> 把文件按照日期分类
String datePath = new DateTime().toString("yyyy/MM/dd");
// 拼接时间 yyyy/MM/dd/filename
fileName = datePath + "/" + fileName;
} else {
fileName = fileName + System.currentTimeMillis();
}
InputStream in = null;
try {
//开始上传
in = file.getInputStream();
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(fileName)
.stream(in, in.available(), -1)
.contentType(file.getContentType())
.build()
);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭流
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
fileName = endpoint + "/" + bucketName + "/" + fileName;
names.add(fileName);
}
//返回图片地址url
return names;
}
/**
* description: 下载文件
*
* @param fileName
* @return: org.springframework.http.ResponseEntity<byte [ ]>
*/
public ResponseEntity<byte[]> download(String fileName) {
ResponseEntity<byte[]> responseEntity = null;
InputStream in = null;
ByteArrayOutputStream out = null;
try {
//通过文件名获取
in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
out = new ByteArrayOutputStream();
IOUtils.copy(in, out);
//封装返回值
byte[] bytes = out.toByteArray();
HttpHeaders headers = new HttpHeaders();
try {
headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
headers.setContentLength(bytes.length);
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setAccessControlExposeHeaders(Arrays.asList("*"));
responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return responseEntity;
}
/**
* 查看文件对象
* @param bucketName 存储bucket名称
* @return 存储bucket内文件对象信息
*/
public List<MinioBean> listObjects(String bucketName) {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).build());
List<MinioBean> objectItems = new ArrayList<>();
try {
for (Result<Item> result : results) {
Item item = result.get();
MinioBean objectItem = new MinioBean();
objectItem.setName(item.objectName());
objectItem.setSize(item.size());
objectItems.add(objectItem);
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
return objectItems;
}
/**
* 删除指定文件
* @param name
* @param file
*/
public boolean deleteFile(String name,String file){
try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(name).object(file).build());
}catch (Exception e){
return false;
}
return true;
}
//批量删除文件
//批量删除Minio里面的图片
for (Photo photo : photoList) {
String url = photo.getUrl();
String fileName = url.substring(url.lastIndexOf("/") + 1);
minioClient.removeObject(RemoveObjectArgs.builder().bucket(MinioClientConfig.BUCKET_NAME).object(fileName).build());
}
}
获取文件信息的一个实体类
@Data
@Component
@AllArgsConstructor
@NoArgsConstructor
public class MinioBean {
private String name;
private Long size;
}
控制层
@Api(tags = "Minio测试")
@RestController
public class MinioController {
@Autowired
private MinioUtil minioUtil;
//多文件上传,因为是一个数组接收,所以可以一次性上传多个文件
@PostMapping("/upload")
public Object upload(MultipartFile[] file) {
List<String> upload = minioUtil.upload(file);
return upload;
}
//判断bucket是否存在
@GetMapping("/exist")
public boolean exists(@RequestParam("bucketName") String bucketName){
return minioUtil.existBucket(bucketName);
}
//创建存储bucketName
@PostMapping("/create")
public boolean makeBucket(@RequestParam("bucketName") String bucketName){
return minioUtil.makeBucket(bucketName);
}
//删除存储bucketName
@DeleteMapping("/delete")
public boolean deleteBucket(@RequestParam("bucketName") String bucketName){
return minioUtil.removeBucket(bucketName);
}
@PostMapping("/download")
public Object download(@RequestParam("fileName") String fileName){
return minioUtil.download(fileName);
}
//查看文件对象
@ApiOperation(value = "查看文件对象")
@GetMapping("/list")
public List<MinioBean> list(@RequestParam("bucketName") String bucketName){
return minioUtil.listObjects(bucketName);
}
@ApiOperation(value = "批量删除文件对象")
//批量删除文件对象
@DeleteMapping("/batchDelete/{bucketName}")
public boolean batchDeleteBucket(@PathVariable("bucketName") String bucketName, @RequestParam List<String> fileNames) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
return minioUtil.batchDeleteBucket(bucketName,fileNames);
}
}
3、测试小插曲
List传参数问题
@ApiOperation(value = "批量删除文件对象")
//批量删除文件对象
@DeleteMapping("/batchDelete/{bucketName}")
public boolean batchDeleteBucket(@PathVariable("bucketName") String bucketName, @RequestParam List<String> fileNames) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
return minioUtil.batchDeleteBucket(bucketName,fileNames);
}
测试时逗号连接即可

4、文件返回地址展示
