MinIO Linux 安装使用 & SpringBoot整合MinIO

目录

Minio 是一个基于Apache License v2.0开源协议的对象存储服务。它可以运行在多种操作系统上,包括 Linux 和 Windows 等。
它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5TB不等。

Minio官网:https://min.io/

中文地址:https://www.minio.org.cn

中文文档:https://www.minio.org.cn/docs/minio/linux/index.html

演示:https://play.minio.org.cn 用户名:minioadmin、密码minioadmin

MinIO Linux 安装

单节点部署

Download: https://www.minio.org.cn/download.shtml#/linux

使用以下命令下载安装最新版本的稳定 MinIO二进制包, 并设置 $PATH :

bash 复制代码
[root@localhost ~]# wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio
[root@localhost ~]# chmod +x minio
[root@localhost ~]# sudo mv minio /usr/local/bin/

创建 systemd 系统启动服务文件

创建 minio.service 启动文件,确保文件在 /usr/lib/systemd/system/minio.service

bash 复制代码
[root@localhost ~]# vi /usr/lib/systemd/system/minio.service

minio.service

bash 复制代码
[Unit]
Description=MinIO
Documentation=https://min.io/docs/minio/linux/index.html
Wants=network-online.target
After=network-online.target
AssertFileIsExecutable=/usr/local/bin/minio

[Service]
WorkingDirectory=/usr/local

User=minio-user
Group=minio-user
ProtectProc=invisible

EnvironmentFile=-/etc/default/minio
ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi"
ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES

# MinIO RELEASE.2023-05-04T21-44-30Z adds support for Type=notify (https://www.freedesktop.org/software/systemd/man/systemd.service.html#Type=)
# This may improve systemctl setups where other services use `After=minio.server`
# Uncomment the line to enable the functionality
# Type=notify

# Let systemd restart this service always
Restart=always

# Specifies the maximum file descriptor number that can be opened by this process
LimitNOFILE=65536

# Specifies the maximum number of threads this process can create
TasksMax=infinity

# Disable timeout logic and wait until process is stopped
TimeoutStopSec=infinity
SendSIGKILL=no

[Install]
WantedBy=multi-user.target

# Built for ${project.name}-${project.version} (${project.name})

默认情况下, minio.service 文件以 minio-user 用户和组的身份运行。 您可以使用 groupadduseradd 创建用户和组。 命令。下面的示例创建了用户和组,并设置了权限 来访问供 MinIO 使用的文件夹路径。这些命令通常 需要 root ( sudo ) 权限。

bash 复制代码
[root@localhost ~]# groupadd -r minio-user
[root@localhost ~]# useradd -M -r -g minio-user minio-user
# 存储路径
[root@localhost ~]# chown minio-user:minio-user /mnt/data
chown: 无法访问"/mnt/data": 没有那个文件或目录
[root@localhost ~]# mkdir /mnt/data
[root@localhost ~]# chown minio-user:minio-user /mnt/data

本例中的驱动器路径由 MINIO_VOLUMES 环境变量指定。更改此处和环境变量文件中的值,使其与 MinIO 打算使用的驱动器路径相匹配。

创建环境变量文件

/etc/default/minio 创建环境变量文件。 MinIO 服务器容器可以将此文件作为所有 environment variables

下面的示例提供了一个起始环境文件:
密码不能小于8个字符, 否则无法启动

bash 复制代码
[root@localhost ~]# vi /etc/default/minio
bash 复制代码
# MINIO_ROOT_USER and MINIO_ROOT_PASSWORD sets the root account for the MinIO server.
# This user has unrestricted permissions to perform S3 and administrative API operations on any resource in the deployment.
# Omit to use the default values 'minioadmin:minioadmin'.
# MinIO recommends setting non-default values as a best practice, regardless of environment
# 用户名长度不能小于3个字符
MINIO_ROOT_USER=admin
# 密码不能小于8个字符, 否则无法启动
MINIO_ROOT_PASSWORD=minioadmin

# MINIO_VOLUMES sets the storage volume or path to use for the MinIO server.

MINIO_VOLUMES="/mnt/data"

# MINIO_OPTS sets any additional commandline options to pass to the MinIO server.
# For example, `--console-address :9001` sets the MinIO Console listen port
MINIO_OPTS="--console-address :9001"

启动MinIO服务

启动 MinIO SNSD 部署即服务:

bash 复制代码
[root@localhost ~]# sudo systemctl start minio.service

使用以下命令确认服务是否在线和功能正常:

