+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.storage.constant; + +/** + * 存储 常量 + * + * @author echo + * @date 2024/12/16 19:09 + */ +public class StorageConstant { + + /** + * 默认存储 Key + */ + public static final String DEFAULT_KEY = "storage:default_config"; + + /** + * 云服务商 域名前缀 + *
目前只支持 阿里云-oss 华为云-obs 腾讯云-cos
+ */ + public static final String[] CLOUD_SERVICE_PREFIX = new String[] {"oss", "cos", "obs"}; + + /** + * 缩略图后缀 + */ + public static final String SMALL_SUFFIX = "small"; +} diff --git a/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/dao/StorageDao.java b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/dao/StorageDao.java new file mode 100644 index 0000000000000000000000000000000000000000..ed55e9f3781924bd895a19d36669f4aea63bf31c --- /dev/null +++ b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/dao/StorageDao.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.storage.dao; + +import top.continew.starter.storage.model.resp.UploadResp; + +/** + * 存储记录持久层接口 + * + * @author echo + * @date 2024/12/17 16:49 + */ +public interface StorageDao { + + /** + * 记录上传信息 + * + * @param uploadResp 上传信息 + */ + void add(UploadResp uploadResp); +} diff --git a/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/dao/impl/StorageDaoDefaultImpl.java b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/dao/impl/StorageDaoDefaultImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..0f29d672b2871295168c615a4ffb5a269d2feee4 --- /dev/null +++ b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/dao/impl/StorageDaoDefaultImpl.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package top.continew.starter.storage.dao.impl; + +import top.continew.starter.storage.dao.StorageDao; +import top.continew.starter.storage.model.resp.UploadResp; + +/** + * 默认记录实现,此类并不能真正保存记录,只是用来脱离数据库运行,保证文件上传功能可以正常使用 + * + * @author echo + * @date 2024/12/18 08:48 + **/ +public class StorageDaoDefaultImpl implements StorageDao { + @Override + public void add(UploadResp uploadResp) { + + } +} diff --git a/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/decorator/AbstractStorageDecorator.java b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/decorator/AbstractStorageDecorator.java new file mode 100644 index 0000000000000000000000000000000000000000..e9fdabcbcc6423637650a14968410f4ca7e782a7 --- /dev/null +++ b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/decorator/AbstractStorageDecorator.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *
+ * http://www.gnu.org/licenses/lgpl.html + *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.decorator;
+
+import top.continew.starter.storage.model.resp.ThumbnailResp;
+import top.continew.starter.storage.model.resp.UploadResp;
+import top.continew.starter.storage.strategy.StorageStrategy;
+
+import java.io.InputStream;
+
+/**
+ * 装饰器基类 - 用于重写
+ *
+ * @author echo
+ * @date 2024/12/30 19:33
+ */
+public abstract class AbstractStorageDecorator
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.enums;
+
+import cn.hutool.core.util.StrUtil;
+import top.continew.starter.core.enums.BaseEnum;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 文件类型枚举
+ *
+ * @author Charles7c
+ * @since 2023/12/23 13:38
+ */
+public enum FileTypeEnum implements BaseEnum
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.manger;
+
+import top.continew.starter.cache.redisson.util.RedisUtils;
+import top.continew.starter.core.validation.ValidationUtils;
+import top.continew.starter.storage.constant.StorageConstant;
+import top.continew.starter.storage.strategy.StorageStrategy;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 存储策略管理器
+ *
+ * @author echo
+ * @date 2024/12/16
+ */
+public class StorageManager {
+
+ /**
+ * 存储策略连接信息
+ */
+ private static final Map
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.model.req;
+
+/**
+ * 存储配置信息
+ *
+ * @author echo
+ * @date 2024/11/04 15:13
+ **/
+public class StorageProperties {
+
+ /**
+ * 编码
+ */
+ private String code;
+
+ /**
+ * 访问密钥
+ */
+ private String accessKey;
+
+ /**
+ * 私有密钥
+ */
+ private String secretKey;
+
+ /**
+ * 终端节点
+ */
+ private String endpoint;
+
+ /**
+ * 桶名称
+ */
+ private String bucketName;
+
+ /**
+ * 域名
+ */
+ private String domain;
+
+ /**
+ * 作用域
+ */
+ private String region;
+
+ /**
+ * 是否是默认存储
+ */
+ private Boolean isDefault;
+
+ public StorageProperties() {
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getAccessKey() {
+ return accessKey;
+ }
+
+ public void setAccessKey(String accessKey) {
+ this.accessKey = accessKey;
+ }
+
+ public String getSecretKey() {
+ return secretKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+
+ public String getBucketName() {
+ return bucketName;
+ }
+
+ public void setBucketName(String bucketName) {
+ this.bucketName = bucketName;
+ }
+
+ public String getDomain() {
+ return domain;
+ }
+
+ public void setDomain(String domain) {
+ this.domain = domain;
+ }
+
+ public String getRegion() {
+ return region;
+ }
+
+ public void setRegion(String region) {
+ this.region = region;
+ }
+
+ public Boolean getIsDefault() {
+ return isDefault;
+ }
+
+ public void setIsDefault(Boolean isDefault) {
+ this.isDefault = isDefault;
+ }
+
+ public StorageProperties(String code,
+ String accessKey,
+ String secretKey,
+ String endpoint,
+ String bucketName,
+ String domain,
+ String region,
+ Boolean isDefault) {
+ this.code = code;
+ this.accessKey = accessKey;
+ this.secretKey = secretKey;
+ this.endpoint = endpoint;
+ this.bucketName = bucketName;
+ this.domain = domain;
+ this.region = region;
+ this.isDefault = isDefault;
+
+ }
+}
diff --git a/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/model/resp/ThumbnailResp.java b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/model/resp/ThumbnailResp.java
new file mode 100644
index 0000000000000000000000000000000000000000..5233b4cbcf6285bfa513bdf48d90d9b047b53368
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/model/resp/ThumbnailResp.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.model.resp;
+
+/**
+ * 缩略图
+ *
+ * @author echo
+ * @date 2024/12/20 17:00
+ */
+public class ThumbnailResp {
+
+ /**
+ * 缩略图大小(字节)
+ */
+ private Long thumbnailSize;
+
+ /**
+ * 缩略图地址 格式 xxx/xxx/xxx.small.jpg
+ */
+ private String thumbnailPath;
+
+ public ThumbnailResp() {
+ }
+
+ public ThumbnailResp(Long thumbnailSize, String thumbnailPath) {
+ this.thumbnailSize = thumbnailSize;
+ this.thumbnailPath = thumbnailPath;
+ }
+
+ public Long getThumbnailSize() {
+ return thumbnailSize;
+ }
+
+ public void setThumbnailSize(Long thumbnailSize) {
+ this.thumbnailSize = thumbnailSize;
+ }
+
+ public String getThumbnailPath() {
+ return thumbnailPath;
+ }
+
+ public void setThumbnailPath(String thumbnailPath) {
+ this.thumbnailPath = thumbnailPath;
+ }
+
+}
diff --git a/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/model/resp/UploadResp.java b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/model/resp/UploadResp.java
new file mode 100644
index 0000000000000000000000000000000000000000..d08f3d6cf41460423a9b34fd3413893d3b02c8c3
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/model/resp/UploadResp.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.model.resp;
+
+import java.time.LocalDateTime;
+
+/**
+ * 上传结果
+ *
+ * @author echo
+ * @date 2024/12/10
+ */
+public class UploadResp {
+
+ /**
+ * 存储 code
+ */
+ private String code;
+
+ /**
+ * 访问地址
+ * 如果桶为私有,则提供临时链接,时间默认为 12 小时
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.strategy;
+
+import top.continew.starter.storage.model.resp.ThumbnailResp;
+import top.continew.starter.storage.model.resp.UploadResp;
+
+import java.io.InputStream;
+
+/**
+ * 存储策略接口
+ *
+ * @author echo
+ * @date 2024/12/16 11:19
+ */
+public interface StorageStrategy S3: 检查桶是否存在 local: 检查 默认路径 是否存在 S3: 创建桶 local: 创建 默认路径下 指定文件夹
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.util;
+
+import javax.imageio.ImageIO;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * 图像缩略图工具
+ *
+ * @author echo
+ * @date 2024/12/20 16:49
+ */
+public class ImageThumbnailUtils {
+
+ // 默认缩略图尺寸:100x100
+ private static final int DEFAULT_WIDTH = 100;
+ private static final int DEFAULT_HEIGHT = 100;
+
+ /**
+ * 根据输入流生成默认大小(100x100)的缩略图并写入输出流
+ *
+ * @param inputStream 原始图片的输入流
+ * @param outputStream 缩略图输出流
+ * @param suffix 后缀
+ * @throws IOException IOException
+ */
+ public static void generateThumbnail(InputStream inputStream,
+ OutputStream outputStream,
+ String suffix) throws IOException {
+ generateThumbnail(inputStream, outputStream, DEFAULT_WIDTH, DEFAULT_HEIGHT, suffix);
+ }
+
+ /**
+ * 根据输入流和自定义尺寸生成缩略图并写入输出流
+ *
+ * @param inputStream 原始图片的输入流
+ * @param outputStream 缩略图输出流
+ * @param width 缩略图宽度
+ * @param height 缩略图高度
+ * @param suffix 后缀
+ * @throws IOException IOException
+ */
+ public static void generateThumbnail(InputStream inputStream,
+ OutputStream outputStream,
+ int width,
+ int height,
+ String suffix) throws IOException {
+ // 读取原始图片
+ BufferedImage originalImage = ImageIO.read(inputStream);
+
+ // 调整图片大小
+ Image tmp = originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH);
+ BufferedImage thumbnail = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
+
+ // 画出缩略图
+ Graphics2D g2d = thumbnail.createGraphics();
+ g2d.drawImage(tmp, 0, 0, null);
+ g2d.dispose();
+ // 写入输出流
+ ImageIO.write(thumbnail, suffix, outputStream);
+ }
+}
diff --git a/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/util/StorageUtils.java b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/util/StorageUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..4cfca27a1da0a71ca2caad7bb1b7429abf75780b
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-core/src/main/java/top/continew/starter/storage/util/StorageUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.util;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.StrUtil;
+import top.continew.starter.core.constant.StringConstants;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * 储存工具
+ *
+ * @author echo
+ * @date 2024/12/16 19:55
+ */
+public class StorageUtils {
+ public StorageUtils() {
+ }
+
+ /**
+ * 格式文件名
+ *
+ * @param fileName 文件名
+ * @return {@link String }
+ */
+ public static String formatFileName(String fileName) {
+ // 获取文件后缀名
+ String suffix = FileUtil.extName(fileName);
+ // 获取当前时间的年月日时分秒格式
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+ String datetime = LocalDateTime.now().format(formatter);
+ // 获取当前时间戳
+ String timestamp = String.valueOf(System.currentTimeMillis());
+ // 生成新的文件名
+ return datetime + timestamp + "." + suffix;
+ }
+
+ /**
+ * 默认文件目录
+ *
+ * @param fileName 文件名
+ * @return {@link String }
+ */
+ public static String defaultFileDir(String fileName) {
+ LocalDate today = LocalDate.now();
+ return Paths.get(String.valueOf(today.getYear()), String.valueOf(today.getMonthValue()), String.valueOf(today
+ .getDayOfMonth()), fileName).toString();
+ }
+
+ /**
+ * 默认路径地址 格式 2024/03/10/
+ *
+ * @return {@link String }
+ */
+ public static String defaultPath() {
+ LocalDate today = LocalDate.now();
+ return Paths.get(String.valueOf(today.getYear()), String.valueOf(today.getMonthValue()), String.valueOf(today
+ .getDayOfMonth())) + StringConstants.SLASH;
+ }
+
+ /**
+ * 根据 endpoint 判断是否带有 http 或 https,如果没有则加上 http 前缀。
+ *
+ * @param endpoint 输入的 endpoint 字符串
+ * @return URI 对象
+ */
+ public static URI createUriWithProtocol(String endpoint) {
+ // 判断 endpoint 是否包含 http:// 或 https:// 前缀
+ if (!endpoint.startsWith("http://") && !endpoint.startsWith("https://")) {
+ // 如果没有协议前缀,则加上 http://
+ endpoint = "http://" + endpoint;
+ }
+ // 返回 URI 对象
+ return URI.create(endpoint);
+ }
+
+ /**
+ * 生成缩略图文件名
+ *
+ * @param fileName 文件名
+ * @param suffix 后缀
+ * @return {@link String }
+ */
+ public static String buildThumbnailFileName(String fileName, String suffix) {
+ // 获取文件的扩展名
+ String extName = FileNameUtil.extName(fileName);
+ // 去掉扩展名
+ String baseName = StrUtil.subBefore(fileName, StringConstants.DOT, true);
+ // 拼接新的路径:原始路径 + .缩略图后缀 + .扩展名
+ return baseName + "." + suffix + "." + extName;
+ }
+
+ /**
+ * 可重复读流
+ *
+ * @param inputStream 输入流
+ * @return {@link InputStream }
+ */
+ public static InputStream ensureByteArrayStream(InputStream inputStream) {
+ return (inputStream instanceof ByteArrayInputStream)
+ ? inputStream
+ : new ByteArrayInputStream(IoUtil.readBytes(inputStream));
+ }
+
+}
diff --git a/continew-starter-storage/continew-starter-storage-local/pom.xml b/continew-starter-storage/continew-starter-storage-local/pom.xml
index 0ee4e8ea7e100f8bb29893a92d70e702341b17b2..8d2638ea41412663f8f7080fa5ff4d9e0e09e444 100644
--- a/continew-starter-storage/continew-starter-storage-local/pom.xml
+++ b/continew-starter-storage/continew-starter-storage-local/pom.xml
@@ -13,10 +13,10 @@
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.autoconfigure;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import top.continew.starter.storage.dao.StorageDao;
+import top.continew.starter.storage.dao.impl.StorageDaoDefaultImpl;
+
+/**
+ * 本地存储 - 存储自动配置
+ *
+ * @author echo
+ * @date 2024/12/17 20:23
+ */
+@AutoConfiguration
+public class LocalStorageAutoconfigure {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public StorageDao storageDao() {
+ return new StorageDaoDefaultImpl();
+ }
+}
diff --git a/continew-starter-storage/continew-starter-storage-local/src/main/java/top/continew/starter/storage/client/LocalClient.java b/continew-starter-storage/continew-starter-storage-local/src/main/java/top/continew/starter/storage/client/LocalClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..7bec647b8d511fc887965e5a517953f517654a21
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-local/src/main/java/top/continew/starter/storage/client/LocalClient.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import top.continew.starter.storage.model.req.StorageProperties;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/**
+ * 本地客户端
+ *
+ * @author echo
+ * @date 2024/12/16 19:37
+ */
+public class LocalClient {
+ private static final Logger log = LoggerFactory.getLogger(LocalClient.class);
+
+ /**
+ * 配置属性
+ */
+ private final StorageProperties properties;
+
+ /**
+ * 构造函数
+ *
+ * @param properties 配置属性
+ */
+ public LocalClient(StorageProperties properties) {
+ this.properties = properties;
+ // 判断是否是默认存储,若不存在桶目录,则创建
+ if (Boolean.TRUE.equals(properties.getIsDefault())) {
+ String bucketName = properties.getBucketName();
+ if (bucketName != null && !bucketName.isEmpty()) {
+ createBucketDirectory(bucketName);
+ } else {
+ log.info("默认存储-存储桶已存在 => {}", bucketName);
+ }
+ }
+ log.info("加载 Local 存储 => {}", properties.getCode());
+ }
+
+ /**
+ * 获取属性
+ *
+ * @return {@link StorageProperties }
+ */
+ public StorageProperties getProperties() {
+ return properties;
+ }
+
+ /**
+ * 创建桶目录
+ *
+ * @param bucketName 桶名称
+ */
+ private void createBucketDirectory(String bucketName) {
+ Path bucketPath = Path.of(bucketName);
+ try {
+ if (Files.notExists(bucketPath)) {
+ Files.createDirectories(bucketPath);
+ log.info("默认存储-存储桶创建成功 : {}", bucketPath.toAbsolutePath());
+ }
+ } catch (IOException e) {
+ log.error("创建默认存储-存储桶失败 => 路径: {}", bucketPath.toAbsolutePath(), e);
+ }
+ }
+}
diff --git a/continew-starter-storage/continew-starter-storage-local/src/main/java/top/continew/starter/storage/local/autoconfigure/LocalStorageAutoConfiguration.java b/continew-starter-storage/continew-starter-storage-local/src/main/java/top/continew/starter/storage/local/autoconfigure/LocalStorageAutoConfiguration.java
deleted file mode 100644
index 38f8f66de037630dc3cb71989028801eace15050..0000000000000000000000000000000000000000
--- a/continew-starter-storage/continew-starter-storage-local/src/main/java/top/continew/starter/storage/local/autoconfigure/LocalStorageAutoConfiguration.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
- *
- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package top.continew.starter.storage.local.autoconfigure;
-
-import cn.hutool.core.text.CharSequenceUtil;
-import jakarta.annotation.PostConstruct;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.boot.autoconfigure.AutoConfiguration;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.web.servlet.config.annotation.EnableWebMvc;
-import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import top.continew.starter.core.constant.PropertiesConstants;
-import top.continew.starter.core.constant.StringConstants;
-
-import java.util.Map;
-
-/**
- * 本地文件自动配置
- *
- * @author Charles7c
- * @since 1.1.0
- */
-@EnableWebMvc
-@AutoConfiguration
-@EnableConfigurationProperties(LocalStorageProperties.class)
-@ConditionalOnProperty(prefix = PropertiesConstants.STORAGE_LOCAL, name = PropertiesConstants.ENABLED, havingValue = "true", matchIfMissing = true)
-public class LocalStorageAutoConfiguration implements WebMvcConfigurer {
-
- private static final Logger log = LoggerFactory.getLogger(LocalStorageAutoConfiguration.class);
- private final LocalStorageProperties properties;
-
- public LocalStorageAutoConfiguration(LocalStorageProperties properties) {
- this.properties = properties;
- }
-
- @Override
- public void addResourceHandlers(ResourceHandlerRegistry registry) {
- Map
- * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.gnu.org/licenses/lgpl.html
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package top.continew.starter.storage.local.autoconfigure;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.util.unit.DataSize;
-import top.continew.starter.core.constant.PropertiesConstants;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * 本地存储配置属性
- *
- * @author Charles7c
- * @since 1.1.0
- */
-@ConfigurationProperties(PropertiesConstants.STORAGE_LOCAL)
-public class LocalStorageProperties {
-
- /**
- * 是否启用本地存储
- */
- private boolean enabled = true;
-
- /**
- * 存储映射
- */
- private Map
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.strategy;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import top.continew.starter.core.constant.StringConstants;
+import top.continew.starter.core.exception.BusinessException;
+import top.continew.starter.core.validation.CheckUtils;
+import top.continew.starter.core.validation.ValidationUtils;
+import top.continew.starter.storage.client.LocalClient;
+import top.continew.starter.storage.constant.StorageConstant;
+import top.continew.starter.storage.dao.StorageDao;
+import top.continew.starter.storage.enums.FileTypeEnum;
+import top.continew.starter.storage.model.req.StorageProperties;
+import top.continew.starter.storage.model.resp.ThumbnailResp;
+import top.continew.starter.storage.model.resp.UploadResp;
+import top.continew.starter.storage.util.ImageThumbnailUtils;
+import top.continew.starter.storage.util.LocalUtils;
+import top.continew.starter.storage.util.StorageUtils;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.NoSuchAlgorithmException;
+import java.time.LocalDateTime;
+import java.util.Base64;
+
+/**
+ * 本地存储策略
+ *
+ * @author echo
+ * @date 2024/12/16 19:48
+ */
+public class LocalStorageStrategy implements StorageStrategy
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.util;
+
+import cn.hutool.core.io.IoUtil;
+import net.dreamlu.mica.core.utils.DigestUtil;
+
+import java.io.InputStream;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 本地存储工具
+ *
+ * @author echo
+ * @date 2024/12/27 11:58
+ */
+public class LocalUtils {
+ public LocalUtils() {
+ }
+
+ /**
+ * 计算MD5
+ *
+ * @param inputStream 输入流
+ * @return {@link String }
+ * @throws NoSuchAlgorithmException 没有这样算法例外
+ */
+ public static String calculateMD5(InputStream inputStream) throws NoSuchAlgorithmException {
+ byte[] fileBytes = IoUtil.readBytes(inputStream);
+ return DigestUtil.md5Hex(fileBytes);
+ }
+}
diff --git a/continew-starter-storage/continew-starter-storage-local/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/continew-starter-storage/continew-starter-storage-local/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 93613475a272d6ff3670ed503e174b550afb8c6d..169edcb258b2d73c112549ddbc981553952eff65 100644
--- a/continew-starter-storage/continew-starter-storage-local/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/continew-starter-storage/continew-starter-storage-local/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1 +1 @@
-top.continew.starter.storage.local.autoconfigure.LocalStorageAutoConfiguration
\ No newline at end of file
+top.continew.starter.storage.autoconfigure.LocalStorageAutoconfigure
\ No newline at end of file
diff --git a/continew-starter-storage/continew-starter-storage-oss/pom.xml b/continew-starter-storage/continew-starter-storage-oss/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d4d8734bb65d6b39f739b52faed7c20875fead94
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-oss/pom.xml
@@ -0,0 +1,63 @@
+
+
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.autoconfigure;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import top.continew.starter.storage.dao.StorageDao;
+import top.continew.starter.storage.dao.impl.StorageDaoDefaultImpl;
+
+/**
+ * 对象存储 - 存储自动配置
+ *
+ * @author echo
+ * @date 2024/12/17 20:23
+ */
+@AutoConfiguration
+public class OssStorageAutoconfigure {
+
+ @Bean
+ @ConditionalOnMissingBean
+ public StorageDao storageDao() {
+ return new StorageDaoDefaultImpl();
+ }
+
+}
diff --git a/continew-starter-storage/continew-starter-storage-oss/src/main/java/top/continew/starter/storage/client/OssClient.java b/continew-starter-storage/continew-starter-storage-oss/src/main/java/top/continew/starter/storage/client/OssClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..b1b519bcdcd917335ea6415e7c786c4d576e93c3
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-oss/src/main/java/top/continew/starter/storage/client/OssClient.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.client;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.services.s3.S3AsyncClient;
+import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
+import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
+import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
+import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
+import software.amazon.awssdk.services.s3.presigner.S3Presigner;
+import software.amazon.awssdk.transfer.s3.S3TransferManager;
+import top.continew.starter.core.exception.BusinessException;
+import top.continew.starter.storage.model.req.StorageProperties;
+import top.continew.starter.storage.util.OssUtils;
+import top.continew.starter.storage.util.StorageUtils;
+
+import java.net.URI;
+import java.time.Duration;
+
+/**
+ * S3客户端
+ *
+ * @author echo
+ * @date 2024/12/16
+ */
+public class OssClient {
+
+ private static final Logger log = LoggerFactory.getLogger(OssClient.class);
+
+ /**
+ * 配置属性
+ */
+ private final StorageProperties properties;
+
+ /**
+ * s3 异步客户端
+ */
+ private final S3AsyncClient client;
+
+ /**
+ * S3 数据传输的高级工具
+ */
+ private final S3TransferManager transferManager;
+
+ /**
+ * S3 预签名
+ */
+ private final S3Presigner presigner;
+
+ /**
+ * 获取属性
+ *
+ * @return {@link StorageProperties }
+ */
+ public StorageProperties getProperties() {
+ return properties;
+ }
+
+ /**
+ * 构造方法
+ *
+ * @param s3PropertiesReq 微型性能要求
+ */
+ public OssClient(StorageProperties s3PropertiesReq) {
+ this.properties = s3PropertiesReq;
+
+ // 创建认证信息
+ StaticCredentialsProvider auth = StaticCredentialsProvider.create(AwsBasicCredentials.create(properties
+ .getAccessKey(), properties.getSecretKey()));
+
+ URI uriWithProtocol = StorageUtils.createUriWithProtocol(properties.getEndpoint());
+
+ // 创建 客户端连接
+ client = S3AsyncClient.crtBuilder()
+ .credentialsProvider(auth) // 认证信息
+ .endpointOverride(uriWithProtocol) // 连接端点
+ .region(OssUtils.getRegion(properties.getRegion()))
+ .targetThroughputInGbps(20.0) //吞吐量
+ .minimumPartSizeInBytes(10 * 1025 * 1024L)
+ .checksumValidationEnabled(false)
+ .httpConfiguration(S3CrtHttpConfiguration.builder()
+ .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
+ .build())
+ .build();
+
+ // 基于 CRT 创建 S3 Transfer Manager 的实例
+ this.transferManager = S3TransferManager.builder().s3Client(this.client).build();
+
+ this.presigner = S3Presigner.builder()
+ .region(OssUtils.getRegion(properties.getRegion()))
+ .credentialsProvider(auth)
+ .endpointOverride(uriWithProtocol)
+ .build();
+
+ // 只创建 默认存储的的桶
+ if (s3PropertiesReq.getIsDefault()) {
+ try {
+ // 检查存储桶是否存在
+ client.headBucket(HeadBucketRequest.builder().bucket(properties.getBucketName()).build());
+ log.info("默认存储-存储桶 {} 已存在", properties.getBucketName());
+ } catch (NoSuchBucketException e) {
+ log.info("默认存储桶 {} 不存在,尝试创建...", properties.getBucketName());
+ try {
+ // 创建存储桶
+ client.createBucket(CreateBucketRequest.builder().bucket(properties.getBucketName()).build());
+ log.info("默认存储-存储桶 {} 创建成功", properties.getBucketName());
+ } catch (Exception createException) {
+ log.error("创建默认存储-存储桶 {} 失败", properties.getBucketName(), createException);
+ throw new BusinessException("创建默认存储-桶出错", createException);
+ }
+ } catch (Exception e) {
+ log.error("检查默认存储-存储桶 {} 时出错", properties.getBucketName(), e);
+ throw new BusinessException("检查默认存储-桶时出错", e);
+ }
+ }
+ log.info("加载 S3 存储 => {}", properties.getCode());
+ }
+
+ /**
+ * 获得客户端
+ *
+ * @return {@link S3TransferManager }
+ */
+ public S3AsyncClient getClient() {
+ return client;
+ }
+
+ /**
+ * 获得 高效连接客户端 主要用于 上传下载 复制 删除
+ *
+ * @return {@link S3TransferManager }
+ */
+ public S3TransferManager getTransferManager() {
+ return transferManager;
+ }
+
+ /**
+ * 获得 S3 预签名
+ *
+ * @return {@link S3Presigner }
+ */
+ public S3Presigner getPresigner() {
+ return presigner;
+ }
+}
diff --git a/continew-starter-storage/continew-starter-storage-oss/src/main/java/top/continew/starter/storage/strategy/OssStorageStrategy.java b/continew-starter-storage/continew-starter-storage-oss/src/main/java/top/continew/starter/storage/strategy/OssStorageStrategy.java
new file mode 100644
index 0000000000000000000000000000000000000000..f6eb6f4084e450c243a0e9f0d7d1b8a040e7c425
--- /dev/null
+++ b/continew-starter-storage/continew-starter-storage-oss/src/main/java/top/continew/starter/storage/strategy/OssStorageStrategy.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.strategy;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.core.async.AsyncResponseTransformer;
+import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
+import software.amazon.awssdk.services.s3.model.*;
+import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
+import software.amazon.awssdk.transfer.s3.model.CompletedUpload;
+import software.amazon.awssdk.transfer.s3.model.Download;
+import software.amazon.awssdk.transfer.s3.model.DownloadRequest;
+import software.amazon.awssdk.transfer.s3.model.Upload;
+import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
+import top.continew.starter.core.constant.StringConstants;
+import top.continew.starter.core.exception.BusinessException;
+import top.continew.starter.core.validation.CheckUtils;
+import top.continew.starter.core.validation.ValidationUtils;
+import top.continew.starter.storage.client.OssClient;
+import top.continew.starter.storage.constant.StorageConstant;
+import top.continew.starter.storage.dao.StorageDao;
+import top.continew.starter.storage.enums.FileTypeEnum;
+import top.continew.starter.storage.model.req.StorageProperties;
+import top.continew.starter.storage.model.resp.ThumbnailResp;
+import top.continew.starter.storage.model.resp.UploadResp;
+import top.continew.starter.storage.util.ImageThumbnailUtils;
+import top.continew.starter.storage.util.OssUtils;
+import top.continew.starter.storage.util.StorageUtils;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Base64;
+import java.util.List;
+import java.util.concurrent.CompletionException;
+
+/**
+ * OSS存储策略
+ *
+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.gnu.org/licenses/lgpl.html
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package top.continew.starter.storage.util;
+
+import cn.hutool.core.util.StrUtil;
+import software.amazon.awssdk.regions.Region;
+import top.continew.starter.core.constant.StringConstants;
+import top.continew.starter.storage.constant.StorageConstant;
+
+/**
+ * OSS 工具
+ *
+ * @author echo
+ * @date 2024/12/17 13:48
+ */
+public class OssUtils {
+ public OssUtils() {
+ }
+
+ /**
+ * 获取作用域
+ * 如果 region 参数非空,使用 Region.of 方法创建对应的 S3 区域对象,否则返回默认区域