springboot整合minio分布式存储

Minio基础概念

  • S3
    • Simple Storage Service,简单存储服务,这个概念是 Amazon 在 2006 年推出的,
  • Object
    • 存储到 Minio 的基本对象,如文件、字节流,Anything…
  • Bucket
    • 用来存储 Object 的逻辑空间。每个 Bucket 之间的数据是相互隔离的。
  • Drive
    • 部署 Minio 时设置的磁盘,Minio 中所有的对象数据都会存储在 Drive 里。
  • Set
    • 一组 Drive 的集合,分布式部署根据集群规模自动划分一个或多个 Set ,每个 Set 中的 Drive 分布在不同位置。
      • 一个对象存储在一个 Set 上
      • 一个集群划分为多个 Set
      • 一个 Set 包含的 Drive 数量是固定的,默认由系统根据集群规模自动计算得出
      • 一个 SET 中的 Drive 尽可能分布在不同的节点上
    • Set /Drive 的关系
      • Set /Drive 这两个概念是 MINIO 里面最重要的两个概念,一个对象最终是存储在 Set 上面的。
      • Set 是另外一个概念,Set 是一组 Drive 的集合,图中,所有蓝色、橙色背景的 Drive(硬盘)的就组成了一个 Set。
        在这里插入图片描述
  • 纠删码(Erasure Code)
    • 纠删码(Erasure Code)简称 EC,是一种数据保护方法,它将数据分割成片段,把冗余数据块扩展、编码,并将其存储在不同的位置,比如磁盘、存储节点或者其它地理位置。
    • 纠删码保证了高可用,使用highwayhash来梳理数据损坏(Bit Rot Protection)
    • 纠删码是一种恢复丢失和损坏数据的数学算法,目前,纠删码技术在分布式存储系统中的应用主要有三类,阵列纠删码(Array Code: RAID5、RAID6 等)、RS(Reed-Solomon)里德-所罗门类纠删码和LDPC(LowDensity Parity Check Code)低密度奇偶校验纠删码
    • Erasure Code 是一种编码技术,它可以将 n 份原始数据,增加 m 份校验数据,并能通过 n+m 份中的任意 n 份原始数据,还原为原始数据。
    • 即如果有任意小于等于 m 份的校验数据失效,仍然能通过剩下的数据还原出来。
    • Minio 采用 Reed-Solomon code 将对象拆分成 N/2 数据和 N/2 奇偶校验块。
    • 在同一集群内,MinIO 自己会自动生成若干纠删组(Set),用于分布存放桶数据。一个纠删组中的一定数量的磁盘发生的故障(故障磁盘的数量小于等于校验盘的数量),通过纠删码校验算法可以恢复出正确的数据。

安装minio

systemctl status firewalld.service
systemctl stop firewalld.service
  • 本文我们使用基于docker的形式安装minio
  • docker安装minio
    • docker pull minio/minio 下载镜像
      在这里插入图片描述
  • 在home目录下新建docker文件夹
    • docker文件夹下新建minio目录和docker-compose.yml文件
      • minio目录下新建目录config和data
        • chmod 777 config
        • chmod 777 data
      • docker-compose.yml如下
version: '3'
services:
    minio:
        image: minio/minio
        restart: always
        ports:
            - '9000:9000'
            - '9001:9001'
        networks:
            - front-ms
        privileged: true
        container_name: minio
        volumes:
            - $PWD/minio/data:/data
            - $PWD/minio/config:/root/.minio/
        environment:
            - "MINIO_ROOT_USER=admin"
            - "MINIO_ROOT_PASSWORD=admin123456"
        command: server --console-address ':9001' /data

networks:
    front-ms:
        driver: bridge
  • 在docker-compose.yml目录执行 docker-compose up -d 启动容器

在这里插入图片描述

通过图形界面操作minio

  • 创建一个桶

在这里插入图片描述

  • 上传一个文件
    在这里插入图片描述

  • 点击上传的文件

    • 可以查看详情
    • preview可以预览
      在这里插入图片描述
  • 在test桶中新建一个目录
    在这里插入图片描述

  • 新建好photo目录后上传一个文件
    在这里插入图片描述

  • 回到上级目录
    在这里插入图片描述

  • 在服务器查看文件

    • 目录结构和从控制台看一致
      在这里插入图片描述
      在这里插入图片描述

