SpringBoot(十八)SpringBoot集成Minio

213 阅读8分钟

项目上传文件集成一下Minio,下面是我在项目中集成Minio的全过程。

 

首先介绍一下Minio:MinIO是高性能的对象存储,单个对象最大可达5TB。适合存储图片、视频、文档、备份数据、安装包等一系列文件。是一款主要采用Golang语言实现发开的高性能、分布式的对象存储系统。客户端支持Java,Net,Python,Javacript,Golang语言。客户端与服务器之间采用http/https通信协议。

 

官网:min.io/

 

一:安装Minio

1:下载Minio

cd /usr/local/download
wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio

 

2:授权

chmod +x minio

 

3:启动Minio

/usr/local/download/minio server --console-address :9001 --address 0.0.0.0:9002 /usr/local/minio/data > /usr/local/minio/log/minio.log
# /usr/local/download/minio minio文件
# --console-address :9001 控制台端口--address 0.0.0.0:9002 服务端口
# /usr/local/minio/data 数据目录
# /usr/local/minio/log/minio.log 日志目录

启动成功如下图所示:

1.jpg.png

 

4:浏览器访问:

http://1.15.157.156:9001/login

2.jpg.png

 

5:创建service服务文件

touch /usr/lib/systemd/system/minio.service

文件内容如下:

[Unit]
Description=Minio
Documentation=https://docs.minio.io
Wants=network-online.target
After=network-online.target
#minio文件具体位置
AssertFileIsExecutable=/usr/local/download/minio
 
[Service]
WorkingDirectory=/usr/local/minio/data
# User and group 用户/组
User=root
Group=root
PermissionsStartOnly=true
 
#创建的配置文件 minio.conf
EnvironmentFile=/etc/default/minio
 
ExecStartPre=/bin/bash -c "[ -n "${MINIO_VOLUMES}" ] || echo "Variable MINIO_VOLUMES not set in /etc/default/minio""
# $MINIO_OPTS $MINIO_VOLUMES  //这其实就是minio服务启动命令 /root/minio是服务位置 后面是端口号和数据存放目录
ExecStart=/usr/local/download/minio server $MINIO_OPTS $MINIO_VOLUMES
 
## 如果不用/etc/default/minio 默认配置,通过--confit-dir 指定自定义conf。 /path/to/minio 可执行文件路径,/path/to/config替换为MinIO的配置文件路径。  
## ExecStart=/path/to/minio server --config-dir=/path/to/config
 
StandardOutput=journal
StandardError=inherit
 
# Specifies the maximum file descriptor number that can be opened by this process*
LimitNOFILE=65536
 
# Disable timeout logic and wait until process is stopped*
TimeoutStopSec=0
 
# SIGTERM signal is used to stop Minio*
KillSignal=SIGTERM
 
SendSIGKILL=no
SuccessExitStatus=0
 
[Install]
WantedBy=multi-user.target

 

文件编辑完成记得重载一下systemd

systemctl daemon-reload

 

6:创建环境变量

touch /etc/default/minio

内容如下:

# 设置用户名和密码
## 启动的时候看提示 新版本
MINIO_ROOT_USER=minioadmin
MINIO_ROOT_PASSWORD=xxxxxx
 
# 对应minio的安装目录位置
MINIO_VOLUMES="/usr/local/minio/data"
 
# 注意这里的ip要改成你们自己的ip地址, 这里的ip特别注意,需要内网ip,否则服务起不来。固定端口号设置,避免重启后 端口号随机
MINIO_OPTS="--address :9002 --console-address :9001"
 
# MINIO_SERVER_URL="http://minio.example.net:9000"

 

6:启动服务

#设置开机启动
systemctl enable minio.service
#启动服务
systemctl start minio.service
 
#停止服务
systemctl stop minio.service
#重启服务
systemctl restrat minio.service
#查看服务状态
systemctl status minio.service

 

二:springboot集成Minio

1:添加POM依赖

<!--   Minio依赖     -->
 <dependency>
     <groupId>io.minio</groupId>
     <artifactId>minio</artifactId>
     <version>8.2.1</version>
 </dependency>
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-configuration-processor</artifactId>
     <optional>true</optional>
 </dependency>

 

2:配置application.yml

spring:
   servlet:
     multipart:
       max-file-size: 10MB
       max-request-size: 10MB
   minio:
     access-key: xxxxxxxxx#这个需要再控制台创建 ,后边有截图示例
     secret-key: xxxxxxxxx#这个需要再控制台创建,后边有截图示例
     url: http://1.15.157.156:9002
     bucket-name: test   # 登陆minio创建的文件桶
     secure: false
     image-size: 10485760
     file-size: 1073741824
     endpoint: http://1.15.157.156
     port: 9000

 

