SpringBoot整合MinIO

85 阅读3分钟

1、MinIO是什么

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

2、官网

www.minio.org.cn/

3、优点

  • 部署简单,支持各种平台
  • 海量存储,支持单个对象最大5TB
  • 兼容 Amazon S3接口
  • 低冗余,磁盘损坏高容忍
  • 读写性能优异

4、通过docker部署MinIO

参考docker部署安装MinIO

创建Access Key

image.png

image.png

5、SpringBoot整合MinIO

pom依赖引入

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.5.5</version>
</dependency>

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.11.0</version>
</dependency>

yml文件配置

spring:
  application:
    name: springboot-demo
  # 配置文件上传大小限制
  servlet:
    multipart:
      max-file-size: 200MB
      max-request-size: 200MB

minio:
  endpoint: http://ip:9000
  access-key: 你的access-key
  secret-key: 你的secret-key
  bucket: 桶名

创建minio配置类

package com.example.springbootdemo.config;

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * <p>
 *  minio配置类
 * </p>
 *
 * @author yurenwei
 * @since 2023/9/22
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfig {

    /**
     * 访问地址
     */
    private String endpoint;

    /**
     * 账户
     */
    private String accessKey;

    /**
     * 秘钥
     */
    private String secretKey;

    /**
     * 桶
     */
    private String bucket;

    /**
     * 创建MinioClient客户端
     *
     * @return
     */
    @Bean
    public MinioClient getMinioClient()  {
        return MinioClient.builder()
                .endpoint(getEndpoint())
                .credentials(getAccessKey(),getSecretKey())
                .build();
    }

}

创建minio工具类

package com.example.springbootdemo.util;

import cn.hutool.core.date.DatePattern;
import com.example.springbootdemo.vo.FileVO;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * minio工具类
 * </p>
 *
 * @author yurenwei
 * @since 2023/9/22
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtil {

    private final MinioClient minioClient;


    /**
     * 创建存储桶
     * 如果没有存储桶则创建
     *
     * @param bucketName 存储桶
     */
    @SneakyThrows(Exception.class)
    private void createBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 判断存储桶是否存在
     *
     * @param bucketName 存储桶
     * @return boolean
     */
    @SneakyThrows(Exception.class)
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 获得所有存储桶列表
     *
     * @return List<Bucket>
     */
    @SneakyThrows(Exception.class)
    public List<Bucket> getAllBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 根据存储桶获取其相关信息
     *
     * @param bucketName 存储桶
     * @return Bucket
     */
    @SneakyThrows(Exception.class)
    public Bucket getBucket(String bucketName) {
        return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst().orElse(null);
    }

    /**
     * 根据bucketName删除存储桶
     *
     * @param bucketName 存储桶
     */
    @SneakyThrows(Exception.class)
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 上传文件
     *
     * @param fileList 文件集合
     * @param bucketName 存储桶
     * @return List<FileVO>
     */
    public List<FileVO> uploadFile(MultipartFile[] fileList, String bucketName) {
        try {
            List<FileVO> list = new ArrayList<>();
            createBucket(bucketName);

            for (MultipartFile file : fileList) {
                String oldName = file.getOriginalFilename();
                assert oldName != null;
                String fileName = LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN))+oldName.substring(oldName.lastIndexOf("."));

                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .stream(file.getInputStream(), file.getSize(), 0)
                        .contentType(file.getContentType())
                        .build()
                );

                String url = this.getObjUrl(bucketName, fileName);
                list.add(new FileVO().setOldFileName(oldName)
                                .setNewFileName(fileName)
                                .setFileUrl(url.substring(0, url.indexOf("?")))
                );
            }
            return list;
        } catch (Exception e) {
            log.error("上传文件出错:{}", e.getMessage());
            return null;
        }
    }


    /**
     * 获取文件链接
     *
     * @param bucketName 存储桶
     * @param fileName 文件名称
     * @return String
     */
    @SneakyThrows(Exception.class)
    public String getObjUrl(String bucketName, String fileName) {
        return minioClient.getPresignedObjectUrl(
                GetPresignedObjectUrlArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .method(Method.GET)
                        .expiry(30, TimeUnit.SECONDS)
                        .build()
        );
    }

    /**
     * 下载文件
     *
     * @param bucketName 存储桶
     * @param fileName 文件名称
     */
    @SneakyThrows(Exception.class)
    public InputStream  download(String bucketName, String fileName) {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
    }


    /**
     * 删除文件
     *
     * @param bucketName 存储桶
     * @param fileName 文件名称
     */
    @SneakyThrows(Exception.class)
    public void removeFile(String bucketName, String fileName) {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(fileName).build());
    }

}

创建文件对象

package com.example.springbootdemo.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * <p>
 *  文件对象
 * </p>
 *
 * @author yurenwei
 * @since 2023/9/22
 */
@ApiModel(value = "不同状态数量视图对象", description = "不同状态数量")
@Accessors(chain = true)
@Data
public class FileVO implements Serializable {

    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "原文件名")
    private String oldFileName;

    @ApiModelProperty(value = "新文件名")
    private String newFileName;

    @ApiModelProperty(value = "文件地址")
    private String fileUrl;

}

创建controller

package com.example.springbootdemo.controller;

import com.example.springbootdemo.config.MinioConfig;
import com.example.springbootdemo.util.MinioUtil;
import com.example.springbootdemo.util.Result;
import com.example.springbootdemo.vo.FileVO;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.util.List;

/**
 * <p>
 *  minio控制器
 * </p>
 *
 * @author yurenwei
 * @since 2023/9/22
 */
@Slf4j
@Api(tags = "minio接口")
@RestController
@RequestMapping("/api/minio")
public class MinioController {

    @Autowired
    private MinioUtil minioUtil;
    @Autowired
    private MinioConfig minioConfig;

    /**
     * 文件上传
     *
     * @param file
     */
    @PostMapping("/upload")
    public Result<List<FileVO>> upload(@RequestParam("file") MultipartFile file) {
        List<FileVO> fileList = minioUtil.uploadFile(new MultipartFile[]{file}, minioConfig.getBucket());
        return Result.ok(fileList);
    }

    /**
     * 删除
     *
     * @param fileName 文件名
     */
    @DeleteMapping("/delete")
    public Result delete(@RequestParam("fileName") String fileName) {
        minioUtil.removeFile(minioConfig.getBucket(), fileName);
        return Result.ok();
    }


    /**
     * 文件下载
     *
     * @param fileName 文件名
     * @param response 响应
     */
    @GetMapping("/download")
    public void download(@RequestParam("fileName") String fileName, HttpServletResponse response) {
        try {
            InputStream stream = minioUtil.download(minioConfig.getBucket(), fileName);
            ServletOutputStream output = response.getOutputStream();
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName.substring(fileName.lastIndexOf("/") + 1), "UTF-8"));
            response.setContentType("application/octet-stream");
            response.setCharacterEncoding("UTF-8");
            IOUtils.copy(stream, output);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

6、测试

测试上传文件

image.png 查看minio客户端

image.png 发现已成功上传值minio

测试下载文件

image.png

image.png 打开文件是我上传的excel文件的内容数据

测试删除文件

image.png

查看minio

image.png 发现刚才的文件已被删除了

至此,SpringBoot整合MinIO完成结束了。