【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储

文章目录

  • 1.简介
  • 2.安装使用
    • 2.1.下载二进制文件安装
      • 2.1.1 单机部署
      • 2.1.2.集群安装
        • 2.1.2.1 创建存储路径
        • 2.1.2.2.创建集群启动脚本
        • 2.1.2.3.分别启动4个节点
    • 2.2.使用docker安装
  • 3.实战
    • 3.1.引入依赖
    • 3.2.配置Minio连接信息
    • 3.3.编辑Minio配置类
    • 3.4 编写Mino工具类
    • 3.5.测试接口
  • 4.测试
    • 4.1.上传文件
    • 4.2.下载文件
    • 4.3.删除文件
  • 5.设置桶里的文件访问权限为公开的
  • 6.项目配套代码

1.简介

是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等,相对于FastDFS部署起来要轻量很多。

中文文档: http://docs.minio.org.cn/docs/master/distributed-minio-quickstart-guide

2.安装使用

2.1.下载二进制文件安装

2.1.1 单机部署

# 下载 minio
wget https://dl.min.io/server/minio/release/linux-amd64/minio
# 添加可执行权限
chmod +x minio
# 设置登录minio的 access key
export MINIO_ACCESS_KEY=minioadmin
# 设置登录minio的 secret key
export MINIO_SECRET_KEY=minioadmin
# 启动 minio 并设置文件存储的路径为/data目录,如果需要退出命令行程序还在需要在最前面加上 nohup 命令
 ./minio server /data

启动后可以看到如下信息
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第1张图片

  • 控制台(页面)访问地址: http://192.168.94.128:37788/
  • API(SDK)访问地址: http://192.168.94.128:9000/
    【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第2张图片
    【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第3张图片

2.1.2.集群安装

参考:http://docs.minio.org.cn/docs/master/distributed-minio-quickstart-guide

2.1.2.1 创建存储路径

创建minio文件存储路径

mkdir -p /data/minio

注意事项

  • 新创建的目录需要挂载到磁盘下要不然启动会报错
  • 分布式Minio里所有的节点需要有同样的access秘钥和secret秘钥,这样这些节点才能建立联接。为了实现这个,你需要在执行minio server命令之前,先将access秘钥和secret秘钥export成环境变量。
  • 分布式Minio使用的磁盘里必须是干净的,里面没有数据。
  • 分布式Minio里的节点时间差不能超过3秒,你可以使用NTP 来保证时间一致。

注意:需要将新建的目录挂在到对应的磁盘下,磁盘不挂载好,集群启动会报错 Waiting for a minimum of 2 disks to come online :找不到磁盘:

mount /dev/sda1  /data

查看是否挂载成功

lsblk

【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第4张图片

2.1.2.2.创建集群启动脚本
  • 集群环境建议把MINIO_ACCESS_KEY和MINIO_SECRET_KEY换成MINIO_ROOT_USER和MINIO_ROOT_PASSWORD
  • 需要把下面的ip地址换成你部署节点的
vi minio-run.sh
#!/bin/bash
export MINIO_ROOT_USER=minioadmin
export MINIO_ROOT_PASSWORD=minioadmin
/usr/local/minio/minio  server \
 http://192.168.94.128/data/minio \
 http://192.168.94.129/data/minio \
 http://192.168.94.131/data/minio \
 http://192.168.94.132/data/minio
2.1.2.3.分别启动4个节点
./minio-run.sh

【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第5张图片

访问任意一个节点的控制台可以查看到集群信息,表示集群环境搭建成功了。
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第6张图片

2.2.使用docker安装

创建挂载目录

mkdir -p /docker/minio/data  /docker/minio/config

配置ip4转发

vim /etc/sysctl.conf
#配置转发
net.ipv4.ip_forward=1

#重启服务,让配置生效
systemctl restart network
#查看是否成功,如果返回为“net.ipv4.ip_forward = 1”则表示成功
sysctl net.ipv4.ip_forward

构建并运行容器

