From 5ce1cf8680dd91cc9482832ab734168b84d5ec09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com> Date: Sun, 19 Jan 2025 17:27:22 +0800 Subject: [PATCH 1/3] =?UTF-8?q?refactor(system):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=AD=98=E5=82=A8=EF=BC=8C=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E8=A1=A8=EF=BC=8C=E6=94=B9=E4=B8=BA=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- continew-common/pom.xml | 25 ++- .../exception/GlobalExceptionHandler.java | 11 + .../system/config/file/FileRecorderImpl.java | 88 ++------ .../config/file/FileStorageConfigLoader.java | 38 ++-- .../system/config/file/FileStorageInit.java | 148 +++++++++++++ .../system/enums/OptionCategoryEnum.java | 6 + .../admin/system/mapper/StorageMapper.java | 29 --- .../admin/system/model/entity/FileDO.java | 70 +----- .../system/model/query/StorageQuery.java | 53 ----- .../admin/system/model/req/StorageReq.java | 134 ----------- .../admin/system/model/resp/StorageResp.java | 119 ---------- .../admin/system/service/FileService.java | 6 +- .../admin/system/service/StorageService.java | 62 ------ .../system/service/impl/FileServiceImpl.java | 99 ++------- .../service/impl/OptionServiceImpl.java | 18 ++ .../service/impl/StorageServiceImpl.java | 209 ------------------ .../admin/ContiNewAdminApplication.java | 2 - .../controller/common/CommonController.java | 5 +- .../schedule/DemoEnvironmentJob.java | 6 - .../controller/system/StorageController.java | 39 ---- .../db/changelog/mysql/main_data.sql | 17 +- .../db/changelog/mysql/main_table.sql | 30 +-- .../db/changelog/postgresql/main_data.sql | 17 +- .../db/changelog/postgresql/main_table.sql | 51 +---- 24 files changed, 306 insertions(+), 976 deletions(-) create mode 100644 continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java delete mode 100644 continew-module-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java delete mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/query/StorageQuery.java delete mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java delete mode 100644 continew-module-system/src/main/java/top/continew/admin/system/model/resp/StorageResp.java delete mode 100644 continew-module-system/src/main/java/top/continew/admin/system/service/StorageService.java delete mode 100644 continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java delete mode 100644 continew-webapi/src/main/java/top/continew/admin/controller/system/StorageController.java diff --git a/continew-common/pom.xml b/continew-common/pom.xml index 9d4246bd..d9ef7094 100644 --- a/continew-common/pom.xml +++ b/continew-common/pom.xml @@ -29,17 +29,6 @@ sms4j-spring-boot-starter - - - org.dromara.x-file-storage - x-file-storage-spring - - - - com.amazonaws - aws-java-sdk-s3 - - org.freemarker @@ -147,5 +136,19 @@ top.continew continew-starter-json-jackson + + + + + top.continew + continew-starter-storage-local + + + + + + top.continew + continew-starter-storage-oss + \ No newline at end of file diff --git a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java index f2f7562d..ba1714ac 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java @@ -25,6 +25,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; import top.continew.starter.core.exception.BadRequestException; import top.continew.starter.core.exception.BusinessException; @@ -69,6 +70,16 @@ public class GlobalExceptionHandler { return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), "参数 '%s' 类型不匹配".formatted(e.getName())); } + /** + * 拦截文件上传大小超过限制异常 + */ + @ExceptionHandler(MaxUploadSizeExceededException.class) + public R handleMaxUploadSizeExceededException(MaxUploadSizeExceededException e, + HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), "上传文件大小超过限制"); + } + /** * 拦截文件上传异常-超过上传大小限制 */ 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 b15681d7..74a61d79 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,25 +16,19 @@ 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 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.storage.dao.StorageDao; +import top.continew.starter.storage.model.resp.UploadResp; -import java.util.Optional; /** * 文件记录实现类 @@ -43,77 +37,31 @@ import java.util.Optional; * @since 2023/12/24 22:31 */ @Slf4j -@Component @RequiredArgsConstructor -public class FileRecorderImpl implements FileRecorder { - +public class FileRecorderImpl implements StorageDao { private final FileMapper fileMapper; - private final StorageMapper storageMapper; @Override - public boolean save(FileInfo fileInfo) { + public void add(UploadResp uploadResp) { FileDO file = new FileDO(); - String originalFilename = EscapeUtil.unescape(fileInfo.getOriginalFilename()); + file.setStorageCode(uploadResp.getCode()); + String originalFilename = EscapeUtil.unescape(uploadResp.getOriginalFilename()); file.setName(StrUtil.contains(originalFilename, StringConstants.DOT) - ? StrUtil.subBefore(originalFilename, StringConstants.DOT, true) - : originalFilename); - file.setUrl(fileInfo.getUrl()); - file.setSize(fileInfo.getSize()); - file.setExtension(fileInfo.getExt()); + ? StrUtil.subBefore(originalFilename, StringConstants.DOT, true) + : originalFilename); + file.setUrl(uploadResp.getUrl()); + file.setPath(uploadResp.getBasePath()); + file.setSize(uploadResp.getSize()); + file.setThumbnailUrl(uploadResp.getThumbnailUrl()); + file.setThumbnailSize(uploadResp.getThumbnailSize()); + file.setExtension(uploadResp.getExt()); file.setType(FileTypeEnum.getByExtension(file.getExtension())); - file.setThumbnailUrl(fileInfo.getThUrl()); - file.setThumbnailSize(fileInfo.getThSize()); - StorageDO storage = (StorageDO)fileInfo.getAttr().get(ClassUtil.getClassName(StorageDO.class, false)); - file.setStorageId(storage.getId()); - file.setCreateTime(DateUtil.toLocalDateTime(fileInfo.getCreateTime())); + file.setETag(uploadResp.geteTag()); + file.setBucketName(uploadResp.getBucketName()); + file.setCreateTime(uploadResp.getCreateTime()); file.setUpdateUser(UserContextHolder.getUserId()); file.setUpdateTime(file.getCreateTime()); fileMapper.insert(file); - return true; - } - - @Override - public FileInfo getByUrl(String url) { - FileDO file = this.getFileByUrl(url); - if (null == file) { - return null; - } - StorageDO storageDO = storageMapper.lambdaQuery().eq(StorageDO::getId, file.getStorageId()).one(); - return file.toFileInfo(storageDO); - } - - @Override - public boolean delete(String url) { - FileDO file = this.getFileByUrl(url); - return fileMapper.lambdaUpdate().eq(FileDO::getUrl, file.getUrl()).remove(); - } - - @Override - public void update(FileInfo fileInfo) { - /* 不使用分片功能则无需重写 */ - } - - @Override - public void saveFilePart(FilePartInfo filePartInfo) { - /* 不使用分片功能则无需重写 */ } - @Override - public void deleteFilePartByUploadId(String s) { - /* 不使用分片功能则无需重写 */ - } - - /** - * 根据 URL 查询文件 - * - * @param url URL - * @return 文件信息 - */ - private FileDO getFileByUrl(String url) { - Optional fileOptional = fileMapper.lambdaQuery().eq(FileDO::getUrl, url).oneOpt(); - return fileOptional.orElseGet(() -> fileMapper.lambdaQuery() - .likeLeft(FileDO::getUrl, StrUtil.subAfter(url, StringConstants.SLASH, true)) - .oneOpt() - .orElse(null)); - } } \ No newline at end of file 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..64967a45 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 @@ -16,20 +16,21 @@ package top.continew.admin.system.config.file; -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.collection.CollUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; -import top.continew.admin.common.enums.DisEnableStatusEnum; -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; +import top.continew.admin.system.enums.OptionCategoryEnum; +import top.continew.admin.system.mapper.FileMapper; +import top.continew.admin.system.service.OptionService; +import top.continew.starter.storage.client.OssClient; +import top.continew.starter.storage.dao.StorageDao; +import top.continew.starter.storage.manger.StorageManager; +import top.continew.starter.storage.strategy.StorageStrategy; -import java.util.List; +import java.util.Map; /** * 文件存储配置加载器 @@ -42,16 +43,21 @@ import java.util.List; @RequiredArgsConstructor public class FileStorageConfigLoader implements ApplicationRunner { - private final StorageService storageService; + private final OptionService optionService; + private final FileStorageInit fileStorageInit; @Override public void run(ApplicationArguments args) { - StorageQuery query = new StorageQuery(); - query.setStatus(DisEnableStatusEnum.ENABLE); - List storageList = storageService.list(query, null); - if (CollUtil.isEmpty(storageList)) { - return; - } - storageList.forEach(s -> storageService.load(BeanUtil.copyProperties(s, StorageReq.class))); + // 查询存储配置 + Map map = optionService.getByCategory(OptionCategoryEnum.STORAGE); + fileStorageInit.load(map); + } + + /** + * 存储持久层接口本地实现类 + */ + @Bean + public StorageDao storageDao(FileMapper fileMapper) { + return new FileRecorderImpl(fileMapper); } } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java new file mode 100644 index 00000000..f15aea63 --- /dev/null +++ b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.admin.system.config.file; + +import cn.hutool.core.map.MapUtil; +import cn.hutool.extra.spring.SpringUtil; +import org.springframework.stereotype.Component; +import top.continew.admin.system.enums.StorageTypeEnum; +import top.continew.starter.cache.redisson.util.RedisUtils; +import top.continew.starter.storage.client.LocalClient; +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.manger.StorageManager; +import top.continew.starter.storage.model.req.StorageProperties; +import top.continew.starter.storage.strategy.LocalStorageStrategy; +import top.continew.starter.storage.strategy.OssStorageStrategy; +import top.continew.starter.storage.util.StorageUtils; +import top.continew.starter.web.util.SpringWebUtils; + +import java.util.Map; + +/** + * 文件存储初始化 + * + * @author echo + * @date 2024/12/20 11:10 + */ +@Component +public class FileStorageInit { + + public void load(Map map) { + StorageManager.unload(StorageTypeEnum.LOCAL.name()); + StorageManager.unload(StorageTypeEnum.S3.name()); + // 获取默认存储值并缓存 + String storageDefault = cacheDefaultStorage(map); + // 获取本地终端地址 和桶地址 + String localEndpoint = map.get("STORAGE_LOCAL_ENDPOINT"); + String localBucket = map.get("STORAGE_LOCAL_BUCKET"); + + // 构建并加载本地存储配置 + StorageProperties localProperties = buildStorageProperties( + StorageTypeEnum.LOCAL.name(), + localBucket, + storageDefault, + localEndpoint); + // 本地静态资源映射 + SpringWebUtils.registerResourceHandler(MapUtil.of(StorageUtils.createUriWithProtocol(localEndpoint).getPath(), localBucket)); + StorageManager.load( + localProperties.getCode(), + new LocalStorageStrategy( + new LocalClient(localProperties), SpringUtil.getBean(StorageDao.class) + ) + ); + + // 构建并加载 S3 存储配置 + StorageProperties ossProperties = buildStorageProperties( + StorageTypeEnum.S3.name(), + map.get("STORAGE_S3_BUCKET"), + storageDefault, + map.get("STORAGE_S3_ACCESS_KEY"), + map.get("STORAGE_S3_SECRET_KEY"), + map.get("STORAGE_S3_ENDPOINT"), + map.get("STORAGE_S3_REGION")); + + StorageManager.load( + ossProperties.getCode(), new OssStorageStrategy( + new OssClient(ossProperties), SpringUtil.getBean(StorageDao.class) + ) + ); + } + + public void unLoad(String code) { + StorageManager.unload(code); + } + + /** + * 将默认存储值放入缓存 + * + * @param map + * @return {@link String } + */ + private String cacheDefaultStorage(Map map) { + String storageDefault = MapUtil.getStr(map, "STORAGE_DEFAULT"); + RedisUtils.set(StorageConstant.DEFAULT_KEY, storageDefault); + return storageDefault; + } + + /** + * 构建本地存储配置属性 + * + * @param code 存储码 + * @param bucketName 桶名称 + * @param defaultCode 默认存储码 + * @return {@link StorageProperties } + */ + private StorageProperties buildStorageProperties(String code, + String bucketName, + String defaultCode, + String endpoint) { + StorageProperties properties = new StorageProperties(); + properties.setCode(code); + properties.setBucketName(bucketName); + properties.setEndpoint(endpoint); + properties.setIsDefault(code.equals(defaultCode)); + return properties; + } + + /** + * 构建 S3 存储配置属性 + * + * @param code 存储码 + * @param bucketName 桶名称 + * @param defaultCode 默认存储码 + * @param accessKey 访问密钥 + * @param secretKey 秘密密钥 + * @param endpoint 端点 + * @param region 区域 + * @return {@link StorageProperties } + */ + private StorageProperties buildStorageProperties(String code, + String bucketName, + String defaultCode, + String accessKey, + String secretKey, + String endpoint, + String region) { + StorageProperties properties = buildStorageProperties(code, bucketName, defaultCode, endpoint); + properties.setAccessKey(accessKey); + properties.setSecretKey(secretKey); + properties.setRegion(region); + return properties; + } +} diff --git a/continew-module-system/src/main/java/top/continew/admin/system/enums/OptionCategoryEnum.java b/continew-module-system/src/main/java/top/continew/admin/system/enums/OptionCategoryEnum.java index 2533d112..cb19cfb1 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/enums/OptionCategoryEnum.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/enums/OptionCategoryEnum.java @@ -43,4 +43,10 @@ public enum OptionCategoryEnum { * 登录配置 */ LOGIN, + + /** + * 存储配置 + */ + STORAGE, + ; } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java b/continew-module-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java deleted file mode 100644 index 06d8c5f6..00000000 --- a/continew-module-system/src/main/java/top/continew/admin/system/mapper/StorageMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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.admin.system.mapper; - -import top.continew.admin.system.model.entity.StorageDO; -import top.continew.starter.data.mp.base.BaseMapper; - -/** - * 存储 Mapper - * - * @author Charles7c - * @since 2023/12/26 22:09 - */ -public interface StorageMapper extends BaseMapper { -} \ No newline at end of file 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 95ce396a..b15a0105 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,19 +16,12 @@ package top.continew.admin.system.model.entity; -import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; -import lombok.SneakyThrows; -import org.dromara.x.file.storage.core.FileInfo; 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 top.continew.admin.common.model.entity.BaseDO; import java.io.Serial; -import java.net.URL; /** * 文件实体 @@ -79,64 +72,23 @@ public class FileDO extends BaseDO { private String thumbnailUrl; /** - * 存储 ID + * 存储code */ - private Long storageId; + private String storageCode; /** - * 转换为 X-File-Storage 文件信息对象 - * - * @param storageDO 存储桶信息 - * @return X-File-Storage 文件信息对象 + * 基础路径 */ - public FileInfo toFileInfo(StorageDO storageDO) { - FileInfo fileInfo = new FileInfo(); - fileInfo.setUrl(this.url); - fileInfo.setSize(this.size); - 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.setExt(this.extension); - fileInfo.setPlatform(storageDO.getCode()); - fileInfo.setThUrl(this.thumbnailUrl); - fileInfo.setThFilename(StrUtil.contains(this.thumbnailUrl, StringConstants.SLASH) - ? StrUtil.subAfter(this.thumbnailUrl, StringConstants.SLASH, true) - : this.thumbnailUrl); - fileInfo.setThSize(this.thumbnailSize); - return fileInfo; - } + private String path; /** - * 将文件路径处理成资源路径 - * 例如: - * 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.split(relativePath, storageDO.getBucketName()).get(1); - } - return relativePath; - } + private String bucketName; + + /** + * 文件标识 + */ + private String eTag; } diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/query/StorageQuery.java b/continew-module-system/src/main/java/top/continew/admin/system/model/query/StorageQuery.java deleted file mode 100644 index 7dd6f592..00000000 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/query/StorageQuery.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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.admin.system.model.query; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.starter.data.core.annotation.Query; -import top.continew.starter.data.core.enums.QueryType; - -import java.io.Serial; -import java.io.Serializable; - -/** - * 存储查询条件 - * - * @author Charles7c - * @since 2023/12/26 22:09 - */ -@Data -@Schema(description = "存储查询条件") -public class StorageQuery implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 关键词 - */ - @Schema(description = "关键词", example = "本地存储") - @Query(columns = {"name", "code", "description"}, type = QueryType.LIKE) - private String description; - - /** - * 状态 - */ - @Schema(description = "状态", example = "1") - private DisEnableStatusEnum status; -} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java b/continew-module-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java deleted file mode 100644 index 9a529cac..00000000 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/req/StorageReq.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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.admin.system.model.req; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; -import lombok.Data; -import org.hibernate.validator.constraints.Length; -import top.continew.admin.common.constant.RegexConstants; -import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.system.enums.StorageTypeEnum; -import top.continew.admin.system.validation.ValidationGroup; - -import java.io.Serial; -import java.io.Serializable; - -/** - * 存储请求参数 - * - * @author Charles7c - * @since 2023/12/26 22:09 - */ -@Data -@Schema(description = "存储请求参数") -public class StorageReq implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 名称 - */ - @Schema(description = "名称", example = "存储1") - @NotBlank(message = "名称不能为空") - @Length(max = 100, message = "名称长度不能超过 {max} 个字符") - private String name; - - /** - * 编码 - */ - @Schema(description = "编码", example = "local") - @NotBlank(message = "编码不能为空") - @Pattern(regexp = RegexConstants.GENERAL_CODE, message = "编码长度为 2-30 个字符,支持大小写字母、数字、下划线,以字母开头") - private String code; - - /** - * 类型 - */ - @Schema(description = "类型", example = "2") - @NotNull(message = "类型非法") - private StorageTypeEnum type; - - /** - * 访问密钥 - */ - @Schema(description = "访问密钥", example = "") - @Length(max = 255, message = "访问密钥长度不能超过 {max} 个字符") - @NotBlank(message = "访问密钥不能为空", groups = ValidationGroup.Storage.S3.class) - private String accessKey; - - /** - * 私有密钥 - */ - @Schema(description = "私有密钥", example = "") - @NotBlank(message = "私有密钥不能为空", groups = ValidationGroup.Storage.S3.class) - private String secretKey; - - /** - * 终端节点 - */ - @Schema(description = "终端节点", example = "") - @Length(max = 255, message = "终端节点长度不能超过 {max} 个字符") - @NotBlank(message = "终端节点不能为空", groups = ValidationGroup.Storage.S3.class) - private String endpoint; - - /** - * 桶名称 - */ - @Schema(description = "桶名称", example = "C:/continew-admin/data/file/") - @Length(max = 255, message = "桶名称长度不能超过 {max} 个字符") - @NotBlank(message = "桶名称不能为空", groups = ValidationGroup.Storage.S3.class) - @NotBlank(message = "存储路径不能为空", groups = ValidationGroup.Storage.Local.class) - private String bucketName; - - /** - * 域名 - */ - @Schema(description = "域名", example = "http://localhost:8000/file") - @Length(max = 255, message = "域名长度不能超过 {max} 个字符") - @NotBlank(message = "域名不能为空") - private String domain; - - /** - * 排序 - */ - @Schema(description = "排序", example = "1") - private Integer sort; - - /** - * 描述 - */ - @Schema(description = "描述", example = "存储描述") - @Length(max = 200, message = "描述长度不能超过 {max} 个字符") - private String description; - - /** - * 是否为默认存储 - */ - @Schema(description = "是否为默认存储", example = "true") - @NotNull(message = "是否为默认存储不能为空") - private Boolean isDefault; - - /** - * 状态 - */ - @Schema(description = "状态", example = "1") - private DisEnableStatusEnum status; -} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/model/resp/StorageResp.java b/continew-module-system/src/main/java/top/continew/admin/system/model/resp/StorageResp.java deleted file mode 100644 index 439b15bf..00000000 --- a/continew-module-system/src/main/java/top/continew/admin/system/model/resp/StorageResp.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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.admin.system.model.resp; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import top.continew.admin.common.model.resp.BaseDetailResp; -import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.system.enums.StorageTypeEnum; -import top.continew.starter.security.mask.annotation.JsonMask; - -import java.io.Serial; - -/** - * 存储响应信息 - * - * @author Charles7c - * @since 2023/12/26 22:09 - */ -@Data -@Schema(description = "存储响应信息") -public class StorageResp extends BaseDetailResp { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 名称 - */ - @Schema(description = "名称", example = "存储1") - private String name; - - /** - * 编码 - */ - @Schema(description = "编码", example = "local") - private String code; - - /** - * 状态 - */ - @Schema(description = "状态", example = "1") - private DisEnableStatusEnum status; - - /** - * 类型 - */ - @Schema(description = "类型", example = "2") - private StorageTypeEnum type; - - /** - * 访问密钥 - */ - @Schema(description = "访问密钥", example = "") - private String accessKey; - - /** - * 私有密钥 - */ - @Schema(description = "私有密钥", example = "") - @JsonMask(left = 4, right = 3) - private String secretKey; - - /** - * 终端节点 - */ - @Schema(description = "终端节点", example = "") - private String endpoint; - - /** - * 桶名称 - */ - @Schema(description = "桶名称", example = "C:/continew-admin/data/file/") - private String bucketName; - - /** - * 域名 - */ - @Schema(description = "域名", example = "http://localhost:8000/file") - private String domain; - - /** - * 描述 - */ - @Schema(description = "描述", example = "存储描述") - private String description; - - /** - * 是否为默认存储 - */ - @Schema(description = "是否为默认存储", example = "true") - private Boolean isDefault; - - /** - * 排序 - */ - @Schema(description = "排序", example = "1") - private Integer sort; - - @Override - public Boolean getDisabled() { - return this.getIsDefault(); - } - -} \ No newline at end of file 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 a87cbfde..5e650ed1 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 @@ -16,13 +16,13 @@ package top.continew.admin.system.service; -import org.dromara.x.file.storage.core.FileInfo; import org.springframework.web.multipart.MultipartFile; import top.continew.admin.system.model.entity.FileDO; import top.continew.admin.system.model.query.FileQuery; import top.continew.admin.system.model.req.FileReq; import top.continew.admin.system.model.resp.FileResp; import top.continew.admin.system.model.resp.FileStatisticsResp; +import top.continew.admin.system.model.resp.FileUploadResp; import top.continew.starter.data.mp.service.IService; import top.continew.starter.extension.crud.service.BaseService; @@ -42,7 +42,7 @@ public interface FileService extends BaseService, IService { - - /** - * 查询默认存储 - * - * @return 存储信息 - */ - StorageDO getDefaultStorage(); - - /** - * 根据编码查询 - * - * @param code 编码 - * @return 存储信息 - */ - StorageDO getByCode(String code); - - /** - * 加载存储 - * - * @param req 存储信息 - */ - void load(StorageReq req); - - /** - * 卸载存储 - * - * @param req 存储信息 - */ - void unload(StorageReq req); -} \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java index 42419d25..a6d0e31d 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/FileServiceImpl.java @@ -17,20 +17,12 @@ package top.continew.admin.system.service.impl; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.io.file.FileNameUtil; -import cn.hutool.core.util.ClassUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; -import jakarta.annotation.Resource; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.core.FileInfo; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.ProgressListener; -import org.dromara.x.file.storage.core.upload.UploadPretreatment; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import top.continew.admin.system.enums.FileTypeEnum; import top.continew.admin.system.mapper.FileMapper; import top.continew.admin.system.model.entity.FileDO; import top.continew.admin.system.model.entity.StorageDO; @@ -38,18 +30,20 @@ import top.continew.admin.system.model.query.FileQuery; import top.continew.admin.system.model.req.FileReq; import top.continew.admin.system.model.resp.FileResp; import top.continew.admin.system.model.resp.FileStatisticsResp; +import top.continew.admin.system.model.resp.FileUploadResp; import top.continew.admin.system.service.FileService; -import top.continew.admin.system.service.StorageService; import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.core.exception.BusinessException; import top.continew.starter.core.util.StrUtils; import top.continew.starter.core.util.URLUtils; -import top.continew.starter.core.validation.CheckUtils; import top.continew.starter.extension.crud.service.BaseServiceImpl; +import top.continew.starter.storage.manger.StorageManager; +import top.continew.starter.storage.model.resp.UploadResp; +import top.continew.starter.storage.strategy.StorageStrategy; -import java.time.LocalDate; +import java.io.IOException; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; + /** * 文件业务实现 @@ -62,65 +56,31 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class FileServiceImpl extends BaseServiceImpl implements FileService { - private final FileStorageService fileStorageService; - @Resource - private StorageService storageService; @Override protected 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()) { - StorageDO storage = storageService.getById(entry.getKey()); - for (FileDO file : entry.getValue()) { - FileInfo fileInfo = file.toFileInfo(storage); - fileStorageService.delete(fileInfo); - } - } + fileList.forEach(file -> { + StorageStrategy instance = StorageManager.instance(file.getStorageCode()); + instance.delete(file.getBucketName(), file.getPath()); + }); } @Override - public FileInfo upload(MultipartFile file, String storageCode) { - StorageDO storage; + public FileUploadResp upload(MultipartFile file, String storageCode) { + StorageStrategy instance; if (StrUtil.isBlank(storageCode)) { - storage = storageService.getDefaultStorage(); - CheckUtils.throwIfNull(storage, "请先指定默认存储"); + instance = StorageManager.instance(); } else { - storage = storageService.getByCode(storageCode); - CheckUtils.throwIfNotExists(storage, "StorageDO", "Code", storageCode); + instance = StorageManager.instance(storageCode); } - LocalDate today = LocalDate.now(); - String path = today.getYear() + StringConstants.SLASH + today.getMonthValue() + StringConstants.SLASH + today - .getDayOfMonth() + StringConstants.SLASH; - UploadPretreatment uploadPretreatment = fileStorageService.of(file) - .setPlatform(storage.getCode()) - .putAttr(ClassUtil.getClassName(StorageDO.class, false), storage) - .setPath(path); - // 图片文件生成缩略图 - if (FileTypeEnum.IMAGE.getExtensions().contains(FileNameUtil.extName(file.getOriginalFilename()))) { - uploadPretreatment.thumbnail(img -> img.size(100, 100)); + UploadResp uploadResp; + try { + uploadResp = instance.upload(file.getOriginalFilename(), null, file.getInputStream(), file.getContentType(), true); + } catch (IOException e) { + throw new BusinessException("文件上传失败", e); } - uploadPretreatment.setProgressMonitor(new ProgressListener() { - @Override - public void start() { - log.info("开始上传"); - } - - @Override - public void progress(long progressSize, Long allSize) { - log.info("已上传 [{}],总大小 [{}]", progressSize, allSize); - } - - @Override - public void finish() { - log.info("上传结束"); - } - }); - // 处理本地存储文件 URL - FileInfo fileInfo = uploadPretreatment.upload(); - String domain = StrUtil.appendIfMissing(storage.getDomain(), StringConstants.SLASH); - fileInfo.setUrl(URLUtil.normalize(domain + fileInfo.getPath() + fileInfo.getFilename())); - return fileInfo; + return FileUploadResp.builder().url(uploadResp.getUrl()).build(); } @Override @@ -128,7 +88,7 @@ public class FileServiceImpl extends BaseServiceImpl URLUtil - .normalize(prefix + thUrl)); - fileResp.setThumbnailUrl(thumbnailUrl); - fileResp.setStorageName("%s (%s)".formatted(storage.getName(), storage.getCode())); - } - } } \ No newline at end of file diff --git a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java index 3ac3c472..c2a4fc85 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/service/impl/OptionServiceImpl.java @@ -26,6 +26,7 @@ import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWra import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import top.continew.admin.common.constant.CacheConstants; +import top.continew.admin.system.config.file.FileStorageInit; import top.continew.admin.system.enums.OptionCategoryEnum; import top.continew.admin.system.enums.PasswordPolicyEnum; import top.continew.admin.system.mapper.OptionMapper; @@ -57,6 +58,7 @@ import java.util.stream.Collectors; public class OptionServiceImpl implements OptionService { private final OptionMapper baseMapper; + private final FileStorageInit fileStorageInit; @Override public List list(OptionQuery query) { @@ -98,6 +100,7 @@ public class OptionServiceImpl implements OptionService { PasswordPolicyEnum passwordPolicy = PasswordPolicyEnum.valueOf(code); passwordPolicy.validateRange(Integer.parseInt(value), passwordPolicyOptionMap); } + storageReload(options); RedisUtils.deleteByPattern(CacheConstants.OPTION_KEY_PREFIX + StringConstants.ASTERISK); baseMapper.updateById(BeanUtil.copyToList(options, OptionDO.class)); } @@ -138,4 +141,19 @@ public class OptionServiceImpl implements OptionService { RedisUtils.set(CacheConstants.OPTION_KEY_PREFIX + code, value); return mapper.apply(value); } + + /** + * 存储重新加载 + * + * @param options 选项 + */ + private void storageReload(List options) { + Map storage = options.stream() + .filter(option -> option.getCode() != null && option.getCode().startsWith("STORAGE_")) + .collect(Collectors.toMap(OptionReq::getCode, OptionReq::getValue)); + if (ObjectUtil.isNotEmpty(storage)) { + fileStorageInit.load(storage); + } + + } } \ 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 deleted file mode 100644 index 3aa01447..00000000 --- a/continew-module-system/src/main/java/top/continew/admin/system/service/impl/StorageServiceImpl.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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.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; -import jakarta.annotation.Resource; -import lombok.RequiredArgsConstructor; -import org.dromara.x.file.storage.core.FileStorageProperties; -import org.dromara.x.file.storage.core.FileStorageService; -import org.dromara.x.file.storage.core.FileStorageServiceBuilder; -import org.dromara.x.file.storage.core.platform.FileStorage; -import org.springframework.stereotype.Service; -import top.continew.admin.common.enums.DisEnableStatusEnum; -import top.continew.admin.common.util.SecureUtils; -import top.continew.admin.system.enums.StorageTypeEnum; -import top.continew.admin.system.mapper.StorageMapper; -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.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; -import top.continew.starter.web.util.SpringWebUtils; - -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - -/** - * 存储业务实现 - * - * @author Charles7c - * @since 2023/12/26 22:09 - */ -@Service -@RequiredArgsConstructor -public class StorageServiceImpl extends BaseServiceImpl implements StorageService { - - private final FileStorageService fileStorageService; - @Resource - private FileService fileService; - - @Override - public void beforeAdd(StorageReq req) { - this.decodeSecretKey(req, null); - CheckUtils.throwIf(Boolean.TRUE.equals(req.getIsDefault()) && this.isDefaultExists(null), "请先取消原有默认存储"); - String code = req.getCode(); - CheckUtils.throwIf(this.isCodeExists(code, null), "新增失败,[{}] 已存在", code); - this.load(req); - } - - @Override - public void beforeUpdate(StorageReq req, Long id) { - StorageDO oldStorage = super.getById(id); - CheckUtils.throwIfNotEqual(req.getCode(), oldStorage.getCode(), "不允许修改存储编码"); - CheckUtils.throwIfNotEqual(req.getType(), oldStorage.getType(), "不允许修改存储类型"); - DisEnableStatusEnum newStatus = req.getStatus(); - CheckUtils.throwIf(Boolean.TRUE.equals(oldStorage.getIsDefault()) && DisEnableStatusEnum.DISABLE - .equals(newStatus), "[{}] 是默认存储,不允许禁用", oldStorage.getName()); - this.decodeSecretKey(req, oldStorage); - DisEnableStatusEnum oldStatus = oldStorage.getStatus(); - if (Boolean.TRUE.equals(req.getIsDefault())) { - CheckUtils.throwIf(this.isDefaultExists(id), "请先取消原有默认存储"); - CheckUtils.throwIf(!DisEnableStatusEnum.ENABLE.equals(oldStatus) && !DisEnableStatusEnum.ENABLE - .equals(newStatus), "请先启用该存储"); - } - // 先卸载 - if (DisEnableStatusEnum.ENABLE.equals(oldStatus)) { - this.unload(BeanUtil.copyProperties(oldStorage, StorageReq.class)); - } - // 再加载 - if (DisEnableStatusEnum.ENABLE.equals(newStatus)) { - this.load(req); - } - } - - @Override - public void beforeDelete(List 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)); - } - }); - } - - @Override - public StorageDO getDefaultStorage() { - return baseMapper.lambdaQuery().eq(StorageDO::getIsDefault, true).one(); - } - - @Override - public StorageDO getByCode(String code) { - return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).one(); - } - - @Override - public void load(StorageReq req) { - CopyOnWriteArrayList 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.S3.equals(type)) { - ValidationUtils.validate(req, ValidationGroup.Storage.S3.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)); - } - } - - @Override - public void unload(StorageReq req) { - 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())); - } - - /** - * 解密 SecretKey - * - * @param req 请求参数 - * @param storage 存储信息 - */ - private void decodeSecretKey(StorageReq req, StorageDO storage) { - if (!StorageTypeEnum.S3.equals(req.getType())) { - return; - } - // 修改时,如果 SecretKey 不修改,需要手动修正 - String newSecretKey = req.getSecretKey(); - boolean isSecretKeyNotUpdate = StrUtil.isBlank(newSecretKey) || newSecretKey.contains(StringConstants.ASTERISK); - if (null != storage && isSecretKeyNotUpdate) { - req.setSecretKey(storage.getSecretKey()); - return; - } - // 新增时或修改了 SecretKey - String secretKey = ExceptionUtils.exToNull(() -> SecureUtils.decryptByRsaPrivateKey(newSecretKey)); - 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(); - } - - /** - * 编码是否存在 - * - * @param code 编码 - * @param id ID - * @return 是否存在 - */ - private boolean isCodeExists(String code, Long id) { - return baseMapper.lambdaQuery().eq(StorageDO::getCode, code).ne(null != id, StorageDO::getId, id).exists(); - } -} \ No newline at end of file diff --git a/continew-webapi/src/main/java/top/continew/admin/ContiNewAdminApplication.java b/continew-webapi/src/main/java/top/continew/admin/ContiNewAdminApplication.java index 5c7fa8f5..17d9ffa1 100644 --- a/continew-webapi/src/main/java/top/continew/admin/ContiNewAdminApplication.java +++ b/continew-webapi/src/main/java/top/continew/admin/ContiNewAdminApplication.java @@ -25,7 +25,6 @@ import com.github.xiaoymin.knife4j.spring.configuration.Knife4jProperties; import io.swagger.v3.oas.annotations.Hidden; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.dromara.x.file.storage.spring.EnableFileStorage; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; @@ -45,7 +44,6 @@ import top.continew.starter.web.model.R; * @since 2022/12/8 23:15 */ @Slf4j -@EnableFileStorage @EnableMethodCache(basePackages = "top.continew.admin") @EnableGlobalResponse @EnableCrudRestController 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 07301ef8..81a9293b 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 @@ -26,7 +26,6 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; -import org.dromara.x.file.storage.core.FileInfo; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; @@ -66,9 +65,9 @@ public class CommonController { @Operation(summary = "上传文件", description = "上传文件") @PostMapping("/file") - public FileUploadResp upload(@NotNull(message = "文件不能为空") MultipartFile file) { + public FileUploadResp upload(@NotNull(message = "文件不能为空") @RequestPart("file") MultipartFile file) { ValidationUtils.throwIf(file::isEmpty, "文件不能为空"); - FileInfo fileInfo = fileService.upload(file); + FileUploadResp fileInfo = fileService.upload(file); return FileUploadResp.builder().url(fileInfo.getUrl()).build(); } diff --git a/continew-webapi/src/main/java/top/continew/admin/controller/schedule/DemoEnvironmentJob.java b/continew-webapi/src/main/java/top/continew/admin/controller/schedule/DemoEnvironmentJob.java index 62365a16..15c9bb07 100644 --- a/continew-webapi/src/main/java/top/continew/admin/controller/schedule/DemoEnvironmentJob.java +++ b/continew-webapi/src/main/java/top/continew/admin/controller/schedule/DemoEnvironmentJob.java @@ -46,7 +46,6 @@ public class DemoEnvironmentJob { private final DictItemMapper dictItemMapper; private final DictMapper dictMapper; - private final StorageMapper storageMapper; private final NoticeMapper noticeMapper; private final MessageMapper messageMapper; private final MessageUserMapper messageUserMapper; @@ -83,8 +82,6 @@ public class DemoEnvironmentJob { this.log(dictItemCount, "字典项"); Long dictCount = dictMapper.lambdaQuery().gt(DictDO::getId, DELETE_FLAG).count(); this.log(dictCount, "字典"); - Long storageCount = storageMapper.lambdaQuery().gt(StorageDO::getId, DELETE_FLAG).count(); - this.log(storageCount, "存储"); Long noticeCount = noticeMapper.lambdaQuery().gt(NoticeDO::getId, DELETE_FLAG).count(); this.log(noticeCount, "公告"); Long messageCount = messageMapper.lambdaQuery().count(); @@ -110,9 +107,6 @@ public class DemoEnvironmentJob { this.clean(dictCount, "字典", CacheConstants.DICT_KEY_PREFIX, () -> dictMapper.lambdaUpdate() .gt(DictDO::getId, DELETE_FLAG) .remove()); - this.clean(storageCount, "存储", null, () -> storageMapper.lambdaUpdate() - .gt(StorageDO::getId, DELETE_FLAG) - .remove()); this.clean(noticeCount, "公告", null, () -> noticeMapper.lambdaUpdate() .gt(NoticeDO::getId, DELETE_FLAG) .remove()); diff --git a/continew-webapi/src/main/java/top/continew/admin/controller/system/StorageController.java b/continew-webapi/src/main/java/top/continew/admin/controller/system/StorageController.java deleted file mode 100644 index e6d5f3c4..00000000 --- a/continew-webapi/src/main/java/top/continew/admin/controller/system/StorageController.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * 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.admin.controller.system; - -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.RestController; -import top.continew.admin.common.controller.BaseController; -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; -import top.continew.starter.extension.crud.annotation.CrudRequestMapping; -import top.continew.starter.extension.crud.enums.Api; - -/** - * 存储管理 API - * - * @author Charles7c - * @since 2023/12/26 22:09 - */ -@Tag(name = "存储管理 API") -@RestController -@CrudRequestMapping(value = "/system/storage", api = {Api.PAGE, Api.DETAIL, Api.ADD, Api.UPDATE, Api.DELETE}) -public class StorageController extends BaseController { -} \ No newline at end of file diff --git a/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql b/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql index 76973267..ebee3429 100644 --- a/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql +++ b/continew-webapi/src/main/resources/db/changelog/mysql/main_data.sql @@ -180,7 +180,15 @@ VALUES (19, 'MAIL', '密码', 'MAIL_PASSWORD', NULL, NULL, NULL), (20, 'MAIL', '是否启用SSL', 'MAIL_SSL_ENABLED', NULL, '1', NULL), (21, 'MAIL', 'SSL端口', 'MAIL_SSL_PORT', NULL, '465', NULL), -(22, 'LOGIN', '是否启用验证码', 'LOGIN_CAPTCHA_ENABLED', NULL, '1', '是否启用验证码(1:是;0:否)'); +(22, 'LOGIN', '是否启用验证码', 'LOGIN_CAPTCHA_ENABLED', NULL, '1', '是否启用验证码(1:是;0:否)'), +(23, 'STORAGE', '默认存储', 'STORAGE_DEFAULT', 'S3', 'LOCAL', '默认存储'), +(24, 'STORAGE', '本地存储桶', 'STORAGE_LOCAL_BUCKET', 'F:\\file', '/Users/echo/Downloads/bdca/2024', '本地储桶-绝对路径'), +(25, 'STORAGE', '本地终端节点', 'STORAGE_LOCAL_ENDPOINT', 'localhost:8000/file', 'localhost:8000', '本地后端映射地址'), +(26, 'STORAGE', 'S3访问密钥', 'STORAGE_S3_ACCESS_KEY', 'xxxx', 'xxxx', 'S3存储服务的访问密钥'), +(27, 'STORAGE', 'S3私有密钥', 'STORAGE_S3_SECRET_KEY', 'xxxx', 'xxxx', 'S3存储服务的私有密钥'), +(28, 'STORAGE', 'S3存储桶', 'STORAGE_S3_BUCKET', 'continew', 'continew', 'S3存储服务的存储桶名称'), +(29, 'STORAGE', 'S3终端节点', 'STORAGE_S3_ENDPOINT', '192.168.20.222:50000', '192.168.20.222:50000', 'S3存储服务的终端节点'), +(30, 'STORAGE', 'S3作用域', 'STORAGE_S3_REGION', 'cn-hangzhou', 'cn-hangzhou', 'S3存储服务的作用域/区域'); -- 初始化默认字典 INSERT INTO `sys_dict` @@ -238,13 +246,6 @@ VALUES -- 初始化默认角色和部门关联数据 INSERT INTO `sys_role_dept` (`role_id`, `dept_id`) VALUES (547888897925840927, 547887852587843593); --- 初始化默认存储 -INSERT INTO `sys_storage` -(`id`, `name`, `code`, `type`, `access_key`, `secret_key`, `endpoint`, `bucket_name`, `domain`, `description`, `is_default`, `sort`, `status`, `create_user`, `create_time`) -VALUES -(1, '开发环境', 'local_dev', 2, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file', '本地存储', b'1', 1, 1, 1, NOW()), -(2, '生产环境', 'local_prod', 2, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file', '本地存储', b'0', 2, 2, 1, NOW()); - -- 初始化客户端数据 INSERT INTO `sys_client` (`id`, `client_id`, `client_key`, `client_secret`, `auth_type`, `client_type`, `active_timeout`, `timeout`, `status`, `create_user`, `create_time`) diff --git a/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql b/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql index d76c5be3..34217794 100644 --- a/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql +++ b/continew-webapi/src/main/resources/db/changelog/mysql/main_table.sql @@ -250,29 +250,6 @@ CREATE TABLE IF NOT EXISTS `sys_notice` ( INDEX `idx_update_user`(`update_user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公告表'; -CREATE TABLE IF NOT EXISTS `sys_storage` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', - `name` varchar(100) NOT NULL COMMENT '名称', - `code` varchar(30) NOT NULL COMMENT '编码', - `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:兼容S3协议存储;2:本地存储)', - `access_key` varchar(255) DEFAULT NULL COMMENT 'Access Key(访问密钥)', - `secret_key` varchar(255) DEFAULT NULL COMMENT 'Secret Key(私有密钥)', - `endpoint` varchar(255) DEFAULT NULL COMMENT 'Endpoint(终端节点)', - `bucket_name` varchar(255) DEFAULT NULL COMMENT '桶名称', - `domain` varchar(255) NOT NULL DEFAULT '' COMMENT '域名', - `description` varchar(200) DEFAULT NULL COMMENT '描述', - `is_default` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否为默认存储', - `sort` int NOT NULL DEFAULT 999 COMMENT '排序', - `status` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '状态(1:启用;2:禁用)', - `create_user` bigint(20) NOT NULL COMMENT '创建人', - `create_time` datetime NOT NULL COMMENT '创建时间', - `update_user` bigint(20) DEFAULT NULL COMMENT '修改人', - `update_time` datetime DEFAULT NULL COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE INDEX `uk_code`(`code`), - INDEX `idx_create_user`(`create_user`), - INDEX `idx_update_user`(`update_user`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='存储表'; CREATE TABLE IF NOT EXISTS `sys_file` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', @@ -280,10 +257,13 @@ CREATE TABLE IF NOT EXISTS `sys_file` ( `size` bigint(20) NOT NULL COMMENT '大小(字节)', `url` varchar(512) NOT NULL COMMENT 'URL', `extension` varchar(100) DEFAULT NULL COMMENT '扩展名', + `e_tag` varchar(100) DEFAULT NULL COMMENT '文件唯一标识', `thumbnail_size` bigint(20) DEFAULT NULL COMMENT '缩略图大小(字节)', `thumbnail_url` varchar(512) DEFAULT NULL COMMENT '缩略图URL', `type` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:其他;2:图片;3:文档;4:视频;5:音频)', - `storage_id` bigint(20) NOT NULL COMMENT '存储ID', + `storage_code` varchar(255) DEFAULT NULL COMMENT '存储唯一标识', + `bucket_name` varchar(255) DEFAULT NULL COMMENT '存储桶名称', + `path` varchar(512) DEFAULT NULL COMMENT '基础路径', `create_user` bigint(20) NOT NULL COMMENT '创建人', `create_time` datetime NOT NULL COMMENT '创建时间', `update_user` bigint(20) NOT NULL COMMENT '修改人', @@ -293,7 +273,7 @@ CREATE TABLE IF NOT EXISTS `sys_file` ( INDEX `idx_type`(`type`), INDEX `idx_create_user`(`create_user`), INDEX `idx_update_user`(`update_user`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文件表'; CREATE TABLE IF NOT EXISTS `sys_client` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', diff --git a/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql b/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql index 2863e35f..510fe316 100644 --- a/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql +++ b/continew-webapi/src/main/resources/db/changelog/postgresql/main_data.sql @@ -180,7 +180,15 @@ VALUES (19, 'MAIL', '密码', 'MAIL_PASSWORD', NULL, NULL, NULL), (20, 'MAIL', '是否启用SSL', 'MAIL_SSL_ENABLED', NULL, '1', NULL), (21, 'MAIL', 'SSL端口', 'MAIL_SSL_PORT', NULL, '465', NULL), -(22, 'LOGIN', '是否启用验证码', 'LOGIN_CAPTCHA_ENABLED', NULL, '1', '是否启用验证码(1:是;0:否)'); +(22, 'LOGIN', '是否启用验证码', 'LOGIN_CAPTCHA_ENABLED', NULL, '1', '是否启用验证码(1:是;0:否)'), +(23, 'STORAGE', '默认存储', 'STORAGE_DEFAULT', 'S3', 'LOCAL', '默认存储'), +(24, 'STORAGE', '本地存储桶', 'STORAGE_LOCAL_BUCKET', 'F:\\file', '/Users/echo/Downloads/bdca/2024', '本地储桶-绝对路径'), +(25, 'STORAGE', '本地终端节点', 'STORAGE_LOCAL_ENDPOINT', 'localhost:8000/file', 'localhost:8000', '本地后端映射地址'), +(26, 'STORAGE', 'S3访问密钥', 'STORAGE_S3_ACCESS_KEY', 'xxxx', 'xxxx', 'S3存储服务的访问密钥'), +(27, 'STORAGE', 'S3私有密钥', 'STORAGE_S3_SECRET_KEY', 'xxxx', 'xxxx', 'S3存储服务的私有密钥'), +(28, 'STORAGE', 'S3存储桶', 'STORAGE_S3_BUCKET', 'continew', 'continew', 'S3存储服务的存储桶名称'), +(29, 'STORAGE', 'S3终端节点', 'STORAGE_S3_ENDPOINT', '192.168.20.222:50000', '192.168.20.222:50000', 'S3存储服务的终端节点'), +(30, 'STORAGE', 'S3作用域', 'STORAGE_S3_REGION', 'cn-hangzhou', 'cn-hangzhou', 'S3存储服务的作用域/区域'); -- 初始化默认字典 INSERT INTO "sys_dict" @@ -238,13 +246,6 @@ VALUES -- 初始化默认角色和部门关联数据 INSERT INTO "sys_role_dept" ("role_id", "dept_id") VALUES (547888897925840927, 547887852587843593); --- 初始化默认存储 -INSERT INTO "sys_storage" -("id", "name", "code", "type", "access_key", "secret_key", "endpoint", "bucket_name", "domain", "description", "is_default", "sort", "status", "create_user", "create_time") -VALUES -(1, '开发环境', 'local_dev', 2, NULL, NULL, NULL, 'C:/continew-admin/data/file/', 'http://localhost:8000/file', '本地存储', true, 1, 1, 1, NOW()), -(2, '生产环境', 'local_prod', 2, NULL, NULL, NULL, '../data/file/', 'http://api.continew.top/file', '本地存储', false, 2, 2, 1, NOW()); - -- 初始化客户端数据 INSERT INTO "sys_client" ("id", "client_id", "client_key", "client_secret", "auth_type", "client_type", "active_timeout", "timeout", "status", "create_user", "create_time") diff --git a/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql b/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql index 7bb274d2..62ca6d77 100644 --- a/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql +++ b/continew-webapi/src/main/resources/db/changelog/postgresql/main_table.sql @@ -417,47 +417,6 @@ COMMENT ON COLUMN "sys_notice"."update_user" IS '修改人'; COMMENT ON COLUMN "sys_notice"."update_time" IS '修改时间'; COMMENT ON TABLE "sys_notice" IS '公告表'; -CREATE TABLE IF NOT EXISTS "sys_storage" ( - "id" int8 NOT NULL, - "name" varchar(100) NOT NULL, - "code" varchar(30) NOT NULL, - "type" int2 NOT NULL DEFAULT 1, - "access_key" varchar(255) DEFAULT NULL, - "secret_key" varchar(255) DEFAULT NULL, - "endpoint" varchar(255) DEFAULT NULL, - "bucket_name" varchar(255) DEFAULT NULL, - "domain" varchar(255) NOT NULL DEFAULT '', - "description" varchar(200) DEFAULT NULL, - "is_default" bool NOT NULL DEFAULT false, - "sort" int4 NOT NULL DEFAULT 999, - "status" int2 NOT NULL DEFAULT 1, - "create_user" int8 NOT NULL, - "create_time" timestamp NOT NULL, - "update_user" int8 DEFAULT NULL, - "update_time" timestamp DEFAULT NULL, - PRIMARY KEY ("id") -); -CREATE UNIQUE INDEX "uk_storage_code" ON "sys_storage" ("code"); -CREATE INDEX "idx_storage_create_user" ON "sys_storage" ("create_user"); -CREATE INDEX "idx_storage_update_user" ON "sys_storage" ("update_user"); -COMMENT ON COLUMN "sys_storage"."id" IS 'ID'; -COMMENT ON COLUMN "sys_storage"."name" IS '名称'; -COMMENT ON COLUMN "sys_storage"."code" IS '编码'; -COMMENT ON COLUMN "sys_storage"."type" IS '类型(1:兼容S3协议存储;2:本地存储)'; -COMMENT ON COLUMN "sys_storage"."access_key" IS 'Access Key(访问密钥)'; -COMMENT ON COLUMN "sys_storage"."secret_key" IS 'Secret Key(私有密钥)'; -COMMENT ON COLUMN "sys_storage"."endpoint" IS 'Endpoint(终端节点)'; -COMMENT ON COLUMN "sys_storage"."bucket_name" IS '桶名称'; -COMMENT ON COLUMN "sys_storage"."domain" IS '域名'; -COMMENT ON COLUMN "sys_storage"."description" IS '描述'; -COMMENT ON COLUMN "sys_storage"."is_default" IS '是否为默认存储'; -COMMENT ON COLUMN "sys_storage"."sort" IS '排序'; -COMMENT ON COLUMN "sys_storage"."status" IS '状态(1:启用;2:禁用)'; -COMMENT ON COLUMN "sys_storage"."create_user" IS '创建人'; -COMMENT ON COLUMN "sys_storage"."create_time" IS '创建时间'; -COMMENT ON COLUMN "sys_storage"."update_user" IS '修改人'; -COMMENT ON COLUMN "sys_storage"."update_time" IS '修改时间'; -COMMENT ON TABLE "sys_storage" IS '存储表'; CREATE TABLE IF NOT EXISTS "sys_file" ( "id" int8 NOT NULL, @@ -468,11 +427,14 @@ CREATE TABLE IF NOT EXISTS "sys_file" ( "thumbnail_size" int8 DEFAULT NULL, "thumbnail_url" varchar(512) DEFAULT NULL, "type" int2 NOT NULL DEFAULT 1, - "storage_id" int8 NOT NULL, "create_user" int8 NOT NULL, "create_time" timestamp NOT NULL, "update_user" int8 NOT NULL, "update_time" timestamp NOT NULL, + "e_tag" varchar(100) DEFAULT NULL, + "storage_code" varchar(255) DEFAULT NULL, + "bucket_name" varchar(255) DEFAULT NULL, + "path" varchar(512) DEFAULT NULL, PRIMARY KEY ("id") ); CREATE INDEX "idx_file_url" ON "sys_file" ("url"); @@ -487,11 +449,14 @@ COMMENT ON COLUMN "sys_file"."extension" IS '扩展名'; COMMENT ON COLUMN "sys_file"."thumbnail_size" IS '缩略图大小(字节)'; COMMENT ON COLUMN "sys_file"."thumbnail_url" IS '缩略图URL'; COMMENT ON COLUMN "sys_file"."type" IS '类型(1:其他;2:图片;3:文档;4:视频;5:音频)'; -COMMENT ON COLUMN "sys_file"."storage_id" IS '存储ID'; COMMENT ON COLUMN "sys_file"."create_user" IS '创建人'; COMMENT ON COLUMN "sys_file"."create_time" IS '创建时间'; COMMENT ON COLUMN "sys_file"."update_user" IS '修改人'; COMMENT ON COLUMN "sys_file"."update_time" IS '修改时间'; +COMMENT ON COLUMN "sys_file"."e_tag" IS '文件唯一标识'; +COMMENT ON COLUMN "sys_file"."storage_code" IS '存储唯一标识'; +COMMENT ON COLUMN "sys_file"."bucket_name" IS '存储桶名称'; +COMMENT ON COLUMN "sys_file"."path" IS '基础路径'; COMMENT ON TABLE "sys_file" IS '文件表'; CREATE TABLE IF NOT EXISTS "sys_client" ( -- Gitee From ac88614eff12ec8bf3c45ffb24542a2e5bfdfd6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com> Date: Sat, 8 Feb 2025 11:01:46 +0800 Subject: [PATCH 2/3] =?UTF-8?q?chore(common):=E5=85=A8=E5=B1=80=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E5=A4=84=E7=90=86=E5=A2=9E=E5=8A=A0=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E4=B8=8D=E5=AD=98=E5=9C=A8=E5=92=8C=E4=B8=8D=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E7=9A=84=E5=BC=82=E5=B8=B8=E6=96=B9=E6=B3=95,doc=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=A2=9E=E5=8A=A0=E8=BF=87=E6=BB=A4=E9=89=B4=E6=9D=83?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE=E5=92=8C=20sa-token=20?= =?UTF-8?q?=E6=9D=83=E9=99=90=E7=A0=81=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doc/GlobalAuthenticationCustomizer.java | 216 ++++++++++++++++++ .../doc/GlobalDescriptionCustomizer.java | 53 +++++ .../OperationDescriptionCustomizer.java | 164 +++++++++++++ .../exception/GlobalExceptionHandler.java | 25 ++ 4 files changed, 458 insertions(+) create mode 100644 continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java create mode 100644 continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java create mode 100644 continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java new file mode 100644 index 00000000..5e2dc67b --- /dev/null +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java @@ -0,0 +1,216 @@ + +package top.continew.admin.common.config.doc; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.core.map.MapUtil; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.PathItem; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springdoc.core.customizers.GlobalOpenApiCustomizer; +import org.springframework.aop.support.AopUtils; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.bind.annotation.*; +import top.continew.starter.apidoc.autoconfigure.SpringDocExtensionProperties; +import top.continew.starter.auth.satoken.autoconfigure.SaTokenExtensionProperties; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; + +import java.lang.reflect.Method; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 全局鉴权参数配置器 + * + * @author echo + * @date 2024/12/31 13:36 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GlobalAuthenticationCustomizer implements GlobalOpenApiCustomizer { + + /** + * SpringDoc 扩展配置属性 + */ + private final SpringDocExtensionProperties properties; + + /** + * Sa-Token 配置属性 + */ + private final SaTokenExtensionProperties saTokenExtensionProperties; + + /** + * Spring 应用上下文 + */ + private final ApplicationContext context; + + /** + * 路径匹配器,用于支持通配符匹配 + */ + private final AntPathMatcher pathMatcher = new AntPathMatcher(); + + /** + * 定制 OpenAPI 文档 + * + * @param openApi 当前 OpenAPI 对象 + */ + @Override + public void customise(OpenAPI openApi) { + if (openApi.getPaths() == null) { + return; + } + + // 收集需要排除的路径(包括 Sa-Token 配置中的排除路径和 @SaIgnore 注解路径) + Set excludedPaths = collectExcludedPaths(); + + // 遍历所有路径,为需要鉴权的路径添加安全认证配置 + openApi.getPaths().forEach((path, pathItem) -> { + if (isPathExcluded(path, excludedPaths)) { + // 路径在排除列表中,跳过处理 + return; + } + // 为路径添加安全认证参数 + addAuthenticationParameters(pathItem); + }); + } + + /** + * 收集所有需要排除的路径 + * + * @return 排除路径集合 + */ + private Set collectExcludedPaths() { + Set excludedPaths = new HashSet<>(); + excludedPaths.addAll(Arrays.asList(saTokenExtensionProperties.getSecurity().getExcludes())); + excludedPaths.addAll(resolveSaIgnorePaths()); + return excludedPaths; + } + + /** + * 为路径项添加认证参数 + * + * @param pathItem 当前路径项 + */ + private void addAuthenticationParameters(PathItem pathItem) { + Components components = properties.getComponents(); + if (components == null || MapUtil.isEmpty(components.getSecuritySchemes())) { + return; + } + Map securitySchemes = components.getSecuritySchemes(); + List schemeNames = securitySchemes.values().stream().map(SecurityScheme::getName).toList(); + pathItem.readOperations().forEach(operation -> { + SecurityRequirement securityRequirement = new SecurityRequirement(); + schemeNames.forEach(securityRequirement::addList); + operation.addSecurityItem(securityRequirement); + }); + } + + /** + * 解析所有带有 @SaIgnore 注解的路径 + * + * @return 被忽略的路径集合 + */ + private Set resolveSaIgnorePaths() { + // 获取所有标注 @RestController 的 Bean + Map controllers = context.getBeansWithAnnotation(RestController.class); + Set ignoredPaths = new HashSet<>(); + + // 遍历所有控制器,解析 @SaIgnore 注解路径 + controllers.values().forEach(controllerBean -> { + Class controllerClass = AopUtils.getTargetClass(controllerBean); + List classPaths = getClassPaths(controllerClass); + + // 类级别的 @SaIgnore 注解 + if (controllerClass.isAnnotationPresent(SaIgnore.class)) { + classPaths.forEach(classPath -> ignoredPaths.add(classPath + "/**")); + } + + // 方法级别的 @SaIgnore 注解 + Arrays.stream(controllerClass.getDeclaredMethods()) + .filter(method -> method.isAnnotationPresent(SaIgnore.class)) + .forEach(method -> ignoredPaths.addAll(combinePaths(classPaths, getMethodPaths(method)))); + }); + + return ignoredPaths; + } + + /** + * 获取类上的所有路径 + * + * @param controller 控制器类 + * @return 类路径列表 + */ + private List getClassPaths(Class controller) { + List classPaths = new ArrayList<>(); + // 处理 @RequestMapping 注解 + if (controller.isAnnotationPresent(RequestMapping.class)) { + RequestMapping mapping = controller.getAnnotation(RequestMapping.class); + classPaths.addAll(Arrays.asList(mapping.value())); + } + // 处理 @CrudRequestMapping 注解 + if (controller.isAnnotationPresent(CrudRequestMapping.class)) { + CrudRequestMapping mapping = controller.getAnnotation(CrudRequestMapping.class); + if (!mapping.value().isEmpty()) { + classPaths.add(mapping.value()); + } + } + return classPaths; + } + + /** + * 获取方法上的所有路径 + * + * @param method 控制器方法 + * @return 方法路径列表 + */ + private List getMethodPaths(Method method) { + List methodPaths = new ArrayList<>(); + + // 检查方法上的各种映射注解 + if (method.isAnnotationPresent(GetMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(GetMapping.class).value())); + } else if (method.isAnnotationPresent(PostMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(PostMapping.class).value())); + } else if (method.isAnnotationPresent(PutMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(PutMapping.class).value())); + } else if (method.isAnnotationPresent(DeleteMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(DeleteMapping.class).value())); + } else if (method.isAnnotationPresent(RequestMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(RequestMapping.class).value())); + } else if (method.isAnnotationPresent(PatchMapping.class)) { + methodPaths.addAll(Arrays.asList(method.getAnnotation(PatchMapping.class).value())); + } + + return methodPaths; + } + + /** + * 组合类路径和方法路径 + * + * @param classPaths 类路径列表 + * @param methodPaths 方法路径列表 + * @return 完整路径集合 + */ + private Set combinePaths(List classPaths, List methodPaths) { + return classPaths.stream() + .flatMap(classPath -> methodPaths.stream().map(methodPath -> classPath + methodPath)) + .collect(Collectors.toSet()); + } + + /** + * 检查路径是否在排除列表中 + * + * @param path 当前路径 + * @param excludedPaths 排除路径集合,支持通配符 + * @return 是否匹配排除规则 + */ + private boolean isPathExcluded(String path, Set excludedPaths) { + return excludedPaths.stream().anyMatch(pattern -> pathMatcher.match(pattern, path)); + } +} diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java new file mode 100644 index 00000000..b9a948ba --- /dev/null +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java @@ -0,0 +1,53 @@ +package top.continew.admin.common.config.doc; + +import cn.hutool.core.util.StrUtil; +import io.swagger.v3.oas.models.Operation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.StringUtils; +import org.springdoc.core.customizers.GlobalOperationCustomizer; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import top.continew.admin.common.config.doc.customizer.OperationDescriptionCustomizer; + +import java.util.ArrayList; +import java.util.List; + +/** + * 全局描述定制器 - 处理 sa-token 的注解权限码 + * + * @author echo + * @date 2025/01/24 14:59 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class GlobalDescriptionCustomizer implements GlobalOperationCustomizer { + + + @Override + public Operation customize(Operation operation, HandlerMethod handlerMethod) { + + // 将sa-token 注解数据添加到operation的描述中 + // 权限 + List noteList = new ArrayList<>(new OperationDescriptionCustomizer().getPermission(handlerMethod)); + + // 如果注解数据列表为空,直接返回原 operation + if (noteList.isEmpty()) { + return operation; + } + // 拼接注解数据为字符串 + String noteStr = StrUtil.join("
", noteList); + // 获取原描述 + String originalDescription = operation.getDescription(); + // 根据原描述是否为空,更新描述 + String newDescription = StringUtils.isNotEmpty(originalDescription) + ? originalDescription + "
" + noteStr + : noteStr; + + // 设置新描述 + operation.setDescription(newDescription); + + return operation; + } +} diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java new file mode 100644 index 00000000..ac21985f --- /dev/null +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java @@ -0,0 +1,164 @@ +package top.continew.admin.common.config.doc.customizer; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaCheckRole; +import cn.dev33.satoken.annotation.SaMode; +import cn.hutool.core.text.CharSequenceUtil; +import org.springframework.web.method.HandlerMethod; +import top.continew.starter.core.constant.StringConstants; +import top.continew.starter.extension.crud.annotation.CrudApi; +import top.continew.starter.extension.crud.annotation.CrudRequestMapping; +import top.continew.starter.extension.crud.enums.Api; + +import java.lang.annotation.Annotation; +import java.util.ArrayList; +import java.util.List; + +/** + * Operation 描述定制器 处理 sa-token 鉴权标识符 + * + * @author echo + * @Date 2024/06/14 11:18 + **/ +public class OperationDescriptionCustomizer { + + /** + * 获取 sa-token 注解信息 + * + * @param handlerMethod 处理程序方法 + * @return 包含权限和角色校验信息的列表 + */ + public List getPermission(HandlerMethod handlerMethod) { + List values = new ArrayList<>(); + + // 获取权限校验信息 + String permissionInfo = getAnnotationInfo(handlerMethod, SaCheckPermission.class, "权限校验:"); + if (!permissionInfo.isEmpty()) { + values.add(permissionInfo); + } + + // 获取角色校验信息 + String roleInfo = getAnnotationInfo(handlerMethod, SaCheckRole.class, "角色校验:"); + if (!roleInfo.isEmpty()) { + values.add(roleInfo); + } + + // 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息 + String crudPermissionInfo = getCrudPermissionInfo(handlerMethod); + if (!crudPermissionInfo.isEmpty()) { + values.add(crudPermissionInfo); + } + + return values; + } + + /** + * 获取类和方法上指定注解的信息 + * + * @param handlerMethod 处理程序方法 + * @param annotationClass 注解类 + * @param title 信息标题 + * @param 注解类型 + * @return 拼接好的注解信息字符串 + */ + @SuppressWarnings("unchecked") + private String getAnnotationInfo(HandlerMethod handlerMethod, Class annotationClass, String title) { + StringBuilder infoBuilder = new StringBuilder(); + + // 获取类上的注解 + A classAnnotation = handlerMethod.getBeanType().getAnnotation(annotationClass); + if (classAnnotation != null) { + appendAnnotationInfo(infoBuilder, "类:", classAnnotation); + } + + // 获取方法上的注解 + A methodAnnotation = handlerMethod.getMethodAnnotation(annotationClass); + if (methodAnnotation != null) { + appendAnnotationInfo(infoBuilder, "方法:", methodAnnotation); + } + + // 如果有注解信息,添加标题 + if (!infoBuilder.isEmpty()) { + infoBuilder.insert(0, "" + title + "
"); + } + + return infoBuilder.toString(); + } + + /** + * 拼接注解信息到 StringBuilder 中 + * + * @param builder 用于拼接信息的 StringBuilder + * @param prefix 前缀信息,如 "类:" 或 "方法:" + * @param annotation 注解对象 + */ + private void appendAnnotationInfo(StringBuilder builder, String prefix, Annotation annotation) { + String[] values = null; + SaMode mode = null; + String type = ""; + String[] orRole = new String[0]; + + if (annotation instanceof SaCheckPermission checkPermission) { + values = checkPermission.value(); + mode = checkPermission.mode(); + type = checkPermission.type(); + orRole = checkPermission.orRole(); + } else if (annotation instanceof SaCheckRole checkRole) { + values = checkRole.value(); + mode = checkRole.mode(); + type = checkRole.type(); + } + + if (values != null && mode != null) { + builder.append(""); + builder.append(prefix); + if (!type.isEmpty()) { + builder.append("(类型:").append(type).append(")"); + } + builder.append(getAnnotationNote(values, mode)); + if (orRole.length > 0) { + builder.append(" 或 角色校验(").append(getAnnotationNote(orRole, mode)).append(")"); + } + builder.append("
"); + } + } + + /** + * 根据注解的模式拼接注解值 + * + * @param values 注解的值数组 + * @param mode 注解的模式(AND 或 OR) + * @return 拼接好的注解值字符串 + */ + private String getAnnotationNote(String[] values, SaMode mode) { + if (mode.equals(SaMode.AND)) { + return String.join(" 且 ", values); + } else { + return String.join(" 或 ", values); + } + } + + /** + * 处理 CrudRequestMapping 和 CrudApi 注解生成的权限信息 + * + * @param handlerMethod 处理程序方法 + * @return 拼接好的权限信息字符串 + */ + private String getCrudPermissionInfo(HandlerMethod handlerMethod) { + CrudRequestMapping crudRequestMapping = handlerMethod.getBeanType().getAnnotation(CrudRequestMapping.class); + CrudApi crudApi = handlerMethod.getMethodAnnotation(CrudApi.class); + + if (crudRequestMapping == null || crudApi == null) { + return ""; + } + + String path = crudRequestMapping.value(); + String prefix = String.join(StringConstants.COLON, CharSequenceUtil.splitTrim(path, StringConstants.SLASH)); + Api api = crudApi.value(); + String apiName = Api.PAGE.equals(api) || Api.TREE.equals(api) ? Api.LIST.name() : api.name(); + String permission = "%s:%s".formatted(prefix, apiName.toLowerCase()); + + return "Crud 权限校验:
方法:" + permission + ""; + + } +} diff --git a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java index ba1714ac..72910864 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/exception/GlobalExceptionHandler.java @@ -22,11 +22,13 @@ import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; +import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; import org.springframework.web.multipart.MaxUploadSizeExceededException; import org.springframework.web.multipart.MultipartException; +import org.springframework.web.servlet.NoHandlerFoundException; import top.continew.starter.core.exception.BadRequestException; import top.continew.starter.core.exception.BusinessException; import top.continew.starter.web.model.R; @@ -70,6 +72,29 @@ public class GlobalExceptionHandler { return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), "参数 '%s' 类型不匹配".formatted(e.getName())); } + /** + * 拦截请求路径不存在异常 + */ + @ExceptionHandler(NoHandlerFoundException.class) + public R noHandlerFoundException(NoHandlerFoundException e, HttpServletRequest request) { + log.error("[{}] {}", request.getMethod(), request.getRequestURI(), e); + String errorMessage = String.format("请求路径不存在,请检查URL是否正确: [%s]", request.getRequestURI()); + return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMessage); + } + + /** + * 拦截不支持的http请求方法异常 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public R httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) { + // 获取请求的 HTTP 方法和 URI + String requestMethod = request.getMethod(); + String requestURI = request.getRequestURI(); + log.error("[{}] {}", requestMethod, requestURI, e); + String errorMessage = String.format("不支持的请求方式: [%s] ,请检查请求方式是否正确,请求路径: [%s]", requestMethod, requestURI); + return R.fail(String.valueOf(HttpStatus.BAD_REQUEST.value()), errorMessage); + } + /** * 拦截文件上传大小超过限制异常 */ -- Gitee From 676b505130d93867849f4fd726b85281290b85b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=B4=E6=B3=BD=E5=A8=81?= <958142070@qq.com> Date: Tue, 18 Feb 2025 11:11:36 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor(system/file):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E5=AD=98=E5=82=A8=E5=92=8C=E6=96=87=E4=BB=B6=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/common/config/doc/GlobalAuthenticationCustomizer.java | 2 +- .../admin/common/config/doc/GlobalDescriptionCustomizer.java | 2 +- .../config/doc/customizer/OperationDescriptionCustomizer.java | 2 +- .../top/continew/admin/system/config/file/FileStorageInit.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java index 5e2dc67b..6bb1c38f 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalAuthenticationCustomizer.java @@ -28,7 +28,7 @@ import java.util.stream.Collectors; * 全局鉴权参数配置器 * * @author echo - * @date 2024/12/31 13:36 + * @since 2024/12/31 13:36 */ @Slf4j @Component diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java index b9a948ba..cf34319c 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/GlobalDescriptionCustomizer.java @@ -17,7 +17,7 @@ import java.util.List; * 全局描述定制器 - 处理 sa-token 的注解权限码 * * @author echo - * @date 2025/01/24 14:59 + * @since 2025/01/24 14:59 */ @Slf4j @Component diff --git a/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java b/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java index ac21985f..7351ceda 100644 --- a/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java +++ b/continew-common/src/main/java/top/continew/admin/common/config/doc/customizer/OperationDescriptionCustomizer.java @@ -18,7 +18,7 @@ import java.util.List; * Operation 描述定制器 处理 sa-token 鉴权标识符 * * @author echo - * @Date 2024/06/14 11:18 + * @since 2024/06/14 11:18 **/ public class OperationDescriptionCustomizer { diff --git a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java index f15aea63..551e68c5 100644 --- a/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java +++ b/continew-module-system/src/main/java/top/continew/admin/system/config/file/FileStorageInit.java @@ -38,7 +38,7 @@ import java.util.Map; * 文件存储初始化 * * @author echo - * @date 2024/12/20 11:10 + * @since 2024/12/20 11:10 */ @Component public class FileStorageInit { -- Gitee