From bc057da265fdafe1ca91cf6206f0da6f676db58d Mon Sep 17 00:00:00 2001 From: Charles7c Date: Wed, 14 May 2025 23:01:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(system):=20=E9=87=8D=E6=9E=84=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E9=85=8D=E7=BD=AE=E5=8F=8A=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 存储配置自动处理:domain 不能以 / 结尾,bucket 必须以 / 结尾 文件上传:path 自动处理 Closes #IC6V43 --- .../system/config/file/FileRecorderImpl.java | 60 ++---- .../config/file/FileStorageConfigLoader.java | 4 +- .../admin/system/enums/StorageTypeEnum.java | 45 ++++- .../admin/system/model/entity/FileDO.java | 90 ++++----- .../admin/system/service/FileService.java | 36 ++-- .../admin/system/service/StorageService.java | 18 +- .../system/service/impl/FileServiceImpl.java | 42 ++-- .../service/impl/StorageServiceImpl.java | 179 ++++++++++-------- .../controller/common/CommonController.java | 6 +- 9 files changed, 261 insertions(+), 219 deletions(-) diff --git a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java index 18c97130..4dc429be 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileRecorderImpl.java @@ -16,26 +16,21 @@ package top.continew.admin.system.config.file; -import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ClassUtil; -import cn.hutool.core.util.EscapeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import cn.hutool.json.JSONUtil; -import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.x.file.storage.core.FileInfo; import org.dromara.x.file.storage.core.recorder.FileRecorder; import org.dromara.x.file.storage.core.upload.FilePartInfo; import org.springframework.stereotype.Component; -import top.continew.admin.common.context.UserContextHolder; -import top.continew.admin.system.enums.FileTypeEnum; import top.continew.admin.system.mapper.FileMapper; import top.continew.admin.system.mapper.StorageMapper; import top.continew.admin.system.model.entity.FileDO; import top.continew.admin.system.model.entity.StorageDO; import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.util.URLUtils; import java.util.Optional; @@ -52,53 +47,24 @@ public class FileRecorderImpl implements FileRecorder { private final FileMapper fileMapper; private final StorageMapper storageMapper; - private final IdentifierGenerator identifierGenerator; - /** - * 文件信息存储 - * - * @param fileInfo 文件信息对象 - * @return 是否保存成功 - */ @Override public boolean save(FileInfo fileInfo) { - FileDO file = new FileDO(); - Number id = identifierGenerator.nextId(fileInfo); - file.setId(id.longValue()); - fileInfo.setId(String.valueOf(id.longValue())); - String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename()); - file.setName(StrUtil.contains(originalFilename, StringConstants.DOT) - ? StrUtil.subBefore(originalFilename, StringConstants.DOT, true) - : originalFilename); + // 保存文件信息 + FileDO file = new FileDO(fileInfo); StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false)); - String filePath = StrUtil.appendIfMissing(fileInfo.getPath(), StringConstants.SLASH); - // 处理fileInfo中存储的地址 - fileInfo.setUrl(URLUtil.normalize(storage.getDomain() + filePath + fileInfo.getFilename())); - fileInfo.setThUrl(URLUtil.normalize(storage.getDomain() + filePath + fileInfo.getThFilename())); - file.setUrl(fileInfo.getUrl()); - file.setSize(fileInfo.getSize()); - String absPath = fileInfo.getPath(); - String tempAbsPath = absPath.length() > 1 ? StrUtil.removeSuffix(absPath, StringConstants.SLASH) : absPath; - String[] pathArr = tempAbsPath.split(StringConstants.SLASH); - if (pathArr.length > 1) { - file.setParentPath(pathArr[pathArr.length - 1]); - } else { - file.setParentPath(StringConstants.SLASH); - } - file.setAbsPath(tempAbsPath); - file.setExtension(fileInfo.getExt()); - file.setType(FileTypeEnum.getByExtension(file.getExtension())); - file.setContentType(fileInfo.getContentType()); - file.setSha256(fileInfo.getHashInfo().getSha256()); - file.setMetadata(JSONUtil.toJsonStr(fileInfo.getMetadata())); - file.setThumbnailUrl(fileInfo.getThUrl()); - file.setThumbnailSize(fileInfo.getThSize()); - file.setThumbnailMetadata(JSONUtil.toJsonStr(fileInfo.getThMetadata())); file.setStorageId(storage.getId()); - file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); - file.setUpdateUser(UserContextHolder.getUserId()); - file.setUpdateTime(file.getCreateTime()); fileMapper.insert(file); + // 方便文件上传完成后获取文件信息 + fileInfo.setId(String.valueOf(file.getId())); + if (!URLUtils.isHttpUrl(fileInfo.getUrl())) { + String prefix = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH); + String url = URLUtil.normalize(prefix + fileInfo.getUrl()); + fileInfo.setUrl(url); + if (StrUtil.isNotBlank(fileInfo.getThUrl())) { + fileInfo.setThUrl(URLUtil.normalize(prefix + fileInfo.getThUrl())); + } + } return true; } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java index 6e0d5b4e..8e3e26b0 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageConfigLoader.java @@ -24,8 +24,8 @@ import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import top.continew.admin.common.enums.DisEnableStatusEnum; +import top.continew.admin.system.model.entity.StorageDO; import top.continew.admin.system.model.query.StorageQuery; -import top.continew.admin.system.model.req.StorageReq; import top.continew.admin.system.model.resp.StorageResp; import top.continew.admin.system.service.StorageService; @@ -52,6 +52,6 @@ public class FileStorageConfigLoader implements ApplicationRunner { if (CollUtil.isEmpty(storageList)) { return; } - storageList.forEach(s -> storageService.load(BeanUtil.copyProperties(s, StorageReq.class))); + storageList.forEach(storage -> storageService.load(BeanUtil.copyProperties(storage, StorageDO.class))); } } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java b/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java index 54868f5e..54981411 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/enums/StorageTypeEnum.java @@ -16,9 +16,15 @@ package top.continew.admin.system.enums; +import cn.hutool.core.util.StrUtil; import lombok.Getter; import lombok.RequiredArgsConstructor; +import top.continew.admin.system.model.req.StorageReq; +import top.continew.admin.system.validation.ValidationGroup; +import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.enums.BaseEnum; +import top.continew.starter.core.util.URLUtils; +import top.continew.starter.core.validation.ValidationUtils; /** * 存储类型枚举 @@ -33,13 +39,48 @@ public enum StorageTypeEnum implements BaseEnum { /** * 本地存储 */ - LOCAL(1, "本地存储"), + LOCAL(1, "本地存储") { + @Override + public void validate(StorageReq req) { + ValidationUtils.validate(req, ValidationGroup.Storage.Local.class); + ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "访问路径格式不正确"); + } + + @Override + public void pretreatment(StorageReq req) { + super.pretreatment(req); + req.setBucketName(StrUtil.appendIfMissing(req.getBucketName() + .replace(StringConstants.BACKSLASH, StringConstants.SLASH), StringConstants.SLASH)); + } + }, /** * 对象存储 */ - OSS(2, "对象存储"); + OSS(2, "对象存储") { + @Override + public void validate(StorageReq req) { + ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class); + ValidationUtils.throwIf(!URLUtils.isHttpUrl(req.getDomain()), "域名格式不正确"); + } + }; private final Integer value; private final String description; + + /** + * 校验 + * + * @param req 请求参数 + */ + public abstract void validate(StorageReq req); + + /** + * 处理参数 + * + * @param req 请求参数 + */ + public void pretreatment(StorageReq req) { + req.setDomain(StrUtil.removeSuffix(req.getDomain(), StringConstants.SLASH)); + } } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java b/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java index 2af3d336..6578b8d9 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/model/entity/FileDO.java @@ -16,20 +16,21 @@ package top.continew.admin.system.model.entity; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.EscapeUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import lombok.SneakyThrows; +import lombok.NoArgsConstructor; import org.dromara.x.file.storage.core.FileInfo; import top.continew.admin.common.model.entity.BaseDO; import top.continew.admin.system.enums.FileTypeEnum; -import top.continew.admin.system.enums.StorageTypeEnum; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.util.StrUtils; import java.io.Serial; -import java.net.URL; import java.util.Map; /** @@ -39,6 +40,7 @@ import java.util.Map; * @since 2023/12/23 10:38 */ @Data +@NoArgsConstructor @TableName("sys_file") public class FileDO extends BaseDO { @@ -116,65 +118,63 @@ public class FileDO extends BaseDO { private Long storageId; /** - * 转换为 X-File-Storage 文件信息对象 + * 基于 {@link FileInfo} 构建文件信息对象 * - * @param storageDO 存储桶信息 - * @return X-File-Storage 文件信息对象 + * @param fileInfo {@link FileInfo} 文件信息 */ - public FileInfo toFileInfo(StorageDO storageDO) { + public FileDO(FileInfo fileInfo) { + this.name = FileNameUtil.getPrefix(EscapeUtil.unescape(fileInfo.getOriginalFilename())); + this.size = fileInfo.getSize(); + this.url = fileInfo.getUrl(); + this.absPath = StrUtil.isEmpty(fileInfo.getPath()) + ? StringConstants.SLASH + : StrUtil.prependIfMissing(fileInfo.getPath(), StringConstants.SLASH); + String[] pathAttr = this.absPath.split(StringConstants.SLASH); + this.parentPath = pathAttr.length > 1 ? pathAttr[pathAttr.length - 1] : StringConstants.SLASH; + this.extension = fileInfo.getExt(); + this.contentType = fileInfo.getContentType(); + this.type = FileTypeEnum.getByExtension(this.extension); + this.sha256 = fileInfo.getHashInfo().getSha256(); + this.metadata = JSONUtil.toJsonStr(fileInfo.getMetadata()); + this.thumbnailSize = fileInfo.getThSize(); + this.thumbnailUrl = fileInfo.getThUrl(); + this.thumbnailMetadata = JSONUtil.toJsonStr(fileInfo.getThMetadata()); + this.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); + } + + /** + * 转换为 {@link FileInfo} 文件信息对象 + * + * @param storage 存储配置信息 + * @return {@link FileInfo} 文件信息对象 + */ + public FileInfo toFileInfo(StorageDO storage) { FileInfo fileInfo = new FileInfo(); - fileInfo.setUrl(this.url); - fileInfo.setSize(this.size); + fileInfo.setPlatform(storage.getCode()); fileInfo.setFilename(StrUtil.contains(this.url, StringConstants.SLASH) ? StrUtil.subAfter(this.url, StringConstants.SLASH, true) : this.url); fileInfo.setOriginalFilename(StrUtils .blankToDefault(this.extension, this.name, ex -> this.name + StringConstants.DOT + ex)); fileInfo.setBasePath(StringConstants.EMPTY); - // 优化 path 处理 - fileInfo.setPath(extractRelativePath(this.url, storageDO)); - + fileInfo.setSize(this.size); + fileInfo.setUrl(this.url); + fileInfo.setPath(StringConstants.SLASH.equals(this.absPath) + ? StringConstants.EMPTY + : StrUtil.removePrefix(this.absPath, StringConstants.SLASH)); fileInfo.setExt(this.extension); - fileInfo.setPlatform(storageDO.getCode()); - fileInfo.setThUrl(this.thumbnailUrl); + if (StrUtil.isNotBlank(this.metadata)) { + fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class)); + } + // 缩略图信息 fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH) ? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true) : this.thumbnailUrl); fileInfo.setThSize(this.thumbnailSize); + fileInfo.setThUrl(this.thumbnailUrl); if (StrUtil.isNotBlank(this.thumbnailMetadata)) { fileInfo.setThMetadata(JSONUtil.toBean(this.thumbnailMetadata, Map.class)); } - if (StrUtil.isNotBlank(this.metadata)) { - fileInfo.setMetadata(JSONUtil.toBean(this.metadata, Map.class)); - } return fileInfo; } - - /** - * 将文件路径处理成资源路径 - * 例如: - * http://domain.cn/bucketName/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/ - * http://bucketName.domain.cn/2024/11/27/6746ec3b2907f0de80afdd70.png => 2024/11/27/ - * - * @param url 文件路径 - * @param storageDO 存储桶信息 - * @return - */ - @SneakyThrows - private static String extractRelativePath(String url, StorageDO storageDO) { - url = StrUtil.subBefore(url, StringConstants.SLASH, true) + StringConstants.SLASH; - if (storageDO.getType().equals(StorageTypeEnum.LOCAL)) { - return url; - } - // 提取 URL 中的路径部分 - String fullPath = new URL(url).getPath(); - // 移除开头的斜杠 - String relativePath = fullPath.startsWith(StringConstants.SLASH) ? fullPath.substring(1) : fullPath; - // 如果路径以 bucketName 开头,则移除 bucketName 例如: bucketName/2024/11/27/ -> 2024/11/27/ - if (relativePath.startsWith(storageDO.getBucketName())) { - return StrUtil.subAfter(relativePath, storageDO.getBucketName(), false); - } - return relativePath; - } - } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java b/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java index 34df8b61..192141b1 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/FileService.java @@ -89,9 +89,29 @@ public interface FileService extends BaseService createDir(FileReq req); + /** * 获取默认文件路径 * + *

