一、MinIO概述


官网地址:http://www.minio.org.cn/

文档地址:http://docs.minio.org.cn/docs/

MinIO是一款基于Apache License v2.0开源协议的分布式文件系统(或者叫对象存储服务),可以做为云存储的解决方案用来保存海量的图片、视频、文档等。由于采用Golang实现,服务端可以工作在Windows、Linux、 OS X和FreeBSD上。配置简单,基本是复制可执行程序,单行命令就可以运行起来。

MinIO兼容亚马逊S3(Simple Storage Service,简单存储服务)云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而且每个对象文件可以是任意大小,从几kb到最大5T不等。
在这里插入图片描述


MinIO特点:

  1. 高性能:作为高性能对象存储,在标准硬件条件下它能达到55GB/s的读、35GB/s的写速率;
  2. 可扩容:不同MinIO集群可以组成联邦,并形成一个全局的命名空间,并跨越多个数据中心;
  3. SDK支持: 基于Minio轻量的特点,它得到类似Java、Python或Go等语言的sdk支持;
  4. 支持纠删码:MinIO使用纠删码、Checksum来防止硬件错误和静默数据污染。在最高冗余度配置下,即使丢失1/2的磁盘也能恢复数据;

MinIO基本概念:

  • bucket(桶) :相当于文件夹
  • Object : 相当于文件
  • Keys :相当于文件名
  • MINIO_ACCESS_KEY:访问key,类似账号;
  • MINIO_SECRET_KEY:秘钥,类似密码。

文件存储方式对比:

存储方式优点缺点
服务器磁盘开发便捷,成本低扩展困难
分布式文件系统(开源:FastDFS、MinIO)容易实现扩展复杂度高
第三方存储(阿里云OSS、七牛云)开发简单,功能强大,免维护收费

分布式文件系统对比:

  • FastDFS是阿里余庆做的个人项目,也是一款开源高性能的分布式文件系统,适合小规模文件数据存储,默认不提供UI界面,安装部署(运维)复杂,很难达到以G为单位的每秒读写速度,没有完备的官方文档,环境搭建较为复杂;
  • MinIO是由MinIO.Inc运营的开源项目,号称世界上速度最快的对象存储服务器,并且社区活跃度高,标准硬件条件下它能达到55GB/s的读、35GB/s的写速率,而且MinIO部署自带管理界面,不需要额外安装;MinIO提供了所有主流开发语言的SDK,并且兼容亚马逊S3云存储服务接口,在MinIO中一个对象文件可以是任意大小,从几KB到最大的5T不等;最后它提供了与k8s、etcd、docker等容器技术深度集成方案,可以说就是为云原生而生的。(缺点,不支持动态增加节点)
  • 更详细文章参考:https://www.cnblogs.com/loveer/p/16857625.html

二、MinIO环境搭建


本文基于Docker实现MinIO环境快速部署,Docker安装参考:https://blog.csdn.net/qq_46921028/article/details/129096134

1、拉取镜像(https://hub.docker.com/r/minio/minio/tags)

# 下载新版minio
docker pull minio/minio

# 或者下载指定版本的minio
docker pull minio/minio:RELEASE.2021-04-06T23-11-00Z  

2、创建容器

docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /docker/minio/data:/data -v /docker/minio/config:/root/.minio minio/minio server /data

# 或者指定镜像版本并创建容器
docker run -p 9000:9000 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=minio123" -v /docker/minio/data:/data -v /docker/minio/config:/root/.minio minio/minio:RELEASE.2021-04-06T23-11-00Z server /data
  • -p 9000:9000 ,端口映射
  • -e,环境变量
  • -d,后台运行
  • –name,给容器起名字
  • –restart=always,开机自启
  • -e “MINIO_ACCESS_KEY=minio”,设置账号
  • -e “MINIO_SECRET_KEY=minio123”,设置密码
  • -v 挂载数据卷

在这里插入图片描述


3、测试访问MinIO后台系统,直接浏览器访问 http://ip:9000 即可,账号minio,密码minio123

在这里插入图片描述

管理界面:

在这里插入图片描述


4、创建 bucket(桶)

首先点击右下角的"+"号,创建一个桶

在这里插入图片描述

给创建的桶起个名字:

在这里插入图片描述

最后在minio管理界面就能看到刚刚创建的桶结构:

在这里插入图片描述


三、快速入门


基于SpringBoot快速整合MinIO

1、创建springboot工程

2、导入相关依赖

<dependencies>
    <!--minio-->
    <dependency>
        <groupId>io.minio</groupId>
        <artifactId>minio</artifactId>
        <version>7.1.0</version>
    </dependency>
    <!--web-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--test-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--knife4j(swagger) -->
    <dependency>
        <groupId>com.github.xiaoymin</groupId>
        <artifactId>knife4j-spring-boot-starter</artifactId>
        <version>3.0.2</version>
    </dependency>
    <!--lombok-->
     <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
</dependencies>

3、整合swagger

3.1、编写Swagger配置类

package com.baidou.config;

import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * Swagger2配置类(生成接口文档)
 *
 * @author 白豆五
 * @version 2023/04/21
 * @since JDK8
 */
@Configuration
@EnableSwagger2 //开启swagger2注解支持
@EnableKnife4j  //开启Knife4j注解支持
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig() { //生成接口文档的清单
        // 文档类型
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                //指定controller包扫描路径
                .apis(RequestHandlerSelectors.basePackage("com.baidou.controller"))
                .paths(PathSelectors.any())
                .build();
    }

    //配置在线api文档信息
    private ApiInfo webApiInfo() {
        return new ApiInfoBuilder()
                .title("网站-API文档")
                .description("本文档描述了xxx管理系统微服务接口定义")
                .version("1.0")
                .contact(new Contact("白豆五", "https://blog.csdn.net/qq_46921028", "13212341234@163.com"))
                .build();
    }
}

