欢迎光临
我们一直在努力

MinioUtil工具类

更加完整详细内容可查看【免费版Java学习笔记】和【免费版Java面试题】

免费版Java学习笔记(28w字)链接:https://www.yuque.com/aoyouaoyou/sgcqr8
免费版Java面试题(20w字)链接:https://www.yuque.com/aoyouaoyou/wh3hto
完整版Java学习笔记200w字,附有代码实现,图解清楚,仅需9.9
完整版Java面试题,150w字,高频面试题,内容详细,仅需9.9
完整版链接:
https://www.xiaohongshu.com/user/profile/63c2d512000000002601232c
祝您新的一年事事马到成功,身体健康,阖家幸福,大展宏图!

MinioUtil工具类

package com.aoyou.commonUtil.util;

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.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class MinioUtil {

@Autowired
private MinioClient minioClient;

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

/**
* 检查存储桶是否存在
* @param bucketName 存储桶名称
* @return 是否存在
*/
@SneakyThrows
public boolean bucketExists(String bucketName) {
return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
}

/**
* 创建存储桶
* @param bucketName 存储桶名称
*/
@SneakyThrows
public void makeBucket(String bucketName) {
if (!bucketExists(bucketName)) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
}

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

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

/**
* 获得Bucket的策略
* @param bucketName
* @return
* @throws Exception
*/
public String getBucketPolicy(String bucketName) throws Exception {

String bucketPolicy = minioClient
.getBucketPolicy(
GetBucketPolicyArgs
.builder()
.bucket(bucketName)
.build()
);
return bucketPolicy;
}

/**
* 根据bucketName获取其相关信息
* @param bucketName
* @return
* @throws Exception
*/
public Optional<Bucket> getBucket(String bucketName) throws Exception {

return listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
}

/**
* 判断文件是否存在
* @param bucketName 存储桶
* @param objectName 文件名
* @return
*/
public boolean isObjectExist(String bucketName, String objectName) {

boolean exist = true;
try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
} catch (Exception e) {
exist = false;
}
return exist;
}

/**
* 判断文件夹是否存在
* @param bucketName 存储桶
* @param objectName 文件夹名称
* @return
*/
public boolean isFolderExist(String bucketName, String objectName) {

boolean exist = false;
try {
Iterable<Result<Item>> results = minioClient.listObjects(
ListObjectsArgs.builder().bucket(bucketName).prefix(objectName).recursive(false).build());
for (Result<Item> result : results) {

Item item = result.get();
if (item.isDir() && objectName.equals(item.objectName())) {

exist = true;
}
}
} catch (Exception e) {

exist = false;
}
return exist;
}

/**
* 简单文件上传
* @param file 文件
* @param bucketName 存储桶名称
* @return 文件信息
*/
@SneakyThrows
public Map<String, String> uploadFile(MultipartFile file, String bucketName) {
if (file == null || file.isEmpty()) {
return null;
}

if (!bucketExists(bucketName)) {
makeBucket(bucketName);
}

String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));

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

Map<String, String> resultMap = new HashMap<>();
resultMap.put("fileName", fileName);
resultMap.put("originalFilename", originalFilename);
resultMap.put("url", getObjectUrl(bucketName, fileName, 7));

return resultMap;
}

/**
* 简单文件上传(使用默认存储桶)
* @param file 文件
* @return 文件信息
*/
public Map<String, String> uploadFile(MultipartFile file) {
return uploadFile(file, defaultBucketName);
}

/**
* 批量文件上传
* @param files 文件列表
* @param bucketName 存储桶名称
* @return 文件信息列表
*/
public List<Map<String, String>> uploadFiles(List<MultipartFile> files, String bucketName) {
return files.stream()
.map(file -> uploadFile(file, bucketName))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

/**
* 批量文件上传(使用默认存储桶)
* @param files 文件列表
* @return 文件信息列表
*/
public List<Map<String, String>> uploadFiles(List<MultipartFile> files) {
return uploadFiles(files, defaultBucketName);
}

/**
* 下载文件
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 输入流
*/
@SneakyThrows
public InputStream downloadFile(String bucketName, String objectName) {
return minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
}

/**
* 下载文件(使用默认存储桶)
* @param objectName 对象名称
* @return 输入流
*/
public InputStream downloadFile(String objectName) {
return downloadFile(defaultBucketName, objectName);
}

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

/**
* 删除文件(使用默认存储桶)
* @param objectName 对象名称
*/
public void deleteFile(String objectName) {
deleteFile(defaultBucketName, objectName);
}

/**
* 批量删除文件
* @param bucketName 存储桶名称
* @param objectNames 对象名称列表
* @return 删除错误列表
*/
@SneakyThrows
public List<DeleteError> deleteFiles(String bucketName, List<String> objectNames) {
List<DeleteObject> objects = objectNames.stream()
.map(DeleteObject::new)
.collect(Collectors.toList());

Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(objects)
.build());

List<DeleteError> errors = new ArrayList<>();
for (Result<DeleteError> result : results) {
errors.add(result.get());
}
return errors;
}