+ * 默认文件路径:yyyy/MM/dd/ + *

+ * * @return 默认文件路径 */ default String getDefaultFilePath() { @@ -99,20 +119,4 @@ public interface FileService extends BaseService createDir(FileReq req); } \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/StorageService.java b/continew-module-system/src/main/java/top/continew/admin/system/service/StorageService.java index 50eb35b0..940f9932 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/StorageService.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/StorageService.java @@ -50,29 +50,29 @@ public interface StorageService extends BaseService ids) { + public void beforeDelete(List ids) { List fileList = baseMapper.lambdaQuery().in(FileDO::getId, ids).list(); Map> fileListGroup = fileList.stream().collect(Collectors.groupingBy(FileDO::getStorageId)); for (Map.Entry> entry : fileListGroup.entrySet()) { @@ -87,23 +88,17 @@ public class FileServiceImpl extends BaseServiceImpl allExtensions = FileTypeEnum.getAllExtensions(); CheckUtils.throwIf(!allExtensions.contains(extName), "不支持的文件类型,仅支持 {} 格式的文件", String .join(StringConstants.CHINESE_COMMA, allExtensions)); - // 获取存储信息 - StorageDO storage; - if (StrUtil.isBlank(storageCode)) { - storage = storageService.getDefaultStorage(); - CheckUtils.throwIfNull(storage, "请先指定默认存储"); - } else { - storage = storageService.getByCode(storageCode); - CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", storageCode); - } // 构建上传预处理对象 + StorageDO storage = storageService.getByCode(storageCode); + CheckUtils.throwIf(DisEnableStatusEnum.DISABLE.equals(storage.getStatus()), "请先启用存储 [{}]", storage.getCode()); UploadPretreatment uploadPretreatment = fileStorageService.of(file) .setPlatform(storage.getCode()) .setHashCalculatorSha256(true) .putAttr(ClassUtil.getClassName(StorageDO.class, false), storage) - .setPath(path); + .setPath(this.pretreatmentPath(path)); // 图片文件生成缩略图 if (FileTypeEnum.IMAGE.getExtensions().contains(extName)) { + uploadPretreatment.setIgnoreThumbnailException(true, true); uploadPretreatment.thumbnail(img -> img.size(100, 100)); } uploadPretreatment.setProgressMonitor(new ProgressListener() { @@ -122,6 +117,7 @@ public class FileServiceImpl extends BaseServiceImpl + * 1.如果 path 为空,则使用 {@link FileService#getDefaultFilePath()} 作为默认值
+ * 2.如果 path 为 {@code /},则设置为空
+ * 3.如果 path 不以 {@code /} 结尾,则添加后缀 {@code /}
+ * 4.如果 path 以 {@code /} 开头,则移除前缀 {@code /}
+ * 示例:yyyy/MM/dd/ + *

+ * + * @param path 路径 + * @return 处理路径 + */ + private String pretreatmentPath(String path) { + if (StrUtil.isBlank(path)) { + return this.getDefaultFilePath(); + } + if (StringConstants.SLASH.equals(path)) { + return StringConstants.EMPTY; + } + return StrUtil.appendIfMissing(StrUtil.removePrefix(path, StringConstants.SLASH), StringConstants.SLASH); + } } \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java index f87cc06f..88adf51f 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java @@ -16,7 +16,6 @@ package top.continew.admin.system.service.impl; -import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; @@ -39,10 +38,8 @@ import top.continew.admin.system.model.req.StorageReq; import top.continew.admin.system.model.resp.StorageResp; import top.continew.admin.system.service.FileService; import top.continew.admin.system.service.StorageService; -import top.continew.admin.system.validation.ValidationGroup; import top.continew.starter.core.constant.StringConstants; import top.continew.starter.core.util.ExceptionUtils; -import top.continew.starter.core.util.URLUtils; import top.continew.starter.core.validation.CheckUtils; import top.continew.starter.core.validation.ValidationUtils; import top.continew.starter.extension.crud.service.BaseServiceImpl; @@ -68,33 +65,55 @@ public class StorageServiceImpl extends BaseServiceImpl ids) { CheckUtils.throwIf(fileService.countByStorageIds(ids) > 0, "所选存储存在文件关联,请删除文件后重试"); List storageList = baseMapper.lambdaQuery().in(StorageDO::getId, ids).list(); - storageList.forEach(s -> { - CheckUtils.throwIfEqual(Boolean.TRUE, s.getIsDefault(), "[{}] 是默认存储,不允许删除", s.getName()); - // 卸载启用状态的存储 - if (DisEnableStatusEnum.ENABLE.equals(s.getStatus())) { - this.unload(BeanUtil.copyProperties(s, StorageReq.class)); - } + storageList.forEach(storage -> { + CheckUtils.throwIfEqual(Boolean.TRUE, storage.getIsDefault(), "[{}] 是默认存储,不允许删除", storage.getName()); + // 卸载存储引擎 + this.unload(storage); }); } @@ -123,14 +140,13 @@ public class StorageServiceImpl extends BaseServiceImpl fileStorageList = fileStorageService.getFileStorageList(); - String domain = req.getDomain(); - ValidationUtils.throwIf(!URLUtils.isHttpUrl(domain), "域名格式不正确"); - String bucketName = req.getBucketName(); - StorageTypeEnum type = req.getType(); - if (StorageTypeEnum.LOCAL.equals(type)) { - ValidationUtils.validate(req, ValidationGroup.Storage.Local.class); - req.setBucketName(StrUtil.appendIfMissing(bucketName - .replace(StringConstants.BACKSLASH, StringConstants.SLASH), StringConstants.SLASH)); - FileStorageProperties.LocalPlusConfig config = new FileStorageProperties.LocalPlusConfig(); - config.setPlatform(req.getCode()); - config.setStoragePath(bucketName); - fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections - .singletonList(config))); - SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), bucketName)); - } else if (StorageTypeEnum.OSS.equals(type)) { - ValidationUtils.validate(req, ValidationGroup.Storage.OSS.class); - FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config(); - config.setPlatform(req.getCode()); - config.setAccessKey(req.getAccessKey()); - config.setSecretKey(req.getSecretKey()); - config.setEndPoint(req.getEndpoint()); - config.setBucketName(bucketName); - config.setDomain(domain); - fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections - .singletonList(config), null)); + switch (storage.getType()) { + case LOCAL -> { + FileStorageProperties.LocalPlusConfig config = new FileStorageProperties.LocalPlusConfig(); + config.setPlatform(storage.getCode()); + config.setStoragePath(storage.getBucketName()); + fileStorageList.addAll(FileStorageServiceBuilder.buildLocalPlusFileStorage(Collections + .singletonList(config))); + SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage + .getBucketName())); + } + case OSS -> { + FileStorageProperties.AmazonS3Config config = new FileStorageProperties.AmazonS3Config(); + config.setPlatform(storage.getCode()); + config.setAccessKey(storage.getAccessKey()); + config.setSecretKey(storage.getSecretKey()); + config.setEndPoint(storage.getEndpoint()); + config.setBucketName(storage.getBucketName()); + config.setDomain(storage.getDomain()); + fileStorageList.addAll(FileStorageServiceBuilder.buildAmazonS3FileStorage(Collections + .singletonList(config), null)); + } + default -> throw new IllegalArgumentException("不支持的存储类型:%s".formatted(storage.getType())); } } @Override - public void unload(StorageReq req) { + public void unload(StorageDO storage) { + FileStorage fileStorage = fileStorageService.getFileStorage(storage.getCode()); + if (fileStorage == null) { + return; + } CopyOnWriteArrayList fileStorageList = fileStorageService.getFileStorageList(); - FileStorage fileStorage = fileStorageService.getFileStorage(req.getCode()); fileStorageList.remove(fileStorage); fileStorage.close(); - SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(req.getDomain()).getPath(), req - .getBucketName())); + // 本地存储引擎需要移除资源映射 + if (StorageTypeEnum.LOCAL.equals(storage.getType())) { + SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(storage.getDomain()).getPath(), storage + .getBucketName())); + } } /** * 解密 SecretKey * - * @param req 请求参数 - * @param storage 存储信息 + * @param encryptSecretKey 加密的 SecretKey + * @param storage 存储信息 + * @return 解密后的 SecretKey */ - private void decodeSecretKey(StorageReq req, StorageDO storage) { - if (!StorageTypeEnum.OSS.equals(req.getType())) { - return; - } + private String decryptSecretKey(String encryptSecretKey, StorageDO storage) { // 修改时,如果 SecretKey 不修改,需要手动修正 - String newSecretKey = req.getSecretKey(); - boolean isSecretKeyNotUpdate = StrUtil.isBlank(newSecretKey) || newSecretKey.contains(StringConstants.ASTERISK); - if (null != storage && isSecretKeyNotUpdate) { - req.setSecretKey(storage.getSecretKey()); - return; + if (null != storage) { + boolean isSecretKeyNotUpdate = StrUtil.isBlank(encryptSecretKey) || encryptSecretKey + .contains(StringConstants.ASTERISK); + if (isSecretKeyNotUpdate) { + return storage.getSecretKey(); + } } - // 新增时或修改了 SecretKey - String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(newSecretKey)); + // 新增场景,直接解密 SecretKey + String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(encryptSecretKey)); ValidationUtils.throwIfNull(secretKey, "私有密钥解密失败"); ValidationUtils.throwIf(secretKey.length() > 255, "私有密钥长度不能超过 255 个字符"); - req.setSecretKey(secretKey); - } - - /** - * 默认存储是否存在 - * - * @param id ID - * @return 是否存在 - */ - private boolean isDefaultExists(Long id) { - return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).ne(null != id, StorageDO::getId, id).exists(); + return secretKey; } /** diff --git a/continew-webapi/src/main/java/top/continew/admin/controller/common/CommonController.java b/continew-webapi/src/main/java/top/continew/admin/controller/common/CommonController.java index 39879641..4d1be6cd 100644 --- a/continew-webapi/src/main/java/top/continew/admin/controller/common/CommonController.java +++ b/continew-webapi/src/main/java/top/continew/admin/controller/common/CommonController.java @@ -35,8 +35,6 @@ import top.continew.admin.system.enums.OptionCategoryEnum; import top.continew.admin.system.model.query.*; import top.continew.admin.system.model.resp.file.FileUploadResp; import top.continew.admin.system.service.*; -import top.continew.starter.core.constant.StringConstants; -import top.continew.starter.core.util.StrUtils; import top.continew.starter.core.validation.ValidationUtils; import top.continew.starter.extension.crud.model.query.SortQuery; import top.continew.starter.extension.crud.model.resp.LabelValueResp; @@ -71,9 +69,7 @@ public class CommonController { @PostMapping("/file") public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file, String path) throws IOException { ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); - String fixedPath = StrUtils.blankToDefault(path, StringConstants.SLASH, p -> StrUtil - .appendIfMissing(p, StringConstants.SLASH)); - FileInfo fileInfo = fileService.upload(file, fixedPath); + FileInfo fileInfo = fileService.upload(file, path); return FileUploadResp.builder() .id(fileInfo.getId()) .url(fileInfo.getUrl())