docker run -p 9000:9000 -p 9001:9001 --name minio \
-it -d --restart=always \
-e "MINIO_ACCESS_KEY=minioadmin" \
-e "MINIO_SECRET_KEY=minioadmin" \
-v /docker/minio/data:/data \
-v /docker/minio/config:/root/.minio \
minio/minio server /data \
--console-address ":9001" --address ":9090"

控制台用9001端口, API用9000端口
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第7张图片

3.实战

3.1.引入依赖

      
            org.springframework.boot
            spring-boot-starter-web
        
       
            org.projectlombok
            lombok
        
        
        
            io.minio
            minio
            8.3.4
            
                
                    com.squareup.okhttp3
                    okhttp
                
            
        
        
         
          
            com.squareup.okhttp3
            okhttp
            4.9.0
        
        
            com.squareup.okio
            okio
            2.8.0
        

3.2.配置Minio连接信息

server:
  port: 8025
minio:
  #minio部署的机器ip地址
  endpoint: 192.168.94.128
  #minio使用的端口
  port: 9000
  #唯一标识的账户
  accessKey: minioadmin
  #账户的密码
  secretKey: minioadmin
  #是否使用https
  secure: false
  #测试使用的桶名称
  defaultBucketName: test

3.3.编辑Minio配置类

@Configuration
@ConfigurationProperties(prefix = "minio")
@Data
public class MinioConfig {

	/**
	 * minio部署的机器ip地址
	 */
	private String endpoint;

	/**
	 * minio使用的端口
	 */
	private Integer port;

	/**
	 *唯一标识的账户
	 */
	private String accessKey;

	/**
	 * 账户的密码
	 */
	private String secretKey;

	/**
	 * 如果是true,则用的是https而不是http,默认值是true
	 */
	private Boolean secure;

	/**
	 * 默认使用的桶名称
	 */
	private String defaultBucketName;
	
	/**
	 * 对象交给spring管理
	 */
	@Bean
	public MinioClient getMinioClient() {
		MinioClient minioClient = MinioClient.builder()
				.endpoint(endpoint , port , secure)
				.credentials(accessKey, secretKey)
				.build();
		return minioClient;
	}
}

3.4 编写Mino工具类

@Component
@Slf4j
public class MinioUtil {

    @Autowired
    private MinioClient minioClient;

    /**
     * 获取当前日期字符串格式
     * @return 2021/12/5
     */
    public String getDatePath() {
        LocalDateTime now = LocalDateTime.now();
        return String.format("/%s/%s/%s/", now.getYear(), now.getMonthValue(), now.getDayOfMonth());
    }


    /**
     * 判断桶是否存
     * @param bucketName 桶名称
     * @return
     */
    public boolean bucketExists(String bucketName) throws Exception {
        boolean flag = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        if (flag) {
            return true;
        }
        return false;
    }


    /**
     * 创建桶
     */
    public boolean createBucket(String bucketName) {
        try {
            //判断文件存储的桶对象是否存在
            boolean isExist = bucketExists(bucketName);
            if (isExist) {
                log.info("Bucket asiatrip already exists.");
                return false;
            } else {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
                return true;
            }
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return false;
        }
    }