3.2、设置静态资源映射(对Swagger的静态资源放行)

package com.baidou.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

// 对WebMvc进行一些配置,为swagger静态资源进行放行
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    // 静态资源放行
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
               
        // 对swaggger静态资源放行
        registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        
    }
}

4、编写 MinIO属性配置类

package com.baidou.dto;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.io.Serializable;

@Data
@Component
@ConfigurationProperties(prefix = "minio")  //自动注入属性前缀为minio的配置
public class MinIOConfigProperties implements Serializable {

    private String accessKey; // 访问key
    private String secretKey; // 秘钥
    private String bucket;    // 桶
    private String endpoint;  // 地域节点
    private String readPath;  // 读取路径
}

5、编写MinIO配置类,注册MinioClient客户端的Bean对象

package com.baidou.config;

import io.minio.MinioClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MinIO配置类
 *
 * @author 白豆五
 * @version 2023/04/21
 * @since JDK8
 */
@Configuration
public class MinIOConfig {

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    // 注册MinIO实例
    @Bean
    public MinioClient buildMinioClient(){
        return MinioClient
                .builder()
                .credentials(minIOConfigProperties.getAccessKey(), minIOConfigProperties.getSecretKey())
                .endpoint(minIOConfigProperties.getEndpoint())
                .build();
    }
}

6、在application.yml文件中配置minio自定义属性和文件上传大小

minio:
  accessKey: minio
  secretKey: minio123
  bucket: testminio
  endpoint: http://192.168.200.128:9000
  readPath: http://192.168.200.128:9000
  
  servlet:
    multipart:
      # 单个上传文件的最大值是200mb
      max-file-size: 200MB
      # 单次请求的最大值
      max-request-size: 200MB

在这里插入图片描述


7、编写操作minio相关业务接口

package com.baidou.service;

import java.io.InputStream;

/**
 * 操作minio相关业务接口
 *
 * @author 白豆五
 * @version 2023/04/21
 * @since JDK8
 */
public interface FileStorageService {


    /**
     * 上传图片文件
     *
     * @param prefix      文件前缀
     * @param filename    文件名
     * @param inputStream 文件流
     * @return 文件全路径
     */
    public String uploadImgFile(String prefix, String filename, InputStream inputStream);

    /**
     * 上传html文件
     *
     * @param prefix      文件前缀
     * @param filename    文件名
     * @param inputStream 文件流
     * @return 文件全路径
     */
    public String uploadHtmlFile(String prefix, String filename, InputStream inputStream);

    /**
     * 删除文件
     *
     * @param pathUrl 文件全路径
     */
    public void delete(String pathUrl);

    /**
     * 下载文件
     *
     * @param pathUrl 文件全路径
     * @return
     */
    public byte[] downLoadFile(String pathUrl);

}

业务接口实现类:

package com.baidou.service.impl;


import com.baidou.config.MinIOConfigProperties;
import com.baidou.service.FileStorageService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;

@Slf4j
@Service
public class MinIOFileStorageService implements FileStorageService {

    @Autowired
    private MinioClient minioClient;

    @Autowired
    private MinIOConfigProperties minIOConfigProperties;

    private final static String separator = "/"; //文件夹分隔符