/**
* 批量删除文件(使用默认存储桶)
* @param objectNames 对象名称列表
* @return 删除错误列表
*/
public List<DeleteError> deleteFiles(List<String> objectNames) {
return deleteFiles(defaultBucketName, objectNames);
}

/**
* 获取文件URL
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @param expires 过期时间(天)
* @return 文件URL
*/
@SneakyThrows
public String getObjectUrl(String bucketName, String objectName, int expires) {
return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.expiry(expires, TimeUnit.DAYS)
.build());
}

/**
* 获取文件URL(使用默认存储桶)
* @param objectName 对象名称
* @param expires 过期时间(天)
* @return 文件URL
*/
public String getObjectUrl(String objectName, int expires) {
return getObjectUrl(defaultBucketName, objectName, expires);
}

/**
* 检查文件是否存在
* @param bucketName 存储桶名称
* @param objectName 对象名称
* @return 是否存在
*/
@SneakyThrows
public boolean objectExists(String bucketName, String objectName) {
try {
minioClient.statObject(StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
return true;
} catch (Exception e) {
return false;
}
}

/**
* 检查文件是否存在(使用默认存储桶)
* @param objectName 对象名称
* @return 是否存在
*/
public boolean objectExists(String objectName) {
return objectExists(defaultBucketName, objectName);
}

/**
* 列出存储桶中的所有对象
* @param bucketName 存储桶名称
* @return 对象列表
*/
@SneakyThrows
public List<Item> listObjects(String bucketName) {
Iterable<Result<Item>> results = minioClient.listObjects(ListObjectsArgs.builder()
.bucket(bucketName)
.build());

List<Item> items = new ArrayList<>();
for (Result<Item> result : results) {
items.add(result.get());
}
return items;
}

/**
* 列出存储桶中的所有对象(使用默认存储桶)
* @return 对象列表
*/
public List<Item> listObjects() {
return listObjects(defaultBucketName);
}

/**
* 获取文件信息, 如果抛出异常则说明文件不存在
*
* @param bucketName 存储桶
* @param objectName 文件名称
*/
public String getFileStatusInfo(String bucketName, String objectName) throws Exception {

return minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()).toString();
}

/**
* 拷贝文件
*
* @param bucketName 存储桶
* @param objectName 文件名
* @param srcBucketName 目标存储桶
* @param srcObjectName 目标文件名
*/
public ObjectWriteResponse copyFile(String bucketName, String objectName,
String srcBucketName, String srcObjectName) throws Exception {

return minioClient.copyObject(
CopyObjectArgs.builder()
.source(CopySource.builder().bucket(bucketName).object(objectName).build())
.bucket(srcBucketName)
.object(srcObjectName)
.build());
}

/**
* 生成文件哈希值(用于秒传判断)
* @param file 文件
* @return 哈希值
*/
@SneakyThrows
public String generateFileHash(MultipartFile file) {
// 这里使用简单的文件大小和修改时间作为哈希值,实际应用中应使用MD5或SHA-1等算法
return file.getSize() + "-" + file.getOriginalFilename();
}
}

1. 工具类概述

MinioUtil 是基于 MinIO Java SDK 封装的工具类,提供了对象存储服务的核心操作接口,支持存储桶管理、文件上传/下载/删除、权限查询、文件复制等功能。通过 Spring 依赖注入实现 MinioClient 实例管理,简化了在 Spring Boot 项目中集成 MinIO 的流程,适合前后端分离架构中的文件存储场景(如用户头像、文档、图片等)。

