package com.rj.utils; import com.rj.config.MinIOConfig; import io.minio.*; import io.minio.http.Method; import io.minio.messages.Bucket; import io.minio.messages.Item; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; /** * MinIO 工具类 * 提供文件上传、下载、删除等常用操作 * * @author rj * @date 2025-10-02 */ @Slf4j @Component public class MinIOUtil { @Autowired private MinioClient minioClient; @Autowired private MinIOConfig minioConfig; /** * 检查存储桶是否存在 * * @param bucketName 存储桶名称 * @return 是否存在 */ public boolean bucketExists(String bucketName) { try { return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); } catch (Exception e) { log.error("检查存储桶是否存在失败: {}", e.getMessage()); return false; } } /** * 创建存储桶 * * @param bucketName 存储桶名称 * @return 是否创建成功 */ public boolean createBucket(String bucketName) { try { if (!bucketExists(bucketName)) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); log.info("存储桶创建成功: {}", bucketName); return true; } log.info("存储桶已存在: {}", bucketName); return true; } catch (Exception e) { log.error("创建存储桶失败: {}", e.getMessage()); return false; } } /** * 获取所有存储桶 * * @return 存储桶列表 */ public List getAllBuckets() { try { List buckets = minioClient.listBuckets(); List bucketNames = new ArrayList<>(); for (Bucket bucket : buckets) { bucketNames.add(bucket.name()); } return bucketNames; } catch (Exception e) { log.error("获取存储桶列表失败: {}", e.getMessage()); return new ArrayList<>(); } } /** * 删除存储桶 * * @param bucketName 存储桶名称 * @return 是否删除成功 */ public boolean removeBucket(String bucketName) { try { minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build()); log.info("存储桶删除成功: {}", bucketName); return true; } catch (Exception e) { log.error("删除存储桶失败: {}", e.getMessage()); return false; } } /** * 上传文件 * * @param file 文件 * @param bucketName 存储桶名称 * @return 文件名 */ public String uploadFile(MultipartFile file, String bucketName) { return uploadFile(file, bucketName, null); } /** * 上传文件 * * @param file 文件 * @param bucketName 存储桶名称 * @param objectName 对象名称(文件名),如果为null则自动生成 * @return 文件名 */ public String uploadFile(MultipartFile file, String bucketName, String objectName) { try { // 确保存储桶存在 createBucket(bucketName); // 生成文件名 if (objectName == null || objectName.trim().isEmpty()) { objectName = generateFileName(file.getOriginalFilename()); } // 上传文件 minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build() ); log.info("文件上传成功: {}/{}", bucketName, objectName); return objectName; } catch (Exception e) { log.error("文件上传失败: {}", e.getMessage()); throw new RuntimeException("文件上传失败: " + e.getMessage()); } } /** * 上传文件流 * * @param inputStream 文件流 * @param bucketName 存储桶名称 * @param objectName 对象名称 * @param contentType 内容类型 * @return 是否上传成功 */ public boolean uploadFile(InputStream inputStream, String bucketName, String objectName, String contentType) { try { createBucket(bucketName); minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(inputStream, -1, 10485760) // 10MB .contentType(contentType) .build() ); log.info("文件流上传成功: {}/{}", bucketName, objectName); return true; } catch (Exception e) { log.error("文件流上传失败: {}", e.getMessage()); return false; } } /** * 上传字节数组 * * @param data 字节数组 * @param objectName 对象名称 * @param contentType 内容类型 * @return 对象名称 */ public String uploadFile(byte[] data, String objectName, String contentType) { try { // 使用配置中的默认存储桶 String bucketName = minioConfig.getBucketName(); // 确保存储桶存在 createBucket(bucketName); minioClient.putObject( PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(new java.io.ByteArrayInputStream(data), data.length, -1) .contentType(contentType) .build() ); log.info("字节数组上传成功: {}/{}, 大小: {} bytes", bucketName, objectName, data.length); return objectName; } catch (Exception e) { log.error("字节数组上传失败: {}", e.getMessage()); throw new RuntimeException("字节数组上传失败: " + e.getMessage()); } } /** * 下载文件 * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @return 文件流 */ public InputStream downloadFile(String bucketName, String objectName) { try { return minioClient.getObject( GetObjectArgs.builder() .bucket(bucketName) .object(objectName) .build() ); } catch (Exception e) { log.error("文件下载失败: {}", e.getMessage()); throw new RuntimeException("文件下载失败: " + e.getMessage()); } } /** * 下载文件到本地 * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @param fileName 本地文件路径 * @return 是否下载成功 */ public boolean downloadFile(String bucketName, String objectName, String fileName) { try { minioClient.downloadObject( DownloadObjectArgs.builder() .bucket(bucketName) .object(objectName) .filename(fileName) .build() ); log.info("文件下载成功: {}/{} -> {}", bucketName, objectName, fileName); return true; } catch (Exception e) { log.error("文件下载失败: {}", e.getMessage()); return false; } } /** * 删除文件 * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @return 是否删除成功 */ public boolean removeFile(String bucketName, String objectName) { try { minioClient.removeObject( RemoveObjectArgs.builder() .bucket(bucketName) .object(objectName) .build() ); log.info("文件删除成功: {}/{}", bucketName, objectName); return true; } catch (Exception e) { log.error("文件删除失败: {}", e.getMessage()); return false; } } /** * 获取文件信息 * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @return 文件信息 */ public StatObjectResponse getFileInfo(String bucketName, String objectName) { try { return minioClient.statObject( StatObjectArgs.builder() .bucket(bucketName) .object(objectName) .build() ); } catch (Exception e) { log.error("获取文件信息失败: {}", e.getMessage()); return null; } } /** * 获取文件列表 * * @param bucketName 存储桶名称 * @return 文件列表 */ public List listFiles(String bucketName) { return listFiles(bucketName, null); } /** * 获取文件列表 * * @param bucketName 存储桶名称 * @param prefix 文件前缀 * @return 文件列表 */ public List listFiles(String bucketName, String prefix) { List files = new ArrayList<>(); try { Iterable> results = minioClient.listObjects( ListObjectsArgs.builder() .bucket(bucketName) .prefix(prefix) .build() ); for (Result result : results) { Item item = result.get(); files.add(item.objectName()); } } catch (Exception e) { log.error("获取文件列表失败: {}", e.getMessage()); } return files; } /** * 获取文件详细列表 * * @param bucketName 存储桶名称 * @return 文件详细信息列表 */ public List listFileDetails(String bucketName) { return listFileDetails(bucketName, null); } /** * 获取文件详细列表 * * @param bucketName 存储桶名称 * @param prefix 文件前缀 * @return 文件详细信息列表 */ public List listFileDetails(String bucketName, String prefix) { List files = new ArrayList<>(); try { Iterable> results = minioClient.listObjects( ListObjectsArgs.builder() .bucket(bucketName) .prefix(prefix) .build() ); for (Result result : results) { Item item = result.get(); FileInfo fileInfo = new FileInfo(); fileInfo.setObjectName(item.objectName()); fileInfo.setSize(item.size()); fileInfo.setLastModified(item.lastModified()); fileInfo.setEtag(item.etag()); files.add(fileInfo); } } catch (Exception e) { log.error("获取文件详细列表失败: {}", e.getMessage()); } return files; } /** * 获取文件预签名URL(用于临时访问) * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @param expires 过期时间(秒) * @return 预签名URL */ public String getPresignedObjectUrl(String bucketName, String objectName, int expires) { try { // 检查文件是否存在 if (!objectExists(bucketName, objectName)) { log.error("文件不存在: bucket={}, object={}", bucketName, objectName); return null; } log.info("生成预签名URL: bucket={}, object={}, expires={}秒", bucketName, objectName, expires); String presignedUrl = minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(bucketName) .object(objectName) .expiry(expires, TimeUnit.SECONDS) .build() ); log.info("生成的预签名URL: {}", presignedUrl); return presignedUrl; } catch (Exception e) { log.error("获取预签名URL失败: bucket={}, object={}, error={}", bucketName, objectName, e.getMessage()); return null; } } /** * 检查对象是否存在 * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @return 是否存在 */ public boolean objectExists(String bucketName, String objectName) { try { minioClient.statObject( StatObjectArgs.builder() .bucket(bucketName) .object(objectName) .build() ); return true; } catch (Exception e) { log.debug("对象不存在: bucket={}, object={}, error={}", bucketName, objectName, e.getMessage()); return false; } } /** * 获取文件预签名URL(默认7天过期) * * @param bucketName 存储桶名称 * @param objectName 对象名称 * @return 预签名URL */ public String getPresignedObjectUrl(String bucketName, String objectName) { return getPresignedObjectUrl(bucketName, objectName, 7 * 24 * 3600); // 7天 } /** * 复制文件 * * @param sourceBucketName 源存储桶名称 * @param sourceObjectName 源对象名称 * @param targetBucketName 目标存储桶名称 * @param targetObjectName 目标对象名称 * @return 是否复制成功 */ public boolean copyFile(String sourceBucketName, String sourceObjectName, String targetBucketName, String targetObjectName) { try { createBucket(targetBucketName); minioClient.copyObject( CopyObjectArgs.builder() .bucket(targetBucketName) .object(targetObjectName) .source(CopySource.builder() .bucket(sourceBucketName) .object(sourceObjectName) .build()) .build() ); log.info("文件复制成功: {}/{} -> {}/{}", sourceBucketName, sourceObjectName, targetBucketName, targetObjectName); return true; } catch (Exception e) { log.error("文件复制失败: {}", e.getMessage()); return false; } } /** * 生成唯一文件名 * * @param originalFilename 原始文件名 * @return 唯一文件名 */ private String generateFileName(String originalFilename) { String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); String uuid = UUID.randomUUID().toString().replace("-", ""); String extension = ""; if (originalFilename != null && originalFilename.contains(".")) { extension = originalFilename.substring(originalFilename.lastIndexOf(".")); } return timestamp + "_" + uuid + extension; } /** * 文件信息类 */ public static class FileInfo { private String objectName; private Long size; private java.time.ZonedDateTime lastModified; private String etag; // Getters and Setters public String getObjectName() { return objectName; } public void setObjectName(String objectName) { this.objectName = objectName; } public Long getSize() { return size; } public void setSize(Long size) { this.size = size; } public java.time.ZonedDateTime getLastModified() { return lastModified; } public void setLastModified(java.time.ZonedDateTime lastModified) { this.lastModified = lastModified; } public String getEtag() { return etag; } public void setEtag(String etag) { this.etag = etag; } @Override public String toString() { return "FileInfo{" + "objectName='" + objectName + '\'' + ", size=" + size + ", lastModified=" + lastModified + ", etag='" + etag + '\'' + '}'; } } }