在这里插入图片描述

  • 对test桶进行设置
    • Manage
      在这里插入图片描述
  • 设置访问权限
    在这里插入图片描述
  • 可以设置为public,也可以自定义
    在这里插入图片描述
  • public:所有人都可以访问该桶的资源,包括桶内的文件内容和文件目录
  • private:所有人都无法直接访问该桶的资源,如果外部需要访问,只能通过外链(最长有效期7天)

使用含纠删码的方式启动minio

  • 修改docker-compose文件
    • 当挂载的data大于等于4个时,自动启动纠删码模式
    • docker-compose up -d 重启镜像
version: '3'
services:
    minio:
        image: minio/minio
        restart: always
        ports:
            - '9000:9000'
            - '9001:9001'
        networks:
            - front-ms
        privileged: true
        container_name: minio
        volumes:
            - $PWD/minio/data1:/data1
            - $PWD/minio/data2:/data2
            - $PWD/minio/data3:/data3
            - $PWD/minio/data4:/data4
            - $PWD/minio/config:/root/.minio/
        environment:
            - "MINIO_ROOT_USER=admin"
            - "MINIO_ROOT_PASSWORD=admin123456"
        command: server --console-address ':9001' http://minio/data{1...4}

networks:
    front-ms:
        driver: bridge
  • 使用纠删码模式上传完文件后
    • 使用 tree minio/ 查看当前存放的目录结构
      在这里插入图片描述
      在单机上部署纠删码模式只能保证磁盘损坏的情况下,文件不丢失;并不能解决单点故障的问题,所以我们下面为了避免单点故障导致服务不可用,把minio服务改成分布式部署。

springboot整合minio

  • pom.xml
		<!--minio-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.0.3</version>
        </dependency>
  • yml
#file
spring.servlet.multipart.max-file-size=200MB
spring.servlet.multipart.max-request-size=200MB
#minio
minio.endpoint = http://192.168.174.139:9000
minio.accessKey = admin
minio.secretKey = admin123456
minio.bucketName = test
  • TestObjects
    • 请求实体类
import lombok.Data;

import java.util.List;

/**
 * @author jigua
 * @version 1.0
 * @className TestObjects
 * @description
 * @create 2022/9/28 16:19
 */
@Data
public class TestObjects {

    private String name;

    private List<String> list;
}

  • ObjectItem
    • 列表返回实体类
import lombok.Data;

/**
 * @author jigua
 * @version 1.0
 * @className ObjectItem
 * @description
 * @create 2022/9/28 15:30
 */
@Data
public class ObjectItem {

    private String objectName;

    private Long size;
}

  • MinIoClientConfig
    • 创建MinioClient的bean对象
import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;

/**
 * @author jigua
 * @version 1.0
 * @className MinIoClientConfig
 * @description
 * @create 2022/9/28 15:27
 */
@Component
@Data
public class MinIoClientConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.secretKey}")
    private String secretKey;

    @Value("${minio.accessKey}")
    private String accessKey;


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

  • MinioUtils
    • 操作工具类
import com.example.huibaozi.util.StringUtil;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author jigua
 * @version 1.0
 * @className MinioUtils
 * @description
 * @create 2022/9/28 15:30
 */
@Component
public class MinioUtils {


    @Autowired
    private MinioClient minioClient;

    @Value("${minio.bucketName}")
    private String bucketName;

    /**
     * description: 判断bucket是否存在,不存在则创建
     *
     * @return: void
     */
    public boolean existBucket(String name) {
        boolean exists;
        try {
            exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
                exists = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
            exists = false;
        }
        return exists;
    }

