Java 集成 MinIO

完整的项目在 GitHub-mlb

配置类

package com.seliote.mlb.fs.config;

import com.seliote.mlb.common.config.PropertiesInjector;
import io.minio.BucketExistsArgs;
import io.minio.MinioClient;
import io.minio.errors.MinioException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * MinIO 配置类
 *
 * @author seliote
 */
@Slf4j
@Configuration
public class MinioConfig {

    private final PropertiesInjector.Minio minioProp;

    @Autowired
    public MinioConfig(PropertiesInjector propertiesInjector) {
        this.minioProp = propertiesInjector.getMinio();
    }

    /**
     * MinioClient Bean
     *
     * @return MinioClient 对象
     */
    @Bean
    public MinioClient minioClient()
            throws MinioException, IOException, NoSuchAlgorithmException, InvalidKeyException {
        var client = MinioClient.builder()
                .endpoint(minioProp.getEndpoint())
                .credentials(minioProp.getAccessKey(), minioProp.getSecretKey())
                .build();
        // 检查 Bucket 是否存在
        boolean found = client.bucketExists(BucketExistsArgs.builder()
                .bucket(minioProp.getBucket())
                .build());
        if (!found) {
            log.error("Bucket {} not found! Please create it first", minioProp.getBucket());
            throw new IllegalStateException("Bucket not found");
        }
        log.info("Minio bucket: {}", minioProp.getBucket());
        return client;
    }
}

Service

package com.seliote.mlb.fs.service;

import com.seliote.mlb.common.jsr303.MinioBucket;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStream;

/**
 * 文件 Service
 *
 * @author seliote
 */
@Validated
public interface FileService {

    // 图片分类名称
    String IMG = "img";
    // 音频分类名称
    String AUDIO = "audio";

    /**
     * 上传文件到指定目录
     * <em>操作完成后不会关闭流, 需调用者来关闭</em>
     * <em>未正确关闭流会导致句柄泄漏</em>
     *
     * @param catalog       分类
     * @param fileExtension 文件扩展名, 如 mp3, jpg
     * @param inputStream   输入流
     * @return Minio 文件名, 后续需使用此文件名下载文件
     */
    String upload(@MinioBucket String catalog,
                  @NotBlank String fileExtension,
                  @NotNull InputStream inputStream) throws IOException;

    /**
     * 下载指定文件
     * <em>需调用方关闭返回的流</em>
     * <em>未正确关闭流会导致句柄泄漏</em>
     *
     * @param catalog  分类
     * @param filename 文件名称
     */
    InputStream download(@MinioBucket String catalog, @NotBlank String filename) throws IOException;
}

Impl

package com.seliote.mlb.fs.service.impl;

import com.seliote.mlb.common.config.PropertiesInjector;
import com.seliote.mlb.fs.service.FileService;
import io.minio.GetObjectArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.errors.MinioException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

/**
 * 文件 Service 实现
 *
 * @author seliote
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService {

    // Minio 文件的分隔符
    private final String FS_SEPARATOR = "/";

    private final PropertiesInjector.Minio minioProp;
    private final MinioClient minioClient;

    @Autowired
    public FileServiceImpl(PropertiesInjector propertiesInjector,
                           MinioClient minioClient) {
        this.minioProp = propertiesInjector.getMinio();
        this.minioClient = minioClient;
    }

    @Override
    public String upload(String catalog, String fileExtension, InputStream inputStream) throws IOException {
        // 生成随机 UUID 作为应用名称
        String filename = UUID.randomUUID().toString();
        // 将文件放在不同目录
        // Minio 会默认放在同一文件夹下, 手动分开为 36 * 36, 避免同一文件夹下文件数量过多影响 IO
        filename = filename.charAt(0) + FS_SEPARATOR
                + filename.charAt(1) + FS_SEPARATOR
                + filename + "." + fileExtension;
        try {
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(minioProp.getBucket())
                    .object(String.format("%s%s%s", catalog, FS_SEPARATOR, filename))
                    .stream(inputStream, -1, 1024 * 1024 * 10)
                    .build());
            log.info("Upload {} {} success", catalog, filename);
        } catch (MinioException | InvalidKeyException | NoSuchAlgorithmException e) {
            log.error("Upload {} {} failed, {}, message: {}",
                    catalog, filename, e.getClass().getCanonicalName(), e.getMessage());
            throw new IOException(e);
        }
        return filename;
    }

    @Override
    public InputStream download(String catalog, String filename) throws IOException {
        try {
            var getObjectResponse = minioClient.getObject(GetObjectArgs.builder()
                    .bucket(minioProp.getBucket())
                    .object(String.format("%s%s%s", catalog, FS_SEPARATOR, filename))
                    .build());
            log.info("Download {} {} success", catalog, filename);
            return getObjectResponse;
        } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) {
            log.error("Download {} {} failed, {}, message: {}",
                    catalog, filename, e.getClass().getCanonicalName(), e.getMessage());
            throw new IOException(e);
        }
    }
}
原文地址:https://www.cnblogs.com/seliote/p/14828822.html