mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 10:57:15 +08:00 
			
		
		
		
	refactor(storage): 新增存储模块 - 本地和 S3 两种存储模式
This commit is contained in:
		| @@ -0,0 +1,30 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <parent> | ||||
|         <groupId>top.continew</groupId> | ||||
|         <artifactId>continew-starter-storage</artifactId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
|  | ||||
|     <artifactId>continew-starter-storage-core</artifactId> | ||||
|     <description>ContiNew Starter 存储模块 - 核心模块</description> | ||||
|  | ||||
|     <dependencies> | ||||
|  | ||||
|         <!--redisson 缓存模块--> | ||||
|         <dependency> | ||||
|             <groupId>top.continew</groupId> | ||||
|             <artifactId>continew-starter-cache-redisson</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!--图片处理工具-主要用做图片缩略处理--> | ||||
|         <dependency> | ||||
|             <groupId>net.coobird</groupId> | ||||
|             <artifactId>thumbnailator</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
|  | ||||
|  | ||||
| </project> | ||||
| @@ -0,0 +1,42 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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"; | ||||
|  | ||||
|     /** | ||||
|      * 云服务商 域名前缀 | ||||
|      * <p>目前只支持 阿里云-oss 华为云-obs 腾讯云-cos</p> | ||||
|      */ | ||||
|     public static final String[] CLOUD_SERVICE_PREFIX = new String[] {"oss", "cos", "obs"}; | ||||
|  | ||||
|     /** | ||||
|      * 缩略图后缀 | ||||
|      */ | ||||
|     public static final String SMALL_SUFFIX = "small"; | ||||
| } | ||||
| @@ -0,0 +1,35 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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); | ||||
| } | ||||
| @@ -0,0 +1,33 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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) { | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,106 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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<C> implements StorageStrategy<C> { | ||||
|  | ||||
|     protected StorageStrategy<C> delegate; | ||||
|  | ||||
|     protected AbstractStorageDecorator(StorageStrategy<C> delegate) { | ||||
|         this.delegate = delegate; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public C getClient() { | ||||
|         return delegate.getClient(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public boolean bucketExists(String bucketName) { | ||||
|         return delegate.bucketExists(bucketName); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void createBucket(String bucketName) { | ||||
|         delegate.createBucket(bucketName); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UploadResp upload(String fileName, InputStream inputStream, String fileType) { | ||||
|         return delegate.upload(fileName, inputStream, fileType); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UploadResp upload(String fileName, | ||||
|                              String path, | ||||
|                              InputStream inputStream, | ||||
|                              String fileType, | ||||
|                              boolean isThumbnail) { | ||||
|         return delegate.upload(fileName, path, inputStream, fileType, isThumbnail); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public UploadResp upload(String bucketName, | ||||
|                              String fileName, | ||||
|                              String path, | ||||
|                              InputStream inputStream, | ||||
|                              String fileType, | ||||
|                              boolean isThumbnail) { | ||||
|         return delegate.upload(bucketName, fileName, path, inputStream, fileType, isThumbnail); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void upload(String bucketName, String fileName, String path, InputStream inputStream, String fileType) { | ||||
|         delegate.upload(bucketName, fileName, path, inputStream, fileType); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public ThumbnailResp uploadThumbnail(String bucketName, | ||||
|                                          String fileName, | ||||
|                                          String path, | ||||
|                                          InputStream inputStream, | ||||
|                                          String fileType) { | ||||
|         return delegate.uploadThumbnail(bucketName, fileName, path, inputStream, fileType); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public InputStream download(String bucketName, String fileName) { | ||||
|         return delegate.download(bucketName, fileName); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void delete(String bucketName, String fileName) { | ||||
|         delegate.delete(bucketName, fileName); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getImageBase64(String bucketName, String fileName) { | ||||
|         return delegate.getImageBase64(bucketName, fileName); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,102 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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<Integer> { | ||||
|  | ||||
|     /** | ||||
|      * 其他 | ||||
|      */ | ||||
|     UNKNOWN(1, "其他", Collections.emptyList()), | ||||
|  | ||||
|     /** | ||||
|      * 图片 | ||||
|      */ | ||||
|     IMAGE(2, "图片", List | ||||
|         .of("jpg", "jpeg", "png", "gif", "bmp", "webp", "ico", "psd", "tiff", "dwg", "jxr", "apng", "xcf")), | ||||
|  | ||||
|     /** | ||||
|      * 文档 | ||||
|      */ | ||||
|     DOC(3, "文档", List.of("txt", "pdf", "doc", "xls", "ppt", "docx", "xlsx", "pptx")), | ||||
|  | ||||
|     /** | ||||
|      * 视频 | ||||
|      */ | ||||
|     VIDEO(4, "视频", List.of("mp4", "avi", "mkv", "flv", "webm", "wmv", "m4v", "mov", "mpg", "rmvb", "3gp")), | ||||
|  | ||||
|     /** | ||||
|      * 音频 | ||||
|      */ | ||||
|     AUDIO(5, "音频", List.of("mp3", "flac", "wav", "ogg", "midi", "m4a", "aac", "amr", "ac3", "aiff")),; | ||||
|  | ||||
|     private final Integer value; | ||||
|     private final String description; | ||||
|     private final List<String> extensions; | ||||
|  | ||||
|     /** | ||||
|      * 根据扩展名查询 | ||||
|      * | ||||
|      * @param extension 扩展名 | ||||
|      * @return 文件类型 | ||||
|      */ | ||||
|     public static FileTypeEnum getByExtension(String extension) { | ||||
|         return Arrays.stream(FileTypeEnum.values()) | ||||
|             .filter(t -> t.getExtensions().contains(StrUtil.emptyIfNull(extension).toLowerCase())) | ||||
|             .findFirst() | ||||
|             .orElse(FileTypeEnum.UNKNOWN); | ||||
|     } | ||||
|  | ||||
|     FileTypeEnum(Integer value, String description, List<String> extensions) { | ||||
|         this.value = value; | ||||
|         this.description = description; | ||||
|         this.extensions = extensions; | ||||
|     } | ||||
|  | ||||
|     public List<String> getExtensions() { | ||||
|         return this.extensions; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Integer getValue() { | ||||
|         return this.value; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getDescription() { | ||||
|         return this.description; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getColor() { | ||||
|         return BaseEnum.super.getColor(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,80 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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<String, StorageStrategy<?>> STORAGE_STRATEGY = new ConcurrentHashMap<>(); | ||||
|  | ||||
|     /** | ||||
|      * 加载存储策略 | ||||
|      * | ||||
|      * @param code     存储码 | ||||
|      * @param strategy 对应存储策略 | ||||
|      */ | ||||
|     public static void load(String code, StorageStrategy<?> strategy) { | ||||
|         STORAGE_STRATEGY.put(code, strategy); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 卸载存储策略 | ||||
|      * | ||||
|      * @param code 存储码 | ||||
|      */ | ||||
|     public static void unload(String code) { | ||||
|         STORAGE_STRATEGY.remove(code); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据 存储 code 获取对应存储策略 | ||||
|      * | ||||
|      * @param code 代码 | ||||
|      * @return {@link StorageStrategy } | ||||
|      */ | ||||
|     public static StorageStrategy<?> instance(String code) { | ||||
|         StorageStrategy<?> strategy = STORAGE_STRATEGY.get(code); | ||||
|         ValidationUtils.throwIfEmpty(strategy, "未找到存储配置:" + code); | ||||
|         return strategy; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取默认存储策略 | ||||
|      * | ||||
|      * @return {@link StorageStrategy } | ||||
|      */ | ||||
|     public static StorageStrategy<?> instance() { | ||||
|         return instance(RedisUtils.get(StorageConstant.DEFAULT_KEY)); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,152 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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; | ||||
|  | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,61 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,215 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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; | ||||
|  | ||||
|     /** | ||||
|      * 访问地址 | ||||
|      * <p>如果桶为私有,则提供临时链接,时间默认为 12 小时</p> | ||||
|      */ | ||||
|     private String url; | ||||
|  | ||||
|     /** | ||||
|      * 文件基础路径 | ||||
|      */ | ||||
|     private String basePath; | ||||
|  | ||||
|     /** | ||||
|      * 原始 文件名 | ||||
|      */ | ||||
|     private String originalFilename; | ||||
|  | ||||
|     /** | ||||
|      * 扩展名 | ||||
|      */ | ||||
|     private String ext; | ||||
|  | ||||
|     /** | ||||
|      * 文件大小(字节) | ||||
|      */ | ||||
|     private long size; | ||||
|  | ||||
|     /** | ||||
|      * 已上传对象的实体标记(用来校验文件)-S3 | ||||
|      */ | ||||
|     private String eTag; | ||||
|  | ||||
|     /** | ||||
|      * 存储路径 | ||||
|      * <p></p> 格式 桶/文件名 continew/2024/12/24/1234.jpg | ||||
|      */ | ||||
|     private String path; | ||||
|  | ||||
|     /** | ||||
|      * 存储桶 | ||||
|      */ | ||||
|     private String bucketName; | ||||
|  | ||||
|     /** | ||||
|      * 缩略图大小(字节) | ||||
|      */ | ||||
|     private Long thumbnailSize; | ||||
|  | ||||
|     /** | ||||
|      * 缩略图URL | ||||
|      */ | ||||
|     private String thumbnailUrl; | ||||
|  | ||||
|     /** | ||||
|      * 上传时间 | ||||
|      */ | ||||
|     private LocalDateTime createTime; | ||||
|  | ||||
|     public UploadResp() { | ||||
|     } | ||||
|  | ||||
|     public UploadResp(String code, | ||||
|                       String url, | ||||
|                       String basePath, | ||||
|                       String originalFilename, | ||||
|                       String ext, | ||||
|                       long size, | ||||
|                       String eTag, | ||||
|                       String path, | ||||
|                       String bucketName, | ||||
|                       Long thumbnailSize, | ||||
|                       String thumbnailUrl, | ||||
|                       LocalDateTime createTime) { | ||||
|         this.code = code; | ||||
|         this.url = url; | ||||
|         this.basePath = basePath; | ||||
|         this.originalFilename = originalFilename; | ||||
|         this.ext = ext; | ||||
|         this.size = size; | ||||
|         this.eTag = eTag; | ||||
|         this.path = path; | ||||
|         this.bucketName = bucketName; | ||||
|         this.thumbnailSize = thumbnailSize; | ||||
|         this.thumbnailUrl = thumbnailUrl; | ||||
|         this.createTime = createTime; | ||||
|     } | ||||
|  | ||||
|     public String getCode() { | ||||
|         return code; | ||||
|     } | ||||
|  | ||||
|     public void setCode(String code) { | ||||
|         this.code = code; | ||||
|     } | ||||
|  | ||||
|     public String getUrl() { | ||||
|         return url; | ||||
|     } | ||||
|  | ||||
|     public void setUrl(String url) { | ||||
|         this.url = url; | ||||
|     } | ||||
|  | ||||
|     public String getBasePath() { | ||||
|         return basePath; | ||||
|     } | ||||
|  | ||||
|     public void setBasePath(String basePath) { | ||||
|         this.basePath = basePath; | ||||
|     } | ||||
|  | ||||
|     public String getOriginalFilename() { | ||||
|         return originalFilename; | ||||
|     } | ||||
|  | ||||
|     public void setOriginalFilename(String originalFilename) { | ||||
|         this.originalFilename = originalFilename; | ||||
|     } | ||||
|  | ||||
|     public String getExt() { | ||||
|         return ext; | ||||
|     } | ||||
|  | ||||
|     public void setExt(String ext) { | ||||
|         this.ext = ext; | ||||
|     } | ||||
|  | ||||
|     public long getSize() { | ||||
|         return size; | ||||
|     } | ||||
|  | ||||
|     public void setSize(long size) { | ||||
|         this.size = size; | ||||
|     } | ||||
|  | ||||
|     public String geteTag() { | ||||
|         return eTag; | ||||
|     } | ||||
|  | ||||
|     public void seteTag(String eTag) { | ||||
|         this.eTag = eTag; | ||||
|     } | ||||
|  | ||||
|     public String getPath() { | ||||
|         return path; | ||||
|     } | ||||
|  | ||||
|     public void setPath(String path) { | ||||
|         this.path = path; | ||||
|     } | ||||
|  | ||||
|     public String getBucketName() { | ||||
|         return bucketName; | ||||
|     } | ||||
|  | ||||
|     public void setBucketName(String bucketName) { | ||||
|         this.bucketName = bucketName; | ||||
|     } | ||||
|  | ||||
|     public Long getThumbnailSize() { | ||||
|         return thumbnailSize; | ||||
|     } | ||||
|  | ||||
|     public void setThumbnailSize(Long thumbnailSize) { | ||||
|         this.thumbnailSize = thumbnailSize; | ||||
|     } | ||||
|  | ||||
|     public String getThumbnailUrl() { | ||||
|         return thumbnailUrl; | ||||
|     } | ||||
|  | ||||
|     public void setThumbnailUrl(String thumbnailUrl) { | ||||
|         this.thumbnailUrl = thumbnailUrl; | ||||
|     } | ||||
|  | ||||
|     public LocalDateTime getCreateTime() { | ||||
|         return createTime; | ||||
|     } | ||||
|  | ||||
|     public void setCreateTime(LocalDateTime createTime) { | ||||
|         this.createTime = createTime; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,151 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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<C> { | ||||
|  | ||||
|     /** | ||||
|      * 获得客户端 - 用于重写时 获取对应存储 code 客户端 | ||||
|      * | ||||
|      * @return {@link Object } | ||||
|      */ | ||||
|     C getClient(); | ||||
|  | ||||
|     /** | ||||
|      * 检查桶是否存在 | ||||
|      * <p> S3: 检查桶是否存在 </p> | ||||
|      * <p>local: 检查 默认路径 是否存在 </p> | ||||
|      * | ||||
|      * @param bucketName 桶名称 | ||||
|      * @return true 存在 false 不存在 | ||||
|      */ | ||||
|     boolean bucketExists(String bucketName); | ||||
|  | ||||
|     /** | ||||
|      * 创建桶 | ||||
|      * <p> S3: 创建桶 </p> | ||||
|      * <p> local: 创建 默认路径下 指定文件夹 </p> | ||||
|      * | ||||
|      * @param bucketName 桶名称 | ||||
|      */ | ||||
|     void createBucket(String bucketName); | ||||
|  | ||||
|     /** | ||||
|      * 上传文件 - 默认桶 | ||||
|      * | ||||
|      * @param fileName    文件名 | ||||
|      * @param inputStream 输入流 | ||||
|      * @param fileType    文件类型 | ||||
|      * @return 上传响应 | ||||
|      */ | ||||
|     UploadResp upload(String fileName, InputStream inputStream, String fileType); | ||||
|  | ||||
|     /** | ||||
|      * 上传文件 - 默认桶 | ||||
|      * | ||||
|      * @param fileName    文件名 | ||||
|      * @param path        路径 | ||||
|      * @param inputStream 输入流 | ||||
|      * @param fileType    文件类型 | ||||
|      * @param isThumbnail 是缩略图 | ||||
|      * @return {@link UploadResp } | ||||
|      */ | ||||
|     UploadResp upload(String fileName, String path, InputStream inputStream, String fileType, boolean isThumbnail); | ||||
|  | ||||
|     /** | ||||
|      * 上传文件 | ||||
|      * | ||||
|      * @param bucketName  桶名称 | ||||
|      * @param fileName    文件名 | ||||
|      * @param path        路径 | ||||
|      * @param inputStream 输入流 | ||||
|      * @param fileType    文件类型 | ||||
|      * @param isThumbnail 是缩略图 | ||||
|      * @return 上传响应 | ||||
|      */ | ||||
|     UploadResp upload(String bucketName, | ||||
|                       String fileName, | ||||
|                       String path, | ||||
|                       InputStream inputStream, | ||||
|                       String fileType, | ||||
|                       boolean isThumbnail); | ||||
|  | ||||
|     /** | ||||
|      * 文件上传-基础上传 | ||||
|      * | ||||
|      * @param bucketName  桶名称 - 基础上传不做处理 | ||||
|      * @param fileName    文件名 - 基础上传不做处理 | ||||
|      * @param path        路径 - 基础上传不做处理 | ||||
|      * @param inputStream 输入流 | ||||
|      * @param fileType    文件类型 | ||||
|      * @return {@link UploadResp } | ||||
|      */ | ||||
|     void upload(String bucketName, String fileName, String path, InputStream inputStream, String fileType); | ||||
|  | ||||
|     /** | ||||
|      * 上传缩略图 | ||||
|      * | ||||
|      * @param bucketName  桶名称 | ||||
|      * @param fileName    文件名 | ||||
|      * @param inputStream 输入流 | ||||
|      * @param fileType    文件类型 | ||||
|      * @return {@link UploadResp } | ||||
|      */ | ||||
|     ThumbnailResp uploadThumbnail(String bucketName, | ||||
|                                   String fileName, | ||||
|                                   String path, | ||||
|                                   InputStream inputStream, | ||||
|                                   String fileType); | ||||
|  | ||||
|     /** | ||||
|      * 下载文件 | ||||
|      * | ||||
|      * @param bucketName 桶名称 | ||||
|      * @param fileName   文件名 | ||||
|      * @return 文件输入流 | ||||
|      */ | ||||
|     InputStream download(String bucketName, String fileName); | ||||
|  | ||||
|     /** | ||||
|      * 删除文件 | ||||
|      * | ||||
|      * @param bucketName 桶名称 | ||||
|      * @param fileName   文件名 | ||||
|      */ | ||||
|     void delete(String bucketName, String fileName); | ||||
|  | ||||
|     /** | ||||
|      * 获取图像Base64 | ||||
|      * | ||||
|      * @param bucketName 桶名称 | ||||
|      * @param fileName   文件名 | ||||
|      * @return Base64编码的图像 | ||||
|      */ | ||||
|     String getImageBase64(String bucketName, String fileName); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,81 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,128 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * 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 | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * 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)); | ||||
|     } | ||||
|  | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 吴泽威
					吴泽威