bash 复制代码
[root@localhost ~]# sudo systemctl status minio.service
[root@localhost ~]# journalctl -f -u minio.service

自启动,将进程作为主机引导的一部分,在服务器重启的过程中该进程会自动重启,而不用再进行手动管理。

bash 复制代码
[root@localhost ~]# sudo systemctl enable minio.service

连接到MinIO服务

浏览器中输入:http://localhost:9001

登录MinIO的用户名和密码配置参数为 MINIO_ROOT_USERMINIO_ROOT_PASSWORD 这些配置可以在在容器指定的环境文件中进行修改。

您可以使用MinIO控制台进行一般管理任务,如身份和访问管理、指标和日志监控或服务器配置。每个MinIO服务器都包含其自己的嵌入式MinIO控制台。 如果您的本地主机防火墙允许外部访问控制台端口,则同一网络上的其他主机可以使用您的本地主机的IP地址或主机名访问控制台。

SpringBoot项目整合MinIO

https://mvnrepository.com/artifact/io.minio/minio

配置项

添加依赖

xml 复制代码
<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.10</version>
</dependency>

application.yml 配置 minio

yml 复制代码
spring:
     mvc:
      hiddenmethod:
        filter:
          enabled: true
  #设置文件上传大小限制
  servlet:
    multipart:
      max-file-size: -1    #设置单个文件的大小 -1表示不限制
      max-request-size: -1    #单次请求的文件的总大小 -1表示不限制

minio:
  endpoint: http://172.16.3.195:9000
  accessKey: admin
  secretKey: minioadmin
  bucketName: vipsoft-devminioadmin

MinioConfig.java

java 复制代码
package com.vipsoft.oss.config;


import io.minio.MinioClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {
    /**
     * 服务地址:http://172.16.3.195:9000
     */
    private String endpoint;

    /**
     * 用户名
     */
    private String accessKey;

    /**
     * 密码
     */
    private String secretKey;

    /**
     * 存储桶名称
     */
    private String bucketName;


    @Bean
    public MinioClient getMinioClient() {
        MinioClient minioClient = MinioClient.builder().endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
        return minioClient;
    }

    //todo 省去 getter & setter
}

工具类

MinioUtil.java

java 复制代码
package com.vipsoft.oss.util;

