更加完整详细内容可查看【免费版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. 使用注意事项
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(); // 转换为字节数组
}
}




