欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot封裝MinIO工具的實現(xiàn)步驟

 更新時間:2025年07月21日 10:06:06   作者:維基框架  
MinIO是一款高性能、開源、兼容Amazon?S3?API的分布式對象存儲系統(tǒng),Spring?Boot通過封裝配置與工具類,標(biāo)準(zhǔn)化設(shè)計降低開發(fā)復(fù)雜度,提升開發(fā)效率與系統(tǒng)穩(wěn)定性,感興趣的可以了解一下

MinIO 簡介

MinIO 是一款高性能、開源、兼容Amazon S3 API的分布式對象存儲系統(tǒng),專為云原生架構(gòu)和大規(guī)模非結(jié)構(gòu)化數(shù)據(jù)場景設(shè)計。其核心定位是成為私有云/混合云環(huán)境中的標(biāo)準(zhǔn)存儲方案,適用于從數(shù)據(jù)湖到AI/ML、容器化部署等多樣化需求。

背景

為解決業(yè)務(wù)開時對接 MinIO繁瑣接口對工作

目錄結(jié)構(gòu)

  • cdkj-minio MinIO工具

    • annotation 注解

      • EnableAutoMinio 啟用自動MinIO
    • config 配置

      • MinioAutoConfiguration MinIO 自動配置
      • MinioMarkerConfiguration MinIO 標(biāo)記配置
      • MinioProperties MinIO 配置讀取
    • connectivity 連接庫

      • MinioConfiguration MinIO 配置
    • enums 枚舉庫

      • ContentTypeEnums 內(nèi)容類型枚舉
    • MinioUtils MinIO工具庫

項目介紹

POM引入包

    <dependencies>
        <!-- MinIO -->
        <dependency>
            <groupId>io.minio</groupId>
            <artifactId>minio</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.squareup.okhttp3</groupId>
                    <artifactId>okhttp</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
    </dependencies>

EnableAutoMinio 啟用自動MinIO

spring boot 啟動加載工具包

package com.cdkjframework.minio.annotation;

import com.cdkjframework.minio.config.MinioMarkerConfiguration;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio.annotation
 * @ClassName: EnableAutoMinio
 * @Description: java類作用描述
 * @Author: xiaLin
 * @Date: 2024/9/2 11:27
 * @Version: 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({MinioMarkerConfiguration.class})
public @interface EnableAutoMinio {
}

Spring Boot 項目引入

package cn.qjwxlcps.ims.annotation;

import com.cdkjframework.cloud.job.core.handler.annotation.EnableAutoCdkjJob;
import com.cdkjframework.datasource.mongodb.annotation.EnableAutoMongo;
import com.cdkjframework.minio.annotation.EnableAutoMinio;
import com.cdkjframework.swagger.annotation.EnableAutoSwagger;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @ProjectName: com.lesmarthome.interface
 * @Package: com.lesmarthome.interfaces.annotation
 * @ClassName: EnableAutoApi
 * @Description: java類作用描述
 * @Author: xiaLin
 * @Date: 2024/3/29 16:02
 * @Version: 1.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@EnableRetry
@EnableAsync
@Configuration
@EnableAutoMinio
@EnableAutoMongo
@EnableAutoCdkjJob
//@EnableAutoSwagger
@EnableDiscoveryClient
@EnableTransactionManagement
@EnableFeignClients(basePackages = {"com.*.*.client"})
public @interface EnableAutoApi {
}

MinIO 配置讀取

該類主要讀取用戶配置信息

package com.cdkjframework.minio.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio.config
 * @ClassName: MinioProperties
 * @Description: mini配置
 * @Author: xiaLin
 * @Date: 2024/9/2 11:28
 * @Version: 1.0
 */
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "spring.minio")
public class MinioProperties {

    /**
     * 訪問域名
     */
    private String domain;

    /**
     * 存儲端點
     */
    private String endpoint;

    /**
     * 端口
     */
    private Integer port;

    /**
     * 訪問密鑰
     */
    private String accessKey;

    /**
     * 密鑰
     */
    private String secretKey;

    /**
     * 存儲桶名稱
     */
    private String bucketName;

    /**
     * 分片對象過期時間 單位(天)
     */
    private Integer expiry;