3:Minio配置类MinioUtil

package com.springbootblog.utils;
 
 import com.springbootblog.config.MinioProperties;
 import io.minio.*;
 import io.minio.http.Method;
 import io.minio.messages.Bucket;
 import io.minio.messages.DeleteError;
 import io.minio.messages.DeleteObject;
 import io.minio.messages.Item;
 import lombok.RequiredArgsConstructor;
 import lombok.SneakyThrows;
 import org.springframework.stereotype.Component;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 @Component
 @RequiredArgsConstructor
 public class MinioUtil
 {
     private final MinioClient minioClient;
 
     private final MinioProperties minioProperties;
 
     /**
      * 检查存储桶是否存在
      *
      * @param bucketName 存储桶名称
      * @return
      */
     @SneakyThrows
     public boolean bucketExists(String bucketName)
     {
         boolean found =
                 minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
         if (found)
         {
             System.out.println(bucketName + " exists");
         }
         else
         {
             System.out.println(bucketName + " does not exist");
         }
         return found;
     }
 
     /**
      * 创建存储桶
      *
      * @param bucketName 存储桶名称
      */
     @SneakyThrows
     public boolean makeBucket(String bucketName)
     {
         boolean flag = bucketExists(bucketName);
         if (!flag)
         {
             minioClient.makeBucket(
                     MakeBucketArgs.builder()
                             .bucket(bucketName)
                             .build());
 
             return true;
         }
         else
         {
             return false;
         }
     }
 
     /**
      * 列出所有存储桶名称
      *
      * @return
      */
     @SneakyThrows
     public List<String> listBucketNames()
     {
         List<Bucket> bucketList = listBuckets();
         List<String> bucketListName = new ArrayList<>();
         for (Bucket bucket : bucketList)
         {
             bucketListName.add(bucket.name());
         }
         return bucketListName;
     }
 
     /**
      * 列出所有存储桶
      *
      * @return
      */
     @SneakyThrows
     public List<Bucket> listBuckets() {
         return minioClient.listBuckets();
     }
 
 
     /**
      * 删除存储桶
      *
      * @param bucketName 存储桶名称
      * @return
      */
     @SneakyThrows
     public boolean removeBucket(String bucketName)
     {
         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;
             }
         }
         return false;
     }
 
     /**
      * 列出存储桶中的所有对象名称
      *
      * @param bucketName 存储桶名称
      * @return
      */
     @SneakyThrows
     public List<String> listObjectNames(String bucketName)
     {
         List<String> listObjectNames = new ArrayList<>();
         boolean flag = bucketExists(bucketName);
         if (flag)
         {
             Iterable<Result<Item>> myObjects = listObjects(bucketName);
             for (Result<Item> result : myObjects)
             {
                 Item item = result.get();
                 listObjectNames.add(item.objectName());
             }
         }
         else
         {
             listObjectNames.add("存储桶不存在");
         }
         return listObjectNames;
     }
 
 
     /**
      * 列出存储桶中的所有对象
      *
      * @param bucketName 存储桶名称
      * @return
      */
     @SneakyThrows
     public Iterable<Result<Item>> listObjects(String bucketName)
     {
         boolean flag = bucketExists(bucketName);
         if (flag)
         {
             return minioClient.listObjects(
                     ListObjectsArgs.builder().bucket(bucketName).build());
         }
         return null;
     }
 
     /**
      * 文件上传
      *
      * @param bucketName
      * @param multipartFile
      */
     @SneakyThrows
     public void putObject(String bucketName, MultipartFile multipartFile, String filename, String fileType)
     {
         InputStream inputStream = new ByteArrayInputStream(multipartFile.getBytes());
         minioClient.putObject(
                 PutObjectArgs.builder().bucket(bucketName).object(filename).stream(
                                 inputStream, -1, minioProperties.getFileSize())
                         .contentType(fileType)
                         .build());
     }
 
 
     /**
      * 文件访问路径
      *
      * @param bucketName 存储桶名称
      * @param objectName 存储桶里的对象名称
      * @return
      */
     @SneakyThrows
     public String getObjectUrl(String bucketName, String objectName)
     {
         boolean flag = bucketExists(bucketName);
         String url = "";
         if (flag)
         {
             url = minioClient.getPresignedObjectUrl(
                     GetPresignedObjectUrlArgs.builder()
                             .method(Method.GET)
                             .bucket(bucketName)
                             .object(objectName)
                             .expiry(2, TimeUnit.MINUTES)
                             .build());
             System.out.println(url);
         }
         return url;
     }
 
 
     /**
      * 删除一个对象
      *
      * @param bucketName 存储桶名称
      * @param objectName 存储桶里的对象名称
      */
     @SneakyThrows
     public boolean removeObject(String bucketName, String objectName)
     {
         boolean flag = bucketExists(bucketName);
         if (flag)
         {
             minioClient.removeObject(
                     RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
             return true;
         }
         return false;
     }
 
     /**
      * 以流的形式获取一个文件对象
      *
      * @param bucketName 存储桶名称
      * @param objectName 存储桶里的对象名称
      * @return
      */
     @SneakyThrows
     public InputStream getObject(String bucketName, String objectName)
     {
         boolean flag = bucketExists(bucketName);
         if (flag)
         {
             StatObjectResponse statObject = statObject(bucketName, objectName);
             if (statObject != null && statObject.size() > 0)
             {
                 InputStream stream =
                         minioClient.getObject(
                                 GetObjectArgs.builder()
                                         .bucket(bucketName)
                                         .object(objectName)
                                         .build());
                 return stream;
             }
         }
         return null;
     }
 
     /**
      * 获取对象的元数据
      *
      * @param bucketName 存储桶名称
      * @param objectName 存储桶里的对象名称
      * @return
      */
     @SneakyThrows
     public StatObjectResponse statObject(String bucketName, String objectName)
     {
         boolean flag = bucketExists(bucketName);
         if (flag)
         {
             StatObjectResponse stat =
                     minioClient.statObject(
                             StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
             return stat;
         }
         return null;
     }
 
     /**
      * 删除指定桶的多个文件对象,返回删除错误的对象列表,全部删除成功,返回空列表
      *
      * @param bucketName  存储桶名称
      * @param objectNames 含有要删除的多个object名称的迭代器对象
      * @return
      */
     @SneakyThrows
     public boolean removeObject(String bucketName, List<String> objectNames) {
         boolean flag = bucketExists(bucketName);
         if (flag) {
             List<DeleteObject> objects = new LinkedList<>();
             for (int i = 0; i < objectNames.size(); i++) {
                 objects.add(new DeleteObject(objectNames.get(i)));
             }
             Iterable<Result<DeleteError>> results =
                     minioClient.removeObjects(
                             RemoveObjectsArgs.builder().bucket(bucketName).objects(objects).build());
             for (Result<DeleteError> result : results) {
                 DeleteError error = result.get();
                 System.out.println(
                         "Error in deleting object " + error.objectName() + "; " + error.message());
                 return false;
             }
         }
         return true;
     }
 
     /**
      * 以流的形式获取一个文件对象(断点下载)
      *
      * @param bucketName 存储桶名称
      * @param objectName 存储桶里的对象名称
      * @param offset     起始字节的位置
      * @param length     要读取的长度 (可选,如果无值则代表读到文件结尾)
      * @return
      */
     @SneakyThrows
     public InputStream getObject(String bucketName, String objectName, long offset, Long length) {
         boolean flag = bucketExists(bucketName);
         if (flag) {
             StatObjectResponse statObject = statObject(bucketName, objectName);
             if (statObject != null && statObject.size() > 0) {
                 InputStream stream =
                         minioClient.getObject(
                                 GetObjectArgs.builder()
                                         .bucket(bucketName)
                                         .object(objectName)
                                         .offset(offset)
                                         .length(length)
                                         .build());
                 return stream;
             }
         }
         return null;
     }
 
 
     /**
      * 通过InputStream上传对象
      *
      * @param bucketName 存储桶名称
      * @param objectName 存储桶里的对象名称
      * @param inputStream     要上传的流
      * @param contentType     要上传的文件类型 MimeTypeUtils.IMAGE_JPEG_VALUE
      * @return
      */
     @SneakyThrows
     public boolean putObject(String bucketName, String objectName, InputStream inputStream,String contentType) {
         boolean flag = bucketExists(bucketName);
         if (flag) {
             minioClient.putObject(
                     PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(
                                     inputStream, -1, minioProperties.getFileSize())
                             .contentType(contentType)
                             .build());
             StatObjectResponse statObject = statObject(bucketName, objectName);
             if (statObject != null && statObject.size() > 0) {
                 return true;
             }
         }
         return false;
     }
 }

 

4:文件类型工具类FileTypeUtils

package com.springbootblog.utils;
 
 import cn.hutool.core.io.FileTypeUtil;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
 import java.io.InputStream;
 /**
  * 文件类型工具类
  */
 public class FileTypeUtils {
 
     private final static String IMAGE_TYPE = "image/";
     private final static String AUDIO_TYPE = "audio/";
     private final static String VIDEO_TYPE = "video/";
     private final static String APPLICATION_TYPE = "application/";
     private final static String TXT_TYPE = "text/";
 
     public static String getFileType(MultipartFile multipartFile) {
         InputStream inputStream = null;
         String type = null;
         try {
             inputStream = multipartFile.getInputStream();
             type = FileTypeUtil.getType(inputStream);
             System.out.println(type);
             if (type.equalsIgnoreCase("JPG") || type.equalsIgnoreCase("JPEG")
                     || type.equalsIgnoreCase("GIF") || type.equalsIgnoreCase("PNG")
                     || type.equalsIgnoreCase("BMP") || type.equalsIgnoreCase("PCX")
                     || type.equalsIgnoreCase("TGA") || type.equalsIgnoreCase("PSD")
                     || type.equalsIgnoreCase("TIFF")) {
                 return IMAGE_TYPE+type;
             }
             if (type.equalsIgnoreCase("mp3") || type.equalsIgnoreCase("OGG")
                     || type.equalsIgnoreCase("WAV") || type.equalsIgnoreCase("REAL")
                     || type.equalsIgnoreCase("APE") || type.equalsIgnoreCase("MODULE")
                     || type.equalsIgnoreCase("MIDI") || type.equalsIgnoreCase("VQF")
                     || type.equalsIgnoreCase("CD")) {
                 return AUDIO_TYPE+type;
             }
             if (type.equalsIgnoreCase("mp4") || type.equalsIgnoreCase("avi")
                     || type.equalsIgnoreCase("MPEG-1") || type.equalsIgnoreCase("RM")
                     || type.equalsIgnoreCase("ASF") || type.equalsIgnoreCase("WMV")
                     || type.equalsIgnoreCase("qlv") || type.equalsIgnoreCase("MPEG-2")
                     || type.equalsIgnoreCase("MPEG4") || type.equalsIgnoreCase("mov")
                     || type.equalsIgnoreCase("3gp")) {
                 return VIDEO_TYPE+type;
             }
             if (type.equalsIgnoreCase("doc") || type.equalsIgnoreCase("docx")
                     || type.equalsIgnoreCase("ppt") || type.equalsIgnoreCase("pptx")
                     || type.equalsIgnoreCase("xls") || type.equalsIgnoreCase("xlsx")
                     || type.equalsIgnoreCase("zip")||type.equalsIgnoreCase("jar")) {
                 return APPLICATION_TYPE+type;
             }
             if (type.equalsIgnoreCase("txt")) {
                 return TXT_TYPE+type;
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
         return null;
     }
 }

 

5:service接口MinioService

package com.springbootblog.service.fontend;
 
 import io.minio.messages.Bucket;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.InputStream;
 import java.util.List;
 
 public interface MinioService
 {
     /**
      * 判断 bucket是否存在
      * @param bucketName
      * @return
      */
     boolean bucketExists(String bucketName);
 
     /**
      * 创建 bucket
      * @param bucketName
      */
     void makeBucket(String bucketName);
 
     /**
      * 列出所有存储桶名称
      * @return
      */
     List<String> listBucketName();
 
     /**
      * 列出所有存储桶 信息
      *
      * @return
      */
     List<Bucket> listBuckets();
 
     /**
      * 根据桶名删除桶
      * @param bucketName
      */
     boolean removeBucket(String bucketName);
 
     /**
      * 列出存储桶中的所有对象名称
      * @param bucketName
      * @return
      */
     List<String> listObjectNames(String bucketName);
 
     /**
      * 文件上传
      *
      * @param multipartFile
      * @param bucketName
      */
     String putObject(MultipartFile multipartFile, String bucketName, String fileType);
 
     /**
      * 文件流下载
      * @param bucketName
      * @param objectName
      * @return
      */
     InputStream downloadObject(String bucketName, String objectName);
 
 
     /**
      * 删除文件
      * @param bucketName
      * @param objectName
      */
     boolean removeObject(String bucketName, String objectName);
 
 
 
     /**
      * 批量删除文件
      * @param bucketName
      * @param objectNameList
      * @return
      */
     boolean removeListObject(String bucketName, List<String> objectNameList);
 
     /**
      * 获取文件路径
      * @param bucketName
      * @param objectName
      * @return
      */
     String getObjectUrl(String bucketName,String objectName);
 }

 

6:service接口MinioService实现类MinioServiceImpl

package com.springbootblog.service.serviceImpl.fontend;
 
 import com.springbootblog.config.MinioProperties;
 import com.springbootblog.service.fontend.MinioService;
 import com.springbootblog.utils.MinioUtil;
 import io.minio.messages.Bucket;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.io.InputStream;
 import java.util.List;
 import java.util.UUID;
 
 @Service
 @RequiredArgsConstructor
 public class MinioServiceImpl implements MinioService {
 
     private final MinioUtil minioUtil;
 
     private final MinioProperties minioProperties;
 
     @Override
     public boolean bucketExists(String bucketName) {
         return minioUtil.bucketExists(bucketName);
     }
 
     @Override
     public void makeBucket(String bucketName) {
         minioUtil.makeBucket(bucketName);
     }
 
     @Override
     public List<StringlistBucketName() {
         return minioUtil.listBucketNames();
     }
 
     @Override
     public List<BucketlistBuckets() {
         return minioUtil.listBuckets();
     }
 
     @Override
     public boolean removeBucket(String bucketName) {
         return minioUtil.removeBucket(bucketName);
     }
 
 
     @Override
     public List<StringlistObjectNames(String bucketName) {
         return minioUtil.listObjectNames(bucketName);
     }
 
     @Override
     public String putObject(MultipartFile file, String bucketName, String fileType) {
         try {
             // StringUtils.hasLength(bucketName) 判断传入的字串是否有内容
             bucketName = StringUtils.hasLength(bucketName) ? bucketName : minioProperties.getBucketName();
             if (!this.bucketExists(bucketName)) {
                 this.makeBucket(bucketName);
             }
             String fileName = file.getOriginalFilename();
 
             String objectName = UUID.randomUUID().toString().replaceAll("-""")
                     + fileName.substring(fileName.lastIndexOf("."));
             minioUtil.putObject(bucketName, file, objectName,fileType);
             return minioProperties.getEndpoint()+":"+minioProperties.getPort()+"/"+bucketName+"/"+objectName;
         } catch (Exception e) {
             e.printStackTrace();
             return "上传失败";
         }
     }
 
     @Override
     public InputStream downloadObject(String bucketName, String objectName) {
         return minioUtil.getObject(bucketName,objectName);
     }
 
     @Override
     public boolean removeObject(String bucketName, String objectName) {
         return minioUtil.removeObject(bucketName, objectName);
     }
 
     @Override
     public boolean removeListObject(String bucketName, List<String> objectNameList) {
         return minioUtil.removeObject(bucketName,objectNameList);
     }
 
     @Override
     public String getObjectUrl(String bucketName,String objectName) {
         return minioUtil.getObjectUrl(bucketName, objectName);
     }
 }

 

7:控制器MinioController

package com.springbootblog.controller.fontend;
 
 import com.springbootblog.service.fontend.MinioService;
 import com.springbootblog.utils.FileTypeUtils;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 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 javax.annotation.Resource;
 import java.util.List;
 import java.util.Objects;
 
 @RestController
 @RequestMapping("java")
 @RequiredArgsConstructor
 public class MinioController
 {
     // 通过构造注入业务层对象
     private final MinioService minioService;
 //    @Resource
 //    private MinioService minioService;
 
     /**
      * 上传
      *
      * @param file       文件
      * @param bucketName 存储桶名称
      * @return {@link String} 文件的访问路径
      */
     @PostMapping("/minio/upload")
     public String upload(MultipartFile file, String bucketName)
     {
         if (Objects.isNull(file))
         {
             return "上传的文件不能为空!";
         }
         // 继续上传文件
         String type = FileTypeUtils.getFileType(file);
         return minioService.putObject(file, bucketName, type);
     }
 
     /**
      * 列出来存储桶中的所有文件
      *
      * @param bucketName bucket名称
      * @return {@link String} 所有文件
      */
     @GetMapping("minio/list")
     public List<String> list(String bucketName)
     {
         return minioService.listObjectNames(bucketName);
     }
 }

 

至此代码编写完成了,下面我们来填一下上边留下的坑。

 

三:配置Minio

1:我们要在minio创建一个文件桶(Buckets)

3.jpg

 

2:将文件桶的权限设置成public

4.jpg

 

3:创建秘钥

5.jpg

这里创建的秘钥需要配置到application.yml文件中对应的access-key、secret-key字段

 

四:测试上传

以上所有的准备工作都做完了之后,我们就可以上传图片测试一下了。

打开apipost,如下图所示:

6.jpg

上传文件成功,也可以在minio控制台上看到:

7.jpg

以上大概就是Springboot集成Minio的基本使用。

 

有好的建议,请在下方输入你的评论。