• Spring Boot整合Minio实现文件上传和读取


    最近公司有一个需求是关于视频上传播放的,需要设计一个方案,中间谈到了Minio这个技术,于是来学习一下

    一、简介

    1.分布式文件系统应用场景

    互联网海量非结构化数据的存储需求

    • 电商网络:海量商品图片
    • 视频网站:海量视频文件
    • 网盘:海量文件
    • 社交网站:海量图片

    2.Minio介绍

    • Go语言开发,开源,免费的对象存储服务,可以存储海量非结构化的数据,一个对象文件可以是任意大小,从几kb到最大5T不等

    3.Minio优点

    • 部署简单
    • 读写性能优异
    • 支持海量存储

    二、docker部署(windows系统)

    这里我是用自己电脑(windows系统)安装了docker,然后使用docker来部署的

    中文官网单节点多硬盘部署MinIO — MinIO中文文档 | MinIO Container中文文档

    1.创建目录

    • 先进入D盘,创建docker的工作目录 docker_workplace,再创建minio的目录minio,再创建两个文件夹dataconfig,如图所示
      在这里插入图片描述

      拉取镜像,创建容器并运行

    2.拉取镜像

    直接cmd,敲docker命令即可,和linux的语法一样

    在这里插入图片描述

    3.创建容器并运行

    多行:

    
    docker run -d --name minio \
    --privileged=true \
    --restart=always \
    -p 9000:9000 -p 50000:50000 \
    -e "MINIO_ROOT_USER=minio" \
    -e "MINIO_ROOT_PASSWORD=miniominio" \
    -v D:/docker_workplace/data/minio/config:/root/.minio \
    -v D:/docker_workplace/data/minio/data1:/data1 \
    -v D:/docker_workplace/data/minio/data2:/data2 \
    -v D:/docker_workplace/data/minio/data3:/data3 \
    -v D:/docker_workplace/data/minio/data4:/data4 \
    minio/minio \
    server \
    --console-address ":50000" /data{1...4}
    

    单行:

    docker run -p 9000:9000 -p 50000:50000 -d --name minio -e "MINIO_ACCESS_KEY=minio" -e "MINIO_SECRET_KEY=miniominio" -v D:/docker_workplace/data/minio/config:/root/.minio -v D:/docker_workplace/data/minio/data1:/data1 -v D:/docker_workplace/data/minio/data2:/data2 -v D:/docker_workplace/data/minio/data3:/data3 -v D:/docker_workplace/data/minio/data4:/data4 --restart always minio/minio server --console-address ":50000" /data{1...4}
    

    4.访问控制台

    浏览器访问:

    http://localhost:50000/login

    账号/密码(刚刚docker运行命令设置的):minio/miniominio

    这里我第一次访问失败了,看了下docker日志,发现是账号密码长度不符合规范导致(一开始密码是minio,达不到8个字符)

    在这里插入图片描述

    于是改了密码为miniominio,成功运行

    在这里插入图片描述

    5.初始化配置

    • 创建一个桶 Bucket
      在这里插入图片描述

      这里出现了一个报错,原因是说需要分布式部署才能使用,解决办法:挂载多个卷

      在这里插入图片描述

      在这里插入图片描述

    • 配置桶权限为public

      在这里插入图片描述

    到这里,minio单机版就部署好了

    三、Spring Boot整合Minio

    1.创建demo项目

    新建一个spring boot项目

    2.引入依赖

    <dependency>
        <groupId>io.miniogroupId>
        <artifactId>minioartifactId>
        <version>8.3.7version>
    dependency>
    

    3.配置

    application.yml

    spring:
      application:
        name: miniodemo
      servlet:
        multipart:
          # 文件上传大小限制。超过该值直接报错
          max-file-size: 20MB
          # 文件最大请求限制,用于批量上传
          max-request-size: 20MB
      datasource:
        url: jdbc:mysql://localhost:3306/minio?useSSL=false&serverTimezone=UTC
        username: root
        password: root
        driver-class-name: com.mysql.cj.jdbc.Driver
    # MinIO 配置
    minio:
      endpoint: http://localhost:9000      # MinIO服务地址
      fileHost: http://localhost:9000      # 文件地址host
      bucketName: test                      # 存储桶bucket名称
      accessKey: minio                         # 用户名
      secretKey: miniominio                     # 密码
      imgSize: 20                           # 图片大小限制,单位:m
      fileSize: 20                          # 文件大小限制,单位:m
    mybatis:
      mapper-locations: classpath:mapper/*.xml
      type-aliases-package: com.xuyue.miniodemo.domain
    

    4.编写配置类

    MinIOConfig.java

    package com.xuyue.miniodemo.config;
    
    import lombok.Data;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @Data
    public class MinIOConfig {
    
        @Value("${minio.endpoint}")
        private String endpoint;
        @Value("${minio.fileHost}")
        private String fileHost;
        @Value("${minio.bucketName}")
        private String bucketName;
        @Value("${minio.accessKey}")
        private String accessKey;
        @Value("${minio.secretKey}")
        private String secretKey;
    
        @Value("${minio.imgSize}")
        private Integer imgSize;
        @Value("${minio.fileSize}")
        private Integer fileSize;
    
    }
    

    5.MinIO工具类

    MinIoUploadService.java

    package com.xuyue.miniodemo.service;
    
    import com.xuyue.miniodemo.config.MinIOConfig;
    import io.minio.*;
    import io.minio.http.Method;
    import io.minio.messages.Bucket;
    import io.minio.messages.DeleteObject;
    import io.minio.messages.Item;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.annotation.PostConstruct;
    import javax.annotation.Resource;
    import java.io.ByteArrayInputStream;
    import java.io.InputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;
    import java.util.Optional;
    
    /**
     * MinIO工具类
     */
    @Slf4j
    @Service
    public class MinIoUploadService {
    
        @Resource
        private MinIOConfig minIOConfig;
    
        private MinioClient minioClient;
    
        private String endpoint;
        private String bucketName;
        private String accessKey;
        private String secretKey;
        private Integer imgSize;
        private Integer fileSize;
    
    
        private final String SEPARATOR = "/";
    
        @PostConstruct
        public void init() {
            this.endpoint = minIOConfig.getEndpoint();
            this.bucketName = minIOConfig.getBucketName();
            this.accessKey = minIOConfig.getAccessKey();
            this.secretKey = minIOConfig.getSecretKey();
            this.imgSize = minIOConfig.getImgSize();
            this.fileSize = minIOConfig.getFileSize();
            createMinioClient();
        }
    
        /**
         * 创建基于Java端的MinioClient
         */
        public void createMinioClient() {
            try {
                if (null == minioClient) {
                    log.info("开始创建 MinioClient...");
                    minioClient = MinioClient
                            .builder()
                            .endpoint(endpoint)
                            .credentials(accessKey, secretKey)
                            .build();
                    createBucket(bucketName);
                    log.info("创建完毕 MinioClient...");
                }
            } catch (Exception e) {
                log.error("MinIO服务器异常:{}", e);
            }
        }
    
        /**
         * 获取上传文件前缀路径
         *
         * @return
         */
        public String getBasisUrl() {
            return endpoint + SEPARATOR + bucketName + SEPARATOR;
        }
    
        /******************************  Operate Bucket Start  ******************************/
    
        /**
         * 启动SpringBoot容器的时候初始化Bucket
         * 如果没有Bucket则创建
         *
         * @throws Exception
         */
        private void createBucket(String bucketName) throws Exception {
            if (!bucketExists(bucketName)) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
        }
    
        /**
         * 判断Bucket是否存在,true:存在,false:不存在
         *
         * @return
         * @throws Exception
         */
        public boolean bucketExists(String bucketName) throws Exception {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
        }
    
    
        /**
         * 获得Bucket的策略
         *
         * @param bucketName
         * @return
         * @throws Exception
         */
        public String getBucketPolicy(String bucketName) throws Exception {
            String bucketPolicy = minioClient
                    .getBucketPolicy(
                            GetBucketPolicyArgs
                                    .builder()
                                    .bucket(bucketName)
                                    .build()
                    );
            return bucketPolicy;
        }
    
    
        /**
         * 获得所有Bucket列表
         *
         * @return
         * @throws Exception
         */
        public List<Bucket> getAllBuckets() throws Exception {
            return minioClient.listBuckets();
        }
    
        /**
         * 根据bucketName获取其相关信息
         *
         * @param bucketName
         * @return
         * @throws Exception
         */
        public Optional<Bucket> getBucket(String bucketName) throws Exception {
            return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
        }
    
        /**
         * 根据bucketName删除Bucket,true:删除成功; false:删除失败,文件或已不存在
         *
         * @param bucketName
         * @throws Exception
         */
        public void removeBucket(String bucketName) throws Exception {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
        }
    
        /******************************  Operate Bucket End  ******************************/
    
    
        /******************************  Operate Files Start  ******************************/
    
        /**
         * 判断文件是否存在
         *
         * @param bucketName 存储桶
         * @param objectName 文件名
         * @return
         */
        public boolean isObjectExist(String bucketName, String objectName) {
            boolean exist = true;
            try {
                minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            } catch (Exception e) {
                exist = false;
            }
            return exist;
        }
    
        /**
         * 判断文件夹是否存在
         *
         * @param bucketName 存储桶
         * @param objectName 文件夹名称
         * @return
         */
        public boolean isFolderExist(String bucketName, String objectName) {
            boolean exist = false;
            try {
                Iterable<Result<Item>> results = minioClient.listObjects(
                        ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
                for (Result<Item> result : results) {
                    Item item = result.get();
                    if (item.isDir() && objectName.equals(item.objectName())) {
                        exist = true;
                    }
                }
            } catch (Exception e) {
                exist = false;
            }
            return exist;
        }
    
        /**
         * 根据文件前缀查询文件
         *
         * @param bucketName 存储桶
         * @param prefix     前缀
         * @param recursive  是否使用递归查询
         * @return MinioItem 列表
         * @throws Exception
         */
        public List<Item> getAllObjectsByPrefix(String bucketName,
                                                String prefix,
                                                boolean recursive) throws Exception {
            List<Item> list = new ArrayList<>();
            Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
            if (objectsIterator != null) {
                for (Result<Item> o : objectsIterator) {
                    Item item = o.get();
                    list.add(item);
                }
            }
            return list;
        }
    
        /**
         * 获取文件流
         *
         * @param bucketName 存储桶
         * @param objectName 文件名
         * @return 二进制流
         */
        public InputStream getObject(String bucketName, String objectName) throws Exception {
            return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
        }
    
        /**
         * 断点下载
         *
         * @param bucketName 存储桶
         * @param objectName 文件名称
         * @param offset     起始字节的位置
         * @param length     要读取的长度
         * @return 二进制流
         */
        public InputStream getObject(String bucketName, String objectName, long offset, long length) throws Exception {
            return minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .offset(offset)
                            .length(length)
                            .build());
        }
    
        /**
         * 获取路径下文件列表
         *
         * @param bucketName 存储桶
         * @param prefix     文件名称
         * @param recursive  是否递归查找,false:模拟文件夹结构查找
         * @return 二进制流
         */
        public Iterable<Result<Item>> listObjects(String bucketName, String prefix,
                                                  boolean recursive) {
            return minioClient.listObjects(
                    ListObjectsArgs.builder()
                            .bucket(bucketName)
                            .prefix(prefix)
                            .recursive(recursive)
                            .build());
        }
    
        /**
         * 使用MultipartFile进行文件上传
         *
         * @param bucketName  存储桶
         * @param file        文件名
         * @param objectName  对象名
         * @param contentType 类型
         * @return
         * @throws Exception
         */
        public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,
                                              String objectName, String contentType) throws Exception {
            InputStream inputStream = file.getInputStream();
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .contentType(contentType)
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
        }
    
        /**
         * 上传本地文件
         *
         * @param bucketName 存储桶
         * @param objectName 对象名称
         * @param fileName   本地文件路径
         */
        public ObjectWriteResponse uploadFile(String bucketName, String objectName,
                                              String fileName) throws Exception {
            return minioClient.uploadObject(
                    UploadObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .filename(fileName)
                            .build());
        }
    
        /**
         * 通过流上传文件
         *
         * @param bucketName  存储桶
         * @param objectName  文件对象
         * @param inputStream 文件流
         */
        public ObjectWriteResponse uploadFile(String bucketName, String objectName, InputStream inputStream) throws Exception {
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
        }
    
        /**
         * 创建文件夹或目录
         *
         * @param bucketName 存储桶
         * @param objectName 目录路径
         */
        public ObjectWriteResponse createDir(String bucketName, String objectName) throws Exception {
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
                            .build());
        }
    
        /**
         * 获取文件信息, 如果抛出异常则说明文件不存在
         *
         * @param bucketName 存储桶
         * @param objectName 文件名称
         */
        public String getFileStatusInfo(String bucketName, String objectName) throws Exception {
            return minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build()).toString();
        }
    
        /**
         * 拷贝文件
         *
         * @param bucketName    存储桶
         * @param objectName    文件名
         * @param srcBucketName 目标存储桶
         * @param srcObjectName 目标文件名
         */
        public ObjectWriteResponse copyFile(String bucketName, String objectName,
                                            String srcBucketName, String srcObjectName) throws Exception {
            return minioClient.copyObject(
                    CopyObjectArgs.builder()
                            .source(CopySource.builder().bucket(bucketName).object(objectName).build())
                            .bucket(srcBucketName)
                            .object(srcObjectName)
                            .build());
        }
    
        /**
         * 删除文件
         *
         * @param bucketName 存储桶
         * @param objectName 文件名称
         */
        public void removeFile(String bucketName, String objectName) throws Exception {
            minioClient.removeObject(
                    RemoveObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .build());
        }
    
        /**
         * 批量删除文件
         *
         * @param bucketName 存储桶
         * @param keys       需要删除的文件列表
         * @return
         */
        public void removeFiles(String bucketName, List<String> keys) {
            List<DeleteObject> objects = new LinkedList<>();
            keys.forEach(s -> {
                objects.add(new DeleteObject(s));
                try {
                    removeFile(bucketName, s);
                } catch (Exception e) {
                    log.error("批量删除失败!error:{}", e);
                }
            });
        }
    
        /**
         * 获取文件外链
         *
         * @param bucketName 存储桶
         * @param objectName 文件名
         * @param expires    过期时间 <=7 秒 (外链有效时间(单位:秒))
         * @return url
         * @throws Exception
         */
        public String getPresignedObjectUrl(String bucketName, String objectName, Integer expires) throws Exception {
            GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().expiry(expires).bucket(bucketName).object(objectName).build();
            return minioClient.getPresignedObjectUrl(args);
        }
    
        /**
         * 获得文件外链
         *
         * @param bucketName
         * @param objectName
         * @return url
         * @throws Exception
         */
        public String getPresignedObjectUrl(String bucketName, String objectName) throws Exception {
            GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .method(Method.GET).build();
            return minioClient.getPresignedObjectUrl(args);
        }
    
        /**
         * 将URLDecoder编码转成UTF8
         *
         * @param str
         * @return
         * @throws UnsupportedEncodingException
         */
        public String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
            String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
            return URLDecoder.decode(url, "UTF-8");
        }
    
        /******************************  Operate Files End  ******************************/
    
    
    }
    

    6.文件上传

    FileController.java

    package com.xuyue.miniodemo.controller;
    
    import com.xuyue.miniodemo.config.MinIOConfig;
    import com.xuyue.miniodemo.service.MinIoUploadService;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    @RestController
    @Slf4j
    public class FileController {
        @Autowired
        private MinIoUploadService minIoUploadService;
        @Autowired
        private MinIOConfig minIOConfig;
    
        /**
         * 上传文件,返回url
         * @param file
         * @return
         * @throws Exception
         */
        @PostMapping("upload")
        public String upload(MultipartFile file) throws Exception {
            String fileName = file.getOriginalFilename();
            minIoUploadService.uploadFile(minIOConfig.getBucketName(), fileName, file.getInputStream());
            String imgUrl = minIOConfig.getFileHost()
                    + "/"
                    + minIOConfig.getBucketName()
                    + "/"
                    + fileName;
    
            return imgUrl;
        }
    
    }
    

    测试

    启动项目

    使用postman请求接口,返回文件地址

    在这里插入图片描述

    访问地址:

    在这里插入图片描述

    查看minio控制台:

    在这里插入图片描述

  • 相关阅读:
    数据库安全定义以及重要性简单讲解
    Python爬虫网易云音乐,Tkinter制作音乐播放器
    反射、枚举以及lambda表达式
    Python Opencv实践 - 入门使用Tesseract识别图片中的文字
    哈希的应用 —— 布隆过滤器
    通过C语言实现计算机模拟疫情扩散
    Matlab论文插图绘制模板第60期—瀑布图(Waterfall)
    k8s自动扩缩容基于HPA
    处理uniapp打包后有广告的问题
    短视频矩阵源码系统
  • 原文地址:https://blog.csdn.net/m0_52620144/article/details/140426316