From 5a5499b6151931c5992d11f322da262ba410fa39 Mon Sep 17 00:00:00 2001
From: AprilWind <2100166581@qq.com>
Date: Mon, 18 Aug 2025 16:06:17 +0800
Subject: [PATCH] =?UTF-8?q?add=20=E5=AE=9E=E7=8E=B0=E9=80=9A=E7=94=A8?=
=?UTF-8?q?=E5=AD=98=E5=82=A8=E6=9C=8D=E5=8A=A1=E6=8E=A5=E5=8F=A3=E5=8F=8A?=
=?UTF-8?q?=E5=85=B6=E5=AE=9E=E7=8E=B0=EF=BC=8C=E6=94=AF=E6=8C=81=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E4=B8=8A=E4=BC=A0=E3=80=81=E4=B8=8B=E8=BD=BD=E5=92=8C?=
=?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../core/domain/event/OssUploadEvent.java | 53 ++
.../common/oss/constant/OssConstant.java | 5 -
.../dromara/common/oss/core/OssClient.java | 525 ----------------
.../dromara/common/oss/entity}/SysOssExt.java | 6 +-
.../common/oss/entity/UploadResult.java | 15 +
.../{OssFactory.java => StorageFactory.java} | 20 +-
.../oss/handler/OssUploadEventHandler.java | 38 ++
.../common/oss/service/StorageService.java | 223 +++++++
.../service/impl/OssStorageServiceImpl.java | 570 ++++++++++++++++++
.../common/oss/utils/StorageUtils.java | 128 ++++
...ot.autoconfigure.AutoConfiguration.imports | 1 +
.../service/impl/SysOssServiceImpl.java | 77 ++-
12 files changed, 1079 insertions(+), 582 deletions(-)
create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/OssUploadEvent.java
delete mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
rename {ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain => ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/entity}/SysOssExt.java (85%)
rename ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/factory/{OssFactory.java => StorageFactory.java} (76%)
create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/handler/OssUploadEventHandler.java
create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/service/StorageService.java
create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/service/impl/OssStorageServiceImpl.java
create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/utils/StorageUtils.java
create mode 100644 ruoyi-common/ruoyi-common-oss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/OssUploadEvent.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/OssUploadEvent.java
new file mode 100644
index 000000000..1a95006f9
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/domain/event/OssUploadEvent.java
@@ -0,0 +1,53 @@
+package org.dromara.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * OSS 上传事件
+ *
+ * @author AprilWind
+ */
+@Data
+public class OssUploadEvent implements Serializable {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * 对象存储主键
+ */
+ private Long ossId;
+
+ /**
+ * 文件名
+ */
+ private String fileName;
+
+ /**
+ * 原名
+ */
+ private String originalName;
+
+ /**
+ * 文件后缀名
+ */
+ private String fileSuffix;
+
+ /**
+ * URL地址
+ */
+ private String url;
+
+ /**
+ * 扩展字段
+ */
+ private String ext1;
+
+ /**
+ * 服务商
+ */
+ private String service;
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
index 9d8db9335..74b4fcffc 100644
--- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
+++ b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/constant/OssConstant.java
@@ -17,11 +17,6 @@ public interface OssConstant {
*/
String DEFAULT_CONFIG_KEY = GlobalConstants.GLOBAL_REDIS_KEY + "sys_oss:default_config";
- /**
- * 预览列表资源开关Key
- */
- String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";
-
/**
* 系统数据ids
*/
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
deleted file mode 100644
index b9a90dc75..000000000
--- a/ruoyi-common/ruoyi-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java
+++ /dev/null
@@ -1,525 +0,0 @@
-package org.dromara.common.oss.core;
-
-import cn.hutool.core.io.IoUtil;
-import cn.hutool.core.util.IdUtil;
-import lombok.extern.slf4j.Slf4j;
-import org.dromara.common.core.constant.Constants;
-import org.dromara.common.core.utils.DateUtils;
-import org.dromara.common.core.utils.StringUtils;
-import org.dromara.common.core.utils.file.FileUtils;
-import org.dromara.common.oss.constant.OssConstant;
-import org.dromara.common.oss.entity.UploadResult;
-import org.dromara.common.oss.enums.AccessPolicyType;
-import org.dromara.common.oss.exception.OssException;
-import org.dromara.common.oss.properties.OssProperties;
-import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
-import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
-import software.amazon.awssdk.core.async.*;
-import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
-import software.amazon.awssdk.regions.Region;
-import software.amazon.awssdk.services.s3.S3AsyncClient;
-import software.amazon.awssdk.services.s3.S3Configuration;
-import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.presigner.S3Presigner;
-import software.amazon.awssdk.transfer.s3.S3TransferManager;
-import software.amazon.awssdk.transfer.s3.model.*;
-import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
-
-import java.io.*;
-import java.net.URI;
-import java.net.URL;
-import java.nio.channels.Channels;
-import java.nio.channels.WritableByteChannel;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.time.Duration;
-import java.util.Optional;
-import java.util.function.Consumer;
-
-/**
- * S3 存储协议 所有兼容S3协议的云厂商均支持
- * 阿里云 腾讯云 七牛云 minio
- *
- * @author AprilWind
- */
-@Slf4j
-public class OssClient {
-
- /**
- * 服务商
- */
- private final String configKey;
-
- /**
- * 配置属性
- */
- private final OssProperties properties;
-
- /**
- * Amazon S3 异步客户端
- */
- private final S3AsyncClient client;
-
- /**
- * 用于管理 S3 数据传输的高级工具
- */
- private final S3TransferManager transferManager;
-
- /**
- * AWS S3 预签名 URL 的生成器
- */
- private final S3Presigner presigner;
-
- /**
- * 构造方法
- *
- * @param configKey 配置键
- * @param ossProperties Oss配置属性
- */
- public OssClient(String configKey, OssProperties ossProperties) {
- this.configKey = configKey;
- this.properties = ossProperties;
- try {
- // 创建 AWS 认证信息
- StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
- AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
-
- // MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
- boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
-
- // 创建AWS基于 Netty 的 S3 客户端
- this.client = S3AsyncClient.builder()
- .credentialsProvider(credentialsProvider)
- .endpointOverride(URI.create(getEndpoint()))
- .region(of())
- .forcePathStyle(isStyle)
- .httpClient(NettyNioAsyncHttpClient.builder()
- .connectionTimeout(Duration.ofSeconds(60)).build())
- .build();
-
- //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
- this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
-
- // 创建 S3 配置对象
- S3Configuration config = S3Configuration.builder().chunkedEncodingEnabled(false)
- .pathStyleAccessEnabled(isStyle).build();
-
- // 创建 预签名 URL 的生成器 实例,用于生成 S3 预签名 URL
- this.presigner = S3Presigner.builder()
- .region(of())
- .credentialsProvider(credentialsProvider)
- .endpointOverride(URI.create(getDomain()))
- .serviceConfiguration(config)
- .build();
-
- } catch (Exception e) {
- if (e instanceof OssException) {
- throw e;
- }
- throw new OssException("配置错误! 请检查系统配置:[" + e.getMessage() + "]");
- }
- }
-
- /**
- * 上传文件到 Amazon S3,并返回上传结果
- *
- * @param filePath 本地文件路径
- * @param key 在 Amazon S3 中的对象键
- * @param md5Digest 本地文件的 MD5 哈希值(可选)
- * @param contentType 文件内容类型
- * @return UploadResult 包含上传后的文件信息
- * @throws OssException 如果上传失败,抛出自定义异常
- */
- public UploadResult upload(Path filePath, String key, String md5Digest, String contentType) {
- try {
- // 构建上传请求对象
- FileUpload fileUpload = transferManager.uploadFile(
- x -> x.putObjectRequest(
- y -> y.bucket(properties.getBucketName())
- .key(key)
- .contentMD5(StringUtils.isNotEmpty(md5Digest) ? md5Digest : null)
- .contentType(contentType)
- // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
- // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
- //.acl(getAccessPolicy().getObjectCannedACL())
- .build())
- .addTransferListener(LoggingTransferListener.create())
- .source(filePath).build());
-
- // 等待上传完成并获取上传结果
- CompletedFileUpload uploadResult = fileUpload.completionFuture().join();
- String eTag = uploadResult.response().eTag();
-
- // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
- return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
- } catch (Exception e) {
- // 捕获异常并抛出自定义异常
- throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
- } finally {
- // 无论上传是否成功,最终都会删除临时文件
- FileUtils.del(filePath);
- }
- }
-
- /**
- * 上传 InputStream 到 Amazon S3
- *
- * @param inputStream 要上传的输入流
- * @param key 在 Amazon S3 中的对象键
- * @param length 输入流的长度
- * @param contentType 文件内容类型
- * @return UploadResult 包含上传后的文件信息
- * @throws OssException 如果上传失败,抛出自定义异常
- */
- public UploadResult upload(InputStream inputStream, String key, Long length, String contentType) {
- // 如果输入流不是 ByteArrayInputStream,则将其读取为字节数组再创建 ByteArrayInputStream
- if (!(inputStream instanceof ByteArrayInputStream)) {
- inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream));
- }
- try {
- // 创建异步请求体(length如果为空会报错)
- BlockingInputStreamAsyncRequestBody body = BlockingInputStreamAsyncRequestBody.builder()
- .contentLength(length)
- .subscribeTimeout(Duration.ofSeconds(120))
- .build();
-
- // 使用 transferManager 进行上传
- Upload upload = transferManager.upload(
- x -> x.requestBody(body).addTransferListener(LoggingTransferListener.create())
- .putObjectRequest(
- y -> y.bucket(properties.getBucketName())
- .key(key)
- .contentType(contentType)
- // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同,
- // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置)
- //.acl(getAccessPolicy().getObjectCannedACL())
- .build())
- .build());
-
- // 将输入流写入请求体
- body.writeInputStream(inputStream);
-
- // 等待文件上传操作完成
- CompletedUpload uploadResult = upload.completionFuture().join();
- String eTag = uploadResult.response().eTag();
-
- // 提取上传结果中的 ETag,并构建一个自定义的 UploadResult 对象
- return UploadResult.builder().url(getUrl() + StringUtils.SLASH + key).filename(key).eTag(eTag).build();
- } catch (Exception e) {
- throw new OssException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]");
- }
- }
-
- /**
- * 下载文件从 Amazon S3 到临时目录
- *
- * @param path 文件在 Amazon S3 中的对象键
- * @return 下载后的文件在本地的临时路径
- * @throws OssException 如果下载失败,抛出自定义异常
- */
- public Path fileDownload(String path) {
- // 构建临时文件
- Path tempFilePath = FileUtils.createTempFile().toPath();
- // 使用 S3TransferManager 下载文件
- FileDownload downloadFile = transferManager.downloadFile(
- x -> x.getObjectRequest(
- y -> y.bucket(properties.getBucketName())
- .key(removeBaseUrl(path))
- .build())
- .addTransferListener(LoggingTransferListener.create())
- .destination(tempFilePath)
- .build());
- // 等待文件下载操作完成
- downloadFile.completionFuture().join();
- return tempFilePath;
- }
-
- /**
- * 下载文件从 Amazon S3 到 输出流
- *
- * @param key 文件在 Amazon S3 中的对象键
- * @param out 输出流
- * @param consumer 自定义处理逻辑
- * @throws OssException 如果下载失败,抛出自定义异常
- */
- public void download(String key, OutputStream out, Consumer
+ * 定义了对象存储系统常见的功能,包括:
+ *
+ *
+ *
+ * 用于标识不同的存储服务实例,例如: + * - "local" 表示本地存储 + * - "s3" 表示 Amazon S3 + * - "qiniu" 表示七牛云 + *
+ * + * @return 存储服务配置键 + */ + String getConfigKey(); + + /** + * 获取当前桶权限类型 + * + * @return 当前桶权限类型code + */ + AccessPolicyType getAccessPolicy(); + + /** + * 检查当前存储客户端配置是否与传入配置相同 + *+ * 用于判断是否需要重新创建存储客户端实例。 + *
+ * + * @param properties 待比较的 OSS 配置属性 + * @return 如果配置相同返回 true,否则返回 false + */ + boolean checkPropertiesSame(OssProperties properties); + + // ========== 上传相关 ========== + + /** + * 上传 {@link MultipartFile} 到对象存储 + * + * @param ossId 对象存储主键 + * @param file 要上传的 MultipartFile 对象 + * @param ext1 扩展信息(可选),用于存储额外的业务字段 + * @return 上传结果,包含文件 URL、ossId、文件名等信息 + * @throws IOException 文件读取失败 + * @throws OssException 上传失败时抛出 + */ + UploadResult upload(Long ossId, MultipartFile file, SysOssExt ext1) throws IOException; + + /** + * 上传本地 {@link File} 到对象存储 + * + * @param ossId 对象存储主键 + * @param file 要上传的文件对象 + * @param ext1 扩展信息 + * @return 上传结果 + * @throws OssException 上传失败时抛出 + */ + UploadResult upload(Long ossId, File file, SysOssExt ext1); + + /** + * 上传 {@link File},指定文件后缀 + * + * @param ossId 对象存储主键 + * @param file 本地文件 + * @param fileSuffix 文件后缀(如 ".jpg") + * @param ext1 扩展信息 + * @return 上传结果 + */ + UploadResult uploadSuffix(Long ossId, File file, String fileSuffix, SysOssExt ext1); + + /** + * 上传字节数组 + * + * @param ossId 对象存储主键 + * @param originalName 原始文件名 + * @param data 文件字节数组 + * @param fileSuffix 文件后缀 + * @param contentType 内容类型(MIME) + * @param ext1 扩展信息 + * @return 上传结果 + */ + UploadResult uploadSuffix(Long ossId, String originalName, byte[] data, + String fileSuffix, String contentType, SysOssExt ext1); + + /** + * 上传输入流 + * + * @param ossId 对象存储主键 + * @param originalName 原始文件名 + * @param inputStream 输入流 + * @param fileSuffix 文件后缀 + * @param length 文件长度 + * @param contentType 内容类型 + * @param ext1 扩展信息 + * @return 上传结果 + */ + UploadResult uploadSuffix(Long ossId, String originalName, InputStream inputStream, + String fileSuffix, Long length, String contentType, SysOssExt ext1); + + /** + * 上传输入流(指定对象键) + * + * @param ossId 对象存储主键 + * @param originalName 原始文件名 + * @param fileSuffix 文件后缀 + * @param inputStream 输入流 + * @param key 存储对象键 + * @param length 文件长度 + * @param contentType 内容类型 + * @param ext1 扩展信息 + * @return 上传结果 + */ + UploadResult upload(Long ossId, String originalName, String fileSuffix, + InputStream inputStream, String key, Long length, String contentType, SysOssExt ext1); + + /** + * 上传文件路径(指定对象键) + * + * @param ossId 对象存储主键 + * @param originalName 原始文件名 + * @param fileSuffix 文件后缀 + * @param filePath 本地文件路径 + * @param key 存储对象键 + * @param md5Digest 文件 MD5 校验值(可选) + * @param contentType 内容类型 + * @param ext1 扩展信息 + * @return 上传结果 + */ + UploadResult upload(Long ossId, String originalName, String fileSuffix, + Path filePath, String key, String md5Digest, String contentType, SysOssExt ext1); + + // ========== 下载相关 ========== + + /** + * 下载文件到输出流 + * + * @param key 存储对象键 + * @param out 输出流 + * @param consumer 可选的回调,处理文件大小/进度 + * @throws OssException 下载失败抛出 + */ + void download(String key, OutputStream out, Consumer
+ * 前端可以使用该 URL 直接上传文件到 OSS,无需经过后端中转。
+ *
+ * @param key 文件对象键
+ * @param expiredTime 链接有效期
+ * @param contentType 上传文件的 MIME 类型,例如 "image/png"
+ * @return 预签名上传 URL
+ */
+ @Override
+ public String getTemporaryUploadUrl(String key, Duration expiredTime, String contentType) {
+ URL url = presigner.presignPutObject(
+ x -> x.signatureDuration(expiredTime)
+ .putObjectRequest(y -> y.bucket(properties.getBucketName())
+ .key(key)
+ .contentType(contentType)
+ .build())
+ .build())
+ .url();
+ return url.toString();
+ }
+
+ /**
+ * 下载文件从 Amazon S3 到 输出流
+ *
+ * @param key 文件在 Amazon S3 中的对象键
+ * @param contentLengthConsumer 文件大小消费者函数
+ * @return 写出订阅器
+ * @throws OssException 如果下载失败,抛出自定义异常
+ */
+ private WriteOutSubscriber
+ * 提供一些和路径、URL、协议、桶权限相关的通用方法
+ *
+ * @author AprilWind
+ */
+public class StorageUtils {
+
+ /**
+ * 获取是否使用 HTTPS 的协议头
+ *
+ * @param properties Oss 配置
+ * @return "https://" 或 "http://"
+ */
+ public static String getIsHttps(OssProperties properties) {
+ return OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? Constants.HTTPS : Constants.HTTP;
+ }
+
+ /**
+ * 获取完整终端点 URL
+ *
+ * @param properties Oss 配置
+ * @return 终端点 URL
+ */
+ public static String getEndpoint(OssProperties properties) {
+ String header = getIsHttps(properties);
+ return header + properties.getEndpoint();
+ }
+
+ /**
+ * 获取完整域名 URL(优先域名,其次 endpoint)
+ *
+ * @param properties Oss 配置
+ * @return URL 字符串
+ */
+ public static String getDomain(OssProperties properties) {
+ // 从配置中获取域名、终端点、是否使用 HTTPS 等信息
+ String domain = properties.getDomain();
+ String endpoint = properties.getEndpoint();
+ String header = getIsHttps(properties);
+
+ // 如果是云服务商,直接返回域名或终端点
+ if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+ return StringUtils.isNotEmpty(domain) ? header + domain : header + endpoint;
+ }
+
+ // 如果是 MinIO,处理域名并返回
+ if (StringUtils.isNotEmpty(domain)) {
+ return domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP) ? domain : header + domain;
+ }
+
+ // 返回终端点
+ return header + endpoint;
+ }
+
+ /**
+ * 获取访问 URL
+ *
+ * @param properties Oss 配置
+ * @return URL
+ */
+ public static String getUrl(OssProperties properties) {
+ String domain = properties.getDomain();
+ String endpoint = properties.getEndpoint();
+ String header = getIsHttps(properties);
+
+ if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) {
+ return header + (StringUtils.isNotEmpty(domain) ? domain : properties.getBucketName() + "." + endpoint);
+ }
+
+ if (StringUtils.isNotEmpty(domain)) {
+ return (domain.startsWith(Constants.HTTPS) || domain.startsWith(Constants.HTTP)) ?
+ domain + "/" + properties.getBucketName() :
+ header + domain + "/" + properties.getBucketName();
+ }
+
+ return header + endpoint + "/" + properties.getBucketName();
+ }
+
+ /**
+ * 根据 prefix + suffix 构造唯一文件路径
+ *
+ * @param prefix 前缀
+ * @param suffix 后缀
+ * @return 文件路径
+ */
+ public static String getPath(String prefix, String suffix) {
+ String uuid = java.util.UUID.randomUUID().toString().replace("-", "");
+ String datePath = org.dromara.common.core.utils.DateUtils.datePath();
+ String path = (prefix != null && !prefix.isEmpty()) ? prefix + "/" + datePath + "/" + uuid : datePath + "/" + uuid;
+ return path + suffix;
+ }
+
+ /**
+ * 移除 URL 的基础路径,得到相对路径
+ *
+ * @param url 完整 URL
+ * @param properties Oss 配置
+ * @return 相对路径
+ */
+ public static String removeBaseUrl(String url, OssProperties properties) {
+ return url.replace(getUrl(properties) + "/", "");
+ }
+
+ /**
+ * 根据传入的 region 参数返回相应的 AWS 区域
+ * 如果 region 参数非空,使用 Region.of 方法创建并返回对应的 AWS 区域对象
+ * 如果 region 参数为空,返回一个默认的 AWS 区域(例如,us-east-1),作为广泛支持的区域
+ *
+ * @return 对应的 AWS 区域对象,或者默认的广泛支持的区域(us-east-1)
+ */
+ public static Region of(OssProperties properties) {
+ //AWS 区域字符串
+ String region = properties.getRegion();
+ // 如果 region 参数非空,使用 Region.of 方法创建对应的 AWS 区域对象,否则返回默认区域
+ return StringUtils.isNotEmpty(region) ? Region.of(region) : Region.US_EAST_1;
+ }
+
+}
diff --git a/ruoyi-common/ruoyi-common-oss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/ruoyi-common/ruoyi-common-oss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 000000000..319fbd944
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-oss/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.dromara.common.oss.handler.OssUploadEventHandler
diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
index 33341a91d..62d0be4ba 100644
--- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
+++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysOssServiceImpl.java
@@ -4,35 +4,36 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.domain.dto.OssDTO;
+import org.dromara.common.core.domain.event.OssUploadEvent;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.service.OssService;
-import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
-import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
-import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enums.AccessPolicyType;
-import org.dromara.common.oss.factory.OssFactory;
+import org.dromara.common.oss.factory.StorageFactory;
+import org.dromara.common.oss.service.StorageService;
import org.dromara.system.domain.SysOss;
-import org.dromara.system.domain.SysOssExt;
import org.dromara.system.domain.bo.SysOssBo;
import org.dromara.system.domain.vo.SysOssVo;
import org.dromara.system.mapper.SysOssMapper;
import org.dromara.system.service.ISysOssService;
-import org.jetbrains.annotations.NotNull;
import org.springframework.cache.annotation.Cacheable;
+import org.springframework.context.event.EventListener;
import org.springframework.http.MediaType;
+import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@@ -49,11 +50,13 @@ import java.util.Map;
*
* @author Lion Li
*/
+@Slf4j
@RequiredArgsConstructor
@Service
public class SysOssServiceImpl implements ISysOssService, OssService {
private final SysOssMapper baseMapper;
+ private final IdentifierGenerator identifierGenerator;
/**
* 查询OSS对象存储列表
@@ -164,6 +167,23 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
return baseMapper.selectVoById(ossId);
}
+ /**
+ * 监听 OSS 文件上传事件,并将上传信息保存到数据库
+ */
+ @Async
+ @EventListener
+ public void handleOssUpload(OssUploadEvent uploadEvent) {
+ SysOss oss = new SysOss();
+ oss.setOssId(uploadEvent.getOssId());
+ oss.setUrl(uploadEvent.getUrl());
+ oss.setFileSuffix(uploadEvent.getFileSuffix());
+ oss.setFileName(uploadEvent.getFileName());
+ oss.setOriginalName(uploadEvent.getOriginalName());
+ oss.setService(uploadEvent.getService());
+ oss.setExt1(uploadEvent.getExt1());
+ baseMapper.insert(oss);
+ log.info("OSS 上传记录已保存: {}", oss.getFileName());
+ }
/**
* 文件下载方法,支持一次性下载完整文件
@@ -179,7 +199,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
}
FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
- OssClient storage = OssFactory.instance(sysOss.getService());
+ StorageService storage = StorageFactory.instance(sysOss.getService());
storage.download(sysOss.getFileName(), response.getOutputStream(), response::setContentLengthLong);
}
@@ -192,20 +212,15 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
*/
@Override
public SysOssVo upload(MultipartFile file) {
- String originalfileName = file.getOriginalFilename();
- String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
- OssClient storage = OssFactory.instance();
+ StorageService storage = StorageFactory.instance();
+ Long ossId = identifierGenerator.nextId(null).longValue();
UploadResult uploadResult;
try {
- uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
+ uploadResult = storage.upload(ossId, file, null);
} catch (IOException e) {
throw new ServiceException(e.getMessage());
}
- SysOssExt ext1 = new SysOssExt();
- ext1.setFileSize(file.getSize());
- ext1.setContentType(file.getContentType());
- // 保存文件信息
- return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
+ return BeanUtil.toBean(uploadResult, SysOssVo.class);
}
/**
@@ -216,28 +231,10 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
*/
@Override
public SysOssVo upload(File file) {
- String originalfileName = file.getName();
- String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
- OssClient storage = OssFactory.instance();
- UploadResult uploadResult = storage.uploadSuffix(file, suffix);
- SysOssExt ext1 = new SysOssExt();
- ext1.setFileSize(file.length());
- // 保存文件信息
- return buildResultEntity(originalfileName, suffix, storage.getConfigKey(), uploadResult, ext1);
- }
-
- @NotNull
- private SysOssVo buildResultEntity(String originalfileName, String suffix, String configKey, UploadResult uploadResult, SysOssExt ext1) {
- SysOss oss = new SysOss();
- oss.setUrl(uploadResult.getUrl());
- oss.setFileSuffix(suffix);
- oss.setFileName(uploadResult.getFilename());
- oss.setOriginalName(originalfileName);
- oss.setService(configKey);
- oss.setExt1(JsonUtils.toJsonString(ext1));
- baseMapper.insert(oss);
- SysOssVo sysOssVo = MapstructUtils.convert(oss, SysOssVo.class);
- return this.matchingUrl(sysOssVo);
+ StorageService storage = StorageFactory.instance();
+ Long ossId = identifierGenerator.nextId(null).longValue();
+ UploadResult uploadResult = storage.upload(ossId, file, null);
+ return BeanUtil.toBean(uploadResult, SysOssVo.class);
}
/**
@@ -254,7 +251,7 @@ public class SysOssServiceImpl implements ISysOssService, OssService {
}
List