2. 核心设计与依赖

(1)核心依赖
  • MinIO Java SDK:版本由 Maven 依赖指定(如 8.5.9),提供 MinIO 服务的底层交互能力。
  • Spring 框架:通过 @Component 注册为 Spring 组件,依赖注入 MinioClient 实例和配置参数。
  • 配置参数:从 application.yml 读取 minio.bucket 作为默认存储桶,简化无指定桶时的操作。
(2)核心属性

属性名

类型

作用

minioClient

MinioClient

MinIO 客户端实例,用于所有操作

defaultBucketName

String

默认存储桶名称

3. 核心功能分类

(1)存储桶(Bucket)操作

方法组

功能描述

关键方法

存在性与创建

检查桶是否存在、创建桶(不存在时)

bucketExists、makeBucket

列举与删除

列举所有桶、删除桶(需为空桶)

listBuckets、removeBucket

权限与详情

获取桶策略、查询桶详情

getBucketPolicy、getBucket

(2)文件/对象(Object)操作

方法组

功能描述

关键方法

上传(单个/批量)

支持MultipartFile上传,生成唯一文件名

uploadFile、uploadFiles

下载

下载文件为输入流(需手动关闭)

downloadFile

删除(单个/批量)

支持批量删除,返回失败列表

deleteFile、deleteFiles

存在性检查

检查文件/文件夹是否存在

isObjectExist、isFolderExist

访问URL生成

生成带签名的临时访问URL(指定过期时间)

getObjectUrl

列举与信息查询

列举桶中所有文件、获取文件状态信息

listObjects、getFileStatusInfo

(3)高级操作

方法名

功能描述

适用场景

copyFile

复制文件到目标存储桶

跨桶文件迁移、备份

generateFileHash

生成文件哈希值(简易实现)

秒传功能的文件唯一性校验

4. 关键特性

  • 默认桶支持:多数方法提供默认桶重载(如 uploadFile(file)),减少重复传参。
  • 自动创建桶:上传文件时若桶不存在,自动创建,简化调用流程。
  • 唯一文件名:上传时通过 UUID + 原始后缀 生成文件名,避免重名覆盖。
  • 异常简化:使用 @SneakyThrows 简化 checked 异常处理(生产可根据需求自定义异常)。
  • 流式处理:批量操作通过 Stream API 实现,代码简洁且易读。

