1、简介
Minio 是一个基于Apache License v2.0开源协议的对象存储服务
。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化
的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
Minio是一个非常轻量的服务,可以很简单的和其他应用的结合,类似 NodeJS, Redis 或者 MySQL。
2、容器安装
将独立的 MinIO 服务器作为容器运行,独立的 MinIO 服务器最适合早期开发和评估。 某些功能,例如版本控制、对象锁定和存储桶复制 需要使用擦除编码分布式部署 MinIO。
2.1、Docker
docker run -p 9000:9000 \
--name minio1 \
-v /mnt/data:/data \
-e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
-e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
minio/minio server /data
# 命令详解
# -e MINIO_ROOT_USER 指定用户名
# -e MINIO_ROOT_PASSWORD 指定密码
# -v 挂载目录,持久化minio目录
2.2、Linux
# 下载
wget https://dl.min.io/server/minio/release/linux-amd64/minio
# 加权
chmod +x minio
# 启动 你可以更换后面的目录为你自己需要的目录
./minio server /data
2.3、windows(详细)
(1)下载:
http://dl.minio.org.cn/server/minio/release/windows-amd64/minio.exe
(2)启动:
minio.exe server D:\
将“D:\”替换为您希望 MinIO 存储数据的驱动器或目录的路径。 您必须将终端或 powershell 目录更改为 minio.exe
可执行文件的位置,或将该目录的路径添加到系统 $PATH
中。
注:也就是需要先创建好存储文件的目录,如我的启动命令为:minio.exe server D:\data,保存为bat文件,双击启动即可。
上图就启动成功了,警告先不管,访问http://192.168.0.123:9000即可。之前创建的文件也有初始化内容了,还没有文件。
3、使用说明
(1)访问地址:
http://{ip}:{port}/minio/ 或 http://{ip}:{port}
默认的 root 凭据 minioadmin:minioadmin。
这样就可以使用浏览器来创建桶、上传对象以及浏览 MinIO 服务器的内容了。
(2)创建buckets
(3)上传文件
去看一下之前的目录,文件也有了
4、JAVA使用minio的方法
上面的是直接才界面控制台进行创建桶、上传文件、删除文件等操作,下面来使用java代码操作,写个简单demo,演示连接到一个对象存储服务,创建一个存储桶并上传一个文件到该桶中。
(1)添加依赖
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.1.0</version>
</dependency>
(2)参数配置
需要有存储服务的三个参数才能连接到该服务
参数 | 说明 |
Endpoint | 对象存储服务的URL |
Access Key | Access key就像用户ID,可以唯一标识你的账户。 |
Secret Key | Secret key是你账户的密码。 |
我这里配置在yaml文件中的:
(3)编写minio配置信息
package com.xxx.xxx.common.config;
import com.xxx.xxx.common.exception.MinioException;
import io.minio.BucketExistsArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.SetBucketPolicyArgs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* minio自动配置.
*
* @author Mei rx
* @since 2021/08/05
*/
@Configuration
@ConfigurationProperties(prefix = "minio")
@ConditionalOnClass({MinioClient.class})
@ConditionalOnProperty({"minio.endpoint"})
public class MinioAutoConfiguration {
@Autowired
private Environment environment;
public MinioAutoConfiguration() {
}
@Bean
public MinioClient minioClient() {
String endpoint = this.environment.getProperty("minio.endpoint");
String accessKey = this.environment.getProperty("minio.accessKey");
String secretKey = this.environment.getProperty("minio.secretKey");
String bucketName = this.environment.getProperty("minio.bucketName");
if (endpoint != null && !"".equals(endpoint)) {
if (accessKey != null && !"".equals(accessKey)) {
if (secretKey != null && !"".equals(secretKey)) {
if (bucketName != null && !"".equals(bucketName)) {
MinioClient minioClient = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
this.makeBucket(minioClient, bucketName);
return minioClient;
} else {
throw new MinioException("存储桶名称未在application.yml配置!");
}
} else {
throw new MinioException("Minio密码未在application.yml配置!");
}
} else {
throw new MinioException("Minio用户名未在application.yml配置!");
}
} else {
throw new MinioException("Minio的URL未在application.yml配置!");
}
}
private void makeBucket(MinioClient minioClient, String bucketName) {
try {
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
String policyJson = "Bucket already exists.";
minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(policyJson).build());
}
} catch (Exception e) {
throw new MinioException("创建minio存储桶异常", e);
}
}
}
(4)编写上传代码
public static String uploadFile(InputStream inputStream, String objectName, String fileName) {
if (StringUtil.isNotBlank(fileName)) {
objectName = objectName + "/" + fileName;
}
try {
if (objectName != null && !"".equals(objectName)) {
try {
minioUtil.minioClient.putObject(PutObjectArgs
.builder()
.bucket(minioUtil.bucketName)
.object(objectName)
.stream(inputStream, inputStream.available(), -1)
.build());
log.info("文件上传成功!");
} catch (Exception var4) {
log.error("添加存储对象异常", var4);
throw new MinioException("添加存储对象异常", var4);
}
} else {
throw new MinioException("存储对象名称objectName不能为空!");
}
log.info("文件上传成功!");
return minioUtil.getUrl(objectName);
} catch (Exception var4) {
var4.printStackTrace();
log.error("上传发生错误: {}!", var4.getMessage());
return var4.getMessage();
}
}
(5)调用上传方法
/**
* 上传材料
*/
@PostMapping("/upload")
@ApiOperation("上传文件")
public String upload(
@ApiParam(value = "文件", example = "11.jpg", required = true)
@RequestPart(value = "file") MultipartFile file) throws IOException {
log.info("uploadFile:上传文件[file:{}]", file);
return MinioUtil.uploadFile(file.getInputStream(), "my-file", file.getOriginalFilename());
}
(6)测试
调用接口上传文件成功,返回一个可访问的url,访问一下
注意:如果出现如下提示,很有可能是buckets的权限问题,修改一下权限即可
附上工具类:
package com.xxx.xxx.util;
import com.xxx.core.minio.exception.MinioException;
import com.xxx.core.tool.utils.Charsets;
import com.xxx.core.tool.utils.DateUtil;
import com.xxx.core.tool.utils.StringUtil;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* minio存储对象工具类.
*
* @author Mei rx
* @since 2021/09/24
*/
@Component
@Slf4j
public class MinioUtil {
@Value("${minio.bucketName}")
private String bucketName;
@Resource
private MinioClient minioClient;
private static MinioUtil minioUtil;
public MinioUtil() {
}
@PostConstruct
public void init() {
minioUtil = this;
}
public static String uploadFile(InputStream inputStream, String objectName, String fileName) {
if (StringUtil.isNotBlank(fileName)) {
objectName = objectName + "/" + fileName;
}
try {
if (objectName != null && !"".equals(objectName)) {
try {
minioUtil.minioClient.putObject(PutObjectArgs
.builder()
.bucket(minioUtil.bucketName)
.object(objectName)
.stream(inputStream, inputStream.available(), -1)
.build());
log.info("文件上传成功!");
} catch (Exception e) {
log.error("添加存储对象异常", e);
throw new MinioException("添加存储对象异常", e);
}
} else {
throw new MinioException("存储对象名称objectName不能为空!");
}
log.info("文件上传成功!");
return minioUtil.getUrl(objectName);
} catch (Exception ex) {
ex.printStackTrace();
log.error("上传发生错误: {}!", ex.getMessage());
return ex.getMessage();
}
}
public static InputStream download(String fileUrl) {
try {
fileUrl = fileUrl.substring(fileUrl.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
InputStream inputStream = minioUtil.get(fileUrl);
log.info("下载成功");
return inputStream;
} catch (Exception e) {
e.printStackTrace();
log.error("下载发生错误: {}!", e.getMessage());
return null;
}
}
public static void batchDownload(List<String> fileUrlList, String zipName, HttpServletResponse httpServletResponse) {
ZipOutputStream zos;
ZipEntry zipEntry;
byte[] buff = new byte[1024];
if (fileUrlList != null && !fileUrlList.isEmpty()) {
try {
if (StringUtil.isEmpty(zipName)) {
zipName = "批量下载" + DateUtil.format(new Date(), "yyyyMMddHHmmss");
}
//清除缓冲区中存在的所有数据以及状态代码和标头。如果已提交响应,则此方法将抛出IllegalStateException
httpServletResponse.reset();
//Content-Disposition为属性名,attachment以附件方式下载,filename下载文件默认名字
httpServletResponse.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(zipName, Charsets.UTF_8.name()) + ".zip");
//另存为弹框加载
httpServletResponse.setContentType("application/x-msdownload");
httpServletResponse.setCharacterEncoding("utf-8");
zos = new ZipOutputStream(httpServletResponse.getOutputStream());
for (String fileUrl : fileUrlList) {
//获取minio对应路径文件流(从数据库中获取的url是编码的,这里要先进行解码,不然minioClient.getObject()方法里面会再进行一次编码,就获取不到对象)
String url = URLDecoder.decode(fileUrl,Charsets.UTF_8.name());
String downloadUrl = url.substring(url.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
InputStream inputStream = minioUtil.get(downloadUrl);
String fileName = url.substring(url.lastIndexOf("/"), url.lastIndexOf("."));
zipEntry = new ZipEntry(fileName + url.substring(url.lastIndexOf(".")));
zos.putNextEntry(zipEntry);
int length;
while ((length = inputStream.read(buff)) > 0) {
zos.write(buff, 0, length);
}
}
log.info("批量下载成功!");
zos.close();
} catch (IOException ioException) {
ioException.printStackTrace();
log.error("批量下载发生错误! msg=" + ioException.getMessage());
} finally {
}
} else {
log.error("批量下载发生错误,文件访问路径集合不能为空!");
}
}
public static void removeFile(String fileUrl) {
try {
String downloadUrl = fileUrl.substring(fileUrl.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
minioUtil.rm(downloadUrl);
log.info("文件删除成功!");
} catch (Exception e) {
e.printStackTrace();
log.error("文件删除失败! msg=" + e.getMessage());
}
}
public static void batchRemoveFile(List<String> fileUrlList) {
if (fileUrlList != null && !fileUrlList.isEmpty()) {
try {
for (String fileUrl : fileUrlList) {
String downloadUrl = fileUrl.substring(fileUrl.indexOf(minioUtil.bucketName) + minioUtil.bucketName.length());
minioUtil.rm(downloadUrl);
}
log.info("文件批量删除成功!");
} catch (Exception e) {
e.printStackTrace();
log.error("文件批量删除失败! msg=" + e.getMessage());
}
}
}
public InputStream get(String objectName) {
InputStream inputStream;
try {
inputStream = this.minioClient.getObject(GetObjectArgs.builder().bucket(this.bucketName).object(objectName).build());
return inputStream;
} catch (Exception e) {
log.error("读取存储对象异常", e);
throw new MinioException("读取存储对象异常", e);
}
}
public void download(String objectName, String fileName) {
try {
this.minioClient.downloadObject(DownloadObjectArgs.builder().bucket(this.bucketName).object(objectName).filename(fileName).build());
} catch (Exception e) {
log.error("下载发生错误:[{}]", e.getMessage());
throw new MinioException("下载发生错误", e);
}
}
public String getUrl(String objectName) {
try {
return this.minioClient.getObjectUrl(this.bucketName, objectName);
} catch (Exception e) {
log.error("获取存储对象url异常", e);
throw new MinioException("获取存储对象url异常", e);
}
}
public void rm(String objectName) {
try {
this.minioClient.removeObject(RemoveObjectArgs.builder().bucket(this.bucketName).object(objectName).build());
} catch (Exception e) {
log.error("删除存储对象异常", e);
throw new MinioException("删除存储对象异常", e);
}
}
public void rmBatch(Collection<String> objectNames) {
if (!CollectionUtils.isEmpty(objectNames)) {
objectNames.forEach(this::rm);
}
}
}