    /**
     * 斷點續(xù)傳有效時間,在redis存儲任務(wù)的時間 單位(天)
     */
    private Integer breakpointTime;
}

內(nèi)容類型枚舉

package com.cdkjframework.minio.enums;

import com.cdkjframework.constant.IntegerConsts;
import com.cdkjframework.util.tool.StringUtils;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio.enums
 * @ClassName: ContentTypeEnums
 * @Description: 內(nèi)容類型枚舉
 * @Author: xiaLin
 * @Date: 2024/9/2 13:30
 * @Version: 1.0
 */
public enum ContentTypeEnums {
    /**
     * 默認類型
     */
    DEFAULT("default", "application/octet-stream"),
    JPG("jpg", "image/jpeg"),
    TIFF("tiff", "image/tiff"),
    GIF("gif", "image/gif"),
    JFIF("jfif", "image/jpeg"),
    PNG("png", "image/png"),
    TIF("tif", "image/tiff"),
    ICO("ico", "image/x-icon"),
    JPEG("jpeg", "image/jpeg"),
    WBMP("wbmp", "image/vnd.wap.wbmp"),
    FAX("fax", "image/fax"),
    NET("net", "image/pnetvue"),
    JPE("jpe", "image/jpeg"),
    RP("rp", "image/vnd.rn-realpix"),
    MP4("mp4", "video/mp4");

    /**
     * 文件名后綴
     */
    private final String suffix;

    /**
     * 返回前端請求頭中,Content-Type具體的值
     */
    private final String value;

    ContentTypeEnums(String suffix, String value) {
        this.suffix = suffix;
        this.value = value;
    }

    /**
     * 根據(jù)文件后綴,獲取Content-Type
     *
     * @param suffix 文件后綴
     * @return 返回結(jié)果
     */
    public static String formContentType(String suffix) {
        if (StringUtils.isNullAndSpaceOrEmpty(suffix)) {
            return DEFAULT.getValue();
        }
        int beginIndex = suffix.lastIndexOf(StringUtils.POINT) + IntegerConsts.ONE;
        suffix = suffix.substring(beginIndex);
        for (ContentTypeEnums value : ContentTypeEnums.values()) {
            if (suffix.equalsIgnoreCase(value.getSuffix())) {
                return value.getValue();
            }
        }
        return DEFAULT.getValue();
    }

    public String getSuffix() {
        return suffix;
    }

    public String getValue() {
        return value;
    }
}

MinIO 標(biāo)記配置

package com.cdkjframework.minio.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio.config
 * @ClassName: MinioMarkerConfiguration
 * @Description: Minio標(biāo)記配置
 * @Author: xiaLin
 * @Date: 2024/9/2 11:28
 * @Version: 1.0
 */
@Configuration(proxyBeanMethods = false)
public class MinioMarkerConfiguration {
    @Bean
    public Marker mybatisMarker() {
        return new Marker();
    }

    public static class Marker {

    }
}

MinIO 自動配置

注入 Bean 之前需要先加載配置 MinioProperties,且基于Bean的條件 MinioMarkerConfiguration.Marker 完成。開始調(diào)用 MinioConfiguration 中的start Bean方法。

package com.cdkjframework.minio.config;

import com.cdkjframework.minio.connectivity.MinioConfiguration;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio.config
 * @ClassName: MinioAutoConfiguration
 * @Description: Minio自動配置
 * @Author: xiaLin
 * @Date: 2024/9/2 11:29
 * @Version: 1.0
 */
@Lazy(false)
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties({
        MinioProperties.class
})
@AutoConfigureAfter({WebClientAutoConfiguration.class})
@ConditionalOnBean(MinioMarkerConfiguration.Marker.class)
public class MinioAutoConfiguration {

    /**
     * 配置信息
     */
    private final MinioProperties minioProperties;

    /**
     * minio配置
     *
     * @return 返回配置信息
     */
    @Bean(initMethod = "start")
    public MinioConfiguration minioConfiguration() {
        return new MinioConfiguration(minioProperties);
    }
}

MinIO 配置

package com.cdkjframework.minio.connectivity;