5. 使用注意事项

  • 存储桶命名规范:必须使用小写字母、数字、短横线(-),不能包含空格或特殊字符。
  • 删除桶前提:删除存储桶前必须清空所有文件,否则会抛出 BucketNotEmptyException。
  • 输入流关闭:downloadFile 方法返回的 InputStream 需调用方手动关闭,避免资源泄露。
  • 哈希值实现:generateFileHash 为简易实现(基于大小和文件名),生产环境需改为基于文件内容的哈希(如 MD5)。
  • 权限控制:通过 getBucketPolicy 可查询桶权限,需根据业务需求提前配置(如私有/公开读)。
  • 大文件处理:当前工具类适合小文件上传,大文件建议使用分片上传(可扩展添加分片相关方法)。
  • 6. 典型使用场景示例

    (1)单个文件上传(默认桶)

    @Autowired
    private MinioUtil minioUtil;

    public String uploadAvatar(MultipartFile avatar) {
    Map<String, String> fileInfo = minioUtil.uploadFile(avatar);
    return fileInfo.get("url"); // 返回7天有效的访问URL
    }

    (2)文件下载接口

    @GetMapping("/download")
    public ResponseEntity<byte[]> download(String fileName) throws IOException {
    InputStream in = minioUtil.downloadFile(fileName);
    byte[] bytes = in.readAllBytes();
    in.close(); // 关闭输入流

    HttpHeaders headers = new HttpHeaders();
    headers.setContentDispositionFormData("attachment", fileName);
    return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
    }

    (3)检查文件是否存在并删除

    public void deleteIfExists(String fileName) {
    if (minioUtil.objectExists(fileName)) {
    minioUtil.deleteFile(fileName);
    }
    }

    7. 单元测试

    package com.aoyou.commonUtil;

    import com.aoyou.commonUtil.util.MinioUtil;
    import io.minio.messages.Bucket;
    import io.minio.messages.DeleteError;
    import io.minio.messages.Item;
    import org.junit.jupiter.api.AfterEach;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.mock.web.MockMultipartFile;
    import org.springframework.web.multipart.MultipartFile;

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.Optional;

    import static org.junit.jupiter.api.Assertions.*;

    @SpringBootTest
    public class MinioUtilTest {

    @Autowired
    private MinioUtil minioUtil;

    // 测试专用存储桶(测试后自动清理)
    private static final String TEST_BUCKET = "minio-util-test-bucket";
    // 测试文件名
    private static final String TEST_FILE_NAME = "test-file.txt";
    // 测试文件内容
    private static final String TEST_FILE_CONTENT = "Hello, MinIO!";

    /**
    * 测试前准备:创建测试桶
    */
    @BeforeEach
    void setUp() {
    // 确保测试桶存在
    if (!minioUtil.bucketExists(TEST_BUCKET)) {
    minioUtil.makeBucket(TEST_BUCKET);
    }
    }

    /**
    * 测试后清理:删除测试桶及其中的文件
    */
    @AfterEach
    void tearDown() {
    if (minioUtil.bucketExists(TEST_BUCKET)) {
    // 先删除桶内所有文件
    List<Item> items = minioUtil.listObjects(TEST_BUCKET);
    for (Item item : items) {
    minioUtil.deleteFile(TEST_BUCKET, item.objectName());
    }
    // 再删除桶
    minioUtil.removeBucket(TEST_BUCKET);
    }
    }

    /**
    * 测试存储桶操作:创建、存在性、删除
    */
    @Test
    void testBucketOperations() {
    String tempBucket = "temp-test-bucket";

    // 测试创建桶
    assertFalse(minioUtil.bucketExists(tempBucket), "初始时临时桶应不存在");
    minioUtil.makeBucket(tempBucket);
    assertTrue(minioUtil.bucketExists(tempBucket), "创建后临时桶应存在");

    // 测试列举桶
    List<Bucket> buckets = minioUtil.listBuckets();
    Optional<Bucket> found = buckets.stream()
    .filter(b -> b.name().equals(tempBucket))
    .findFirst();
    assertTrue(found.isPresent(), "列举桶时应包含临时桶");

    // 测试删除桶
    minioUtil.removeBucket(tempBucket);
    assertFalse(minioUtil.bucketExists(tempBucket), "删除后临时桶应不存在");
    }

    /**
    * 测试文件上传与存在性检查
    */
    @Test
    void testUploadFile() {
    // 创建测试文件
    MultipartFile file = new MockMultipartFile(
    "test",
    TEST_FILE_NAME,
    "text/plain",
    TEST_FILE_CONTENT.getBytes()
    );

    // 测试上传
    Map<String, String> fileInfo = minioUtil.uploadFile(file, TEST_BUCKET);
    assertNotNull(fileInfo, "上传结果不应为null");
    String uploadedFileName = fileInfo.get("fileName");
    assertNotNull(uploadedFileName, "上传后的文件名不应为null");

    // 测试文件存在性
    assertTrue(minioUtil.objectExists(TEST_BUCKET, uploadedFileName), "上传的文件应存在");
    }

    /**
    * 测试文件下载
    */
    @Test
    void testDownloadFile() throws IOException {
    // 先上传文件
    MultipartFile file = new MockMultipartFile(
    "test",
    TEST_FILE_NAME,
    "text/plain",
    TEST_FILE_CONTENT.getBytes()
    );
    String uploadedFileName = minioUtil.uploadFile(file, TEST_BUCKET).get("fileName");

    // 测试下载
    InputStream in = minioUtil.downloadFile(TEST_BUCKET, uploadedFileName);
    assertNotNull(in, "下载的输入流不应为null");

    // 验证文件内容
    byte[] content = readFile(in);
    assertEquals(TEST_FILE_CONTENT, new String(content), "下载的文件内容应与上传时一致");
    in.close();
    }

    /**
    * 测试文件删除
    */
    @Test
    void testDeleteFile() {
    // 先上传文件
    MultipartFile file = new MockMultipartFile(
    "test",
    TEST_FILE_NAME,
    "text/plain",
    TEST_FILE_CONTENT.getBytes()
    );
    String uploadedFileName = minioUtil.uploadFile(file, TEST_BUCKET).get("fileName");
    assertTrue(minioUtil.objectExists(TEST_BUCKET, uploadedFileName), "删除前文件应存在");

    // 测试删除
    minioUtil.deleteFile(TEST_BUCKET, uploadedFileName);
    assertFalse(minioUtil.objectExists(TEST_BUCKET, uploadedFileName), "删除后文件应不存在");
    }

    /**
    * 测试批量删除文件
    */
    @Test
    void testDeleteFiles() {
    // 上传两个测试文件
    MultipartFile file1 = new MockMultipartFile("f1", "f1.txt", "text/plain", "1".getBytes());
    MultipartFile file2 = new MockMultipartFile("f2", "f2.txt", "text/plain", "2".getBytes());
    String name1 = minioUtil.uploadFile(file1, TEST_BUCKET).get("fileName");
    String name2 = minioUtil.uploadFile(file2, TEST_BUCKET).get("fileName");

    // 测试批量删除

    List<DeleteError> errors = minioUtil.deleteFiles(TEST_BUCKET, Arrays.asList(name1, name2));
    assertTrue(errors.isEmpty(), "批量删除不应有错误");

    // 验证删除结果
    assertFalse(minioUtil.objectExists(TEST_BUCKET, name1));
    assertFalse(minioUtil.objectExists(TEST_BUCKET, name2));
    }

    /**
    * 测试生成临时访问URL
    */
    @Test
    void testGetObjectUrl() throws Exception {
    // 先上传文件
    MultipartFile file = new MockMultipartFile(
    "test",
    TEST_FILE_NAME,
    "text/plain",
    TEST_FILE_CONTENT.getBytes()
    );
    String uploadedFileName = minioUtil.uploadFile(file, TEST_BUCKET).get("fileName");

    // 测试生成URL
    String url = minioUtil.getObjectUrl(TEST_BUCKET, uploadedFileName, 1);
    System.out.println(url);
    assertNotNull(url, "生成的URL不应为null");
    System.out.println(minioUtil.getBucket(TEST_BUCKET).get().name());
    assertTrue(url.contains(minioUtil.getBucket(TEST_BUCKET).get().name()), "URL应包含桶名");
    assertTrue(url.contains(uploadedFileName), "URL应包含文件名");
    }

    /**
    * 测试文件复制
    */
    @Test
    void testCopyFile() throws Exception {
    // 先上传源文件
    MultipartFile file = new MockMultipartFile(
    "test",
    TEST_FILE_NAME,
    "text/plain",
    TEST_FILE_CONTENT.getBytes()
    );
    String srcFileName = minioUtil.uploadFile(file, TEST_BUCKET).get("fileName");
    String destFileName = "copied-" + srcFileName;

    // 测试复制文件
    minioUtil.copyFile(TEST_BUCKET, srcFileName, TEST_BUCKET, destFileName);
    assertTrue(minioUtil.objectExists(TEST_BUCKET, destFileName), "复制后的文件应存在");

    // 验证内容一致
    InputStream srcIn = minioUtil.downloadFile(TEST_BUCKET, srcFileName);
    InputStream destIn = minioUtil.downloadFile(TEST_BUCKET, destFileName);
    assertArrayEquals(readFile(srcIn), readFile(destIn), "复制的文件内容应与源文件一致");
    srcIn.close();
    destIn.close();
    }

    public byte[] readFile(InputStream in) throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024]; // 缓冲区大小,可根据需求调整(如 4096)
    int len;
    // 循环读取输入流内容到缓冲区
    while ((len = in.read(buffer)) != -1) {
    out.write(buffer, 0, len); // 将缓冲区内容写入输出流
    }
    return out.toByteArray(); // 转换为字节数组
    }

    }

  • 测试范围:覆盖了存储桶管理、文件上传/下载/删除、批量操作、URL生成、文件复制等核心功能。
  • 环境隔离:使用专用测试桶 minio-util-test-bucket,测试前后自动创建和清理,避免影响其他数据。
  • 异常验证:通过 assertTrue/assertFalse 验证操作结果,确保方法按预期执行。
  • 注意事项:测试需确保 MinIO 服务可访问,配置文件中的 minio.endpoint、accessKey、secretKey 正确。
  • 赞(0)
    未经允许不得转载:171主机测评 » MinioUtil工具类
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址