Files
aida-seller/src/main/java/com/aida/seller/util/MinioUtil.java
litianxiang 85c1779deb 1
2026-04-21 10:25:39 +08:00

461 lines
17 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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("文件上传失败", 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("获取临时访问地址失败", 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("无效的对象路径,格式应为 bucketName/filePath");
}
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("文件删除失败", 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("无效的对象路径,格式应为 bucketName/filePath");
}
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("批量删除文件失败", 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("base64图片上传失败", 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("文件上传失败", 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("文件内容不能为空");
}
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("字节数组上传失败", e);
}
}
public InputStream downloadFile(String logicalPath) {
int index = logicalPath.indexOf("/");
if (index <= 0) {
throw new MinioException("逻辑路径格式错误,应包含桶名: " + logicalPath);
}
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("桶不存在: " + bucketName);
}
} catch (Exception e) {
log.error("验证桶存在性失败: {}", e.getMessage(), e);
throw new MinioException("验证桶存在性失败bucketName:{}", bucketName);
}
try {
return minioClient.getObject(
GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
} catch (Exception e) {
log.error("文件下载失败: {}", e.getMessage(), e);
throw new MinioException("文件下载失败", 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("预签名URL路径格式无效应包含桶名和对象名: " + presignedUrl);
}
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("预签名URL解析失败", 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("URL不能为空");
}
if (isMinioLogicalPath(url)) {
return url.trim();
} else if (isPresignedUrl(url)) {
return getLogicalPathFromPresignedUrl(url);
} else {
throw new MinioException("无法识别的MinIO资源格式: " + url + "请提供有效的预签名URL或逻辑路径");
}
}
public void deleteImagesByUrls(Collection<String> urls) {
if (urls == null || urls.isEmpty()) {
return;
}
for (String url : urls) {
String logicalPath = convertToLogicalPath(url);
deleteImage(logicalPath);
}
}
}