import com.cdkjframework.minio.MinioUtils;
import com.cdkjframework.minio.config.MinioProperties;
import io.minio.MinioClient;
import org.springframework.context.annotation.Bean;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio.connectivity
 * @ClassName: MinioConfiguration
 * @Description: minio配置
 * @Author: xiaLin
 * @Date: 2024/9/2 11:30
 * @Version: 1.0
 */
public class MinioConfiguration {

    /**
     * 配置信息
     */
    private final MinioProperties minioProperties;

    /**
     * 構(gòu)建函數(shù)
     */
    public MinioConfiguration(MinioProperties minioProperties) {
        this.minioProperties = minioProperties;
    }

    /**
     * 啟動
     */
    @Bean(name = "start")
    public void start() {
        MinioClient.Builder builder = MinioClient.builder();
        if (minioProperties.getPort() == null) {
            builder.endpoint(minioProperties.getEndpoint());
        } else {
            builder.endpoint(minioProperties.getEndpoint(), minioProperties.getPort(), Boolean.FALSE);
        }
        MinioClient client = builder
                // 服務(wù)端用戶名 、 服務(wù)端密碼
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();

        // 實例化工具類
        new MinioUtils(client);
    }
}

MinIO工具庫

該工具庫主要提供靜態(tài)接口方法

package com.cdkjframework.minio;

import com.cdkjframework.builder.ResponseBuilder;
import com.cdkjframework.constant.IntegerConsts;
import com.cdkjframework.exceptions.GlobalException;
import com.cdkjframework.exceptions.GlobalRuntimeException;
import com.cdkjframework.minio.enums.ContentTypeEnums;
import com.cdkjframework.util.tool.StringUtils;
import com.google.common.collect.Sets;
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 org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @ProjectName: cdkjframework
 * @Package: com.cdkjframework.minio
 * @ClassName: MinioUtils
 * @Description: minio工具類
 * @Author: xiaLin
 * @Date: 2024/9/2 13:32
 * @Version: 1.0
 */
public class MinioUtils {

    /**
     * 默認的臨時文件存儲桶
     */
    private static final String DEFAULT_TEMP_BUCKET_NAME = "temp-bucket";

    /**
     * minio客戶端
     */
    private static MinioClient client = null;

    /**
     * 構(gòu)造函數(shù)
     */
    public MinioUtils(MinioClient client) {
        MinioUtils.client = client;
    }

    /**
     * 將URLDecoder編碼轉(zhuǎn)成UTF8
     *
     * @param str 待轉(zhuǎn)碼的字符串
     * @return 轉(zhuǎn)碼后的字符串
     */
    public static String getUtf8ByURLDecoder(String str) throws UnsupportedEncodingException {
        String url = str.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        return URLDecoder.decode(url, StandardCharsets.UTF_8);
    }

    /**
     * 把路徑開頭的"/"去掉,并在末尾添加"/",這個是minio對象名的樣子。
     *
     * @param projectPath 以"/"開頭、以字母結(jié)尾的路徑
     * @return 去掉開頭"/"
     */
    private static String trimHead(String projectPath) {
        return projectPath.substring(IntegerConsts.ONE);
    }

    /**
     * 把路徑開頭的"/"去掉,并在末尾添加"/",這個是minio對象名的樣子。
     *
     * @param projectPath 以"/"開頭、以字母結(jié)尾的路徑
     * @return 添加結(jié)尾"/"
     */
    private static String addTail(String projectPath) {
        return projectPath + StringUtils.BACKSLASH;
    }

    /**
     * 獲取數(shù)值的位數(shù)(用于構(gòu)造臨時文件名)
     *
     * @param number
     * @return
     */
    private static int countDigits(int number) {
        if (number == IntegerConsts.ZERO) {
            // 0 本身有一位
            return IntegerConsts.ONE;
        }
        int count = IntegerConsts.ZERO;
        while (number != IntegerConsts.ZERO) {
            number /= IntegerConsts.TEN;
            count++;
        }
        return count;
    }

