SpringBoot整合Minio

SpringBoot整合Minio

在企业开发中,我们经常会使用到文件存储的业务,Minio就是一个不错的文件存储工具,下面我们来看看如何在SpringBoot中整合Minio

POM

pom文件指定SpringBoot项目所依赖的软件工具包

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zsxq</groupId>
    <artifactId>csg_idc_zsxq</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>csg_idc_zsxq</name>
    <description>csg_idc_zsxq</description>
    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.7.3</spring-boot.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-transports-http</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.apache.cxf</groupId>
            <artifactId>cxf-rt-frontend-jaxws</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.12</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.7.7</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.16</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.12</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>

        <!--minio-->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <version>8.0.3</version>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <mainClass>com.idc.zsxq.CsgIdcZsxqApplication</mainClass>
                    <skip>true</skip>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

YML

SpringBoot配置文件

yaml 复制代码
server:
  port: 11112
# springdoc-openapi项目配置
knife4j:
  enable: true
  setting:
    language: zh_cn
spring:
  redis:
    password:
    host: 127.0.0.1
    port: 6379
    username:
# minio
minio:
  endpoint: http://127.0.0.1:9000
  accessKey: minioadmin
  secretKey: minioadmin
  bucketName: bucket

MinioClientConfig

Minio的配置类

java 复制代码
package com.idc.zsxq.config;

import io.minio.MinioClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Data
@Component
public class MinIoClientConfig {

    @Value("${minio.endpoint}")
    private String endpoint;

    @Value("${minio.accessKey}")
    private String accessKey;

    @Value("${minio.secretKey}")
    private String secretKey;

    /**
     * 注入minio 客户端
     *
     * @return
     */
    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(endpoint)
                .credentials(accessKey, secretKey)
                .build();
    }

}

MinioUtil

操作Minio的工具类,实现了判断Bucket是否存在,创建Bucket,上传文件,下载文件等功能

java 复制代码
package com.idc.zsxq.util;

import com.idc.zsxq.model.ObjectItem;
import io.minio.*;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @description: minio工具类
 * @version:3.0
 */
@Component
public class MinioUtil {

    @Resource
    private MinioClient minioClient;

    @Value("${minio.bucketName}")
    private String bucketName;