    /**
     * 列出桶里的所有对象
     * @param bucketName 桶名称
     */
    public Iterable<Result<Item>> listObjects(String bucketName) {
        return minioClient.listObjects(ListObjectsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 删除桶
     * @param bucketName 桶名称
     * @return 是否删除成功
     */
    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;
                }
            }
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return false;
        }
        return false;
    }

    /**
     * 获取所有桶信息
     */
    public List<Bucket> getAllBucket() {
        try {
            // 获取minio中所以的bucket
            List<Bucket> buckets = minioClient.listBuckets();
            for (Bucket bucket : buckets) {
                log.info("bucket 名称:  {}      bucket 创建时间: {}", bucket.name(), bucket.creationDate());
            }
            return buckets;
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return Collections.emptyList();
        }
    }


    /**
     * 上传本地文件到指定桶下
     * @param bucketName    桶名称
     * @param objectName    对象名称
     * @param localFileName 要上传的文件路径
     * @return
     */
    public boolean upload(String bucketName, String objectName, String localFileName) {
        try {
            File file = new File(localFileName);
            FileInputStream fileInputStream = new FileInputStream(file);
            minioClient.putObject(PutObjectArgs.builder()
                    .stream(fileInputStream, file.length(), PutObjectArgs.MIN_MULTIPART_SIZE)
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return false;
        }
    }


    /**
     * 上传MultipartFile到指定桶下
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param file       文件流
     */
    public boolean upload(String bucketName, String objectName, MultipartFile file) {
        try {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .stream(file.getInputStream(), file.getSize(), PutObjectArgs.MIN_MULTIPART_SIZE)
                    .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return false;
        }
    }

    /**
     * 下载文件到本地
     * @param bucketName    桶名称
     * @param objectName    对象名称
     * @param localFileName 本地文件存储路径
     */
    public boolean downLocal(String bucketName, String objectName, String localFileName) {
        try {
            minioClient.downloadObject(DownloadObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .filename(localFileName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return false;
        }
    }


    /**
     * 下载文件写入到HttpServletResponse
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param response   HttpServletResponse对象
     */
    @SneakyThrows
    public void downResponse(String bucketName, String objectName, HttpServletResponse response) {
        GetObjectResponse object = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
        response.setHeader("Content-Disposition", "attachment;filename=" + objectName.substring(objectName.lastIndexOf("/") + 1));
        response.setContentType("application/force-download");
        response.setCharacterEncoding("UTF-8");
        IOUtils.copy(object, response.getOutputStream());
    }


    /**
     * 删除指定桶的指定文件对象
     * @param bucketName 桶名称
     * @param objectName 对象名称
     */
    public boolean delete(String bucketName, String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
            return true;
        }catch (Exception e){
            log.error("errorMsg={}",e);
            return false;
        }
    }

    /**
     * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
     * @param bucketName  存储桶名称
     * @param objectNames 含有要删除的多个object名称的迭代器对象
     * @return
     */
    public boolean deletes(String bucketName, List<String> objectNames) {
        try {
            List<String> deleteErrorNames = new ArrayList<>();

            List<DeleteObject> list = new LinkedList<>();
            objectNames.forEach(item -> list.add(new DeleteObject(item)));
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(list).build());
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                deleteErrorNames.add(error.objectName());
            }
            return deleteErrorNames.size() == 0 ? true : false;
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return false;
        }
    }

    /**
     * 获取文件带时效的访问链接   失效时间(以秒为单位),默认是7天不得大于七天
     * @param bucketName     桶名称
     * @param remoteFileName 对象名称
     * @param timeout        时间
     * @param unit           单位
     * @return 文件访问链接
     */
    public String getPresignedObjectUrl(String bucketName, String remoteFileName, long timeout, TimeUnit unit) {
        try {
            return minioClient.getPresignedObjectUrl(
                    GetPresignedObjectUrlArgs.builder()
                            .method(Method.GET)
                            .bucket(bucketName)
                            .object(remoteFileName)
                            .expiry((int) unit.toSeconds(timeout))
                            .build());
        } catch (Exception e) {
            log.error("errorMsg={}",e);
            return null;
        }
    }

}

重源码中能看到getPresignedObjectUrl函数可以设置的外链的时效范围为 7天>= expiry >=1,默认值为7天
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.5.测试接口

@RestController
@RequestMapping("/file")
public class FileController {

    @Resource
    private MinioConfig minioConfig;

    @Autowired
    private MinioUtil minioUtil;

    /**
     * 上传文件
     * @param file
     * @param bucketName 桶名称
     * @return 返回对象名称和外链地址
     */
    @PostMapping(value = "/")
    public ResponseEntity<HashMap<String, String>> uploadFile(MultipartFile file, @RequestParam(required = false) String bucketName) {
        bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioConfig.getDefaultBucketName();
        String objectName = minioUtil.getDatePath() + file.getOriginalFilename();
        minioUtil.upload(bucketName, objectName, file);
        String viewPath = minioUtil.getPresignedObjectUrl(bucketName, objectName, 60, TimeUnit.SECONDS);
        HashMap<String, String> objectInfo = new HashMap<>();
        objectInfo.put("objectName", objectName);
        //只能预览图片、txt等部分文件
        objectInfo.put("viewPath", viewPath);
        return ResponseEntity.ok(objectInfo);
    }