    /**
     * 檢查是否空
     *
     * @param objects 待檢查的對象
     */
    private static void checkNull(Object... objects) {
        for (Object o : objects) {
            if (o == null) {
                throw new GlobalRuntimeException("Null param");
            }
            if (o instanceof String && StringUtils.isNullAndSpaceOrEmpty(o)) {
                throw new GlobalRuntimeException("Empty string");
            }
        }
    }

    /**
     * 給定一個字符串,返回其"/"符號后面的字符串
     *
     * @param input 輸入字符串
     * @return 截取后的字符串
     */
    private static String getContentAfterSlash(String input) {
        if (StringUtils.isNullAndSpaceOrEmpty(input)) {
            return StringUtils.Empty;
        }
        int slashIndex = input.indexOf(StringUtils.BACKSLASH);
        if (slashIndex != IntegerConsts.MINUS_ONE && slashIndex < input.length() - IntegerConsts.ONE) {
            return input.substring(slashIndex + IntegerConsts.ONE);
        }
        return StringUtils.Empty;
    }

    /**
     * 判斷Bucket是否存在
     *
     * @return true:存在,false:不存在
     */
    private static boolean bucketExists(String bucketName) throws Exception {
        return client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 如果一個桶不存在,則創(chuàng)建該桶
     */
    public static void createBucket(String bucketName) throws Exception {
        if (!bucketExists(bucketName)) {
            client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 獲取 Bucket 的相關(guān)信息
     */
    public static Optional<Bucket> getBucketInfo(String bucketName) throws Exception {
        return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /**
     * 使用MultipartFile進行文件上傳
     *
     * @param bucketName  存儲桶
     * @param file        文件
     * @param fileName    對象名
     * @param contentType 類型
     * @return
     * @throws Exception
     */
    public static ObjectWriteResponse uploadFile(String bucketName, MultipartFile file,
                                                                                             String fileName, ContentTypeEnums contentType) throws Exception {
        InputStream inputStream = file.getInputStream();
        return client.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .contentType(contentType.getValue())
                        .stream(inputStream, inputStream.available(), IntegerConsts.MINUS_ONE)
                        .build());
    }

    /**
     * 將文件進行分片上傳
     * <p>有一個未處理的bug(雖然概率很低很低):</p>
     * 當(dāng)兩個線程同時上傳md5相同的文件時,由于兩者會定位到同一個桶的同一個臨時目錄,兩個線程會相互產(chǎn)生影響!
     *
     * @param file        分片文件
     * @param currIndex   當(dāng)前文件的分片索引
     * @param totalPieces 切片總數(shù)(對于同一個文件,請確保切片總數(shù)始終不變)
     * @param md5         整體文件MD5
     * @return 剩余未上傳的文件索引集合
     */
    public static ResponseBuilder uploadFileFragment(MultipartFile file,
                                                                                                     Integer currIndex, Integer totalPieces, String md5) throws Exception {
        checkNull(currIndex, totalPieces, md5);
        // 把當(dāng)前分片上傳至臨時桶
        if (!bucketExists(DEFAULT_TEMP_BUCKET_NAME)) {
            createBucket(DEFAULT_TEMP_BUCKET_NAME);
        }
        uploadFileStream(DEFAULT_TEMP_BUCKET_NAME, getFileTempPath(md5, currIndex, totalPieces), file.getInputStream());
        // 得到已上傳的文件索引
        Iterable<Result<Item>> results = getFilesByPrefix(DEFAULT_TEMP_BUCKET_NAME, md5.concat(StringUtils.BACKSLASH), Boolean.FALSE);
        Set<Integer> savedIndex = Sets.newHashSet();
        boolean fileExists = Boolean.FALSE;
        for (Result<Item> item : results) {
            Integer idx = Integer.valueOf(getContentAfterSlash(item.get().objectName()));
            if (currIndex.equals(idx)) {
                fileExists = Boolean.TRUE;
            }
            savedIndex.add(idx);
        }
        // 得到未上傳的文件索引
        Set<Integer> remainIndex = Sets.newTreeSet();
        for (int i = IntegerConsts.ZERO; i < totalPieces; i++) {
            if (!savedIndex.contains(i)) {
                remainIndex.add(i);
            }
        }
        if (fileExists) {
            return ResponseBuilder.failBuilder("index [" + currIndex + "] exists");
        }
        // 還剩一個索引未上傳,當(dāng)前上傳索引剛好是未上傳索引,上傳完當(dāng)前索引后就完全結(jié)束了。
        if (remainIndex.size() == IntegerConsts.ONE && remainIndex.contains(currIndex)) {
            return ResponseBuilder.successBuilder("completed");
        }
        return ResponseBuilder.failBuilder("index [" + currIndex + "] has been uploaded");
    }

    /**
     * 合并分片文件,并放到指定目錄
     * 前提是之前已把所有分片上傳完畢。
     *
     * @param bucketName  目標(biāo)文件桶名
     * @param targetName  目標(biāo)文件名(含完整路徑)
     * @param totalPieces 切片總數(shù)(對于同一個文件,請確保切片總數(shù)始終不變)
     * @param md5         文件md5
     * @return minio原生對象,記錄了文件上傳信息
     */
    public static boolean composeFileFragment(String bucketName, String targetName,
                                                                                        Integer totalPieces, String md5) throws Exception {
        checkNull(bucketName, targetName, totalPieces, md5);
        // 檢查文件索引是否都上傳完畢
        Iterable<Result<Item>> results = getFilesByPrefix(DEFAULT_TEMP_BUCKET_NAME, md5.concat(StringUtils.BACKSLASH), false);
        Set<String> savedIndex = Sets.newTreeSet();
        for (Result<Item> item : results) {
            savedIndex.add(item.get().objectName());
        }
        if (savedIndex.size() == totalPieces) {
            // 文件路徑 轉(zhuǎn) 文件合并對象
            List<ComposeSource> sourceObjectList = savedIndex.stream()
                    .map(filePath -> ComposeSource.builder()
                            .bucket(DEFAULT_TEMP_BUCKET_NAME)
                            .object(filePath)
                            .build())
                    .collect(Collectors.toList());
            ObjectWriteResponse objectWriteResponse = client.composeObject(
                    ComposeObjectArgs.builder()
                            .bucket(bucketName)
                            .object(targetName)
                            .sources(sourceObjectList)
                            .build());
            // 上傳成功,則刪除所有的臨時分片文件
            List<String> filePaths = Stream.iterate(IntegerConsts.ZERO, i -> ++i)
                    .limit(totalPieces)
                    .map(i -> getFileTempPath(md5, i, totalPieces))
                    .collect(Collectors.toList());
            Iterable<Result<DeleteError>> deleteResults = removeFiles(DEFAULT_TEMP_BUCKET_NAME, filePaths);
            // 遍歷錯誤集合(無元素則成功)
            for (Result<DeleteError> result : deleteResults) {
                DeleteError error = result.get();
                System.err.printf("[Bigfile] 分片'%s'刪除失敗! 錯誤信息: %s", error.objectName(), error.message());
            }
            return true;
        }
        throw new GlobalException("The fragment index is not complete. Please check parameters [totalPieces] or [md5]");
    }

    /**
     * 上傳本地文件
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名稱
     * @param filePath   本地文件路徑
     */
    public ObjectWriteResponse uploadFile(String bucketName, String fileName,
                                                                                String filePath) throws Exception {
        return client.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .filename(filePath)
                        .build());
    }

    /**
     * 通過流上傳文件
     *
     * @param bucketName  存儲桶
     * @param fileName    文件名
     * @param inputStream 文件流
     */
    public static ObjectWriteResponse uploadFileStream(String bucketName, String fileName, InputStream inputStream) throws Exception {
        return client.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .stream(inputStream, inputStream.available(), IntegerConsts.MINUS_ONE)
                        .build());
    }

    /**
     * 判斷文件是否存在
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名
     * @return true: 存在
     */
    public static boolean isFileExist(String bucketName, String fileName) {
        boolean exist = true;
        try {
            client.statObject(StatObjectArgs.builder().bucket(bucketName).object(fileName).build());
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 判斷文件夾是否存在
     *
     * @param bucketName 存儲桶
     * @param folderName 目錄名稱:本項目約定路徑是以"/"開頭,不以"/"結(jié)尾
     * @return true: 存在
     */
    public boolean isFolderExist(String bucketName, String folderName) {
        // 去掉頭"/",才能搜索到相關(guān)前綴
        folderName = trimHead(folderName);
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = client.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(folderName).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                // 增加尾"/",才能匹配到目錄名字
                String objectName = addTail(folderName);
                if (item.isDir() && objectName.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            exist = false;
        }
        return exist;
    }

    /**
     * 獲取路徑下文件列表
     *
     * @param bucketName 存儲桶
     * @param prefix     文件名稱
     * @param recursive  是否遞歸查找,false:模擬文件夾結(jié)構(gòu)查找
     * @return 二進制流
     */
    public static Iterable<Result<Item>> getFilesByPrefix(String bucketName, String prefix,
                                                                                                                boolean recursive) {
        return client.listObjects(
                ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(recursive)
                        .build());

    }

    /**
     * 獲取文件信息, 如果拋出異常則說明文件不存在
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名稱
     */
    public StatObjectResponse getFileStatusInfo(String bucketName, String fileName) throws Exception {
        return client.statObject(
                StatObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .build());
    }

    /**
     * 根據(jù)文件前綴查詢文件
     *
     * @param bucketName 存儲桶
     * @param prefix     前綴
     * @param recursive  是否使用遞歸查詢
     * @return MinioItem 列表
     */
    public List<Item> getAllFilesByPrefix(String bucketName,
                                                                                String prefix,
                                                                                boolean recursive) throws Exception {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = client.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 filePaths<String> 需要刪除的文件列表
     * @return Result
     */
    public static Iterable<Result<DeleteError>> removeFiles(String bucketName, List<String> filePaths) {
        List<DeleteObject> objectPaths = filePaths.stream()
                .map(filePath -> new DeleteObject(filePath))
                .collect(Collectors.toList());
        return client.removeObjects(
                RemoveObjectsArgs.builder().bucket(bucketName).objects(objectPaths).build());
    }

    /**
     * 獲取文件的二進制流
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名
     * @return 二進制流
     */
    public InputStream getFileStream(String bucketName, String fileName) throws Exception {
        return client.getObject(GetObjectArgs.builder().bucket(bucketName).object(fileName).build());
    }

    /**
     * 斷點下載
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名稱
     * @param offset     起始字節(jié)的位置
     * @param length     要讀取的長度
     * @return 二進制流
     */
    public InputStream getFileStream(String bucketName, String fileName, long offset, long length) throws Exception {
        return client.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .offset(offset)
                        .length(length)
                        .build());
    }

    /**
     * 拷貝文件
     *
     * @param bucketName    存儲桶
     * @param fileName      文件名
     * @param srcBucketName 目標(biāo)存儲桶
     * @param srcFileName   目標(biāo)文件名
     */
    public ObjectWriteResponse copyFile(String bucketName, String fileName,
                                                                            String srcBucketName, String srcFileName) throws Exception {
        return client.copyObject(
                CopyObjectArgs.builder()
                        .source(CopySource.builder().bucket(bucketName).object(fileName).build())
                        .bucket(srcBucketName)
                        .object(srcFileName)
                        .build());
    }

    /**
     * 刪除文件夾(未完成)
     *
     * @param bucketName 存儲桶
     * @param fileName   路徑
     */
    @Deprecated
    public void removeFolder(String bucketName, String fileName) throws Exception {
        // 加尾
        fileName = addTail(fileName);
        client.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .build());
    }

    /**
     * 刪除文件
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名稱
     */
    public void removeFile(String bucketName, String fileName) throws Exception {
        // 掐頭
        fileName = trimHead(fileName);
        client.removeObject(
                RemoveObjectArgs.builder()
                        .bucket(bucketName)
                        .object(fileName)
                        .build());
    }

    /**
     * 獲得文件外鏈
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名
     * @return url 返回地址
     * @throws Exception
     */
    public static String getPresignedObjectUrl(String bucketName, String fileName) throws Exception {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(fileName)
                .method(Method.GET).build();
        return client.getPresignedObjectUrl(args);
    }

    /**
     * 獲取文件外鏈
     *
     * @param bucketName 存儲桶
     * @param fileName   文件名
     * @param expires    過期時間 <=7 秒 (外鏈有效時間(單位:秒))
     * @return url
     * @throws Exception
     */
    public String getPresignedObjectUrl(String bucketName, String fileName, Integer expires) throws Exception {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .method(Method.GET)
                .expiry(expires, TimeUnit.SECONDS)
                .bucket(bucketName)
                .object(fileName)
                .build();
        return client.getPresignedObjectUrl(args);
    }

    /**
     * 通過文件的md5,以及分片文件的索引,構(gòu)造分片文件的臨時存儲路徑
     *
     * @param md5         文件md5
     * @param currIndex   分片文件索引(從0開始)
     * @param totalPieces 總分片
     * @return 臨時存儲路徑
     */
    private static String getFileTempPath(String md5, Integer currIndex, Integer totalPieces) {

        int zeroCnt = countDigits(totalPieces) - countDigits(currIndex);
        StringBuilder name = new StringBuilder(md5);
        name.append(StringUtils.BACKSLASH);
        for (int i = IntegerConsts.ZERO; i < zeroCnt; i++) {
            name.append(IntegerConsts.ZERO);
        }
        name.append(currIndex);
        return name.toString();
    }

    /**
     * 創(chuàng)建目錄
     *
     * @param bucketName 存儲桶
     * @param folderName 目錄路徑:本項目約定路徑是以"/"開頭,不以"/"結(jié)尾
     */
    public ObjectWriteResponse createFolder(String bucketName, String folderName) throws Exception {
        // 這是minio的bug,只有在路徑的尾巴加上"/",才能當(dāng)成文件夾。
        folderName = addTail(folderName);
        return client.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(folderName)
                        .stream(new ByteArrayInputStream(new byte[]{}), IntegerConsts.ZERO, IntegerConsts.MINUS_ONE)
                        .build());
    }

}

資源目錄下新建 META-INF\spring

該方式支持 Spring Boot 3.x

新建文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports 內(nèi)容

com.cdkjframework.minio.config.MinioAutoConfiguration

總結(jié)

Spring Boot 封裝 MinIO 工具的核心意義在于將分布式存儲能力轉(zhuǎn)化為可復(fù)用的基礎(chǔ)設(shè)施,通過標(biāo)準(zhǔn)化、模塊化的設(shè)計,顯著降低開發(fā)復(fù)雜度,提升系統(tǒng)健壯性和可維護性。這種封裝不僅是技術(shù)層面的優(yōu)化,更是工程實踐中的最佳選擇,尤其適用于需要快速迭代、高并發(fā)處理及多云兼容的現(xiàn)代應(yīng)用架構(gòu)。

到此這篇關(guān)于SpringBoot封裝MinIO工具的實現(xiàn)步驟的文章就介紹到這了,更多相關(guān)SpringBoot封裝MinIO內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JAVA利用接口實現(xiàn)多繼承問題的代碼實操演示