    /**
     * 创建存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * description: 上传文件
     *
     * @param file
     * @return: java.lang.String
     */
    public String upload(MultipartFile file, String bucketNameStr) {
        String fileName = file.getOriginalFilename();
        String[] split = fileName.split("\\.");
        if (split.length > 1) {
            fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1];
        } else {
            fileName = fileName + System.currentTimeMillis();
        }
        InputStream in = null;
        try {
            if (StringUtil.isEmpty(bucketNameStr)) {
                bucketNameStr = bucketName;
            }
            in = file.getInputStream();
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketNameStr)
                    .object(fileName)
                    .stream(in, in.available(), -1)
                    .contentType(file.getContentType())
                    .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bucketNameStr + "/" + fileName;
    }

    /**
     * description: 下载文件
     *
     * @param fileName
     * @return: org.springframework.http.ResponseEntity<byte [ ]>
     */
    public ResponseEntity<byte[]> download(String fileName) {
        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            out = new ByteArrayOutputStream();
            IOUtils.copy(in, out);
            //封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            try {
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseEntity;
    }

    /**
     * 查看文件对象
     *
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<ObjectItem> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<ObjectItem> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                ObjectItem objectItem = new ObjectItem();
                objectItem.setObjectName(item.objectName());
                objectItem.setSize(item.size());
                objectItems.add(objectItem);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectItems;
    }

    /**
     * 批量删除文件对象
     *
     * @param bucketName 存储bucket名称
     * @param objects    对象名称集合
     */
    public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
        List<DeleteObject> dos = objects.stream().map(e -> new DeleteObject(e)).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
        return results;
    }

    /**
     * 根据文件名和桶获取文件路径
     *
     * @param bucketName 存储bucket名称
     */
    public String getFileUrl(String bucketName, String objectFile) {
        try {
            if(StringUtil.isEmpty(bucketName)){
                bucketName = this.bucketName;
            }
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(objectFile)
                    .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }

}

  • MinioController
    • 测试类
import com.alibaba.fastjson.JSONObject;
import com.example.huibaozi.minio.MinioUtils;
import com.example.huibaozi.minio.ObjectItem;
import com.example.huibaozi.minio.TestObjects;
import io.minio.Result;
import io.minio.messages.DeleteError;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

/**
 * @author jigua
 * @version 1.0
 * @className MinioController
 * @description
 * @create 2022/9/28 15:33
 */
@RestController
@RequestMapping("/minio")
public class MinioController {


    @Autowired
    private MinioUtils minioUtils;
    @Value("${minio.endpoint}")
    private String address;
    @Value("${minio.bucketName}")
    private String bucketName;

    @PostMapping("/upload")
    public Object upload(@RequestParam(value = "file") MultipartFile file, @RequestParam("bucketName") String bucketName) {
        return address + "/"  + minioUtils.upload(file, bucketName);
    }

    @PostMapping("/getListByBucket")
    public List<ObjectItem> getListByBucket() {
        List<ObjectItem> list = minioUtils.listObjects(bucketName);
        return list;
    }

    @PostMapping("/existBucket")
    public boolean existBucket(@RequestBody JSONObject jsonObject) {
        return minioUtils.existBucket(jsonObject.getString("name"));
    }

    @PostMapping("/makeBucket")
    public boolean makeBucket(@RequestBody JSONObject jsonObject) {
        return minioUtils.makeBucket(jsonObject.getString("name"));
    }

    @PostMapping("/removeBucket")
    public boolean removeBucket(@RequestBody JSONObject jsonObject) {
        return minioUtils.removeBucket(jsonObject.getString("name"));
    }

    @PostMapping("/getFileUrl")
    public String  getFileUrl(@RequestBody JSONObject jsonObject) {
        return minioUtils.getFileUrl(jsonObject.getString("bucketName"),jsonObject.getString("fileName"));
    }

    @PostMapping("/removeObjects")
    public Iterable<Result<DeleteError>> removeObjects(@RequestBody TestObjects testObjects) {
        return minioUtils.removeObjects(testObjects.getName(), testObjects.getList());
    }


    @GetMapping("/loadFile")
    @ResponseBody
    public ResponseEntity<?> loadFile(@RequestParam("filePath") String filePath) {
        return minioUtils.download(filePath);
    }
}

测试图示

  • 创建桶
    • http://192.168.50.96:9999/api/minio/makeBucket
{
    "name":"test"
}