    /**
     * 删除指定桶里的某个对象
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @return
     */
    @DeleteMapping(value = "/")
    public ResponseEntity<String> deleteByPath(@RequestParam(required = false) String bucketName, String objectName) {
        bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioConfig.getDefaultBucketName();
        minioUtil.delete(bucketName, objectName);
        return ResponseEntity.ok("删除成功");
    }

    /**
     * 下载文件
     * @param bucketName 桶名称
     * @param objectName 对象名称
     * @param response  相应结果
     */
    @GetMapping("/")
    public void downLoad(@RequestParam(required = false) String bucketName, String objectName,HttpServletResponse response) {
        // 获取文件
        minioUtil.downResponse(bucketName,objectName,response);

    }
}

4.测试

4.1.上传文件

通过postman上传文件到minio上面,会返回一个对象名称和一个带时效的外链名称
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第8张图片

可以复制外链地址到浏览器上面进行下载该文件
在这里插入图片描述

因为代码里配置了链接的有效期为1分钟,超时后再访问就失效了
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第9张图片

4.2.下载文件

浏览器访问接口参数为对象的名称可以下载对应的文件
http://localhost:8025/file/?objectName=/test/2021/12/5/booklist.txt
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第10张图片

4.3.删除文件

通过postman设置DELETE请求类型调用删除文件接口
localhost:8025/file/?objectName=2021/12/5/booklist.txt
【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第11张图片

5.设置桶里的文件访问权限为公开的

【SpringBoot框架篇】25.集成Minio实现文件的分布式对象存储_第12张图片
设置完就可以通过浏览器直接访问文件了。
访问方式为http://192.168.94.128:API端口/桶名称/对象名称
例如: http://192.168.94.128:9000/test/2021/12/5/booklist.txt

6.项目配套代码

gitee代码地址

创作不易,要是觉得我写的对你有点帮助的话,麻烦在gitee上帮我点下 Star

【SpringBoot框架篇】其它文章如下,后续会继续更新。

  • 1.搭建第一个springboot项目
  • 2.Thymeleaf模板引擎实战
  • 3.优化代码,让代码更简洁高效
  • 4.集成jta-atomikos实现分布式事务
  • 5.分布式锁的实现方式
  • 6.docker部署,并挂载配置文件到宿主机上面
  • 7.项目发布到生产环境
  • 8.搭建自己的spring-boot-starter
  • 9.dubbo入门实战
  • 10.API接口限流实战
  • 11.Spring Data Jpa实战
  • 12.使用druid的monitor工具查看sql执行性能
  • 13.使用springboot admin对springboot应用进行监控
  • 14.mybatis-plus实战
  • 15.使用shiro对web应用进行权限认证
  • 16.security整合jwt实现对前后端分离的项目进行权限认证
  • 17.使用swagger2生成RESTful风格的接口文档
  • 18.使用Netty加websocket实现在线聊天功能
  • 19.使用spring-session加redis来实现session共享
  • 20.自定义@Configuration配置类启用开关
  • 21.对springboot框架编译后的jar文件瘦身
  • 22.集成RocketMQ实现消息发布和订阅
  • 23.集成smart-doc插件零侵入自动生成RESTful格式API文档
  • 24.集成FastDFS实现文件的分布式存储
  • 25.集成Minio实现文件的私有化对象存储
  • 26.集成spring-boot-starter-validation对接口参数校验
  • 27.集成mail实现邮件推送带网页样式的消息
  • 28.使用JdbcTemplate操作数据库
  • 29.Jpa+vue实现单模型的低代码平台
  • 30.使用sharding-jdbc实现读写分离和分库分表
  • 31.基于分布式锁或xxx-job实现分布式任务调度
  • 32.基于注解+redis实现表单防重复提交
  • 33.优雅集成i18n实现国际化信息返回
  • 34.使用Spring Retry完成任务的重试

你可能感兴趣的:(springBoot,spring,boot,linux,后端,分布式)