    JAVA利用接口實現(xiàn)多繼承問題的代碼實操演示

    Java語言并不支持多繼承,這是由于多繼承會帶來許多復(fù)雜的問題,例如"菱形問題"等,下面這篇文章主要給大家介紹了關(guān)于JAVA利用接口實現(xiàn)多繼承問題的相關(guān)資料,需要的朋友可以參考下
    2024-03-03
  • 10個微妙的Java編碼最佳實踐

    10個微妙的Java編碼最佳實踐

    這篇文章讓我與你分享10個微妙的Java編碼最佳實踐,需要的朋友可以參考下
    2017-11-11
  • MyBatis自定義typeHandler的完整實例

    MyBatis自定義typeHandler的完整實例

    這篇文章主要給大家介紹了關(guān)于MyBatis自定義typeHandler的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用MyBatis具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Mybatis-Plus進階分頁與樂觀鎖插件及通用枚舉和多數(shù)據(jù)源詳解

    Mybatis-Plus進階分頁與樂觀鎖插件及通用枚舉和多數(shù)據(jù)源詳解

    這篇文章主要介紹了Mybatis-Plus的分頁插件與樂觀鎖插件還有通用枚舉和多數(shù)據(jù)源的相關(guān)介紹,文中代碼附有詳細的注釋,感興趣的朋友來看看吧
    2022-03-03
  • 最新評論