Files
aida-seller/src/main/java/com/aida/seller/util/MinioUtil.java
2026-06-04 09:42:34 +08:00

461 lines
17 KiB
Java

package com.aida.seller.util;
import com.aida.seller.common.constants.MinioFileConstants;
import com.aida.seller.common.exception.MinioException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@RequiredArgsConstructor
public class MinioUtil {
@Autowired
private MinioClient minioClient;
@Autowired
private RedisUtil redisUtil;
private static final String REDIS_MINIO_URL_PREFIX = "minio:url:";
private static final long URL_CACHE_EXPIRE_SECONDS = 24 * 60 * 60;
@Value("${minio.default-bucket}")
private String defaultBucketName;
@Value("${minio.endpoint}")
private String endpoint;
private final ObjectMapper objectMapper = new ObjectMapper();
public String uploadImage(MultipartFile file, String bucketName, String userId) {
try {
if (bucketName == null || bucketName.isEmpty()) {
bucketName = defaultBucketName;
}
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
String originalFilename = file.getOriginalFilename();
String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.randomUUID().toString() + fileExtension;
String filePath = (userId != null && !userId.isEmpty()) ? userId + "/" + fileName : fileName;
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(filePath)
.stream(file.getInputStream(), file.getSize(), -1)
.contentType(file.getContentType())
.build());
log.info("文件上传成功,桶名: {}, 文件路径: {}", bucketName, filePath);
return bucketName + "/" + filePath;
} catch (Exception e) {
log.error("文件上传失败: {}", e.getMessage(), e);
throw new MinioException("minio.upload.failed", e);
}
}
public String uploadImage(MultipartFile file, String userId) {
return uploadImage(file, null, userId);
}
public String uploadImage(MultipartFile file) {
return uploadImage(file, null, null);
}
public String getImageUrl(String path, int expires) {
if (!path.contains("/")) {
}
int index = path.indexOf("/");
String bucketName = path.substring(0, index);
String fileName = path.substring(index + 1);
return getImageUrl(bucketName, fileName, expires);
}
public String getImageUrl(String bucketName, String filePath, int expires) {
String cacheKey = REDIS_MINIO_URL_PREFIX + bucketName + "/" + filePath;
Object cachedUrl = redisUtil.get(cacheKey);
if (cachedUrl != null) {
return cachedUrl.toString();
}
try {
String url = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(filePath)
.expiry(expires)
.build());
redisUtil.setWithExpire(cacheKey, url, URL_CACHE_EXPIRE_SECONDS);
return url;
} catch (Exception e) {
log.error("获取临时访问地址失败: {}", e.getMessage(), e);
throw new MinioException("minio.get.presigned.url.failed", e);
}
}
public String getImageUrl(String bucketName, String filePath) {
return getImageUrl(bucketName, filePath, 7 * 24 * 60 * 60);
}
public void deleteImage(String objectPath) {
try {
int index = objectPath.indexOf("/");
if (index == -1) {
throw new MinioException("minio.invalid.object.path");
}
String bucketName = objectPath.substring(0, index);
String filePath = objectPath.substring(index + 1);
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(filePath)
.build());
log.info("文件删除成功,桶名: {}, 文件路径: {}", bucketName, filePath);
} catch (Exception e) {
log.error("文件删除失败: {}", e.getMessage(), e);
throw new MinioException("minio.delete.failed", e);
}
}
public void deleteImages(List<String> objectPaths) {
if (objectPaths == null || objectPaths.isEmpty()) {
return;
}
try {
String firstPath = objectPaths.get(0);
int index = firstPath.indexOf("/");
if (index == -1) {
throw new MinioException("minio.invalid.object.path");
}
String bucketName = firstPath.substring(0, index);
List<DeleteObject> deleteObjects = new ArrayList<>();
for (String objectPath : objectPaths) {
int sepIndex = objectPath.indexOf("/");
if (sepIndex != -1) {
String filePath = objectPath.substring(sepIndex + 1);
deleteObjects.add(new DeleteObject(filePath));
}
}
Iterable<Result<DeleteError>> results = minioClient.removeObjects(RemoveObjectsArgs.builder()
.bucket(bucketName)
.objects(deleteObjects)
.build());
for (Result<DeleteError> result : results) {
DeleteError error = result.get();
log.error("文件删除失败,桶名: {}, 文件路径: {}, 错误信息: {}", bucketName, error.objectName(), error.message());
}
log.info("批量删除文件成功,桶名: {}, 文件数量: {}", bucketName, objectPaths.size());
} catch (Exception e) {
log.error("批量删除文件失败: {}", e.getMessage(), e);
throw new MinioException("minio.batch.delete.failed", e);
}
}
public String uploadBase64Image(String base64Image, String bucketName, String filePath) {
try {
String[] base64Parts = base64Image.split(",");
String imageData = base64Parts[1];
String contentType = base64Parts[0].split(":")[1].split(";")[0];
byte[] imageBytes = java.util.Base64.getDecoder().decode(imageData);
if (bucketName == null || bucketName.isEmpty()) {
bucketName = defaultBucketName;
}
if (filePath == null || filePath.isEmpty()) {
String fileExtension = contentType.split("/")[1];
filePath = UUID.randomUUID().toString() + "." + fileExtension;
}
return uploadImage(imageBytes, bucketName, filePath, contentType);
} catch (Exception e) {
log.error("base64图片上传失败: {}", e.getMessage(), e);
throw new MinioException("minio.base64.upload.failed", e);
}
}
public String uploadBase64Image(String base64Image, String bucketName) {
return uploadBase64Image(base64Image, bucketName, null);
}
public String uploadBase64Image(String base64Image) {
return uploadBase64Image(base64Image, null, null);
}
private String uploadImage(byte[] bytes, String bucketName, String filePath, String contentType) {
try {
if (!minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
}
minioClient.putObject(PutObjectArgs.builder()
.bucket(bucketName)
.object(filePath)
.stream(new ByteArrayInputStream(bytes), bytes.length, -1)
.contentType(contentType)
.build());
return bucketName + "/" + filePath;
} catch (Exception e) {
log.error("文件上传失败: {}", e.getMessage(), e);
throw new MinioException("minio.upload.failed", e);
}
}
public String uploadBytes(byte[] bytes, MinioFileConstants.FileType fileType, String contentType, String bucketName) {
String objectName = MinioFileConstants.generateObjectNameByType(fileType);
return uploadBytes(bytes, objectName, contentType, bucketName);
}
public String uploadBytes(byte[] bytes, String objectName, String contentType, String bucketName) {
if (bytes == null || bytes.length == 0) {
throw new MinioException("minio.file.content.empty");
}
try {
minioClient.putObject(
PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(new ByteArrayInputStream(bytes), bytes.length, -1)
.contentType(contentType)
.build()
);
log.info("字节数组上传成功: {}/{}", bucketName, objectName);
return bucketName + "/" + objectName;
} catch (Exception e) {
log.error("字节数组上传失败: {}", e.getMessage(), e);
throw new MinioException("minio.bytes.upload.failed", e);
}
}
public InputStream downloadFile(String logicalPath) {
int index = logicalPath.indexOf("/");
if (index <= 0) {
throw new MinioException("minio.logical.path.format.error");
}
String bucketName = logicalPath.substring(0, index);
String objectName = logicalPath.substring(index + 1);
try {
boolean bucketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
if (!bucketExists) {
throw new MinioException("minio.bucket.not.exists");
}
} catch (Exception e) {
log.error("验证桶存在性失败: {}", e.getMessage(), e);
throw new MinioException("minio.verify.bucket.failed");
}
try {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
log.error("文件下载失败: {}", e.getMessage(), e);
throw new MinioException("minio.download.failed", e);
}
}
public String getLogicalPathFromPresignedUrl(String presignedUrl) {
try {
URL url = new URL(presignedUrl);
String path = url.getPath();
if (path.startsWith("/")) {
path = path.substring(1);
}
int firstSlashIndex = path.indexOf("/");
if (firstSlashIndex <= 0) {
throw new MinioException("minio.presigned.url.format.invalid");
}
String bucketName = path.substring(0, firstSlashIndex);
String objectName = path.substring(firstSlashIndex + 1);
return bucketName + "/" + objectName;
} catch (Exception e) {
log.error("预签名URL解析失败: {}", e.getMessage(), e);
throw new MinioException("minio.presigned.url.parse.failed", e);
}
}
public boolean isPresignedUrl(String str) {
if (str == null || str.isEmpty()) {
return false;
}
try {
URL url = new URL(str);
String host = url.getHost();
String endpointHost = endpoint;
if (endpointHost.startsWith("http://")) {
endpointHost = endpointHost.substring(7);
} else if (endpointHost.startsWith("https://")) {
endpointHost = endpointHost.substring(8);
}
if (endpointHost.contains(":")) {
endpointHost = endpointHost.substring(0, endpointHost.indexOf(":"));
}
return host.equals(endpointHost);
} catch (Exception e) {
return false;
}
}
public boolean isMinioLogicalPath(String str) {
if (str == null || str.isEmpty()) {
return false;
}
if (!(str instanceof String)) {
return false;
}
String trimStr = str.trim();
if (trimStr.startsWith("http://") || trimStr.startsWith("https://")) {
return false;
}
if (!trimStr.contains("/")) {
return false;
}
if (trimStr.contains(" ") || trimStr.contains("\n") || trimStr.contains("\t")) {
return false;
}
return true;
}
public boolean isMinioResource(String str) {
return isPresignedUrl(str) || isMinioLogicalPath(str);
}
public String processJsonPresignedUrls(String jsonString, int expires) {
if (jsonString == null || jsonString.isEmpty()) {
return jsonString;
}
try {
JsonNode rootNode = objectMapper.readTree(jsonString);
JsonNode processedNode = processNode(rootNode, expires);
return objectMapper.writeValueAsString(processedNode);
} catch (Exception e) {
log.error("处理JSON中的预签名URL失败: {}", e.getMessage(), e);
return jsonString;
}
}
private JsonNode processNode(JsonNode node, int expires) {
if (node.isObject()) {
ObjectNode objectNode = (ObjectNode) node;
Iterator<Map.Entry<String, JsonNode>> fields = objectNode.fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> field = fields.next();
JsonNode value = field.getValue();
if (value.isTextual()) {
String text = value.asText();
if (isMinioResource(text)) {
String newUrl = processMinioResource(text, expires);
objectNode.put(field.getKey(), newUrl);
}
} else if (!value.isNull()) {
JsonNode processedValue = processNode(value, expires);
objectNode.set(field.getKey(), processedValue);
}
}
return objectNode;
} else if (node.isArray()) {
ArrayNode arrayNode = (ArrayNode) node;
for (int i = 0; i < arrayNode.size(); i++) {
JsonNode element = arrayNode.get(i);
if (element.isTextual()) {
String text = element.asText();
if (isMinioResource(text)) {
String newUrl = processMinioResource(text, expires);
arrayNode.set(i, newUrl);
}
} else if (!element.isNull()) {
JsonNode processedElement = processNode(element, expires);
arrayNode.set(i, processedElement);
}
}
return arrayNode;
} else {
return node;
}
}
public String processMinioResource(String resource, int expires) {
try {
String logicalPath;
if (isPresignedUrl(resource)) {
logicalPath = getLogicalPathFromPresignedUrl(resource);
} else if (isMinioLogicalPath(resource)) {
logicalPath = resource.trim();
} else {
log.warn("未识别的MinIO资源格式: {}", resource);
return resource;
}
return getImageUrl(logicalPath, expires);
} catch (Exception e) {
log.error("处理MinIO资源失败: {}, error: {}", resource, e.getMessage(), e);
return resource;
}
}
public String convertToLogicalPath(String url) {
if (url == null || url.isEmpty()) {
throw new MinioException("minio.url.cannot.be.empty");
}
if (isMinioLogicalPath(url)) {
return url.trim();
} else if (isPresignedUrl(url)) {
return getLogicalPathFromPresignedUrl(url);
} else {
throw new MinioException("minio.resource.format.unrecognized");
}
}
public void deleteImagesByUrls(Collection<String> urls) {
if (urls == null || urls.isEmpty()) {
return;
}
for (String url : urls) {
String logicalPath = convertToLogicalPath(url);
deleteImage(logicalPath);
}
}
}