From ca36af08682458893ed61a9495b583592ac2dd4e Mon Sep 17 00:00:00 2001 From: Dftre <3066417822@qq.com> Date: Sun, 23 Feb 2025 14:52:41 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=B8=AD=E7=9A=84=E6=8B=BC=E5=86=99=E9=94=99=E8=AF=AF?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0MinIO=E5=92=8C=E9=98=BF=E9=87=8C?= =?UTF-8?q?=E4=BA=91OSS=E7=9B=B8=E5=85=B3=E5=BC=82=E5=B8=B8=E7=B1=BB?= =?UTF-8?q?=EF=BC=8C=E6=B7=BB=E5=8A=A0=E7=94=9F=E6=88=90=E9=A2=84=E7=AD=BE?= =?UTF-8?q?=E5=90=8DURL=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/resources/application-middleware.yml | 4 +- .../common/service/file/DiskFileService.java | 85 ++++++- .../common/service/file/FileService.java | 10 + .../minio/config/MinioClientProperties.java | 22 +- .../middleware/minio/config/MinioConfig.java | 38 +++- .../minio/controller/MinioController.java | 2 +- .../middleware/minio/domain/MinioBucket.java | 2 +- .../exception/MinioClientErrorException.java | 13 +- .../MinioClientNotFundException.java | 13 +- .../minio/service/MinioFileService.java | 43 +++- .../alibaba/oss/config/AliOssConfig.java | 207 +++++++++++------- .../alibaba/oss/config/AliOssProperties.java | 4 +- .../AliOssClientNotFundException.java | 25 ++- .../oss/service/AliOssFileService.java | 40 ++++ 14 files changed, 402 insertions(+), 106 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application-middleware.yml b/ruoyi-admin/src/main/resources/application-middleware.yml index 875acbc..b118f7c 100644 --- a/ruoyi-admin/src/main/resources/application-middleware.yml +++ b/ruoyi-admin/src/main/resources/application-middleware.yml @@ -42,9 +42,9 @@ minio: url: http://localhost:9000 accessKey: secretKey: - buketName: ruoyi + bucketName: ruoyi # SLAVE: # url: http://127.0.0.1:9000 # accessKey: # secretKey: - # buketName: ry + # bucketName: ry diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/file/DiskFileService.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/file/DiskFileService.java index df87d21..6c346ea 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/service/file/DiskFileService.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/file/DiskFileService.java @@ -6,6 +6,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; +import java.net.URL; import java.nio.file.Paths; import java.util.Objects; @@ -16,17 +17,22 @@ import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.constant.Constants; import com.ruoyi.common.core.domain.entity.FileEntity; import com.ruoyi.common.exception.file.FileNameLengthLimitExceededException; +import com.ruoyi.common.utils.ServletUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileOperateUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.sign.Md5Utils; +import jakarta.servlet.http.HttpServletRequest; + /** * 磁盘文件操作实现类 */ @Component("file:strategy:disk") public class DiskFileService implements FileService { + private static final long URL_EXPIRATION = 3600 * 1000; // URL有效期1小时 + @Override public String upload(String filePath, MultipartFile file) throws Exception { return upload(RuoYiConfig.getProfile(), filePath, file); @@ -46,15 +52,24 @@ public class DiskFileService implements FileService { @Override public InputStream downLoad(String filePath) throws Exception { - // 本地资源路径 + // 标准化路径 + String normalizedPath = normalizeFilePath(filePath); + + // 获取本地存储根路径 String localPath = RuoYiConfig.getProfile(); - // 数据库资源地址 - String downloadPath = localPath + StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX); - // 下载名称 - File file = new File(downloadPath); + + // 拼接完整路径,确保分隔符正确 + String fullPath = localPath + File.separator + normalizedPath; + + // 创建文件对象并检查 + File file = new File(fullPath); if (!file.exists()) { - throw new FileNotFoundException("未找到文件"); + throw new FileNotFoundException("文件不存在: " + fullPath); } + if (!file.isFile()) { + throw new FileNotFoundException("不是有效的文件: " + fullPath); + } + return new FileInputStream(file); } @@ -88,5 +103,61 @@ public class DiskFileService implements FileService { fileEntity.setFileInputSteam(fileInputStream); fileEntity.setByteCount(file.length()); return fileEntity; - }; + } + + @Override + public URL generatePresignedUrl(String filePath) throws Exception { + try { + // 生成临时访问凭证 + String normalizedPath = normalizeFilePath(filePath); + long expireTime = System.currentTimeMillis() + URL_EXPIRATION; + String toHex = Md5Utils.hash(normalizedPath + expireTime); + + // 构建访问URL + String urlString = getUrl() + + "/common/download/resource?resource=" + + normalizedPath + + "&toHex=" + toHex + + "&expires=" + expireTime; + return new URL(urlString); + } catch (Exception e) { + throw new RuntimeException("生成访问URL失败: " + e.getMessage(), e); + } + } + + /** + * 标准化文件路径 + */ + private String normalizeFilePath(String filePath) { + if (StringUtils.isEmpty(filePath)) { + return ""; + } + // 统一使用正斜杠并去除前缀 + String normalizedPath = filePath.replace('\\', '/') + .replace("ruoyi/uploadPath/", "") + .replace("/profile/", ""); + // 去除开头和结尾的斜杠 + normalizedPath = StringUtils.strip(normalizedPath, "/"); + // 处理文件存储重复的斜杠 + normalizedPath = normalizedPath.replaceAll("/+", "/"); + return normalizedPath; + } + + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getSession().getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } } diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/service/file/FileService.java b/ruoyi-common/src/main/java/com/ruoyi/common/service/file/FileService.java index d801b22..a08c218 100644 --- a/ruoyi-common/src/main/java/com/ruoyi/common/service/file/FileService.java +++ b/ruoyi-common/src/main/java/com/ruoyi/common/service/file/FileService.java @@ -2,6 +2,7 @@ package com.ruoyi.common.service.file; import java.io.File; import java.io.InputStream; +import java.net.URL; import org.springframework.web.multipart.MultipartFile; @@ -90,4 +91,13 @@ public interface FileService { * @throws Exception */ public FileEntity getFile(String filePath) throws Exception; + + /** + * 生成预签名URL + * + * @param filePath 文件路径 + * @return 预签名URL + * @throws Exception 如果生成过程中出现错误 + */ + public URL generatePresignedUrl(String filePath) throws Exception; } diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioClientProperties.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioClientProperties.java index 56fb865..0535962 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioClientProperties.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioClientProperties.java @@ -6,7 +6,7 @@ public class MinioClientProperties { private String url; private String accessKey; private String secretKey; - private String buketName; + private String bucketName; private MinioClient client; @@ -34,14 +34,6 @@ public class MinioClientProperties { this.secretKey = secretKey; } - public String getBuketName() { - return buketName; - } - - public void setBuketName(String buketName) { - this.buketName = buketName; - } - public MinioClient getClient() { return client; } @@ -50,13 +42,21 @@ public class MinioClientProperties { this.client = client; } - public MinioClientProperties(String url, String accessKey, String secretKey, String buketName) { + public MinioClientProperties(String url, String accessKey, String secretKey, String bucketName) { this.url = url; this.accessKey = accessKey; this.secretKey = secretKey; - this.buketName = buketName; + this.bucketName = bucketName; } public MinioClientProperties() { } + + public String getBucketName() { + return bucketName; + } + + public void setBucketName(String bucketName) { + this.bucketName = bucketName; + } } diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioConfig.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioConfig.java index 1e2df88..1daeb65 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioConfig.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/config/MinioConfig.java @@ -15,6 +15,8 @@ import com.ruoyi.common.config.RuoYiConfig; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.middleware.minio.domain.MinioBucket; +import com.ruoyi.middleware.minio.exception.MinioClientErrorException; +import com.ruoyi.middleware.minio.exception.MinioClientNotFundException; import io.minio.BucketExistsArgs; import io.minio.MinioClient; @@ -34,7 +36,17 @@ public class MinioConfig implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { - client.forEach((name, props) -> targetMinioBucket.put(name, createMinioClient(name, props))); + if (client == null || client.isEmpty()) { + throw new RuntimeException("Client properties cannot be null or empty"); + } + client.forEach((name, props) -> { + try { + targetMinioBucket.put(name, createMinioClient(name, props)); + } catch (Exception e) { + logger.error("Failed to create MinIO client for {}: {}", name, e.getMessage(), e); + } + }); + if (targetMinioBucket.get(primary) == null) { throw new RuntimeException("Primary client " + primary + " does not exist"); } @@ -71,12 +83,34 @@ public class MinioConfig implements InitializingBean { .credentials(props.getAccessKey(), props.getSecretKey()) .build(); } - MinioBucket minioBucket = new MinioBucket(client, props.getBuketName()); + MinioBucket minioBucket = new MinioBucket(client, props.getBucketName()); validateMinioBucket(minioBucket); logger.info("数据桶:{} - 链接成功", name); return minioBucket; } + /** + * 根据主配置信息创建并返回MinIO客户端实例。 + * + * @return MinioClient 实例 + * @throws MinioClientNotFundException 如果找不到对应的配置时抛出 + * @throws MinioClientErrorException 如果在创建过程中发生错误时抛出 + */ + public MinioClient getPrimaryMinioClient() throws MinioClientNotFundException, MinioClientErrorException { + MinioClientProperties primaryClientProps = this.getClient().get(this.getPrimary()); + if (primaryClientProps == null) { + throw new MinioClientNotFundException("未找到该Minio对象存储服务!"); + } + try { + return MinioClient.builder() + .endpoint(primaryClientProps.getUrl()) + .credentials(primaryClientProps.getAccessKey(), primaryClientProps.getSecretKey()) + .build(); + } catch (Exception e) { + throw new MinioClientErrorException("创建MinIO客户端失败: " + e.getMessage(), e); + } + } + public int getMaxSize() { return maxSize; } diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/controller/MinioController.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/controller/MinioController.java index 0df806a..07aa80b 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/controller/MinioController.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/controller/MinioController.java @@ -37,5 +37,5 @@ public class MinioController { @PathVariable("client") String client, @RequestBody MultipartFile file) throws Exception { return MinioUtil.uploadFile(client, file); - } + } } diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/domain/MinioBucket.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/domain/MinioBucket.java index 58882dc..9b28b56 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/domain/MinioBucket.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/domain/MinioBucket.java @@ -89,7 +89,7 @@ public class MinioBucket { MinioFileVO minioFileVO = new MinioFileVO(); minioFileVO.setFileInputSteam(inputStream); - minioFileVO.setByteCount(Convert.toLong(inputStream.headers().get("Content-Length"),null)); + minioFileVO.setByteCount(Convert.toLong(inputStream.headers().get("Content-Length"), null)); minioFileVO.setFilePath(filePath); minioFileVO.setObject(inputStream.object()); minioFileVO.setRegion(inputStream.region()); diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientErrorException.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientErrorException.java index 35dfde3..ce1f7a4 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientErrorException.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientErrorException.java @@ -1,7 +1,16 @@ package com.ruoyi.middleware.minio.exception; -public class MinioClientErrorException extends RuntimeException{ - public MinioClientErrorException(String msg){ +/** + * 当与MinIO客户端交互过程中发生错误时抛出此异常。 + */ +public class MinioClientErrorException extends RuntimeException { + + public MinioClientErrorException(String msg) { super(msg); } + + public MinioClientErrorException(String message, Throwable cause) { + super(message, cause); + } } + diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientNotFundException.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientNotFundException.java index 02b4d27..e3cccf5 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientNotFundException.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/exception/MinioClientNotFundException.java @@ -1,4 +1,15 @@ package com.ruoyi.middleware.minio.exception; -public class MinioClientNotFundException extends RuntimeException{ +/** + * 当尝试获取MinIO客户端实例但未能找到相应的配置或客户端实例时抛出此异常。 + */ +public class MinioClientNotFundException extends RuntimeException { + + public MinioClientNotFundException(String msg) { + super(msg); + } + + public MinioClientNotFundException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/service/MinioFileService.java b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/service/MinioFileService.java index ab4c46a..0904ea1 100644 --- a/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/service/MinioFileService.java +++ b/ruoyi-middleware/ruoyi-middleware-minio/src/main/java/com/ruoyi/middleware/minio/service/MinioFileService.java @@ -1,7 +1,11 @@ package com.ruoyi.middleware.minio.service; import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; @@ -13,14 +17,21 @@ import com.ruoyi.common.utils.file.FileOperateUtils; import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.middleware.minio.config.MinioConfig; import com.ruoyi.middleware.minio.domain.MinioFileVO; +import com.ruoyi.middleware.minio.exception.MinioClientErrorException; +import com.ruoyi.middleware.minio.exception.MinioClientNotFundException; import com.ruoyi.middleware.minio.utils.MinioUtil; +import io.minio.GetPresignedObjectUrlArgs; +import io.minio.MinioClient; +import io.minio.http.Method; + /** * Minio文件操作实现类 */ @Component("file:strategy:minio") @ConditionalOnProperty(prefix = "minio", name = { "enable" }, havingValue = "true", matchIfMissing = false) public class MinioFileService implements FileService { + private static final Logger logger = LoggerFactory.getLogger(MinioFileService.class); @Autowired private MinioConfig minioConfig; @@ -57,5 +68,35 @@ public class MinioFileService implements FileService { @Override public FileEntity getFile(String filePath) throws Exception { return MinioUtil.getFile(minioConfig.getPrimary(), filePath); - }; + } + + /** + * 生成预签名Minio URL. + * + * @param filePath 文件路径 + * @return 预签名URL + * @throws MinioClientNotFundException 如果找不到对应的配置时抛出 + * @throws MinioClientErrorException 如果在创建或获取预签名URL过程中发生错误时抛出 + */ + @Override + public URL generatePresignedUrl(String filePath) throws MinioClientNotFundException, MinioClientErrorException { + MinioClient minioClient = null; // 创建并且实例化 + try { + minioClient = minioConfig.getPrimaryMinioClient(); // 调用封装好的MinioConfig中的方法获取Minio客户端 + String bucketName = minioConfig.getClient().get(minioConfig.getPrimary()).getBucketName(); + GetPresignedObjectUrlArgs request = GetPresignedObjectUrlArgs.builder() + .method(Method.GET) + .bucket(bucketName) + .object(filePath) + .expiry(1, TimeUnit.HOURS) // 设置过期时间为1小时 + .build(); + // 生成预签名URL + String presignedUrl = minioClient.getPresignedObjectUrl(request); + URL url = new URL(presignedUrl); // 将字符串形式的预签名URL转换为URL对象并返回 + return url; + } catch (Exception e) { + logger.error("生成Minio预签名URL失败: {}", e.getMessage(), e); // 添加日志记录 + throw new MinioClientErrorException("生成Minio预签名URL失败: " + e.getMessage(), e); + } + } } diff --git a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssConfig.java b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssConfig.java index c814cb7..813b5dd 100644 --- a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssConfig.java +++ b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssConfig.java @@ -15,18 +15,146 @@ import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.OSSException; import com.ruoyi.alibaba.oss.domain.AliOssBucket; +import com.ruoyi.alibaba.oss.exception.AliOssClientErrorException; +import com.ruoyi.alibaba.oss.exception.AliOssClientNotFundException; +/** + * 配置类用于管理阿里云OSS客户端实例及其相关属性。 + */ @Configuration("AliOssConfiguration") @ConditionalOnProperty(prefix = "oss", name = "enable", havingValue = "true", matchIfMissing = false) @ConfigurationProperties(prefix = "oss") public class AliOssConfig implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(AliOssConfig.class); - public static int maxSize; + + public static int maxSize; // 最大文件大小或其他配置项 + private String prefix = "/oss"; // 根据需要调整前缀 - private Map client = new HashMap<>(); - private String primary; - private Map targetAliOssBucket = new HashMap<>(); - private AliOssBucket masterBucket; + + private Map client = new HashMap<>(); // 存储所有OSS客户端配置信息 + + private String primary; // 主要使用的OSS客户端名称 + + private Map targetAliOssBucket = new HashMap<>(); // 存储已创建的OSS客户端与桶名映射 + + private AliOssBucket masterBucket; // 主要使用的OSS客户端对应的桶对象 + + /** + * 初始化后检查客户端配置是否正确,并创建相应的OSS客户端实例。 + */ + @Override + public void afterPropertiesSet() throws Exception { + if (client == null || client.isEmpty()) { + throw new RuntimeException("Client properties cannot be null or empty"); + } + + client.forEach((name, props) -> { + try { + AliOssBucket aliOssBucket = createOssClient(name, props); + targetAliOssBucket.put(name, aliOssBucket); + } catch (Exception e) { + logger.error("Failed to create OSS client for {}: {}", name, e.getMessage(), e); + } + }); + + if (targetAliOssBucket.get(primary) == null) { + throw new RuntimeException("Primary client " + primary + " does not exist"); + } + masterBucket = targetAliOssBucket.get(primary); + } + + /** + * 创建并返回一个指定名称的OSS客户端实例。 + * + * @param name 客户端名称 + * @param props 客户端配置属性 + * @return 包含OSS客户端和桶名的对象 + */ + private AliOssBucket createOssClient(String name, AliOssProperties props) { + if (props == null || props.getEndpoint() == null || props.getAccessKeyId() == null || + props.getAccessKeySecret() == null || props.getBucketName() == null) { + throw new IllegalArgumentException("AliOssProperties or its required fields cannot be null"); + } + + OSS client = new OSSClientBuilder().build(props.getEndpoint(), props.getAccessKeyId(), + props.getAccessKeySecret()); + AliOssBucket ossBucket = new AliOssBucket(client, props.getBucketName()); + validateOssBucket(ossBucket); + logger.info("数据桶:{} - 链接成功", name); + return ossBucket; + } + + /** + * 验证给定的OSS桶是否存在。 + * + * @param aliOssBucket 包含OSS客户端和桶名的对象 + */ + private static void validateOssBucket(AliOssBucket aliOssBucket) { + OSS ossClient = aliOssBucket.getOssClient(); + String bucketName = aliOssBucket.getBucketName(); + try { + if (!ossClient.doesBucketExist(bucketName)) { + throw new RuntimeException("Bucket " + bucketName + " does not exist"); + } + } catch (OSSException oe) { + logger.error("OSSException: " + oe.getMessage(), oe); + throw new RuntimeException("OSS error: " + oe.getMessage()); + } catch (ClientException ce) { + logger.error("ClientException: " + ce.getMessage(), ce); + throw new RuntimeException("Client error: " + ce.getMessage()); + } catch (Exception e) { + logger.error("Exception: " + e.getMessage(), e); + throw new RuntimeException("Error validating OSS bucket: " + e.getMessage()); + } + } + + /** + * 获取主桶的名称。 + * + * @return 主桶的名称,如果未设置则返回null + */ + public String getMasterBucketName() { + if (masterBucket != null) { + return masterBucket.getBucketName(); + } + return null; // 或者抛出异常 + } + + /** + * 根据客户端名称获取对应的桶名。 + * + * @param client 客户端名称 + * @return 对应的桶名,如果未找到则返回null + */ + public String getBucketName(String client) { + if (client != null && targetAliOssBucket.containsKey(client)) { + return targetAliOssBucket.get(client).getBucketName(); + } + return null; // 或者抛出异常 + } + + /** + * 根据主配置信息创建并返回OSS客户端实例。 + * + * @return OSS客户端实例 + * @throws AliOssClientNotFundException 如果找不到对应配置时抛出 + * @throws AliOssClientErrorException 如果在创建过程中发生错误时抛出 + */ + public OSS getPrimaryOssClient() throws AliOssClientNotFundException, AliOssClientErrorException { + AliOssProperties primaryClientProps = this.getClient().get(this.getPrimary()); + if (primaryClientProps == null) { + throw new AliOssClientNotFundException("未找到该Oss对象存储服务!"); + } + try { + return new OSSClientBuilder().build( + primaryClientProps.getEndpoint(), + primaryClientProps.getAccessKeyId(), + primaryClientProps.getAccessKeySecret()); + } catch (Exception e) { + // 在尝试构建OSS客户端时发生任何异常,抛出 AliOssClientErrorException + throw new AliOssClientErrorException("创建OSS客户端失败", e); + } + } public int getMaxSize() { return maxSize; @@ -63,73 +191,4 @@ public class AliOssConfig implements InitializingBean { public void setPrefix(String prefix) { this.prefix = prefix; } - - @Override - public void afterPropertiesSet() throws Exception { - if (client == null || client.isEmpty()) { - throw new RuntimeException("Client properties cannot be null or empty"); - } - - client.forEach((name, props) -> { - try { - AliOssBucket aliOssBucket = createOssClient(name, props); - targetAliOssBucket.put(name, aliOssBucket); - } catch (Exception e) { - logger.error("Failed to create OSS client for {}: {}", name, e.getMessage(), e); - } - }); - - if (targetAliOssBucket.get(primary) == null) { - throw new RuntimeException("Primary client " + primary + " does not exist"); - } - masterBucket = targetAliOssBucket.get(primary); - } - - private AliOssBucket createOssClient(String name, AliOssProperties props) { - if (props == null || props.getEndpoint() == null || props.getAccessKeyId() == null || - props.getAccessKeySecret() == null || props.getBucketName() == null) { - throw new IllegalArgumentException("AliOssProperties or its required fields cannot be null"); - } - - OSS client = new OSSClientBuilder().build(props.getEndpoint(), props.getAccessKeyId(), props.getAccessKeySecret()); - AliOssBucket ossBucket = new AliOssBucket(client, props.getBucketName()); - validateOssBucket(ossBucket); - logger.info("数据桶:{} - 链接成功", name); - return ossBucket; - } - - private static void validateOssBucket(AliOssBucket aliOssBucket) { - OSS ossClient = aliOssBucket.getOssClient(); - String bucketName = aliOssBucket.getBucketName(); - try { - if (!ossClient.doesBucketExist(bucketName)) { - throw new RuntimeException("Bucket " + bucketName + " does not exist"); - } - } catch (OSSException oe) { - logger.error("OSSException: " + oe.getMessage(), oe); - throw new RuntimeException("OSS error: " + oe.getMessage()); - } catch (ClientException ce) { - logger.error("ClientException: " + ce.getMessage(), ce); - throw new RuntimeException("Client error: " + ce.getMessage()); - } catch (Exception e) { - logger.error("Exception: " + e.getMessage(), e); - throw new RuntimeException("Error validating OSS bucket: " + e.getMessage()); - } - } - - - public String getMasterBucketName() { - if (masterBucket != null) { - return masterBucket.getBucketName(); - } - return null; // 或者抛出异常,根据业务需求决定 - } - - public String getBucketName(String client) { - if (client != null && targetAliOssBucket.containsKey(client)) { - return targetAliOssBucket.get(client).getBucketName(); - } - return null; // 或者抛出异常,根据业务需求决定 - } - } diff --git a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssProperties.java b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssProperties.java index f20341a..3df750d 100644 --- a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssProperties.java +++ b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/config/AliOssProperties.java @@ -14,9 +14,7 @@ public class AliOssProperties { private OSSClient ossClient; - public AliOssProperties(){ - - } + public AliOssProperties(){ } public AliOssProperties(String endpoint, String accessKeyId, String accessKeySecret, String bucketName) { this.endpoint = endpoint; diff --git a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/exception/AliOssClientNotFundException.java b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/exception/AliOssClientNotFundException.java index 5596ac5..1cadf9a 100644 --- a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/exception/AliOssClientNotFundException.java +++ b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/exception/AliOssClientNotFundException.java @@ -1,4 +1,27 @@ package com.ruoyi.alibaba.oss.exception; -public class AliOssClientNotFundException extends RuntimeException{ +/** + * 当尝试获取阿里云OSS客户端实例但未能找到相应的配置或客户端实例时抛出此异常。 + * 此异常表明系统中存在配置问题或者客户端初始化失败的问题。 + */ +public class AliOssClientNotFundException extends RuntimeException { + + /** + * 使用指定的详细信息创建一个新的 {@code AliOssClientNotFundException} 实例。 + * + * @param msg 描述异常原因的信息。 + */ + public AliOssClientNotFundException(String msg) { + super(msg); + } + + /** + * 使用指定的详细信息和导致此异常的原因创建一个新的 {@code AliOssClientNotFundException} 实例。 + * + * @param message 描述异常原因的信息。 + * @param cause 导致此异常的根本原因。 + */ + public AliOssClientNotFundException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/service/AliOssFileService.java b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/service/AliOssFileService.java index 387be97..1e4e4ed 100644 --- a/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/service/AliOssFileService.java +++ b/ruoyi-plugins/ruoyi-alibaba-oss/src/main/java/com/ruoyi/alibaba/oss/service/AliOssFileService.java @@ -1,14 +1,22 @@ package com.ruoyi.alibaba.oss.service; import java.io.InputStream; +import java.net.URL; +import java.util.Date; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; +import com.aliyun.oss.OSS; +import com.aliyun.oss.model.GeneratePresignedUrlRequest; import com.ruoyi.alibaba.oss.config.AliOssConfig; import com.ruoyi.alibaba.oss.domain.AliOssFileVO; +import com.ruoyi.alibaba.oss.exception.AliOssClientErrorException; +import com.ruoyi.alibaba.oss.exception.AliOssClientNotFundException; import com.ruoyi.alibaba.oss.utils.AliOssUtil; import com.ruoyi.common.core.domain.entity.FileEntity; import com.ruoyi.common.service.file.FileService; @@ -21,6 +29,9 @@ import com.ruoyi.common.utils.file.FileUtils; @Component("file:strategy:oss") @ConditionalOnProperty(prefix = "oss", name = { "enable" }, havingValue = "true", matchIfMissing = false) public class AliOssFileService implements FileService { + + private static final Logger logger = LoggerFactory.getLogger(AliOssConfig.class); + @Autowired private AliOssConfig aliOssConfig; @@ -57,4 +68,33 @@ public class AliOssFileService implements FileService { public FileEntity getFile(String filePath) throws Exception { return AliOssUtil.getFile(filePath); }; + + /** + * 生成预签名Oss URL. + * + * @param filePath 文件路径 + * @return 预签名URL + * @throws AliOssClientNotFundException 如果找不到对应的配置时抛出 + * @throws AliOssClientErrorException 如果在创建或获取预签名URL过程中发生错误时抛出 + */ + @Override + public URL generatePresignedUrl(String filePath) throws AliOssClientNotFundException, AliOssClientErrorException { + OSS ossClient = null; // 创建并且实例化 + try { + ossClient = aliOssConfig.getPrimaryOssClient(); // 调用封装好的AliOssConfig中的方法获取OSS客户端 + String bucketName = aliOssConfig.getClient().get(aliOssConfig.getPrimary()).getBucketName(); + GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(bucketName, filePath); + Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000); // 设置过期时间为1小时 + request.setExpiration(expiration); + // 生成预签名URL + return ossClient.generatePresignedUrl(request); + } catch (Exception e) { + logger.error("生成Oss预签名URL失败: {}", e.getMessage(), e); // 添加日志记录 + throw new AliOssClientErrorException("生成Oss预签名URL失败: " + e.getMessage(), e); + } finally { + if (ossClient != null) { + ossClient.shutdown(); // 手动关闭OSS客户端资源 + } + } + } } \ No newline at end of file