在这里插入图片描述

在这里插入图片描述

  • 不存在则创建桶
    • 执行两次,第二次不会创建
    • http://192.168.50.96:9999/api/minio/existBucket
{
    "name":"testbucket"
}

在这里插入图片描述
在这里插入图片描述

  • 移除桶
    • http://192.168.50.96:9999/api/minio/removeBucket
{
    "name":"testbucket"
}

在这里插入图片描述

在这里插入图片描述

  • 上传文件
    • 使用postman的form-data
    • file
    • bucketName(上传至指定桶,可不传)
      在这里插入图片描述
  • http://192.168.174.139:9000/test/test_1664357658095.jpg
    在这里插入图片描述
  • 无法访问,需要把桶设置为public
    在这里插入图片描述
  • 再次访问
    在这里插入图片描述
  • 查看桶列表
    • http://192.168.50.96:9999/api/minio/getListByBucket
    • 也可以通过参数查看指定桶(请自行实现)
      在这里插入图片描述
  • 根据文件名查找文件位置
    • 使用返回值可在浏览器直接访问
    • http://192.168.50.96:9999/api/minio/getFileUrl
{
    "fileName":"test_1664357658095.jpg"
}

在这里插入图片描述

  • 批量删除文件
    • 先多上传几个文件
    • http://192.168.50.96:9999/api/minio/removeObjects
{
    "name":"test",
    "list":[
        "test_1664357910876.jpg",
        "test_1664357911659.jpg"
    ]
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • GET方式获取文件
    • http://192.168.50.96:9999/api/minio/loadFile?filePath=test_1664357658095.jpg
    • 选择send and download
      在这里插入图片描述
  • 可以发现在public权限下,是可以直接下载文件的
    在这里插入图片描述
  • 把桶设置回private后,发现下载的文件并不是图片
    在这里插入图片描述
    在这里插入图片描述

桶策略

  • 如果把桶的权限配置为public,那这样所有人都可以访问了,显然是不安全的
  • 如果桶为private权限则所有人都没办法访问
    • 只能通过一个最长有效期7天的外链进行访问
    • 例如头像类图片显然无法通过这样的形式获取

剩下的晚点写。。国庆要放假啦。。。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
整合MinioSpring Boot可以通过以下几个步骤完成: 1. 首先,需要在pom.xml文件中添加Minio的依赖项。你可以使用以下代码片段添加依赖项: ```xml <dependency> <groupId>io.minio</groupId> <artifactId>minio</artifactId> <version>8.4.3</version> </dependency> ``` 2. 接下来,在application.yml(或application.properties)文件中配置Minio的连接信息。你需要提供Minio服务端的地址、访问密钥和存储桶名称。以下是一个示例: ```yaml minio: url: 129.0.0.1:9000 access-key: minioadmin secret-key: minioadmin bucket-name: ding_server ``` 3. 最后,在你的代码中使用Minio客户端库进行操作。你可以根据需要使用Minio的API来上传、下载和管理对象。以下是一个使用Minio客户端库的示例: ```java import io.minio.MinioClient; import io.minio.errors.MinioException; // 创建Minio客户端 MinioClient minioClient = new MinioClient("http://localhost:9000", "minioadmin", "minioadmin"); // 上传对象到Minio存储桶 minioClient.putObject("your-bucket-name", "your-object-name", "/path/to/your-file"); // 下载对象从Minio存储桶 minioClient.getObject("your-bucket-name", "your-object-name", "/path/to/save/downloaded-file"); // 列出Minio存储桶中的所有对象 Iterable<Result<Item>> results = minioClient.listObjects("your-bucket-name"); for (Result<Item> result : results) { Item item = result.get(); System.out.println(item.objectName()); } // 删除Minio存储桶中的对象 minioClient.removeObject("your-bucket-name", "your-object-name"); ``` 以上就是在Spring Boot整合Minio的基本步骤。你可以根据具体需求进行进一步的操作和配置。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [springboot整合minio](https://blog.csdn.net/qq_36090537/article/details/128100423)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [SpringBoot整合Minio](https://blog.csdn.net/weixin_46573014/article/details/128476327)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值