MinIO的简单使用

发布时间 2023-11-24 14:59:25作者: 锅巴编程

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.下载:

  • 官网: https://docs.min.io/
  • 说明:各个平台都能装,常见在linux服务器中使用(这里以centos为例)。
    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 &
    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=你想改的密码
    8.保存退出esc   :wq
    9.重载配置
            source /etc/profile
    10.打开127.0.0.1:9000 即可看到运行页面

  • 关闭后台minio
    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);
        }
    }
}