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

avatar
作者
猴君
阅读量:0

文章目录

最近公司有一个需求是关于视频上传播放的,需要设计一个方案,中间谈到了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.minio</groupId>     <artifactId>minio</artifactId>     <version>8.3.7</version> </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控制台:

在这里插入图片描述

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!