    /**
     * 构建文件的绝对路径
     *
     * @param dirPath  文件路径
     * @param filename 文件名  yyyy/mm/dd/file.jpg
     * @return /test
     */
    public String builderFilePath(String dirPath, String filename) {
        StringBuilder stringBuilder = new StringBuilder(50);
        if (!StringUtils.isEmpty(dirPath)) {
            stringBuilder.append(dirPath).append(separator);
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
        String todayStr = sdf.format(new Date());
        stringBuilder.append(todayStr).append(separator);
        stringBuilder.append(filename);
        return stringBuilder.toString();
    }

    /**
     * 上传图片文件
     *
     * @param prefix      文件前缀
     * @param filename    文件名
     * @param inputStream 文件流
     * @return 文件全路径
     */
    @Override
    public String uploadImgFile(String prefix, String filename, InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath)
                    .contentType("image/jpg")
                    .bucket(minIOConfigProperties.getBucket()).stream(inputStream, inputStream.available(), -1)
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator + minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString();
        } catch (Exception ex) {
            log.error("minio put file error.", ex);
            throw new RuntimeException("上传文件失败");
        }
    }

    /**
     * 上传html文件
     *
     * @param prefix      文件前缀
     * @param filename    文件名
     * @param inputStream 文件流
     * @return 文件全路径
     */
    @Override
    public String uploadHtmlFile(String prefix, String filename, InputStream inputStream) {
        String filePath = builderFilePath(prefix, filename);
        try {
            PutObjectArgs putObjectArgs = PutObjectArgs.builder()
                    .object(filePath) //文件名
                    .contentType("text/html")//文件类型
                    .bucket(minIOConfigProperties.getBucket())//桶名称与minio创建的桶一致
                    .stream(inputStream, inputStream.available(), -1)//文件流
                    .build();
            minioClient.putObject(putObjectArgs);
            StringBuilder urlPath = new StringBuilder(minIOConfigProperties.getReadPath());
            urlPath.append(separator + minIOConfigProperties.getBucket());
            urlPath.append(separator);
            urlPath.append(filePath);
            return urlPath.toString(); //文件全路径
        } catch (Exception ex) {
            log.error("minio put file error.", ex);
            ex.printStackTrace();
            throw new RuntimeException("上传文件失败");
        }
    }

    /**
     * 删除文件
     *
     * @param pathUrl 文件全路径
     */
    @Override
    public void delete(String pathUrl) {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + "/", "");
        int index = key.indexOf(separator);
        String bucket = key.substring(0, index);
        String filePath = key.substring(index + 1);
        // 删除Objects
        RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder().bucket(bucket).object(filePath).build();
        try {
            minioClient.removeObject(removeObjectArgs);
        } catch (Exception e) {
            log.error("minio remove file error.  pathUrl:{}", pathUrl);
            e.printStackTrace();
        }
    }


    /**
     * 下载文件
     *
     * @param pathUrl 文件全路径
     * @return 文件流
     */
    @Override
    public byte[] downLoadFile(String pathUrl) {
        String key = pathUrl.replace(minIOConfigProperties.getEndpoint() + "/", "");
        int index = key.indexOf(separator);
        String bucket = key.substring(0, index);
        String filePath = key.substring(index + 1);
        InputStream inputStream = null;
        try {
            inputStream = minioClient.getObject(GetObjectArgs.builder().bucket(minIOConfigProperties.getBucket()).object(filePath).build());
        } catch (Exception e) {
            log.error("minio down file error.  pathUrl:{}", pathUrl);
            e.printStackTrace();
        }

        //字节数组输出流
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buff = new byte[100];
        int rc = 0;
        while (true) {
            try {
                if (!((rc = inputStream.read(buff, 0, 100)) > 0)) break;
            } catch (IOException e) {
                e.printStackTrace();
            }
            byteArrayOutputStream.write(buff, 0, rc);
        }
        return byteArrayOutputStream.toByteArray();
    }
}

8、编写统一结果处理类

package com.baidou.dto;

import lombok.Data;

/**
 * 统一结果处理类
 *
 * @author 白豆五
 * @version 2023/04/21
 * @since JDK8
 */
@Data
public class Result<T> {
    private Integer code; //响应状态码
    private String msg;   //响应消息
    private T data;       //响应数据

    /**
     * 处理成功的返回结果
     *
     * @param data 数据
     * @param <T>
     * @return R<T>
     */
    public static <T> Result<T> success(T data, String msg) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setData(data);
        r.setMsg(msg);
        return r;
    }

    /**
     * 处理失败的返回结果
     *
     * @param msg 错误信息
     * @param <T>
     * @return R<T>
     */
    public static <T> Result<T> error(String msg) {
        Result<T> r = new Result<>();
        r.setCode(503);
        r.setMsg(msg);
        return r;
    }
}

9、编写controller

package com.baidou.controller;

import com.baidou.dto.Result;
import com.baidou.service.FileStorageService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

/**
 * 操作minio的控制器类
 *
 * @author 白豆五
 * @version 2023/04/21
 * @since JDK8
 */
@RestController
@RequestMapping("/minio")
@Api(tags = "minio相关接口")
public class MinioController {

    @Autowired
    private FileStorageService fileStorageService;


    /**
     * 上传图片到minio
     *
     * @param file
     * @return
     */
    @PostMapping("upload")
    @ApiOperation(value = "图片上传接口")
    public Result uploadFile(MultipartFile file) throws IOException {
        try {
            // 获取文件名称
            String fileName = file.getOriginalFilename();
            /*解决多次上传同名文件覆盖问题*/
            // 在文件名称里面添加随机唯一的值
            String uuid = UUID.randomUUID().toString().replaceAll("-", "");
            fileName = uuid + fileName;
            // 获取文件输入流
            InputStream is = file.getInputStream();
            String imgUrl = fileStorageService.uploadImgFile("img", fileName, is);
            return Result.success(imgUrl, "上传成功");
        } catch (IOException e) {
            e.printStackTrace();
            return Result.error("上传失败");
        }
    }
}

10、启动项目,然后用postman测试

在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