MINIO介绍
什么是对象存储?
以阿里云OSS为例:
对象存储服务OSS(Object Storage Service)是一种海量、安全、低成本、高可靠的云存储服务,适合存放任意类型的文件。容量和处理能力弹性扩展,多种存储类型供选择,全面优化存储成本。
优势就在于它可以存储大容量的非结构化数据。
缺点:没有选择业务上云或者想要下云的企业,使用公有云的 OSS,在公网带宽方面就需要有一定的投入,毕竟需要通过公网传输,带宽太小,传输速度就会慢,且在传输过程中数据的安全性和完整性也有损失的风险,走专线的费用又十分昂贵,不实在。
此时MinIO 就是一个不错的选择,麻雀虽小,五脏俱全,企业可以以此快速构建自己内部的对象存储服务。
什么是minio?
Minio 是个基于 Golang 编写的开源对象存储套件,基于Apache License v2.0开源协议,虽然轻量,却拥有着不错的性能。
可以很简单的和NodeJS、Redis、MySQL等结合使用。
minio结构:
由桶(bucket,对应Windows下的文件夹),组成目录结构,桶中直接存放对象(Object,对应Windwos下的文件),桶中不能再创建桶,但是能创建文件夹 。
minio特性:
高性能、可扩展、云原生、图形化界面、支持纠删码。除了Minio自己的文件系统,还支持 DAS、 JBODs、NAS、Google云存储和 Azure Blob存储。Minio服务器通过其兼容AWS SNS / SQS的事件通知服务触发Lambda功能。支持的目标是消息队列,如Kafka,NATS,AMQP,MQTT,Webhooks以及Elasticsearch,Redis,Postgres和MySQL等数据库。
minio使用:
1.下载:
1.在home目录下创建minio文件夹
mkdir /home/minio
2、进入/home/minio 文件夹
cd /home/minio
3、下载文件
wget https://dl.min.io/server/minio/release/linux-amd64/minio
注意:如果上一行报错,请先运行yun install wget
4.创建数据文件夹
mkdir /home/minio/data
mkdir /home/minio/log
5.创建日志文件
touch /home/minio/log/minio.log
6.启动
6.1赋予权限
chmod 777 minio
6.2.1前台启动命令
./minio server /home/minio/data
6.2.2后台启动命令
nohup ./minio server /home/minio/data > /home/minio/log/minio.log &
6.2.3后台指定端口号启动(以9001为例)
nohup ./minio server --console-address ":9001" /home/minio/data > /home/minio/log/minio.log 2>&1 &
7.修改超管账户名和密码(为了安全)
7.1打开 /etc/profile 文件
vim /etc/profile
7.2在文件的最末尾加上以下信息
注意看提示,新版本需要用MINIO_ROOT_USER和MINIO_ROOT_PASSWORD,
旧版需要用MINIO_ACCESS_KEY和MINIO_SECRET_KEY
按 i 键后,在文档末尾输入
(新版)
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=你想改的密码
(旧版)
export MINIO_ACCESS_KEY=minioadmin
export MINIO_SECRET_KEY=你想改的密码
注:这里如果你修改的配置密码不生效,记得重载环境变量配置文件,并重启minio服务(用ps找到然后kill掉重新启动)
8.保存退出esc :wq
9.重载配置
source /etc/profile
10.打开127.0.0.1:9000 即可看到运行页面
1.通过命令查看端口
ps -aux | grep minio
2.kill杀死进程
kill -9 进程号(五位数字)
3.开放下载,设置永久链接
3.1下载客户端
wget https://dl.minio.io/client/mc/release/linux-amd64/mc
3.2赋予权限
chomd 777mc
3.3添加server
./mc config host add minio htt//你的ip:9000/ minioadmin 你的minio密码
3.4设置需要开放下载的bucket
./mc anonymous set download minio/dev
4.文件访问地址:
http://你的ip:9000/dev/年/月/日/文件名
minio在springboot中使用
1.导入依赖 刷新maven
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>7.0.2</version>
</dependency>
2.在application.yml中新增配置
# Miniio配置
minio:
endpoint: 127.0.0.1 #ip地址
port: 9000 # 端口号
accessKey: minioadmin # 账号
secretKey: minioadmin # 密码
secure: false #如果是true,则用的是https而不是http,默认值是true
bucketName: "guoba" # 桶的名字
configDir: "/home/guoba" #保存到本地的路径
3.java代码接口
/**
* 文件上传至Minio
* 使用try catch finally进行上传
* finally里进行资源的回收
*/
@Override
public AjaxResult upload(MultipartFile file) {
InputStream inputStream = null;
//创建Minio的连接对象
MinioClient minioClient = getClient();
//桶对象
String bucketName = minioConfig.getBucketName();
try {
inputStream = file.getInputStream();
//基于官网的内容,判断文件存储的桶是否存在 如果桶不存在就创建桶
boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().build());
if (exists) {
System.out.println("该桶已经存在");
} else {
minioClient.makeBucket(MakeBucketArgs.builder().build());
}
/**
* ================================操作文件================================
* 思路:我们上传的文件是:文件.pdf
* 那么我们应该上传到配置的bucket内 我们配置的bucketName是name
* 那么我们存在桶里的文件应该是什么样的 也叫“文件.pdf”吗?
* 应该按照上传的年月日进行区分
* 举例:2021-05-05日进行上传的
* 那么存在桶里的路径应该是
* name/2021/05/05/这个目录下
* 而对于同一个文件,存在重名问题,所以我们应该利用UUID生成一个新的文件名,并拼接上 .pdf 作为文件后缀
* 那么完整的路径就是 name/2021/05/05/uuid.pdf
*
* 如果上述思路你无法理解,那么就直接存放在桶内生成uuid+.pdf即可
* 即:name/uuid.pdf
*/
//操作文件
String fileName = file.getOriginalFilename();
String objectName = new SimpleDateFormat("yyyy/MM/dd/").format(new Date()) + UUID.randomUUID().toString().replaceAll("-", "")
+ fileName.substring(fileName.lastIndexOf("."));
PutObjectArgs objectArgs = PutObjectArgs.builder().object(objectName)
.bucket(bucketName)
.contentType(file.getContentType())
.stream(file.getInputStream(), file.getSize(), -1).build();
minioClient.putObject(objectArgs);
//封装访问的url给前端
AjaxResult ajax = AjaxResult.success();
ajax.put("fileName", "/" + bucketName + "/" + objectName);
//url需要进行截取
ajax.put("url", minioConfig.getEndpoint() + ":" + minioConfig.getPort() + "/" + minioConfig.getBucketName() + "/" + fileName);
/**
* 构建返回结果集
*/
/**
* 封装需要的数据进行返回
*/
return ajax;
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("上传失败");
} finally {
//防止内存泄漏
if (inputStream != null) {
try {
inputStream.close(); // 关闭流
} catch (IOException e) {
log.debug("inputStream close IOException:" + e.getMessage());
}
}
}
}
//这个方法里面的属性用了一个配置文件实体类(minioConfig)(也就是一些minio的连接属性)
//属性自己看文档定义
/**
* 免费提供一个获取Minio连接的方法
* 获取Minio连接
* @return
*/
private MinioClient getClient() {
MinioClient minioClient =
MinioClient.builder()
.endpoint("http://" + minioConfig.getEndpoint() + ":" + minioConfig.getPort())
.credentials(minioConfig.getAccessKey(), minioConfig.getSecretKey())
.build();
return minioClient;
}
若依Java接口
/**
* 自定义 Minio 服务器上传请求
*/
@PostMapping("/uploadMinio")
public AjaxResult uploadFileMinio(MultipartFile file) throws Exception
{
try
{
// 上传并返回新文件名称
String fileName = FileUploadUtils.uploadMinio(file);
AjaxResult ajax = AjaxResult.success();
ajax.put("url", fileName);
ajax.put("fileName", fileName);
ajax.put("newFileName", FileUtils.getName(fileName));
ajax.put("originalFilename", file.getOriginalFilename());
return ajax;
}
catch (Exception e)
{
return AjaxResult.error(e.getMessage());
}
}
文件上传工具类
package com.ruoyi.common.utils.file;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.Objects;
import org.apache.commons.io.FilenameUtils;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.config.MinioConfig;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException;
import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
import com.ruoyi.common.exception.file.InvalidExtensionException;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.Seq;
/**
* 文件上传工具类
*
* @author ruoyi
*/
public class FileUploadUtils
{
/**
* 默认大小 50M
*/
public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024;
/**
* 默认的文件名最大长度 100
*/
public static final int DEFAULT_FILE_NAME_LENGTH = 100;
/**
* 本地默认上传的地址
*/
private static String defaultBaseDir = RuoYiConfig.getProfile();
/**
* Minio默认上传的地址
*/
private static String bucketName = MinioConfig.getBucketName();
public static void setDefaultBaseDir(String defaultBaseDir)
{
FileUploadUtils.defaultBaseDir = defaultBaseDir;
}
public static String getDefaultBaseDir()
{
return defaultBaseDir;
}
public static String getBucketName()
{
return bucketName;
}
/**
* 以默认配置进行文件上传
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String upload(MultipartFile file) throws IOException
{
try
{
return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 根据文件路径上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @return 文件名称
* @throws IOException
*/
public static final String upload(String baseDir, MultipartFile file) throws IOException
{
try
{
return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 文件上传
*
* @param baseDir 相对应用的基目录
* @param file 上传的文件
* @param allowedExtension 上传文件类型
* @return 返回上传成功的文件名
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws FileNameLengthLimitExceededException 文件名太长
* @throws IOException 比如读写文件出错时
* @throws InvalidExtensionException 文件校验异常
*/
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
String fileName = extractFilename(file);
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
file.transferTo(Paths.get(absPath));
return getPathFileName(baseDir, fileName);
}
/**
* 以默认BucketName配置上传到Minio服务器
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String uploadMinio(MultipartFile file) throws IOException
{
try
{
return uploadMinino(getBucketName(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 自定义bucketName配置上传到Minio服务器
*
* @param file 上传的文件
* @return 文件名称
* @throws Exception
*/
public static final String uploadMinio(MultipartFile file, String bucketName) throws IOException
{
try
{
return uploadMinino(bucketName, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
private static final String uploadMinino(String bucketName, MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
InvalidExtensionException
{
int fileNamelength = file.getOriginalFilename().length();
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
{
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
}
assertAllowed(file, allowedExtension);
try
{
String fileName = extractFilename(file);
String pathFileName = MinioUtil.uploadFile(bucketName, fileName, file);
return pathFileName;
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
/**
* 编码文件名
*/
public static final String extractFilename(MultipartFile file)
{
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
}
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
{
File desc = new File(uploadDir + File.separator + fileName);
if (!desc.exists())
{
if (!desc.getParentFile().exists())
{
desc.getParentFile().mkdirs();
}
}
return desc;
}
public static final String getPathFileName(String uploadDir, String fileName) throws IOException
{
int dirLastIndex = RuoYiConfig.getProfile().length() + 1;
String currentDir = StringUtils.substring(uploadDir, dirLastIndex);
return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName;
}
/**
* 文件大小校验
*
* @param file 上传的文件
* @return
* @throws FileSizeLimitExceededException 如果超出最大大小
* @throws InvalidExtensionException
*/
public static final void assertAllowed(MultipartFile file, String[] allowedExtension)
throws FileSizeLimitExceededException, InvalidExtensionException
{
long size = file.getSize();
if (size > DEFAULT_MAX_SIZE)
{
throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024);
}
String fileName = file.getOriginalFilename();
String extension = getExtension(file);
if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension))
{
if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION)
{
throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION)
{
throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION)
{
throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension,
fileName);
}
else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION)
{
throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension,
fileName);
}
else
{
throw new InvalidExtensionException(allowedExtension, extension, fileName);
}
}
}
/**
* 判断MIME类型是否是允许的MIME类型
*
* @param extension
* @param allowedExtension
* @return
*/
public static final boolean isAllowedExtension(String extension, String[] allowedExtension)
{
for (String str : allowedExtension)
{
if (str.equalsIgnoreCase(extension))
{
return true;
}
}
return false;
}
/**
* 获取文件名的后缀
*
* @param file 表单文件
* @return 后缀名
*/
public static final String getExtension(MultipartFile file)
{
String extension = FilenameUtils.getExtension(file.getOriginalFilename());
if (StringUtils.isEmpty(extension))
{
extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType()));
}
return extension;
}
}
MinioConfig.java
package com.ruoyi.common.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.minio.MinioClient;
/**
* Minio 配置信息
*
* @author ruoyi
*/
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig
{
/**
* 服务地址
*/
private static String url;
/**
* 用户名
*/
private static String accessKey;
/**
* 密码
*/
private static String secretKey;
/**
* 存储桶名称
*/
private static String bucketName;
public static String getUrl()
{
return url;
}
public void setUrl(String url)
{
MinioConfig.url = url;
}
public static String getAccessKey()
{
return accessKey;
}
public void setAccessKey(String accessKey)
{
MinioConfig.accessKey = accessKey;
}
public static String getSecretKey()
{
return secretKey;
}
public void setSecretKey(String secretKey)
{
MinioConfig.secretKey = secretKey;
}
public static String getBucketName()
{
return bucketName;
}
public void setBucketName(String bucketName)
{
MinioConfig.bucketName = bucketName;
}
@Bean
public MinioClient getMinioClient()
{
return MinioClient.builder().endpoint(url).credentials(accessKey, secretKey).build();
}
}
MinioUtil.java
package com.ruoyi.common.utils.file;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.http.Method;
/**
* Minio 文件存储工具类
*
* @author ruoyi
*/
public class MinioUtil
{
/**
* 上传文件
*
* @param bucketName 桶名称
* @param fileName
* @throws IOException
*/
public static String uploadFile(String bucketName, String fileName, MultipartFile multipartFile) throws IOException
{
String url = "";
MinioClient minioClient = SpringUtils.getBean(MinioClient.class);
try (InputStream inputStream = multipartFile.getInputStream())
{
minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(inputStream, multipartFile.getSize(), -1).contentType(multipartFile.getContentType()).build());
url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(fileName).method(Method.GET).build());
url = url.substring(0, url.indexOf('?'));
return ServletUtils.urlDecode(url);
}
catch (Exception e)
{
throw new IOException(e.getMessage(), e);
}
}
}