import cn.hutool.core.io.IoUtil;
import com.cuwor.oss.config.MinioConfig;
import io.minio.*;
import io.minio.MinioClient;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Component
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinioConfig minioConfig;

    private static final int DEFAULT_EXPIRY_TIME = 7 * 24 * 3600;

    /**
     * 判断bucket是否存在
     */
    public boolean bucketExists(String bucketName) {
        boolean exists;
        try {
            exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            e.printStackTrace();
            exists = false;
        }
        return exists;
    }


    /**
     * 创建存储桶,存在则不创建
     */
    public boolean makeBucket(String bucketName) {
        boolean exists;
        try {
            exists = bucketExists(bucketName);
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                exists = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            exists = false;
        }
        return exists;
    }

    /**
     * 删除存储桶 -- 有文件,不让删除
     *
     * @param bucketName 存储桶名称
     * @return boolean
     */
    public boolean removeBucket(String bucketName) {
        try {
            boolean flag = bucketExists(bucketName);
            if (flag) {
                Iterable<Result<Item>> myObjects = listObjects(bucketName);
                for (Result<Item> result : myObjects) {
                    Item item = result.get();
                    // 有对象文件,则删除失败
                    if (item.size() > 0) {
                        return false;
                    }
                }
                // 删除存储桶,注意,只有存储桶为空时才能删除成功。
                minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
                flag = bucketExists(bucketName);
                if (!flag) {
                    return true;
                }
            }
            return false;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 列出存储桶中的所有对象
     *
     * @param bucketName 存储桶名称
     * @return Iterable<Result < Item>>
     */
    public Iterable<Result<Item>> listObjects(String bucketName) {
        boolean flag = bucketExists(bucketName);
        if (flag) {
            return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
        }
        return null;
    }


    /**
     * 列出所有存储桶名称
     *
     * @return List<String>
     */
    public List<String> listBucketNames() {
        List<String> bucketListName = new ArrayList<>();
        try {
            List<Bucket> bucketList = minioClient.listBuckets();
            for (Bucket bucket : bucketList) {
                bucketListName.add(bucket.name());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bucketListName;
    }

    /**
     * 列出存储桶中的所有对象名称
     *
     * @param bucketName 存储桶名称
     * @return List<String>
     */
    public List<String> listObjectNames(String bucketName) {
        List<String> listObjectNames = new ArrayList<>();
        try {
            boolean flag = bucketExists(bucketName);
            if (flag) {
                Iterable<Result<Item>> myObjects = listObjects(bucketName);
                for (Result<Item> result : myObjects) {
                    Item item = result.get();
                    listObjectNames.add(item.objectName());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return listObjectNames;
    }


    /**
     * 获取对象的元数据
     *
     * @param objectName 存储桶里的对象名称
     * @return
     */
    public StatObjectResponse statObject(String objectName) {
        StatObjectResponse statObject = null;
        try {
            statObject = minioClient.statObject(StatObjectArgs.builder().bucket(minioConfig.getBucketName()).object(objectName).build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return statObject;
    }

    //region 上传文件

    /**
     * 上传文件
     *
     * @param file       文件
     * @param objectName 文件名称
     */
    public boolean uploadObject(MultipartFile file, String objectName) {
        // 使用putObject上传一个文件到存储桶中。
        try {
            InputStream inputStream = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .stream(inputStream, file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());
            IoUtil.close(inputStream);
//            return StrUtil.format("{}/{}/{}", minioConfig.getEndpoint(), minioConfig.getBucketName(), objectName);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 通过InputStream上传对象
     *
     * @param objectName  存储桶里的对象名称
     * @param inputStream 要上传的流
     * @param contentType 上传的文件类型 例如 video/mp4  image/jpg
     * @return boolean
     */
    public boolean uploadObject(InputStream inputStream, String objectName, String contentType) {
        try {
            minioClient.putObject(PutObjectArgs.builder().bucket(minioConfig.getBucketName()).object(objectName).stream(
                            //不清楚文件的大小时,可以传-1,10485760。如果知道大小也可以传入size,partsize。
                            inputStream, -1, 10485760)
                    .contentType(contentType)
                    .build());
            IoUtil.close(inputStream);
            StatObjectResponse statObject = statObject(objectName);
            if (statObject != null && statObject.size() > 0) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 通过文件上传到对象
     *
     * @param objectName 存储桶里的对象名称
     * @param fileName   File name
     * @return boolean
     */
    public boolean uploadObject(String objectName, String fileName) {
        try {
            minioClient.uploadObject(UploadObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .filename(fileName)
                    .build());
            StatObjectResponse statObject = statObject(objectName);
            if (statObject != null && statObject.size() > 0) {
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    //endregion


    /**
     * 获取文件访问地址
     *
     * @param fileName 文件名称
     */
    public String getPresignedObjectUrl(String fileName) {
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(minioConfig.getBucketName())
                    .object(fileName)
                    .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public String getObjectUrl(String objectName) {
        String url = "";
        try {
            url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .build());

        } catch (Exception e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 生成一个给HTTP GET请求用的presigned URL。
     * 浏览器/移动端的客户端可以用这个URL进行下载,即使其所在的存储桶是私有的。这个presigned URL可以设置一个失效时间,默认值是7天。
     *
     * @param objectName 存储桶里的对象名称
     * @param expires    失效时间(以小时单位),默认是7天,不得大于七天
     * @return
     */
    public String getObjectUrl(String objectName, Integer expires) {
        String url = "";
        try {
            if (expires < 1 || expires > DEFAULT_EXPIRY_TIME) {
                throw new Exception("Expires must be in range of 1 to " + DEFAULT_EXPIRY_TIME);
            }
            url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .expiry(expires, TimeUnit.HOURS)//动态参数
                    // .expiry(24 * 60 * 60)//用秒来计算一天时间有效期
                    // .expiry(1, TimeUnit.DAYS)//按天传参
                    // .expiry(1, TimeUnit.HOURS)//按小时传参数
                    .build());

        } catch (Exception e) {
            e.printStackTrace();
        }
        return url;
    }

    /**
     * 下载文件,通过 HttpServletResponse 返回
     *
     * @param objectName 存储桶里的对象名称
     */
    public void downloadObject(HttpServletResponse response, String objectName) {
        try {
            InputStream file = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .build());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(objectName, "UTF-8"));
            ServletOutputStream servletOutputStream = response.getOutputStream();
            int len;
            byte[] buffer = new byte[1024];
            while ((len = file.read(buffer)) > 0) {
                servletOutputStream.write(buffer, 0, len);
            }
            servletOutputStream.flush();
            file.close();
            servletOutputStream.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 下载并将文件保存到本地
     *
     * @param objectName 存储桶里的对象名称
     * @param fileName   下载保存的文件名
     * @return boolean
     */
    public boolean downloadObject(String objectName, String fileName) {
        try {
            StatObjectResponse statObject = statObject(objectName);
            if (statObject != null && statObject.size() > 0) {
                minioClient.downloadObject(DownloadObjectArgs.builder()
                        .bucket(minioConfig.getBucketName())
                        .object(objectName)
                        .filename(fileName)
                        .build());
                return true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    /**
     * 以流的形式获取一个文件对象
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @return InputStream
     */
    public InputStream getObject(String bucketName, String objectName) {
        try {
            StatObjectResponse statObject = statObject(objectName);
            if (statObject != null && statObject.size() > 0) {
                InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .build());
                return stream;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 以流的形式获取一个文件对象(断点下载)
     *
     * @param bucketName 存储桶名称
     * @param objectName 存储桶里的对象名称
     * @param offset     起始字节的位置
     * @param length     要读取的长度 (可选,如果无值则代表读到文件结尾)
     * @return InputStream
     */
    public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
        try {
            StatObjectResponse statObject = statObject(objectName);
            if (statObject != null && statObject.size() > 0) {
                InputStream stream = minioClient.getObject(GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .offset(1024L)
                        .length(4096L)
                        .build());
                return stream;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 删除一个对象
     *
     * @param objectName 存储桶里的对象名称
     */
    public boolean removeObject(String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(minioConfig.getBucketName()).object(objectName).build());
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
     *
     * @param bucketName  存储桶名称
     * @param objectNames 含有要删除的多个object名称的迭代器对象 eg:
     *                    List<DeleteObject> objects = new LinkedList<>();
     *                    objects.add(new DeleteObject("my-objectname1"));
     *                    objects.add(new DeleteObject("my-objectname2"));
     *                    objects.add(new DeleteObject("my-objectname3"));
     * @return 如果有值,说明当前文件删除失败
     */
    public List<String> removeObjects(String bucketName, List<DeleteObject> objectNames) {
        List<String> deleteErrorNames = new ArrayList<>();
        try {
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(objectNames).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrorNames.add(error.objectName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return deleteErrorNames;
    }
}

测试

java 复制代码
package com.vipsoft.admin;

import cn.hutool.core.util.StrUtil;
import com.cuwor.oss.util.MinioUtil;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;

@SpringBootTest
public class MinioTest {

    Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private MinioUtil minioUtil;


    @Test
    void makeBucketTest() {
        boolean flag = minioUtil.makeBucket("public");
        Assert.isTrue(flag, "查询异常");
    }


    @Test
    void uploadObjectTest() {
        boolean flag = minioUtil.uploadObject("/avatar/123.png", "D:\\Users\\Pictures\\R-C.png");
        Assert.isTrue(flag, "上传异常");
    }


    @Test
    void getObjectUrlTest() {
        String objectName="/avatar/123.png";
        String url = minioUtil.getObjectUrl(objectName);
        logger.info("url:{}", url);
        url = minioUtil.getObjectUrl(objectName, 3);
        logger.info("expires url:{}", url);
        Assert.isTrue(StrUtil.isNotEmpty(url), "上传异常");
    }
}
相关推荐
带刺的坐椅1 天前
SpringBoot3 使用 SolonMCP 开发 MCP
java·ai·springboot·solon·mcp
LUCIAZZZ2 天前
JVM之虚拟机运行
java·jvm·spring·操作系统·springboot
堕落年代2 天前
SpringSecurity当中的CSRF防范详解
前端·springboot·csrf
一零贰肆2 天前
深入理解SpringBoot中的SpringCache缓存技术
java·springboot·springcache·缓存技术
LUCIAZZZ4 天前
JVM之内存管理(一)
java·jvm·spring·操作系统·springboot
LUCIAZZZ5 天前
JVM之内存管理(二)
java·jvm·后端·spring·操作系统·springboot
天上掉下来个程小白6 天前
缓存套餐-01.Spring Cache入门案例
java·redis·spring·缓存·springboot·springcache
程序员buddha8 天前
SpringBoot+Dubbo+Zookeeper实现分布式系统步骤
分布式·zookeeper·dubbo·springboot
天上掉下来个程小白8 天前
缓存套餐-03.功能测试
redis·缓存·springboot·苍穹外卖·springcache
Ten peaches9 天前
苍穹外卖(订单状态定时处理、来单提醒和客户催单)
java·数据库·sql·springboot