    /**
     * description: 判断bucket是否存在,不存在则创建
     *
     * @return: void
     */
    public void existBucket(String name) {
        try {
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
            if (!exists) {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean makeBucket(String bucketName) {
        try {
            minioClient.makeBucket(MakeBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 删除存储bucket
     *
     * @param bucketName 存储bucket名称
     * @return Boolean
     */
    public Boolean removeBucket(String bucketName) {
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * description: 上传文件
     *
     * @param multipartFile
     * @return: java.lang.String
     */
    public List<String> upload(MultipartFile[] multipartFile) {
        List<String> names = new ArrayList<>(multipartFile.length);
        for (MultipartFile file : multipartFile) {
            String fileName = file.getOriginalFilename();
            String[] split = fileName.split("\\.");
            if (split.length > 1) {
                fileName = split[0] + "_" + System.currentTimeMillis() + "." + split[1];
            } else {
                fileName = fileName + System.currentTimeMillis();
            }
            InputStream in = null;
            try {
                in = file.getInputStream();
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .stream(in, in.available(), -1)
                        .contentType(file.getContentType())
                        .build()
                );
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            names.add(fileName);
        }
        return names;
    }

    /**
     * description: 上传文件流
     *
     * @param in
     * @return: InputStream
     */
    public void upload(InputStream in, String bucketName, String fileName) {
        try {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(fileName)
                    .stream(in, in.available(), -1)
                    .contentType("application/octet-stream;charset=UTF-8")
                    .build()
            );
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * description: 下载文件
     *
     * @param fileName
     * @return: org.springframework.http.ResponseEntity<byte [ ]>
     */
    public ResponseEntity<byte[]> download(String fileName, String bucketName) {
        ResponseEntity<byte[]> responseEntity = null;
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
            out = new ByteArrayOutputStream();
            IOUtils.copy(in, out);
            //封装返回值
            byte[] bytes = out.toByteArray();
            HttpHeaders headers = new HttpHeaders();
            try {
                headers.add("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            //需要设置此属性,否则浏览器默认不会读取到响应头中的Accept-Ranges属性,因此会认为服务器端不支持分片,所以会直接全文下载
            headers.add("Access-Control-Expose-Headers", "Accept-Ranges,Content-Range");
            headers.setContentLength(bytes.length);
            headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
            headers.setAccessControlExposeHeaders(Arrays.asList("*"));
            responseEntity = new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseEntity;
    }

    /**
     * 查看文件对象
     *
     * @param bucketName 存储bucket名称
     * @return 存储bucket内文件对象信息
     */
    public List<ObjectItem> listObjects(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).build());
        List<ObjectItem> objectItems = new ArrayList<>();
        try {
            for (Result<Item> result : results) {
                Item item = result.get();
                ObjectItem objectItem = new ObjectItem();
                objectItem.setObjectName(item.objectName());
                objectItem.setSize(item.size());
                objectItems.add(objectItem);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return objectItems;
    }

    /**
     * 批量删除文件对象
     *
     * @param bucketName 存储bucket名称
     * @param objects    对象名称集合
     */
    public Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> objects) {
        List<DeleteObject> dos = objects.stream().map(e -> new DeleteObject(e)).collect(Collectors.toList());
        Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs
                .builder().bucket(bucketName).objects(dos).build());
        return results;
    }


}

案例

在SpringBoot中如何使用MinioUtil操作Minio,一般我们会使用Minio当做文件缓存

下面这个案例实现了下载文件的功能,逻辑:先从Minio查找是否存在文件,如果存在则从Minio下载文件,如果不存在则从远程文件服务器下载文件

java 复制代码
//获取文件
public byte[] downloadFile(String bucketName, String fileId, String newFileId) {

    log.info("下载文件 开始");

    // 测试
    bucketName = "library";
    fileId = "6cc750db08c943b48a259020ce4e6794";
    newFileId = "0fe8cb9eb5954cc2876c3ac8496692a7";

    String fileName = bucketName + "_" + fileId + "_" +
        (newFileId == null ? "" : newFileId);
    log.info("文件名 {}", fileName);

    try {

        //如果没有bucket,则创建
        minioUtil.existBucket(bucketName);

        //判断Minio中是否有该文件
        List<ObjectItem> objectItems = minioUtil.listObjects(bucketName);
        boolean flag = false;
        if (CollectionUtil.isNotEmpty(objectItems)) {
            for (ObjectItem o : objectItems) {
                if (StringUtils.equals(o.getObjectName(), fileName)) {
                    flag = true;
                    break;
                }
            }
        }

        byte[] bytes = null;
        //如果有,则从Minio中获取文件

        if (flag) {
            log.info("从Minio获取文件");
            ResponseEntity<byte[]> download = minioUtil.download(fileName, bucketName);
            bytes = download.getBody();
        } else {
            log.info("从文件服务器获取文件");
            //如果Minio没有,则从文件服务器获取文件,如果报错,则抛出异常
            //封装基本查询参数url
            String withParamsUrl = CnkiUrlEnum.DOWNLOAD_FILE.getUrl() + "?fileId=" + fileId +
                (StringUtils.isEmpty(newFileId) ? "" : "&newFileId=" + newFileId) +
                "&bucketName=" + bucketName;

            HttpResponse httpResponse = HttpRequest.post(withParamsUrl).
                header("accessToken", ConstantEnum.TOKEN.getValue()).body("").execute();

            String status = httpResponse.getStatus() + "";

            //判断是否请求成功
            if (!"200".equals(status) && !"204".equals(status)) {
                log.error("数据库查询异常, CNKI接口返回状态码为:" + status);
                throw new BusinessException(ResultEnum.ERROR);
            }

            //空文件抛出异常
            if (StringUtils.equals(httpResponse.header("Content-Type"), "application/json")) {
                String jsonBody = httpResponse.body();
                JSONObject dataJson = JSONObject.parseObject(jsonBody);
                String dataCode = dataJson.getString("code");
                log.error("文件内容为空 : " + dataCode);
                throw new BusinessException(ResultEnum.ERROR);
            }

            //获取文件字节流
            bytes = httpResponse.bodyBytes();

            //写入Minio
            minioUtil.upload(new ByteArrayInputStream(bytes), bucketName, fileName);
        }

        log.info("下载文件 结束");
        //写到响应流中
        return bytes;
    } catch (Exception e) {
        log.error("下载文件报错 : {}", e);
    }
    return new byte[]{};

}

效果

通过postman调用接口,返回文件流

相关推荐
葫芦和十三6 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp6 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑7 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯8 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan10 小时前
多Agent之间的区别
后端
青石路11 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充12 小时前
1.面向对象设计思想
后端
IT_陈寒12 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro13 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗13 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端