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