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文件,双击启动即可。

对象存储和es存储的优缺点_minio

         上图就启动成功了,警告先不管,访问http://192.168.0.123:9000即可。之前创建的文件也有初始化内容了,还没有文件。

对象存储和es存储的优缺点_文件服务器_02

3、使用说明

(1)访问地址:

http://{ip}:{port}/minio/ 或 http://{ip}:{port}

对象存储和es存储的优缺点_文件服务器_03

        默认的 root 凭据 minioadmin:minioadmin。

 

对象存储和es存储的优缺点_对象存储和es存储的优缺点_04

        这样就可以使用浏览器来创建桶、上传对象以及浏览 MinIO 服务器的内容了。

(2)创建buckets

对象存储和es存储的优缺点_对象存储_05

 (3)上传文件

对象存储和es存储的优缺点_文件服务器_06

对象存储和es存储的优缺点_对象存储和es存储的优缺点_07

去看一下之前的目录,文件也有了

对象存储和es存储的优缺点_对象存储和es存储的优缺点_08

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文件中的:

对象存储和es存储的优缺点_对象存储_09

 (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)测试

对象存储和es存储的优缺点_对象存储_10

 调用接口上传文件成功,返回一个可访问的url,访问一下

对象存储和es存储的优缺点_MinIO_11

注意:如果出现如下提示,很有可能是buckets的权限问题,修改一下权限即可

对象存储和es存储的优缺点_对象存储和es存储的优缺点_12

对象存储和es存储的优缺点_MinIO_13

对象存储和es存储的优缺点_对象存储_14

 

对象存储和es存储的优缺点_对象存储_15

附上工具类:

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);
        }

    }

}