Ver código fonte

bump: 发布2.2.0

details see: https://helio.uncarbon.cc/#/i18n/zh-CN/appendix/change-log
Uncarbon 1 ano atrás
pai
commit
f2202e5523
66 arquivos alterados com 1417 adições e 1343 exclusões
  1. 2 1
      Dockerfile
  2. 21 26
      attachments/db/PostgreSQL/helio_boot.sql
  3. 21 26
      attachments/db/PostgreSQL/upgrade/1.7.1_to_1.7.2.sql
  4. 3 3
      pom.xml
  5. 24 24
      src/main/java/cc/uncarbon/aspect/extension/SysLogAspectExtensionForSysUserLogin.java
  6. 29 29
      src/main/java/cc/uncarbon/config/NotFoundConfiguration.java
  7. 94 94
      src/main/java/cc/uncarbon/helper/CaptchaHelper.java
  8. 82 82
      src/main/java/cc/uncarbon/helper/RolePermissionCacheHelper.java
  9. 16 16
      src/main/java/cc/uncarbon/module/adminapi/constant/AdminApiConstant.java
  10. 23 27
      src/main/java/cc/uncarbon/module/adminapi/enums/AdminApiErrorEnum.java
  11. 32 32
      src/main/java/cc/uncarbon/module/adminapi/event/KickOutSysUsersEvent.java
  12. 31 31
      src/main/java/cc/uncarbon/module/adminapi/listener/AdminApiEventListener.java
  13. 15 31
      src/main/java/cc/uncarbon/module/adminapi/model/interior/AdminCaptchaContainer.java
  14. 30 30
      src/main/java/cc/uncarbon/module/adminapi/model/response/AdminCaptchaVO.java
  15. 167 171
      src/main/java/cc/uncarbon/module/adminapi/model/response/SelectOptionItemVO.java
  16. 14 1
      src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java
  17. 10 10
      src/main/java/cc/uncarbon/module/oss/config/FileStorageConfiguration.java
  18. 12 12
      src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java
  19. 1 4
      src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java
  20. 34 0
      src/main/java/cc/uncarbon/module/oss/enums/UploadFileCheckResultEnum.java
  21. 40 40
      src/main/java/cc/uncarbon/module/oss/facade/OssUploadDownloadFacade.java
  22. 42 10
      src/main/java/cc/uncarbon/module/oss/facade/impl/OssUploadDownloadFacadeImpl.java
  23. 0 3
      src/main/java/cc/uncarbon/module/oss/model/request/AdminListOssFileInfoDTO.java
  24. 6 2
      src/main/java/cc/uncarbon/module/oss/model/request/UploadFileAttributeDTO.java
  25. 0 2
      src/main/java/cc/uncarbon/module/oss/model/response/OssFileDownloadReplyBO.java
  26. 0 2
      src/main/java/cc/uncarbon/module/oss/model/response/OssFileInfoBO.java
  27. 0 2
      src/main/java/cc/uncarbon/module/oss/model/response/OssFileUploadResultVO.java
  28. 1 1
      src/main/java/cc/uncarbon/module/oss/service/OssFileInfoService.java
  29. 96 0
      src/main/java/cc/uncarbon/module/oss/util/UploadFileChecker.java
  30. 3 27
      src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java
  31. 1 4
      src/main/java/cc/uncarbon/module/sys/enums/SysLogStatusEnum.java
  32. 1 2
      src/main/java/cc/uncarbon/module/sys/enums/SysMenuTypeEnum.java
  33. 1 2
      src/main/java/cc/uncarbon/module/sys/enums/SysUserStatusEnum.java
  34. 34 34
      src/main/java/cc/uncarbon/module/sys/extension/SysLogAspectExtension.java
  35. 70 70
      src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java
  36. 29 29
      src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java
  37. 154 154
      src/main/java/cc/uncarbon/module/sys/facade/impl/SysTenantFacadeImpl.java
  38. 82 82
      src/main/java/cc/uncarbon/module/sys/model/interior/UserDeptContainer.java
  39. 48 48
      src/main/java/cc/uncarbon/module/sys/model/interior/UserRoleContainer.java
  40. 2 2
      src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java
  41. 3 3
      src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java
  42. 2 2
      src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java
  43. 2 2
      src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java
  44. 4 4
      src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java
  45. 1 2
      src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java
  46. 2 2
      src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java
  47. 0 5
      src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictClassifiedInsertOrUpdateDTO.java
  48. 0 5
      src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictClassifiedListDTO.java
  49. 0 5
      src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictItemInsertOrUpdateDTO.java
  50. 0 5
      src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictItemListDTO.java
  51. 2 2
      src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java
  52. 3 3
      src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java
  53. 3 3
      src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java
  54. 53 53
      src/main/java/cc/uncarbon/module/sys/model/response/IPLocationBO.java
  55. 0 5
      src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictClassifiedBO.java
  56. 0 5
      src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictItemBO.java
  57. 26 26
      src/main/java/cc/uncarbon/module/sys/model/response/SysTenantKickOutUsersBO.java
  58. 1 1
      src/main/java/cc/uncarbon/module/sys/service/SysDataDictService.java
  59. 1 1
      src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java
  60. 1 1
      src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java
  61. 1 1
      src/main/java/cc/uncarbon/module/sys/service/SysParamService.java
  62. 1 1
      src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java
  63. 1 1
      src/main/java/cc/uncarbon/module/sys/service/SysTenantService.java
  64. 2 2
      src/main/java/cc/uncarbon/module/sys/service/SysUserService.java
  65. 24 24
      src/main/resources/i18n/messages_en_US.properties
  66. 13 13
      src/main/resources/i18n/messages_zh_CN.properties

+ 2 - 1
Dockerfile

@@ -2,5 +2,6 @@
 FROM eclipse-temurin:17.0.7_7-jre-alpine
 COPY ./target/*.jar ./app.jar
 COPY entrypoint.sh /
-RUN chmod +x ./entrypoint.sh
+RUN chmod +x ./entrypoint.sh && \
+    ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone
 ENTRYPOINT ["./entrypoint.sh"]

+ 21 - 26
attachments/db/PostgreSQL/helio_boot.sql

@@ -939,32 +939,27 @@ ALTER TABLE "oss_file_info"
 
 
 -- v1.7.2 - Uncarbon - 新增上传文件信息管理菜单
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152714, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:27:14',
-        'helio-generator', '上传文件信息管理', 0, 1, 'OssFileInfo', 'ant-design:flag-outlined', 100, 1,
-        '/oss/OssFileInfo/index', '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152715, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:27:14',
-        'helio-generator', '查询', 1572848462892752896, 2, 'OssFileInfo:retrieve', NULL, 1, 1, NULL, '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152716, NULL, 1, 1, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:35:48', 'admin',
-        '新增', 1572848462892752896, 2, 'OssFileInfo:create', NULL, 2, 1, NULL, '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152717, NULL, 1, 1, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:35:55', 'admin',
-        '编辑', 1572848462892752896, 2, 'OssFileInfo:update', NULL, 4, 1, NULL, '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152718, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:36:03', 'admin',
-        '删除', 1572848462892752896, 2, 'OssFileInfo:delete', NULL, 2, 1, NULL, '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152710, NULL, 1, 0, '2022-12-25 23:09:30', 'admin', '2022-12-26 22:02:31', 'admin', '文件管理', 0, 0,
+        'Oss', 'ant-design:file-outlined', 4, 1, NULL, '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152714, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:03:14', 'admin',
+        '上传文件信息管理', 20220922152710, 1, 'OssFileInfo', 'ant-design:save-twotone', 1, 1, '/oss/OssFileInfo/index',
+        '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152715, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:01:58', 'admin', '查询',
+        20220922152714, 2, 'OssFileInfo:retrieve', NULL, 1, 1, NULL, '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152718, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:02:09', 'admin', '删除',
+        20220922152714, 2, 'OssFileInfo:delete', NULL, 2, 1, NULL, '');
 
 -- v1.8.0 - Uncarbon - 新增错误原因堆栈、用户UA、IP属地字段;表注释更新
 ALTER TABLE sys_log

+ 21 - 26
attachments/db/PostgreSQL/upgrade/1.7.1_to_1.7.2.sql

@@ -66,29 +66,24 @@ ALTER TABLE "oss_file_info"
     ADD CONSTRAINT "oss_file_info_pkey" PRIMARY KEY ("id");
 
 -- 增加对应后台管理菜单
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152714, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:27:14',
-        'helio-generator', '上传文件信息管理', 0, 1, 'OssFileInfo', 'ant-design:flag-outlined', 100, 1,
-        '/oss/OssFileInfo/index', '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152715, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:27:14',
-        'helio-generator', '查询', 1572848462892752896, 2, 'OssFileInfo:retrieve', NULL, 1, 1, NULL, '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152716, NULL, 1, 1, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:35:48', 'admin',
-        '新增', 1572848462892752896, 2, 'OssFileInfo:create', NULL, 2, 1, NULL, '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152717, NULL, 1, 1, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:35:55', 'admin',
-        '编辑', 1572848462892752896, 2, 'OssFileInfo:update', NULL, 4, 1, NULL, '');
-INSERT INTO "sys_menu"("id", "tenant_id", "revision", "del_flag", "created_at", "created_by", "updated_at",
-                       "updated_by", "title", "parent_id", "type", "permission", "icon", "sort", "status", "component",
-                       "external_link")
-VALUES (20220922152718, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-09-22 15:36:03', 'admin',
-        '删除', 1572848462892752896, 2, 'OssFileInfo:delete', NULL, 2, 1, NULL, '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152710, NULL, 1, 0, '2022-12-25 23:09:30', 'admin', '2022-12-26 22:02:31', 'admin', '文件管理', 0, 0,
+        'Oss', 'ant-design:file-outlined', 4, 1, NULL, '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152714, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:03:14', 'admin',
+        '上传文件信息管理', 20220922152710, 1, 'OssFileInfo', 'ant-design:save-twotone', 1, 1, '/oss/OssFileInfo/index',
+        '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152715, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:01:58', 'admin', '查询',
+        20220922152714, 2, 'OssFileInfo:retrieve', NULL, 1, 1, NULL, '');
+INSERT INTO sys_menu (id, tenant_id, revision, del_flag, created_at, created_by, updated_at,
+                      updated_by, title, parent_id, type, permission, icon, sort, status, component,
+                      external_link)
+VALUES (20220922152718, NULL, 1, 0, '2022-09-22 15:27:14', 'helio-generator', '2022-12-26 22:02:09', 'admin', '删除',
+        20220922152714, 2, 'OssFileInfo:delete', NULL, 2, 1, NULL, '');

+ 3 - 3
pom.xml

@@ -8,18 +8,18 @@
     <parent>
         <groupId>cc.uncarbon.framework</groupId>
         <artifactId>helio-starters</artifactId>
-        <version>2.1.0</version>
+        <version>2.2.0</version>
     </parent>
 
     <artifactId>helio-boot</artifactId>
     <description>Helio 单模块版单体脚手架</description>
     <!-- 制品版本号,建议每次打包发布时都调整以作区分 -->
-    <version>2.1.0</version>
+    <version>2.2.0</version>
 
     <properties>
         <!-- 这里写其他第三方依赖 -->
         <!-- 文件上传 -->
-        <x-file-storage.version>2.1.0</x-file-storage.version>
+        <x-file-storage.version>2.2.1</x-file-storage.version>
         <minio.version>8.5.8</minio.version>
     </properties>
 

+ 24 - 24
src/main/java/cc/uncarbon/aspect/extension/SysLogAspectExtensionForSysUserLogin.java

@@ -1,24 +1,24 @@
-package cc.uncarbon.aspect.extension;
-
-import cc.uncarbon.module.sys.annotation.SysLog;
-import cc.uncarbon.module.sys.extension.impl.DefaultSysLogAspectExtension;
-import cc.uncarbon.module.sys.model.request.AdminInsertSysLogDTO;
-import cc.uncarbon.module.sys.model.request.SysUserLoginDTO;
-import lombok.NoArgsConstructor;
-import org.aspectj.lang.JoinPoint;
-
-/**
- * SysLog 切面实现类扩展 for 登录后台用户
- */
-@NoArgsConstructor
-public class SysLogAspectExtensionForSysUserLogin extends DefaultSysLogAspectExtension {
-
-    @Override
-    public void beforeSaving(AdminInsertSysLogDTO insertSysLogDTO, JoinPoint joinPoint, SysLog annotation, Throwable e, Object ret) {
-        for (Object arg : joinPoint.getArgs()) {
-            if (arg instanceof SysUserLoginDTO dto) {
-                insertSysLogDTO.setUsername(dto.getUsername());
-            }
-        }
-    }
-}
+package cc.uncarbon.aspect.extension;
+
+import cc.uncarbon.module.sys.annotation.SysLog;
+import cc.uncarbon.module.sys.extension.impl.DefaultSysLogAspectExtension;
+import cc.uncarbon.module.sys.model.request.AdminInsertSysLogDTO;
+import cc.uncarbon.module.sys.model.request.SysUserLoginDTO;
+import lombok.NoArgsConstructor;
+import org.aspectj.lang.JoinPoint;
+
+/**
+ * SysLog 切面实现类扩展 for 登录后台用户
+ */
+@NoArgsConstructor
+public class SysLogAspectExtensionForSysUserLogin extends DefaultSysLogAspectExtension {
+
+    @Override
+    public void beforeSaving(AdminInsertSysLogDTO insertSysLogDTO, JoinPoint joinPoint, SysLog annotation, Throwable e, Object ret) {
+        for (Object arg : joinPoint.getArgs()) {
+            if (arg instanceof SysUserLoginDTO dto) {
+                insertSysLogDTO.setUsername(dto.getUsername());
+            }
+        }
+    }
+}

+ 29 - 29
src/main/java/cc/uncarbon/config/NotFoundConfiguration.java

@@ -1,29 +1,29 @@
-package cc.uncarbon.config;
-
-import io.swagger.v3.oas.annotations.Operation;
-import org.springframework.boot.web.servlet.error.ErrorController;
-import org.springframework.http.HttpHeaders;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.servlet.NoHandlerFoundException;
-
-import jakarta.servlet.http.HttpServletRequest;
-
-/**
- * 由于除 404 以外的异常都会被全局异常处理掉,所以走到这里的请求都是 404 了
- *
- * @author Uncarbon
- */
-@RestController
-public class NotFoundConfiguration implements ErrorController {
-
-    /**
-     * 不生成接口文档
-     * 主动抛出异常,由全局异常处理接管
-     */
-    @Operation(hidden = true)
-    @RequestMapping(value = "/error")
-    public void error(HttpServletRequest request) throws NoHandlerFoundException {
-        throw new NoHandlerFoundException(request.getMethod(), request.getRequestURI(), new HttpHeaders());
-    }
-}
+package cc.uncarbon.config;
+
+import io.swagger.v3.oas.annotations.Operation;
+import org.springframework.boot.web.servlet.error.ErrorController;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.NoHandlerFoundException;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * 由于除 404 以外的异常都会被全局异常处理掉,所以走到这里的请求都是 404 了
+ *
+ * @author Uncarbon
+ */
+@RestController
+public class NotFoundConfiguration implements ErrorController {
+
+    /**
+     * 不生成接口文档
+     * 主动抛出异常,由全局异常处理接管
+     */
+    @Operation(hidden = true)
+    @RequestMapping(value = "/error")
+    public void error(HttpServletRequest request) throws NoHandlerFoundException {
+        throw new NoHandlerFoundException(request.getMethod(), request.getRequestURI(), new HttpHeaders());
+    }
+}

+ 94 - 94
src/main/java/cc/uncarbon/helper/CaptchaHelper.java

@@ -1,94 +1,94 @@
-package cc.uncarbon.helper;
-
-import cc.uncarbon.framework.core.exception.BusinessException;
-import cc.uncarbon.module.adminapi.enums.AdminApiErrorEnum;
-import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer;
-import cn.hutool.captcha.CaptchaUtil;
-import cn.hutool.captcha.ShearCaptcha;
-import cn.hutool.core.date.LocalDateTimeUtil;
-import cn.hutool.core.lang.UUID;
-import cn.hutool.core.text.CharSequenceUtil;
-import lombok.RequiredArgsConstructor;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.stereotype.Component;
-
-import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 验证码助手类;可将验证码答案缓存至 Redis
- *
- * @author Uncarbon
- */
-@Component
-@RequiredArgsConstructor
-public class CaptchaHelper {
-
-    private final RedisTemplate<String, String> stringRedisTemplate;
-
-    private static final String CACHE_KEY_CAPTCHA_ANSWER = "Authorization:captcha:uuid_%s";
-
-    /**
-     * 验证码答案长度
-     */
-    private static final int CAPTCHA_ANSWER_LENGTH = 4;
-
-    /**
-     * 生成一个验证码
-     */
-    public AdminCaptchaContainer generate() {
-        // redis预占位;随机10个UUID,应该有个能成的吧……
-        UUID uuid = UUID.randomUUID();
-        String captchaCacheKey = null;
-        Boolean stubFlag = Boolean.FALSE;
-        for (int count = 0; count < 10; count++) {
-            captchaCacheKey = String.format(CACHE_KEY_CAPTCHA_ANSWER, uuid.toString(true));
-            stubFlag = stringRedisTemplate.opsForValue().setIfAbsent(captchaCacheKey, CharSequenceUtil.EMPTY);
-            if (Boolean.TRUE.equals(stubFlag)) {
-                break;
-            }
-            uuid = UUID.randomUUID();
-        }
-        if (!Boolean.TRUE.equals(stubFlag)) {
-            throw new BusinessException(AdminApiErrorEnum.CAPTCHA_GENERATE_FAILED);
-        }
-
-        // 定义图形验证码的长、宽、验证码字符数、干扰线宽度
-        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(196, 50, CAPTCHA_ANSWER_LENGTH, 4);
-
-        // 将验证码答案保存至 redis, 有效期5分钟
-        stringRedisTemplate.opsForValue().set(captchaCacheKey, captcha.getCode(), 300, TimeUnit.SECONDS);
-        LocalDateTime expiredAt = LocalDateTimeUtil.offset(LocalDateTimeUtil.now(), 300, ChronoUnit.SECONDS);
-
-        return new AdminCaptchaContainer(captcha, uuid.toString(true), expiredAt);
-    }
-
-    /**
-     * 核验验证码是否输入正确
-     *
-     * @param uuid             验证码唯一标识(UUID)
-     * @param captchaAnswer    验证码答案
-     * @return 是否正确
-     */
-    public boolean validate(String uuid, String captchaAnswer) {
-        if (CharSequenceUtil.hasBlank(uuid, captchaAnswer)) {
-            return false;
-        }
-
-        String cacheKey = String.format(CACHE_KEY_CAPTCHA_ANSWER, uuid);
-        boolean equals;
-        try {
-            if (CharSequenceUtil.length(captchaAnswer) != CAPTCHA_ANSWER_LENGTH) {
-                // 长度不同
-                return false;
-            }
-
-            String answerInRedis = stringRedisTemplate.opsForValue().get(cacheKey);
-            equals = CharSequenceUtil.equalsIgnoreCase(answerInRedis, captchaAnswer);
-        } finally {
-            stringRedisTemplate.delete(cacheKey);
-        }
-        return equals;
-    }
-}
+package cc.uncarbon.helper;
+
+import cc.uncarbon.framework.core.exception.BusinessException;
+import cc.uncarbon.module.adminapi.enums.AdminApiErrorEnum;
+import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer;
+import cn.hutool.captcha.CaptchaUtil;
+import cn.hutool.captcha.ShearCaptcha;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.text.CharSequenceUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 验证码助手类;可将验证码答案缓存至 Redis
+ *
+ * @author Uncarbon
+ */
+@Component
+@RequiredArgsConstructor
+public class CaptchaHelper {
+
+    private final RedisTemplate<String, String> stringRedisTemplate;
+
+    private static final String CACHE_KEY_CAPTCHA_ANSWER = "Authorization:captcha:uuid_%s";
+
+    /**
+     * 验证码答案长度
+     */
+    private static final int CAPTCHA_ANSWER_LENGTH = 4;
+
+    /**
+     * 生成一个验证码
+     */
+    public AdminCaptchaContainer generate() {
+        // redis预占位;随机10个UUID,应该有个能成的吧……
+        UUID uuid = UUID.randomUUID();
+        String captchaCacheKey = null;
+        Boolean stubFlag = Boolean.FALSE;
+        for (int count = 0; count < 10; count++) {
+            captchaCacheKey = String.format(CACHE_KEY_CAPTCHA_ANSWER, uuid.toString(true));
+            stubFlag = stringRedisTemplate.opsForValue().setIfAbsent(captchaCacheKey, CharSequenceUtil.EMPTY);
+            if (Boolean.TRUE.equals(stubFlag)) {
+                break;
+            }
+            uuid = UUID.randomUUID();
+        }
+        if (!Boolean.TRUE.equals(stubFlag)) {
+            throw new BusinessException(AdminApiErrorEnum.CAPTCHA_GENERATE_FAILED);
+        }
+
+        // 定义图形验证码的长、宽、验证码字符数、干扰线宽度
+        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(196, 50, CAPTCHA_ANSWER_LENGTH, 4);
+
+        // 将验证码答案保存至 redis, 有效期5分钟
+        stringRedisTemplate.opsForValue().set(captchaCacheKey, captcha.getCode(), 300, TimeUnit.SECONDS);
+        LocalDateTime expiredAt = LocalDateTimeUtil.offset(LocalDateTimeUtil.now(), 300, ChronoUnit.SECONDS);
+
+        return new AdminCaptchaContainer(captcha, uuid.toString(true), expiredAt);
+    }
+
+    /**
+     * 核验验证码是否输入正确
+     *
+     * @param uuid             验证码唯一标识(UUID)
+     * @param captchaAnswer    验证码答案
+     * @return 是否正确
+     */
+    public boolean validate(String uuid, String captchaAnswer) {
+        if (CharSequenceUtil.hasBlank(uuid, captchaAnswer)) {
+            return false;
+        }
+
+        String cacheKey = String.format(CACHE_KEY_CAPTCHA_ANSWER, uuid);
+        boolean equals;
+        try {
+            if (CharSequenceUtil.length(captchaAnswer) != CAPTCHA_ANSWER_LENGTH) {
+                // 长度不同
+                return false;
+            }
+
+            String answerInRedis = stringRedisTemplate.opsForValue().get(cacheKey);
+            equals = CharSequenceUtil.equalsIgnoreCase(answerInRedis, captchaAnswer);
+        } finally {
+            stringRedisTemplate.delete(cacheKey);
+        }
+        return equals;
+    }
+}

+ 82 - 82
src/main/java/cc/uncarbon/helper/RolePermissionCacheHelper.java

@@ -1,82 +1,82 @@
-package cc.uncarbon.helper;
-
-import cc.uncarbon.framework.core.context.UserContextHolder;
-import cn.hutool.core.collection.CollUtil;
-import lombok.RequiredArgsConstructor;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.stereotype.Component;
-
-import java.util.*;
-
-/**
- * 将角色对应权限,缓存至 Redis
- * 参考文章: https://sa-token.cc/doc.html#/fun/jur-cache
- *
- * @author Uncarbon
- */
-@Component
-@RequiredArgsConstructor
-public class RolePermissionCacheHelper {
-
-    private final RedisTemplate<String, Set<String>> stringSetRedisTemplate;
-
-    private static final String CACHE_KEY_ROLE_PERMISSIONS = "Authorization:rolePermissions:roleId_%s";
-
-
-    /**
-     * 从缓存中取得当前用户拥有的所有权限名集合
-     *
-     * @return List<String>
-     */
-    public List<String> getUserPermissions() {
-        Set<Long> rolesIds = UserContextHolder.getUserContext().getRolesIds();
-        // aka * 64
-        List<String> ret = new ArrayList<>(rolesIds.size() << 6);
-
-        rolesIds.forEach(
-                roleId -> {
-                    String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
-                    ret.addAll(CollUtil.emptyIfNull(stringSetRedisTemplate.opsForValue().get(cacheKey)));
-                }
-        );
-
-        return ret;
-    }
-
-    /**
-     * 覆盖更新角色对应权限至 Redis
-     *
-     * @param map key=角色ID value=权限集合
-     */
-    public void putCache(Map<Long, Set<String>> map) {
-        Set<Map.Entry<Long, Set<String>>> entries = map.entrySet();
-        entries.forEach(
-                entry -> this.putCache(entry.getKey(), entry.getValue())
-        );
-    }
-
-    /**
-     * 覆盖更新角色对应权限至 Redis
-     *
-     * @param roleId 角色ID
-     * @param newPermissions 新权限名集合
-     */
-    public void putCache(Long roleId, Set<String> newPermissions) {
-        String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
-        stringSetRedisTemplate.opsForValue().set(cacheKey, newPermissions);
-    }
-
-    /**
-     * 删除角色ID对应的权限缓存
-     *
-     * @param roleIds 角色ID集合
-     */
-    public void deleteCache(Collection<Long> roleIds) {
-        roleIds.forEach(
-                roleId -> {
-                    String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
-                    stringSetRedisTemplate.delete(cacheKey);
-                }
-        );
-    }
-}
+package cc.uncarbon.helper;
+
+import cc.uncarbon.framework.core.context.UserContextHolder;
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import java.util.*;
+
+/**
+ * 将角色对应权限,缓存至 Redis
+ * 参考文章: https://sa-token.cc/doc.html#/fun/jur-cache
+ *
+ * @author Uncarbon
+ */
+@Component
+@RequiredArgsConstructor
+public class RolePermissionCacheHelper {
+
+    private final RedisTemplate<String, Set<String>> stringSetRedisTemplate;
+
+    private static final String CACHE_KEY_ROLE_PERMISSIONS = "Authorization:rolePermissions:roleId_%s";
+
+
+    /**
+     * 从缓存中取得当前用户拥有的所有权限名集合
+     *
+     * @return List<String>
+     */
+    public List<String> getUserPermissions() {
+        Set<Long> rolesIds = UserContextHolder.getUserContext().getRolesIds();
+        // aka * 64
+        List<String> ret = new ArrayList<>(rolesIds.size() << 6);
+
+        rolesIds.forEach(
+                roleId -> {
+                    String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
+                    ret.addAll(CollUtil.emptyIfNull(stringSetRedisTemplate.opsForValue().get(cacheKey)));
+                }
+        );
+
+        return ret;
+    }
+
+    /**
+     * 覆盖更新角色对应权限至 Redis
+     *
+     * @param map key=角色ID value=权限集合
+     */
+    public void putCache(Map<Long, Set<String>> map) {
+        Set<Map.Entry<Long, Set<String>>> entries = map.entrySet();
+        entries.forEach(
+                entry -> this.putCache(entry.getKey(), entry.getValue())
+        );
+    }
+
+    /**
+     * 覆盖更新角色对应权限至 Redis
+     *
+     * @param roleId 角色ID
+     * @param newPermissions 新权限名集合
+     */
+    public void putCache(Long roleId, Set<String> newPermissions) {
+        String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
+        stringSetRedisTemplate.opsForValue().set(cacheKey, newPermissions);
+    }
+
+    /**
+     * 删除角色ID对应的权限缓存
+     *
+     * @param roleIds 角色ID集合
+     */
+    public void deleteCache(Collection<Long> roleIds) {
+        roleIds.forEach(
+                roleId -> {
+                    String cacheKey = String.format(CACHE_KEY_ROLE_PERMISSIONS, roleId);
+                    stringSetRedisTemplate.delete(cacheKey);
+                }
+        );
+    }
+}

+ 16 - 16
src/main/java/cc/uncarbon/module/adminapi/constant/AdminApiConstant.java

@@ -1,16 +1,16 @@
-package cc.uncarbon.module.adminapi.constant;
-
-
-/**
- * 后台管理接口常量
- */
-public final class AdminApiConstant {
-    private AdminApiConstant() {
-    }
-
-    /**
-     * HTTP-API路由前缀
-     */
-    public static final String HTTP_API_URL_PREFIX = "/admin";
-
-}
+package cc.uncarbon.module.adminapi.constant;
+
+
+/**
+ * 后台管理接口常量
+ */
+public final class AdminApiConstant {
+    private AdminApiConstant() {
+    }
+
+    /**
+     * HTTP-API路由前缀
+     */
+    public static final String HTTP_API_URL_PREFIX = "/admin";
+
+}

+ 23 - 27
src/main/java/cc/uncarbon/module/adminapi/enums/AdminApiErrorEnum.java

@@ -1,27 +1,23 @@
-package cc.uncarbon.module.adminapi.enums;
-
-import cc.uncarbon.framework.core.enums.HelioBaseEnum;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-
-/**
- * admin-api模块错误枚举类
- */
-@AllArgsConstructor
-@Getter
-public enum AdminApiErrorEnum implements HelioBaseEnum<Integer> {
-
-    CAPTCHA_GENERATE_FAILED(500, "验证码生成失败,请稍后再试"),
-
-    CAPTCHA_VALIDATE_FAILED(400, "验证码不正确,请重新输入"),
-
-    /**
-     * 很少遇到;但是如果出现了只会提示默认的「请稍后再试」,不方便排查,还是整个文案比较好
-     */
-    UPLOAD_FILE_NOT_EXIST(400, "欲上传的文件可能已被删除,请重新选择");
-
-    private final Integer value;
-    private final String label;
-
-}
+package cc.uncarbon.module.adminapi.enums;
+
+import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+
+/**
+ * admin-api模块错误枚举类
+ */
+@AllArgsConstructor
+@Getter
+public enum AdminApiErrorEnum implements HelioBaseEnum<Integer> {
+
+    CAPTCHA_GENERATE_FAILED(500, "验证码生成失败,请稍后再试"),
+    CAPTCHA_VALIDATE_FAILED(400, "验证码不正确,请重新输入"),
+    // 很少遇到;但是如果出现了只会提示默认的「请稍后再试」,不方便排查,还是整个文案比较好
+    UPLOAD_FILE_NOT_EXIST(400, "欲上传的文件可能已被删除,请重新选择"),;
+
+    private final Integer value;
+    private final String label;
+
+}

+ 32 - 32
src/main/java/cc/uncarbon/module/adminapi/event/KickOutSysUsersEvent.java

@@ -1,32 +1,32 @@
-package cc.uncarbon.module.adminapi.event;
-
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-import org.springframework.context.ApplicationEvent;
-
-import java.util.Collection;
-
-/**
- * 强制登出后台用户事件
- */
-@Getter
-public final class KickOutSysUsersEvent extends ApplicationEvent {
-
-    private final transient EventData data;
-
-    public KickOutSysUsersEvent(EventData data) {
-        super(data);
-        this.data = data;
-    }
-
-    @Getter
-    @RequiredArgsConstructor
-    public static final class EventData {
-
-        /**
-         * 需要被强制登出的后台用户IDs
-         */
-        private final Collection<Long> sysUserIds;
-
-    }
-}
+package cc.uncarbon.module.adminapi.event;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.ApplicationEvent;
+
+import java.util.Collection;
+
+/**
+ * 强制登出后台用户事件
+ */
+@Getter
+public final class KickOutSysUsersEvent extends ApplicationEvent {
+
+    private final transient EventData data;
+
+    public KickOutSysUsersEvent(EventData data) {
+        super(data);
+        this.data = data;
+    }
+
+    @Getter
+    @RequiredArgsConstructor
+    public static final class EventData {
+
+        /**
+         * 需要被强制登出的后台用户IDs
+         */
+        private final Collection<Long> sysUserIds;
+
+    }
+}

+ 31 - 31
src/main/java/cc/uncarbon/module/adminapi/listener/AdminApiEventListener.java

@@ -1,31 +1,31 @@
-package cc.uncarbon.module.adminapi.listener;
-
-import cc.uncarbon.module.adminapi.event.KickOutSysUsersEvent;
-import cc.uncarbon.module.adminapi.util.AdminStpUtil;
-import cn.hutool.core.collection.CollUtil;
-import lombok.RequiredArgsConstructor;
-import org.springframework.context.event.EventListener;
-import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
-import org.springframework.stereotype.Component;
-
-import java.util.Collection;
-
-/**
- * admin-api模块事件监听器
- */
-@Component
-@RequiredArgsConstructor
-public class AdminApiEventListener {
-
-    private final ThreadPoolTaskExecutor taskExecutor;
-
-
-    @EventListener(value = KickOutSysUsersEvent.class)
-    public void handleKickOutSysUsersEvent(KickOutSysUsersEvent event) {
-        Collection<Long> sysUserIds = event.getData().getSysUserIds();
-        if (CollUtil.isNotEmpty(sysUserIds)) {
-            // 异步强制登出;同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险
-            taskExecutor.submit(() -> sysUserIds.forEach(AdminStpUtil::kickout));
-        }
-    }
-}
+package cc.uncarbon.module.adminapi.listener;
+
+import cc.uncarbon.module.adminapi.event.KickOutSysUsersEvent;
+import cc.uncarbon.module.adminapi.util.AdminStpUtil;
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.event.EventListener;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+
+/**
+ * admin-api模块事件监听器
+ */
+@Component
+@RequiredArgsConstructor
+public class AdminApiEventListener {
+
+    private final ThreadPoolTaskExecutor taskExecutor;
+
+
+    @EventListener(value = KickOutSysUsersEvent.class)
+    public void handleKickOutSysUsersEvent(KickOutSysUsersEvent event) {
+        Collection<Long> sysUserIds = event.getData().getSysUserIds();
+        if (CollUtil.isNotEmpty(sysUserIds)) {
+            // 异步强制登出;同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险
+            taskExecutor.submit(() -> sysUserIds.forEach(AdminStpUtil::kickout));
+        }
+    }
+}

+ 15 - 31
src/main/java/cc/uncarbon/module/adminapi/model/interior/AdminCaptchaContainer.java

@@ -1,31 +1,15 @@
-package cc.uncarbon.module.adminapi.model.interior;
-
-import cn.hutool.captcha.AbstractCaptcha;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 后台管理-验证码容器
- */
-@RequiredArgsConstructor
-@Getter
-public class AdminCaptchaContainer {
-
-    /**
-     * 验证码图片对象
-     */
-    private final AbstractCaptcha image;
-
-    /**
-     * 验证码唯一标识(UUID)
-     */
-    private final String uuid;
-
-    /**
-     * 验证码失效时刻
-     */
-    private final LocalDateTime expiredAt;
-
-}
+package cc.uncarbon.module.adminapi.model.interior;
+
+import cn.hutool.captcha.AbstractCaptcha;
+
+import java.time.LocalDateTime;
+
+/**
+ * 后台管理-验证码容器
+ *
+ * @param image     验证码图片对象
+ * @param uuid      验证码唯一标识(UUID)
+ * @param expiredAt 验证码失效时刻
+ */
+public record AdminCaptchaContainer(AbstractCaptcha image, String uuid, LocalDateTime expiredAt) {
+}

+ 30 - 30
src/main/java/cc/uncarbon/module/adminapi/model/response/AdminCaptchaVO.java

@@ -1,30 +1,30 @@
-package cc.uncarbon.module.adminapi.model.response;
-
-import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Getter;
-
-import java.time.LocalDateTime;
-
-/**
- * 后台管理-验证码 VO
- */
-@Getter
-public class AdminCaptchaVO {
-
-    @Schema(description = "验证码图片Base64")
-    private final String captchaImage;
-
-    @Schema(description = "验证码唯一标识")
-    private final String captchaId;
-
-    @Schema(description = "验证码失效时刻")
-    private final LocalDateTime expiredAt;
-
-
-    public AdminCaptchaVO(AdminCaptchaContainer source) {
-        this.captchaImage = source.getImage().getImageBase64Data();
-        this.captchaId = source.getUuid();
-        this.expiredAt = source.getExpiredAt();
-    }
-}
+package cc.uncarbon.module.adminapi.model.response;
+
+import cc.uncarbon.module.adminapi.model.interior.AdminCaptchaContainer;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+
+import java.time.LocalDateTime;
+
+/**
+ * 后台管理-验证码 VO
+ */
+@Getter
+public class AdminCaptchaVO {
+
+    @Schema(description = "验证码图片Base64")
+    private final String captchaImage;
+
+    @Schema(description = "验证码唯一标识")
+    private final String captchaId;
+
+    @Schema(description = "验证码失效时刻")
+    private final LocalDateTime expiredAt;
+
+
+    public AdminCaptchaVO(AdminCaptchaContainer source) {
+        this.captchaImage = source.image().getImageBase64Data();
+        this.captchaId = source.uuid();
+        this.expiredAt = source.expiredAt();
+    }
+}

+ 167 - 171
src/main/java/cc/uncarbon/module/adminapi/model/response/SelectOptionItemVO.java

@@ -1,171 +1,167 @@
-package cc.uncarbon.module.adminapi.model.response;
-
-import cc.uncarbon.framework.core.enums.HelioBaseEnum;
-import cn.hutool.core.collection.CollUtil;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Getter;
-import lombok.NonNull;
-import lombok.Setter;
-import lombok.experimental.Accessors;
-
-import java.io.Serial;
-import java.io.Serializable;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import java.util.stream.Stream;
-
-/**
- * 后台管理-下拉框数据单项 VO
- */
-@JsonInclude(JsonInclude.Include.NON_NULL)
-@Accessors(chain = true)
-@Getter
-public class SelectOptionItemVO implements Serializable {
-
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-    // ID👉名称 一对(用于关联各种实体)
-    @Schema(description = "ID")
-    private Number id;
-    @Schema(description = "名称")
-    private String name;
-    public SelectOptionItemVO(Number id, String name) {
-        this.id = id;
-        this.name = name;
-    }
-
-    // 有时候额外需要上级ID
-    @Schema(description = "上级ID")
-    @Setter
-    private Number parentId;
-    public SelectOptionItemVO(Number id, String name, Number parentId) {
-        this.id = id;
-        this.name = name;
-        this.parentId = parentId;
-    }
-
-
-    // 值👉标签 一对(仅用于枚举)
-    @Schema(description = "值")
-    private Number value;
-    @Schema(description = "标签")
-    private String label;
-    public SelectOptionItemVO(HelioBaseEnum<? extends Number> helioBaseEnum) {
-        this.value = helioBaseEnum.getValue();
-        this.label = helioBaseEnum.getLabel();
-    }
-
-    /*
-    ----------------------------------------------------------------
-                        自定义业务字段都写在这里
-                        都要标记释义、用处、新增时版本号
-                        免得每个人各取一个名,不统一
-    ----------------------------------------------------------------
-     */
-
-
-
-    /*
-    ----------------------------------------------------------------
-                        构造方法 builders
-    ----------------------------------------------------------------
-     */
-
-    /**
-     * 构造List<SelectOptionItemVO>
-     * 将转换源集合中所有集合项
-     * 无需上级ID
-     * @param source 源集合
-     * @param idGetter id getter
-     * @param nameGetter name getter
-     */
-    public static <T> List<SelectOptionItemVO> listOf(
-            Collection<T> source,
-            @NonNull Function<T, Number> idGetter,
-            @NonNull Function<T, String> nameGetter
-    ) {
-        return listOf(source, idGetter, nameGetter, null, null);
-    }
-
-    /**
-     * 构造List<SelectOptionItemVO>
-     * 将转换源集合中所有集合项
-     * 支持上级ID
-     * @param source 源集合
-     * @param idGetter id getter
-     * @param nameGetter name getter
-     */
-    public static <T> List<SelectOptionItemVO> listOf(
-            Collection<T> source,
-            @NonNull Function<T, Number> idGetter,
-            @NonNull Function<T, String> nameGetter,
-            Function<T, Number> parentIdGetter
-    ) {
-        return listOf(source, idGetter, nameGetter, parentIdGetter, null);
-    }
-
-    /**
-     * 构造List<SelectOptionItemVO>
-     * 支持自定义过滤器,仅转换需要的集合项
-     * 支持上级ID
-     * @param source 源集合
-     * @param idGetter id getter
-     * @param nameGetter name getter
-     * @param parentIdGetter (可选)parentId getter
-     * @param sourceItemFilter (可选)集合项过滤器
-     */
-    public static <T> List<SelectOptionItemVO> listOf(
-            Collection<T> source,
-            @NonNull Function<T, Number> idGetter,
-            @NonNull Function<T, String> nameGetter,
-            Function<T, Number> parentIdGetter,
-            Predicate<T> sourceItemFilter
-    ) {
-        if (CollUtil.isEmpty(source)) {
-            return Collections.emptyList();
-        }
-        Stream<T> stream = source.stream();
-        if (sourceItemFilter != null) {
-            stream = stream.filter(sourceItemFilter);
-        }
-        return stream.map(item ->
-                        new SelectOptionItemVO(idGetter.apply(item), nameGetter.apply(item),
-                                parentIdGetter == null ? null : parentIdGetter.apply(item)))
-                .toList();
-    }
-
-    /**
-     * 构造List<SelectOptionItemVO>
-     * 将转换枚举类中所有枚举常量
-     * @param helioBaseEnum 实现了HelioBaseEnum的枚举类
-     */
-    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<SelectOptionItemVO> listOf(Class<E> helioBaseEnum) {
-        return listOf(helioBaseEnum, null);
-    }
-
-    /**
-     * 构造List<SelectOptionItemVO>
-     * 支持自定义过滤器,仅转换需要的枚举常量
-     * @param helioBaseEnum 实现了HelioBaseEnum的枚举类
-     * @param enumConstantFilter (可选)枚举类中枚举常量过滤器
-     */
-    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<SelectOptionItemVO> listOf(
-            Class<E> helioBaseEnum,
-            Predicate<E> enumConstantFilter
-    ) {
-        if (helioBaseEnum == null) {
-            return Collections.emptyList();
-        }
-        Stream<E> stream = Arrays.stream(helioBaseEnum.getEnumConstants());
-        if (enumConstantFilter != null) {
-            stream = stream.filter(enumConstantFilter);
-        }
-        return stream.map(SelectOptionItemVO::new).toList();
-    }
-}
+package cc.uncarbon.module.adminapi.model.response;
+
+import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import cn.hutool.core.collection.CollUtil;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * 后台管理-下拉框数据单项 VO
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+@Accessors(chain = true)
+@Getter
+public class SelectOptionItemVO implements Serializable {
+
+    // ID👉名称 一对(用于关联各种实体)
+    @Schema(description = "ID")
+    private Number id;
+    @Schema(description = "名称")
+    private String name;
+    public SelectOptionItemVO(Number id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    // 有时候额外需要上级ID
+    @Schema(description = "上级ID")
+    @Setter
+    private Number parentId;
+    public SelectOptionItemVO(Number id, String name, Number parentId) {
+        this.id = id;
+        this.name = name;
+        this.parentId = parentId;
+    }
+
+
+    // 值👉标签 一对(仅用于枚举)
+    @Schema(description = "值")
+    private Number value;
+    @Schema(description = "标签")
+    private String label;
+    public SelectOptionItemVO(HelioBaseEnum<? extends Number> helioBaseEnum) {
+        this.value = helioBaseEnum.getValue();
+        this.label = helioBaseEnum.getLabel();
+    }
+
+    /*
+    ----------------------------------------------------------------
+                        自定义业务字段都写在这里
+                        都要标记释义、用处、新增时版本号
+                        免得每个人各取一个名,不统一
+    ----------------------------------------------------------------
+     */
+
+
+
+    /*
+    ----------------------------------------------------------------
+                        构造方法 builders
+    ----------------------------------------------------------------
+     */
+
+    /**
+     * 构造List<SelectOptionItemVO>
+     * 将转换源集合中所有集合项
+     * 无需上级ID
+     * @param source 源集合
+     * @param idGetter id getter
+     * @param nameGetter name getter
+     */
+    public static <T> List<SelectOptionItemVO> listOf(
+            Collection<T> source,
+            @NonNull Function<T, Number> idGetter,
+            @NonNull Function<T, String> nameGetter
+    ) {
+        return listOf(source, idGetter, nameGetter, null, null);
+    }
+
+    /**
+     * 构造List<SelectOptionItemVO>
+     * 将转换源集合中所有集合项
+     * 支持上级ID
+     * @param source 源集合
+     * @param idGetter id getter
+     * @param nameGetter name getter
+     */
+    public static <T> List<SelectOptionItemVO> listOf(
+            Collection<T> source,
+            @NonNull Function<T, Number> idGetter,
+            @NonNull Function<T, String> nameGetter,
+            Function<T, Number> parentIdGetter
+    ) {
+        return listOf(source, idGetter, nameGetter, parentIdGetter, null);
+    }
+
+    /**
+     * 构造List<SelectOptionItemVO>
+     * 支持自定义过滤器,仅转换需要的集合项
+     * 支持上级ID
+     * @param source 源集合
+     * @param idGetter id getter
+     * @param nameGetter name getter
+     * @param parentIdGetter (可选)parentId getter
+     * @param sourceItemFilter (可选)集合项过滤器
+     */
+    public static <T> List<SelectOptionItemVO> listOf(
+            Collection<T> source,
+            @NonNull Function<T, Number> idGetter,
+            @NonNull Function<T, String> nameGetter,
+            Function<T, Number> parentIdGetter,
+            Predicate<T> sourceItemFilter
+    ) {
+        if (CollUtil.isEmpty(source)) {
+            return Collections.emptyList();
+        }
+        Stream<T> stream = source.stream();
+        if (sourceItemFilter != null) {
+            stream = stream.filter(sourceItemFilter);
+        }
+        return stream.map(item ->
+                        new SelectOptionItemVO(idGetter.apply(item), nameGetter.apply(item),
+                                parentIdGetter == null ? null : parentIdGetter.apply(item)))
+                .toList();
+    }
+
+    /**
+     * 构造List<SelectOptionItemVO>
+     * 将转换枚举类中所有枚举常量
+     * @param helioBaseEnum 实现了HelioBaseEnum的枚举类
+     */
+    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<SelectOptionItemVO> listOf(Class<E> helioBaseEnum) {
+        return listOf(helioBaseEnum, null);
+    }
+
+    /**
+     * 构造List<SelectOptionItemVO>
+     * 支持自定义过滤器,仅转换需要的枚举常量
+     * @param helioBaseEnum 实现了HelioBaseEnum的枚举类
+     * @param enumConstantFilter (可选)枚举类中枚举常量过滤器
+     */
+    public static <E extends Enum<?> & HelioBaseEnum<? extends Number>> List<SelectOptionItemVO> listOf(
+            Class<E> helioBaseEnum,
+            Predicate<E> enumConstantFilter
+    ) {
+        if (helioBaseEnum == null) {
+            return Collections.emptyList();
+        }
+        Stream<E> stream = Arrays.stream(helioBaseEnum.getEnumConstants());
+        if (enumConstantFilter != null) {
+            stream = stream.filter(enumConstantFilter);
+        }
+        return stream.map(SelectOptionItemVO::new).toList();
+    }
+}

+ 14 - 1
src/main/java/cc/uncarbon/module/adminapi/web/oss/AdminOssUploadDownloadController.java

@@ -1,14 +1,17 @@
 package cc.uncarbon.module.adminapi.web.oss;
 
+import cc.uncarbon.framework.core.exception.BusinessException;
 import cc.uncarbon.framework.web.model.response.ApiResult;
 import cc.uncarbon.module.adminapi.constant.AdminApiConstant;
 import cc.uncarbon.module.adminapi.enums.AdminApiErrorEnum;
 import cc.uncarbon.module.adminapi.util.AdminStpUtil;
+import cc.uncarbon.module.oss.enums.UploadFileCheckResultEnum;
 import cc.uncarbon.module.oss.facade.OssUploadDownloadFacade;
 import cc.uncarbon.module.oss.model.request.UploadFileAttributeDTO;
 import cc.uncarbon.module.oss.model.response.OssFileDownloadReplyBO;
 import cc.uncarbon.module.oss.model.response.OssFileInfoBO;
 import cc.uncarbon.module.oss.model.response.OssFileUploadResultVO;
+import cc.uncarbon.module.oss.util.UploadFileChecker;
 import cn.dev33.satoken.annotation.SaCheckLogin;
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.text.CharSequenceUtil;
@@ -50,6 +53,13 @@ public class AdminOssUploadDownloadController {
             HttpServletRequest request
     ) throws IOException {
         AdminApiErrorEnum.UPLOAD_FILE_NOT_EXIST.assertNotNull(file);
+        UploadFileCheckResultEnum checkResult = UploadFileChecker.check(file,
+                // 默认只能上传10MB之内的文件,且约束后缀名
+                UploadFileChecker.FILE_SIZE_1MB * 10,
+                new String[]{"jpg", "png", "webp", "gif", "xlsx"});
+        if (checkResult.isNotOK()) {
+            throw new BusinessException(checkResult.getLabel());
+        }
 
         /*
         1. 已存在相同 MD5 文件,直接返回 URL
@@ -65,7 +75,10 @@ public class AdminOssUploadDownloadController {
             attr
                     .setOriginalFilename(file.getOriginalFilename())
                     .setContentType(file.getContentType())
-                    .setMd5(md5);
+                    .setMd5(md5)
+                    // 手动覆盖前端传参
+                    .setPlatform(null)
+                    .setUseOriginalFilenameAsDownloadFileName(false);
             bo = ossUploadDownloadFacade.upload(file.getBytes(), attr);
         }
 

+ 10 - 10
src/main/java/cc/uncarbon/module/oss/config/FileStorageConfiguration.java

@@ -1,10 +1,10 @@
-package cc.uncarbon.module.oss.config;
-
-import org.dromara.x.file.storage.spring.EnableFileStorage;
-import org.springframework.context.annotation.Configuration;
-
-
-@EnableFileStorage
-@Configuration
-public class FileStorageConfiguration {
-}
+package cc.uncarbon.module.oss.config;
+
+import org.dromara.x.file.storage.spring.EnableFileStorage;
+import org.springframework.context.annotation.Configuration;
+
+
+@EnableFileStorage
+@Configuration
+public class FileStorageConfiguration {
+}

+ 12 - 12
src/main/java/cc/uncarbon/module/oss/constant/OssConstant.java

@@ -1,12 +1,12 @@
-package cc.uncarbon.module.oss.constant;
-
-public final class OssConstant {
-    private OssConstant() {
-    }
-
-    /**
-     * 存储平台-本地存储-前缀
-     */
-    public static final String PLATFORM_PREFIX_LOCAL = "local";
-
-}
+package cc.uncarbon.module.oss.constant;
+
+public final class OssConstant {
+    private OssConstant() {
+    }
+
+    /**
+     * 存储平台-本地存储-前缀
+     */
+    public static final String PLATFORM_PREFIX_LOCAL = "local";
+
+}

+ 1 - 4
src/main/java/cc/uncarbon/module/oss/enums/OssErrorEnum.java

@@ -14,12 +14,9 @@ import lombok.Getter;
 public enum OssErrorEnum implements HelioBaseEnum<Integer> {
 
     INVALID_ID(400, "无效ID"),
-
     FILE_UPLOAD_FAILED(500, "文件上传失败,请联系管理员"),
+    FILE_DOWNLOAD_FAILED(500, "文件下载失败,请联系管理员"),;
 
-    FILE_DOWNLOAD_FAILED(500, "文件下载失败,请联系管理员"),
-
-    ;
     @EnumValue
     private final Integer value;
     private final String label;

+ 34 - 0
src/main/java/cc/uncarbon/module/oss/enums/UploadFileCheckResultEnum.java

@@ -0,0 +1,34 @@
+package cc.uncarbon.module.oss.enums;
+
+import cc.uncarbon.framework.core.enums.HelioBaseEnum;
+import com.baomidou.mybatisplus.annotation.EnumValue;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 前端上传文件检查结果枚举类
+ */
+@AllArgsConstructor
+@Getter
+public enum UploadFileCheckResultEnum implements HelioBaseEnum<Integer> {
+
+    OK(200, "正常"),
+    NO_FILE(401, "缺少欲上传的文件"),
+    TOO_MANY_FILES(402, "欲上传的文件数量超出限制"),
+    TOO_LARGE_FILE_SIZE(403, "欲上传的文件大小超出限制"),
+    EMPTY_FILE(404, "欲上传的文件为空"),
+    ILLEGAL_FILE_SUFFIX(405, "欲上传的文件类型超出限制"),;
+
+    @EnumValue
+    private final Integer value;
+    private final String label;
+
+    public boolean isOK() {
+        return this == OK;
+    }
+
+    public boolean isNotOK() {
+        return !isOK();
+    }
+
+}

+ 40 - 40
src/main/java/cc/uncarbon/module/oss/facade/OssUploadDownloadFacade.java

@@ -1,40 +1,40 @@
-package cc.uncarbon.module.oss.facade;
-
-import cc.uncarbon.framework.core.exception.BusinessException;
-import cc.uncarbon.module.oss.model.request.UploadFileAttributeDTO;
-import cc.uncarbon.module.oss.model.response.OssFileDownloadReplyBO;
-import cc.uncarbon.module.oss.model.response.OssFileInfoBO;
-import lombok.NonNull;
-
-/**
- * 文件上传下载门面
- */
-public interface OssUploadDownloadFacade {
-
-    /**
-     * 根据哈希值,查找是否已有文件
-     */
-    OssFileInfoBO findByHash(String md5);
-
-    /**
-     * 正常上传文件到服务端
-     *
-     * @param fileBytes 文件数据
-     * @param attr      附加属性
-     */
-    OssFileInfoBO upload(byte[] fileBytes, @NonNull UploadFileAttributeDTO attr) throws BusinessException;
-
-    /**
-     * 根据文件ID下载
-     *
-     * @throws BusinessException 业务异常,如:文件ID无效;原始文件不存在
-     */
-    OssFileDownloadReplyBO downloadById(Long fileInfoId) throws BusinessException;
-
-    /**
-     * 是否为本地存储平台
-     * @param storagePlatform 存储平台名
-     */
-    boolean isLocalPlatform(String storagePlatform);
-
-}
+package cc.uncarbon.module.oss.facade;
+
+import cc.uncarbon.framework.core.exception.BusinessException;
+import cc.uncarbon.module.oss.model.request.UploadFileAttributeDTO;
+import cc.uncarbon.module.oss.model.response.OssFileDownloadReplyBO;
+import cc.uncarbon.module.oss.model.response.OssFileInfoBO;
+import lombok.NonNull;
+
+/**
+ * 文件上传下载门面
+ */
+public interface OssUploadDownloadFacade {
+
+    /**
+     * 根据哈希值,查找是否已有文件
+     */
+    OssFileInfoBO findByHash(String md5);
+
+    /**
+     * 正常上传文件到服务端
+     *
+     * @param fileBytes 文件数据
+     * @param attr      附加属性
+     */
+    OssFileInfoBO upload(byte[] fileBytes, @NonNull UploadFileAttributeDTO attr) throws BusinessException;
+
+    /**
+     * 根据文件ID下载
+     *
+     * @throws BusinessException 业务异常,如:文件ID无效;原始文件不存在
+     */
+    OssFileDownloadReplyBO downloadById(Long fileInfoId) throws BusinessException;
+
+    /**
+     * 是否为本地存储平台
+     * @param storagePlatform 存储平台名
+     */
+    boolean isLocalPlatform(String storagePlatform);
+
+}

+ 42 - 10
src/main/java/cc/uncarbon/module/oss/facade/impl/OssUploadDownloadFacadeImpl.java

@@ -8,14 +8,21 @@ import cc.uncarbon.module.oss.model.response.OssFileDownloadReplyBO;
 import cc.uncarbon.module.oss.model.response.OssFileInfoBO;
 import cc.uncarbon.module.oss.service.OssFileInfoService;
 import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.ObjectUtil;
 import lombok.NonNull;
 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.constant.Constant;
 import org.dromara.x.file.storage.core.exception.FileStorageRuntimeException;
+import org.dromara.x.file.storage.core.upload.UploadPretreatment;
 import org.springframework.stereotype.Service;
 
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+
 
 /**
  * 文件上传下载门面
@@ -28,6 +35,7 @@ public class OssUploadDownloadFacadeImpl implements OssUploadDownloadFacade {
     private final OssFileInfoService ossFileInfoService;
     private final FileStorageService fileStorageService;
 
+
     @Override
     public OssFileInfoBO findByHash(String md5) {
         return ossFileInfoService.getOneByMd5(md5);
@@ -35,18 +43,26 @@ public class OssUploadDownloadFacadeImpl implements OssUploadDownloadFacade {
 
     @Override
     public OssFileInfoBO upload(byte[] fileBytes, @NonNull UploadFileAttributeDTO attr) throws BusinessException {
+        // 要上传到的平台名
+        String platform = ObjectUtil.defaultIfNull(attr.getPlatform(), fileStorageService.getProperties()::getDefaultPlatform);
+
+        // 如果需要缩略图: .setSaveThFilename().setThContentType()
+        UploadPretreatment uploadPretreatment = fileStorageService
+                .of(fileBytes)
+                .setOriginalFilename(attr.getOriginalFilename())
+                // 不手动指定,由框架自动生成存储文件名
+                .setSaveFilename(null)
+                .setContentType(attr.getContentType())
+                .setPlatform(platform)
+                .setPath(formatDatePath(LocalDateTime.now()));
+        if (attr.isUseOriginalFilenameAsDownloadFileName() && fileStorageService.isSupportMetadata(platform)) {
+            String downFileName = URLEncoder.encode(attr.getOriginalFilename(), StandardCharsets.UTF_8);
+            uploadPretreatment.putMetadata(Constant.Metadata.CONTENT_DISPOSITION, "attachment;filename=" + downFileName);
+        }
+
         FileInfo fileInfo;
         try {
-            fileInfo = fileStorageService
-                    .of(fileBytes)
-                    .setOriginalFilename(attr.getOriginalFilename())
-                    // 不手动指定,由框架自动生成存储文件名
-                    .setSaveFilename(null)
-                    .setContentType(attr.getContentType())
-                    // 如果需要缩略图
-                    // .setSaveThFilename()
-                    // .setThContentType(contentType)
-                    .upload();
+            fileInfo = uploadPretreatment.upload();
         } catch (FileStorageRuntimeException fsre) {
             log.error("[文件上传下载门面][上传] FileStorageRuntimeException >> ", fsre);
             throw new BusinessException(OssErrorEnum.FILE_UPLOAD_FAILED);
@@ -62,6 +78,14 @@ public class OssUploadDownloadFacadeImpl implements OssUploadDownloadFacade {
 
     @Override
     public OssFileDownloadReplyBO downloadById(Long fileInfoId) throws BusinessException {
+        /*
+        注:开启多租户后,因可能允许未登录下载,无法确定当前租户ID,导致查询 SQL 会错误地拼接 AND tenant_id = null 条件
+        解决方法1:Controller层方法解禁 @SaCheckLogin 注解,强制要求在 url-params 中传递 token 才可下载
+        解决方法2:后端使用 try-finally 临时设置特权租户上下文
+                TenantContextHolder.setTenantContext(new TenantContext(HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID, "临时特权租户"))
+                以绕过行级租户过滤器;数据源级租户同理,使其固定访问某个数据源来查询文件信息
+        解决方法3:改造Controller层方法参数,增加传递一个租户ID字段,并在此切换上下文
+         */
         OssFileInfoBO ossFileInfo = ossFileInfoService.getOneById(fileInfoId, true);
 
         /*
@@ -99,4 +123,12 @@ public class OssUploadDownloadFacadeImpl implements OssUploadDownloadFacade {
     public boolean isLocalPlatform(String storagePlatform) {
         return OssFileInfoService.isLocalPlatform(storagePlatform);
     }
+
+    /**
+     * 格式化日期为路径形式,如:2024/01/01/
+     * @param date 任意日期,一般取今日
+     */
+    private String formatDatePath(LocalDateTime date) {
+        return String.format("%d/%02d/%02d/", date.getYear(), date.getMonthValue(), date.getDayOfMonth());
+    }
 }

+ 0 - 3
src/main/java/cc/uncarbon/module/oss/model/request/AdminListOssFileInfoDTO.java

@@ -24,9 +24,6 @@ import java.time.LocalDateTime;
 @Data
 public class AdminListOssFileInfoDTO implements Serializable {
 
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "原始文件名(关键词)")
     private String originalFilename;
 

+ 6 - 2
src/main/java/cc/uncarbon/module/oss/model/request/UploadFileAttributeDTO.java

@@ -18,8 +18,6 @@ import java.io.Serializable;
 @Data
 public class UploadFileAttributeDTO implements Serializable {
 
-    private static final long serialVersionUID = 1L;
-
     @Schema(description = "文件类别", example = "id_card=身份证 driver_license=驾驶证")
     private String classified;
 
@@ -35,4 +33,10 @@ public class UploadFileAttributeDTO implements Serializable {
     @Schema(description = "MD5", hidden = true)
     private String md5;
 
+    @Schema(description = "指定要上传到的平台名(null则取值默认平台)", hidden = true)
+    private String platform;
+
+    @Schema(description = "从对象存储服务器中下载时,以原始文件名命名(通过指定 metadata 实现)", hidden = true)
+    private boolean useOriginalFilenameAsDownloadFileName;
+
 }

+ 0 - 2
src/main/java/cc/uncarbon/module/oss/model/response/OssFileDownloadReplyBO.java

@@ -20,8 +20,6 @@ import java.io.Serializable;
 @Data
 public class OssFileDownloadReplyBO implements Serializable {
 
-    private static final long serialVersionUID = 1L;
-
     @Schema(description = "是否直接重定向到对象存储直链", title = "如果允许客户端直接从“对象存储直链”下载,则本字段可以置 true")
     private boolean redirect2DirectUrl;
 

+ 0 - 2
src/main/java/cc/uncarbon/module/oss/model/response/OssFileInfoBO.java

@@ -24,8 +24,6 @@ import java.time.LocalDateTime;
 @Data
 public class OssFileInfoBO implements Serializable {
 
-    private static final long serialVersionUID = 1L;
-
     @Schema(description = "主键ID")
     private Long id;
 

+ 0 - 2
src/main/java/cc/uncarbon/module/oss/model/response/OssFileUploadResultVO.java

@@ -18,8 +18,6 @@ import java.io.Serializable;
 @Data
 public class OssFileUploadResultVO implements Serializable {
 
-    private static final long serialVersionUID = 1L;
-
     @Schema(description = "文件ID")
     private Long fileId;
 

+ 1 - 1
src/main/java/cc/uncarbon/module/oss/service/OssFileInfoService.java

@@ -107,7 +107,7 @@ public class OssFileInfoService {
         }
 
         // 2. 删除文件记录
-        ossFileInfoMapper.deleteBatchIds(ids);
+        ossFileInfoMapper.deleteByIds(ids);
     }
 
     /**

+ 96 - 0
src/main/java/cc/uncarbon/module/oss/util/UploadFileChecker.java

@@ -0,0 +1,96 @@
+package cc.uncarbon.module.oss.util;
+
+import cc.uncarbon.module.oss.enums.UploadFileCheckResultEnum;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.file.FileNameUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.util.ArrayUtil;
+import jakarta.annotation.Nonnull;
+import lombok.experimental.UtilityClass;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.Collection;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * 前端上传文件检查器
+ * 该检查器较为简易,请按照实际网络安全需要扩展
+ */
+@UtilityClass
+public class UploadFileChecker {
+
+    // 最小文件数量
+    private static final int FILE_QTY_MIN = 1;
+    // 最小文件尺寸
+    private static final int FILE_SIZE_MIN = 1;
+
+    // 文件尺寸 1MB
+    public static final long FILE_SIZE_1MB = 1024 * 1024L;
+
+
+    /**
+     * 批量检查
+     *
+     * @param fileQtyMax        最大允许的文件数量
+     * @param singleFileSizeMax 最大单文件尺寸,单位=KB
+     * @param allFileSizeMax    最大多文件累计尺寸,单位=KB
+     * @param allowedSuffixes   允许上传的文件后缀
+     */
+    public UploadFileCheckResultEnum check(Collection<MultipartFile> multipartFiles, int fileQtyMax,
+                                           long singleFileSizeMax, long allFileSizeMax, String[] allowedSuffixes) {
+        // 限制文件数量
+        int fileQty = CollUtil.size(multipartFiles);
+        if (fileQty < FILE_QTY_MIN) {
+            return UploadFileCheckResultEnum.NO_FILE;
+        } else if (fileQty > fileQtyMax) {
+            return UploadFileCheckResultEnum.TOO_MANY_FILES;
+        }
+        // 限制所有文件总大小
+        long allFileSize = multipartFiles.stream().mapToLong(MultipartFile::getSize).sum();
+        // 除以1024
+        allFileSize = allFileSize >> 10;
+        if (allFileSize > allFileSizeMax) {
+            return UploadFileCheckResultEnum.TOO_LARGE_FILE_SIZE;
+        }
+
+        // 每个文件单独检查
+        for (MultipartFile item : multipartFiles) {
+            UploadFileCheckResultEnum singleCheckRet = check(item, singleFileSizeMax, allowedSuffixes);
+            if (singleCheckRet.isNotOK()) {
+                return singleCheckRet;
+            }
+        }
+        return UploadFileCheckResultEnum.OK;
+    }
+
+    /**
+     * 单个检查
+     * @param singleFileSizeMax 最大单文件尺寸,单位=KB
+     * @param allowedSuffixes   允许上传的文件后缀
+     */
+    public UploadFileCheckResultEnum check(@Nonnull MultipartFile multipartFile, long singleFileSizeMax, String[] allowedSuffixes) {
+        long fileSize = multipartFile.getSize();
+
+        // 确定文件大小区间
+        if (fileSize < FILE_SIZE_MIN) {
+            return UploadFileCheckResultEnum.EMPTY_FILE;
+        }
+        // 除以1024
+        fileSize = fileSize >> 10;
+        if (fileSize > singleFileSizeMax) {
+            return UploadFileCheckResultEnum.TOO_LARGE_FILE_SIZE;
+        }
+
+        // 确定后缀名
+        String suffix = FileNameUtil.getSuffix(multipartFile.getOriginalFilename());
+        if (CharSequenceUtil.isNotBlank(suffix)) {
+            suffix = suffix.toLowerCase(Locale.ROOT);
+        }
+        // 检查后缀名
+        if (Objects.isNull(suffix) || !ArrayUtil.contains(allowedSuffixes, suffix)) {
+            return UploadFileCheckResultEnum.ILLEGAL_FILE_SUFFIX;
+        }
+        return UploadFileCheckResultEnum.OK;
+    }
+}

+ 3 - 27
src/main/java/cc/uncarbon/module/sys/enums/SysErrorEnum.java

@@ -13,57 +13,33 @@ import lombok.Getter;
 public enum SysErrorEnum implements HelioBaseEnum<Integer> {
 
     INVALID_ID(400, "无效ID"),
-
     INCORRECT_PIN_OR_PWD(400, "账号或密码不正确"),
-
     BANNED_USER(400, "用户被封禁"),
-
     INVALID_TENANT(400, "所属租户无效"),
-
     DISABLED_TENANT(400, "所属租户已禁用"),
-
     INCORRECT_OLD_PASSWORD(400, "原密码有误"),
-
     NO_ROLE_AVAILABLE_FOR_CURRENT_USER(400, "当前用户没有可用角色"),
-
     NO_MENU_AVAILABLE_FOR_CURRENT_ROLE(400, "当前角色没有可用菜单"),
 
-    /*
-    以下8个枚举用于后台角色管理的越权检查
-    */
-
+    // 以下8个枚举用于后台角色管理的越权检查
     ROLE_VALUE_CANNOT_BE(403, "角色值 {} 不能用于新增或编辑,请选用其他值"),
-
     CANNOT_DELETE_SUPER_ADMIN_ROLE(403, "不能删除超级管理员角色"),
-
     CANNOT_DELETE_TENANT_ADMIN_ROLE(403, "为减少脏数据,不建议直接删除租户管理员角色,需通过【删除租户】关联删除"),
-
     CANNOT_DELETE_SELF_ROLE(403, "不能删除自身角色"),
-
     CANNOT_BIND_MENUS_FOR_SUPER_ADMIN_ROLE(403, "不能为超级管理员角色绑定菜单"),
-
     CANNOT_BIND_MENUS_FOR_SELF(403, "不能为自身角色绑定菜单"),
-
     BEYOND_AUTHORITY_BIND_MENUS(401, "不得超越自身菜单权限"),
-
     CANNOT_BIND_MENUS_FOR_TENANT_ADMIN_ROLE(403, "无权为租户管理员绑定菜单"),
 
-    /*
-    以下4个枚举用于后台用户管理的越权检查
-    */
+    // 以下4个枚举用于后台用户管理的越权检查
     CANNOT_OPERATE_SELF_USER(403, "不能对自身进行此操作"),
-
     CANNOT_OPERATE_THIS_USER(403, "不能该用户进行此操作"),
-
     CANNOT_UNBIND_SELF_TENANT_ADMIN_ROLE(403, "自身的管理员角色不能被取消"),
-
     BEYOND_AUTHORITY_BIND_ROLES(401, "不得超越自身角色权限"),
 
     CANNOT_DELETE_PRIVILEGED_TENANT(403, "不能删除超级租户"),
+    NEED_DELETE_EXISTING_TENANT_ADMIN_ROLE(500, "租户ID {} 对应的租户管理员角色已存在,请使用超级管理员账号删除"),;
 
-    NEED_DELETE_EXISTING_TENANT_ADMIN_ROLE(500, "租户ID {} 对应的租户管理员角色已存在,请使用超级管理员账号删除"),
-
-    ;
     private final Integer value;
     private final String label;
 

+ 1 - 4
src/main/java/cc/uncarbon/module/sys/enums/SysLogStatusEnum.java

@@ -14,12 +14,9 @@ import lombok.Getter;
 public enum SysLogStatusEnum implements HelioBaseEnum<Integer> {
 
     NON_EXECUTION(0, "未执行"),
-
     SUCCESS(1, "成功"),
+    FAILED(2, "失败"),;
 
-    FAILED(2, "失败"),
-
-    ;
     @EnumValue
     private final Integer value;
     private final String label;

+ 1 - 2
src/main/java/cc/uncarbon/module/sys/enums/SysMenuTypeEnum.java

@@ -34,9 +34,8 @@ public enum SysMenuTypeEnum implements HelioBaseEnum<Integer> {
     /**
      * 外链
      */
-    EXTERNAL_LINK(3, "外链"),
+    EXTERNAL_LINK(3, "外链"),;
 
-    ;
     @EnumValue
     private final Integer value;
     private final String label;

+ 1 - 2
src/main/java/cc/uncarbon/module/sys/enums/SysUserStatusEnum.java

@@ -14,9 +14,8 @@ import lombok.Getter;
 public enum SysUserStatusEnum implements HelioBaseEnum<Integer> {
 
     BANNED(0, "封禁"),
-    ENABLED(1, "正常"),
+    ENABLED(1, "正常"),;
 
-    ;
     @EnumValue
     private final Integer value;
     private final String label;

+ 34 - 34
src/main/java/cc/uncarbon/module/sys/extension/SysLogAspectExtension.java

@@ -1,34 +1,34 @@
-package cc.uncarbon.module.sys.extension;
-
-import cc.uncarbon.module.sys.annotation.SysLog;
-import cc.uncarbon.module.sys.model.request.AdminInsertSysLogDTO;
-import cc.uncarbon.module.sys.model.response.IPLocationBO;
-import org.aspectj.lang.JoinPoint;
-
-/**
- * SysLog 切面实现类扩展
- *
- */
-public interface SysLogAspectExtension {
-
-    /**
-     * 查询IP地址属地
-     * @param ip IPv4或IPv6地址
-     */
-    default IPLocationBO queryIPLocation(String ip) {
-        return IPLocationBO.unknown();
-    }
-
-    /**
-     * 保存到 DB 前
-     * @param insertSysLogDTO 新系统日志DTO
-     * @param joinPoint 切点
-     * @param annotation 注解实例
-     * @param e 异常实例;如果没有发生异常则值为null
-     * @param ret 原始方法过程,于顺利运行时的返回值
-     */
-    default void beforeSaving(final AdminInsertSysLogDTO insertSysLogDTO, final JoinPoint joinPoint, SysLog annotation, final Throwable e, Object ret) {
-
-    }
-
-}
+package cc.uncarbon.module.sys.extension;
+
+import cc.uncarbon.module.sys.annotation.SysLog;
+import cc.uncarbon.module.sys.model.request.AdminInsertSysLogDTO;
+import cc.uncarbon.module.sys.model.response.IPLocationBO;
+import org.aspectj.lang.JoinPoint;
+
+/**
+ * SysLog 切面实现类扩展
+ *
+ */
+public interface SysLogAspectExtension {
+
+    /**
+     * 查询IP地址属地
+     * @param ip IPv4或IPv6地址
+     */
+    default IPLocationBO queryIPLocation(String ip) {
+        return IPLocationBO.unknown();
+    }
+
+    /**
+     * 保存到 DB 前
+     * @param insertSysLogDTO 新系统日志DTO
+     * @param joinPoint 切点
+     * @param annotation 注解实例
+     * @param e 异常实例;如果没有发生异常则值为null
+     * @param ret 原始方法过程,于顺利运行时的返回值
+     */
+    default void beforeSaving(final AdminInsertSysLogDTO insertSysLogDTO, final JoinPoint joinPoint, SysLog annotation, final Throwable e, Object ret) {
+
+    }
+
+}

+ 70 - 70
src/main/java/cc/uncarbon/module/sys/extension/impl/DefaultSysLogAspectExtension.java

@@ -1,70 +1,70 @@
-package cc.uncarbon.module.sys.extension.impl;
-
-import cc.uncarbon.module.sys.extension.SysLogAspectExtension;
-import cc.uncarbon.module.sys.model.response.IPLocationBO;
-import cn.hutool.core.net.NetUtil;
-import cn.hutool.core.text.CharSequenceUtil;
-import cn.hutool.core.text.StrPool;
-import cn.hutool.core.util.CharsetUtil;
-import cn.hutool.http.Header;
-import cn.hutool.http.HttpRequest;
-import cn.hutool.http.HttpResponse;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
-import lombok.extern.slf4j.Slf4j;
-
-@Slf4j
-public class DefaultSysLogAspectExtension implements SysLogAspectExtension {
-
-    @Override
-    public IPLocationBO queryIPLocation(String ip) {
-        if (CharSequenceUtil.contains(ip, StrPool.COLON)) {
-            // 仅根据冒号简易判断;暂不支持IPv6地址
-            return IPLocationBO.unknown();
-        }
-
-        if (NetUtil.isInnerIP(ip)) {
-            return IPLocationBO.intranet();
-        }
-
-        // 该API主要支持中国内地
-        HttpRequest httpRequest = HttpRequest.get("http://whois.pconline.com.cn/ipJson.jsp")
-                .form("ip", ip)
-                .form("json", Boolean.TRUE.toString())
-                .charset(CharsetUtil.CHARSET_GBK)
-                // since 1.11.0,加个UA避免被当成恶意请求,造成查IP失败
-                .header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
-                .timeout(5000);
-        try (HttpResponse httpResponse = httpRequest.execute()) {
-            String repStr = httpResponse.body();
-            if (JSONUtil.isTypeJSONObject(repStr)) {
-                /*
-                返回文本是JSON对象
-                example1:
-                {"ip":"39.156.66.10","pro":"北京市","proCode":"110000","city":"北京市","cityCode":"110000","region":"","regionCode":"0","addr":"北京市 移通","regionNames":"","err":""}
-
-                example2:
-                {"ip":"1.1.1.1","pro":"","proCode":"999999","city":"","cityCode":"0","region":"","regionCode":"0","addr":" 美国APNIC&CloudFlare公共DNS服务器","regionNames":"","err":"noprovince"}
-
-                example3:
-                {"ip":"8.8.8.8","pro":"","proCode":"999999","city":"","cityCode":"0","region":"","regionCode":"0","addr":" 美国","regionNames":"","err":"noprovince"}
-                 */
-                JSONObject repJson = JSONUtil.parseObj(repStr);
-                String pro = repJson.getStr("pro");
-                String err = repJson.getStr("err");
-                if (CharSequenceUtil.isEmpty(pro) || "noprovince".equals(err)) {
-                    // 可能是非中国内地IP
-                    String regionName = repJson.getStr("addr");
-                    return IPLocationBO.unknown().setRegionName(regionName);
-                }
-                return IPLocationBO.inChina()
-                        .setProvinceName(pro)
-                        .setCityName(repJson.getStr("city"));
-            }
-        } catch (Exception e) {
-            log.error("[SysLog切面][查询IP地址属地异常] >> ip={}  \n", ip, e);
-        }
-
-        return IPLocationBO.unknown();
-    }
-}
+package cc.uncarbon.module.sys.extension.impl;
+
+import cc.uncarbon.module.sys.extension.SysLogAspectExtension;
+import cc.uncarbon.module.sys.model.response.IPLocationBO;
+import cn.hutool.core.net.NetUtil;
+import cn.hutool.core.text.CharSequenceUtil;
+import cn.hutool.core.text.StrPool;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class DefaultSysLogAspectExtension implements SysLogAspectExtension {
+
+    @Override
+    public IPLocationBO queryIPLocation(String ip) {
+        if (CharSequenceUtil.contains(ip, StrPool.COLON)) {
+            // 仅根据冒号简易判断;暂不支持IPv6地址
+            return IPLocationBO.unknown();
+        }
+
+        if (NetUtil.isInnerIP(ip)) {
+            return IPLocationBO.intranet();
+        }
+
+        // 该API主要支持中国内地
+        HttpRequest httpRequest = HttpRequest.get("http://whois.pconline.com.cn/ipJson.jsp")
+                .form("ip", ip)
+                .form("json", Boolean.TRUE.toString())
+                .charset(CharsetUtil.CHARSET_GBK)
+                // since 1.11.0,加个UA避免被当成恶意请求,造成查IP失败
+                .header(Header.USER_AGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36")
+                .timeout(5000);
+        try (HttpResponse httpResponse = httpRequest.execute()) {
+            String repStr = httpResponse.body();
+            if (JSONUtil.isTypeJSONObject(repStr)) {
+                /*
+                返回文本是JSON对象
+                example1:
+                {"ip":"39.156.66.10","pro":"北京市","proCode":"110000","city":"北京市","cityCode":"110000","region":"","regionCode":"0","addr":"北京市 移通","regionNames":"","err":""}
+
+                example2:
+                {"ip":"1.1.1.1","pro":"","proCode":"999999","city":"","cityCode":"0","region":"","regionCode":"0","addr":" 美国APNIC&CloudFlare公共DNS服务器","regionNames":"","err":"noprovince"}
+
+                example3:
+                {"ip":"8.8.8.8","pro":"","proCode":"999999","city":"","cityCode":"0","region":"","regionCode":"0","addr":" 美国","regionNames":"","err":"noprovince"}
+                 */
+                JSONObject repJson = JSONUtil.parseObj(repStr);
+                String pro = repJson.getStr("pro");
+                String err = repJson.getStr("err");
+                if (CharSequenceUtil.isEmpty(pro) || "noprovince".equals(err)) {
+                    // 可能是非中国内地IP
+                    String regionName = repJson.getStr("addr");
+                    return IPLocationBO.unknown().setRegionName(regionName);
+                }
+                return IPLocationBO.inChina()
+                        .setProvinceName(pro)
+                        .setCityName(repJson.getStr("city"));
+            }
+        } catch (Exception e) {
+            log.error("[SysLog切面][查询IP地址属地异常] >> ip={}  \n", ip, e);
+        }
+
+        return IPLocationBO.unknown();
+    }
+}

+ 29 - 29
src/main/java/cc/uncarbon/module/sys/facade/SysTenantFacade.java

@@ -1,29 +1,29 @@
-package cc.uncarbon.module.sys.facade;
-
-import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO;
-import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
-import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
-
-import java.util.Collection;
-
-/**
- * 系统租户解耦层,用于解决循环依赖
- */
-public interface SysTenantFacade {
-
-    /**
-     * 后台管理-新增
-     */
-    Long adminInsert(AdminInsertSysTenantDTO dto);
-
-    /**
-     * 后台管理-编辑
-     */
-    SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto);
-
-    /**
-     * 后台管理-删除
-     */
-    SysTenantKickOutUsersBO adminDelete(Collection<Long> ids);
-
-}
+package cc.uncarbon.module.sys.facade;
+
+import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO;
+import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
+import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
+
+import java.util.Collection;
+
+/**
+ * 系统租户解耦层,用于解决循环依赖
+ */
+public interface SysTenantFacade {
+
+    /**
+     * 后台管理-新增
+     */
+    Long adminInsert(AdminInsertSysTenantDTO dto);
+
+    /**
+     * 后台管理-编辑
+     */
+    SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto);
+
+    /**
+     * 后台管理-删除
+     */
+    SysTenantKickOutUsersBO adminDelete(Collection<Long> ids);
+
+}

+ 154 - 154
src/main/java/cc/uncarbon/module/sys/facade/impl/SysTenantFacadeImpl.java

@@ -1,154 +1,154 @@
-package cc.uncarbon.module.sys.facade.impl;
-
-import cc.uncarbon.framework.core.constant.HelioConstant;
-import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
-import cc.uncarbon.framework.core.function.StreamFunction;
-import cc.uncarbon.module.sys.constant.SysConstant;
-import cc.uncarbon.module.sys.entity.SysTenantEntity;
-import cc.uncarbon.module.sys.enums.SysErrorEnum;
-import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
-import cc.uncarbon.module.sys.facade.SysTenantFacade;
-import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysRoleDTO;
-import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysUserDTO;
-import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO;
-import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
-import cc.uncarbon.module.sys.model.response.SysTenantBO;
-import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
-import cc.uncarbon.module.sys.service.SysRoleService;
-import cc.uncarbon.module.sys.service.SysTenantService;
-import cc.uncarbon.module.sys.service.SysUserRoleRelationService;
-import cc.uncarbon.module.sys.service.SysUserService;
-import cn.hutool.core.collection.CollUtil;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
-import java.util.*;
-import java.util.stream.Collectors;
-
-/**
- * 系统租户Facade接口实现类
- */
-@RequiredArgsConstructor
-@Service
-@Slf4j
-public class SysTenantFacadeImpl implements SysTenantFacade {
-
-    private final SysTenantService sysTenantService;
-    private final SysRoleService sysRoleService;
-    private final SysUserRoleRelationService sysUserRoleRelationService;
-    private final SysUserService sysUserService;
-
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public Long adminInsert(AdminInsertSysTenantDTO dto) {
-        log.info("[后台管理-新增系统租户] >> 入参={}", dto);
-        sysTenantService.checkExistence(dto);
-
-        /*
-        1. 加入一个新租户(tenant)
-        这里是直接顺带创建管理员账号了, 你可以根据业务需要决定是否创建
-         */
-        SysTenantEntity entity = sysTenantService.adminInsert(dto);
-
-        Long newTenantEntityId = entity.getId();
-        Long newTenantId = entity.getTenantId();
-
-        /*
-        2. 创建一个新角色(role)
-        注意: 这里并没有指派其可见菜单,需要用超管账号授权
-         */
-        Long newRoleId = sysRoleService.adminInsert(
-                AdminInsertOrUpdateSysRoleDTO.builder()
-                        .tenantId(newTenantId)
-                        .title(dto.getTenantName() + "主管理员")
-                        .value(SysConstant.TENANT_ADMIN_ROLE_VALUE)
-                        .build()
-        );
-
-        // 3. 创建一个新用户
-        Long newUserId = sysUserService.adminInsert(
-                AdminInsertOrUpdateSysUserDTO.builder()
-                        .tenantId(newTenantId)
-                        .username(dto.getTenantAdminUsername())
-                        .passwordOfNewUser(dto.getTenantAdminPassword())
-                        .nickname(dto.getTenantName() + "主管理员")
-                        .email(dto.getTenantAdminEmail())
-                        .phoneNo(dto.getTenantAdminPhoneNo())
-                        // 默认为正常状态
-                        .status(SysUserStatusEnum.ENABLED)
-                        .build()
-        );
-
-        // 4. 将新用户绑定至新角色上
-        sysUserRoleRelationService.adminInsert(newTenantId, newUserId, newRoleId);
-
-        // 5. 把管理员账号更新进库
-        SysTenantEntity update = new SysTenantEntity().setTenantAdminUserId(newUserId);
-        update.setId(newTenantEntityId);
-        sysTenantService.adminUpdate(update);
-        return newTenantId;
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto) {
-        sysTenantService.adminUpdate(dto);
-
-        if (dto.getStatus() == EnabledStatusEnum.DISABLED) {
-            // 新状态是禁用,查出需要强制登出的用户
-            Long tenantId = CollUtil.getFirst(determineTenantIdsByPrimaryKeys(Collections.singleton(dto.getId())).values());
-            if (Objects.nonNull(tenantId)) {
-                List<Long> tenantSysUserIds = sysUserService.listUserIdsByTenantId(tenantId, Collections.singleton(EnabledStatusEnum.ENABLED));
-                return new SysTenantKickOutUsersBO(tenantSysUserIds);
-            }
-        }
-        return new SysTenantKickOutUsersBO();
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public SysTenantKickOutUsersBO adminDelete(Collection<Long> ids) {
-        Collection<Long> tenantIds = determineTenantIdsByPrimaryKeys(ids).values();
-        if (CollUtil.isNotEmpty(tenantIds)) {
-            // 不能删除「超级租户」(租户ID=0)
-            SysErrorEnum.CANNOT_DELETE_PRIVILEGED_TENANT.assertNotContains(tenantIds, HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID);
-
-            // 删除租户管理员角色、租户
-            sysRoleService.adminDeleteTenantRoles(tenantIds, Collections.singleton(SysConstant.TENANT_ADMIN_ROLE_VALUE));
-            sysTenantService.adminDelete(ids);
-
-            // 查出需要强制登出的用户
-            List<Long> tenantSysUserIds = tenantIds.stream()
-                    .map(tenantId -> sysUserService.listUserIdsByTenantId(tenantId, Collections.singleton(EnabledStatusEnum.ENABLED)))
-                    .flatMap(Collection::stream)
-                    .collect(Collectors.toList());
-            return new SysTenantKickOutUsersBO(tenantSysUserIds);
-        }
-        return new SysTenantKickOutUsersBO();
-    }
-
-    /*
-    ----------------------------------------------------------------
-                        私有方法 private methods
-    ----------------------------------------------------------------
-     */
-
-    /**
-     * 根据租户主键ID,确定租户IDs
-     * @return map[主键ID, 租户ID]
-     */
-    private Map<Long, Long> determineTenantIdsByPrimaryKeys(Collection<Long> ids) {
-        if (CollUtil.isEmpty(ids)) {
-            return Collections.emptyMap();
-        }
-        List<SysTenantBO> sysTenantInfos = sysTenantService.listByIds(ids, false);
-        if (CollUtil.isEmpty(sysTenantInfos)) {
-            return Collections.emptyMap();
-        }
-        return sysTenantInfos.stream().collect(Collectors.toMap(SysTenantBO::getId, SysTenantBO::getTenantId, StreamFunction.ignoredThrowingMerger()));
-    }
-
-}
+package cc.uncarbon.module.sys.facade.impl;
+
+import cc.uncarbon.framework.core.constant.HelioConstant;
+import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
+import cc.uncarbon.framework.core.function.StreamFunction;
+import cc.uncarbon.module.sys.constant.SysConstant;
+import cc.uncarbon.module.sys.entity.SysTenantEntity;
+import cc.uncarbon.module.sys.enums.SysErrorEnum;
+import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
+import cc.uncarbon.module.sys.facade.SysTenantFacade;
+import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysRoleDTO;
+import cc.uncarbon.module.sys.model.request.AdminInsertOrUpdateSysUserDTO;
+import cc.uncarbon.module.sys.model.request.AdminInsertSysTenantDTO;
+import cc.uncarbon.module.sys.model.request.AdminUpdateSysTenantDTO;
+import cc.uncarbon.module.sys.model.response.SysTenantBO;
+import cc.uncarbon.module.sys.model.response.SysTenantKickOutUsersBO;
+import cc.uncarbon.module.sys.service.SysRoleService;
+import cc.uncarbon.module.sys.service.SysTenantService;
+import cc.uncarbon.module.sys.service.SysUserRoleRelationService;
+import cc.uncarbon.module.sys.service.SysUserService;
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 系统租户Facade接口实现类
+ */
+@RequiredArgsConstructor
+@Service
+@Slf4j
+public class SysTenantFacadeImpl implements SysTenantFacade {
+
+    private final SysTenantService sysTenantService;
+    private final SysRoleService sysRoleService;
+    private final SysUserRoleRelationService sysUserRoleRelationService;
+    private final SysUserService sysUserService;
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long adminInsert(AdminInsertSysTenantDTO dto) {
+        log.info("[后台管理-新增系统租户] >> 入参={}", dto);
+        sysTenantService.checkExistence(dto);
+
+        /*
+        1. 加入一个新租户(tenant)
+        这里是直接顺带创建管理员账号了, 你可以根据业务需要决定是否创建
+         */
+        SysTenantEntity entity = sysTenantService.adminInsert(dto);
+
+        Long newTenantEntityId = entity.getId();
+        Long newTenantId = entity.getTenantId();
+
+        /*
+        2. 创建一个新角色(role)
+        注意: 这里并没有指派其可见菜单,需要用超管账号授权
+         */
+        Long newRoleId = sysRoleService.adminInsert(
+                AdminInsertOrUpdateSysRoleDTO.builder()
+                        .tenantId(newTenantId)
+                        .title(dto.getTenantName() + "主管理员")
+                        .value(SysConstant.TENANT_ADMIN_ROLE_VALUE)
+                        .build()
+        );
+
+        // 3. 创建一个新用户
+        Long newUserId = sysUserService.adminInsert(
+                AdminInsertOrUpdateSysUserDTO.builder()
+                        .tenantId(newTenantId)
+                        .username(dto.getTenantAdminUsername())
+                        .passwordOfNewUser(dto.getTenantAdminPassword())
+                        .nickname(dto.getTenantName() + "主管理员")
+                        .email(dto.getTenantAdminEmail())
+                        .phoneNo(dto.getTenantAdminPhoneNo())
+                        // 默认为正常状态
+                        .status(SysUserStatusEnum.ENABLED)
+                        .build()
+        );
+
+        // 4. 将新用户绑定至新角色上
+        sysUserRoleRelationService.adminInsert(newTenantId, newUserId, newRoleId);
+
+        // 5. 把管理员账号更新进库
+        SysTenantEntity update = new SysTenantEntity().setTenantAdminUserId(newUserId);
+        update.setId(newTenantEntityId);
+        sysTenantService.adminUpdate(update);
+        return newTenantId;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public SysTenantKickOutUsersBO adminUpdate(AdminUpdateSysTenantDTO dto) {
+        sysTenantService.adminUpdate(dto);
+
+        if (dto.getStatus() == EnabledStatusEnum.DISABLED) {
+            // 新状态是禁用,查出需要强制登出的用户
+            Long tenantId = CollUtil.getFirst(determineTenantIdsByPrimaryKeys(Collections.singleton(dto.getId())).values());
+            if (Objects.nonNull(tenantId)) {
+                List<Long> tenantSysUserIds = sysUserService.listUserIdsByTenantId(tenantId, Collections.singleton(EnabledStatusEnum.ENABLED));
+                return new SysTenantKickOutUsersBO(tenantSysUserIds);
+            }
+        }
+        return new SysTenantKickOutUsersBO();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public SysTenantKickOutUsersBO adminDelete(Collection<Long> ids) {
+        Collection<Long> tenantIds = determineTenantIdsByPrimaryKeys(ids).values();
+        if (CollUtil.isNotEmpty(tenantIds)) {
+            // 不能删除「超级租户」(租户ID=0)
+            SysErrorEnum.CANNOT_DELETE_PRIVILEGED_TENANT.assertNotContains(tenantIds, HelioConstant.Tenant.DEFAULT_PRIVILEGED_TENANT_ID);
+
+            // 删除租户管理员角色、租户
+            sysRoleService.adminDeleteTenantRoles(tenantIds, Collections.singleton(SysConstant.TENANT_ADMIN_ROLE_VALUE));
+            sysTenantService.adminDelete(ids);
+
+            // 查出需要强制登出的用户
+            List<Long> tenantSysUserIds = tenantIds.stream()
+                    .map(tenantId -> sysUserService.listUserIdsByTenantId(tenantId, Collections.singleton(EnabledStatusEnum.ENABLED)))
+                    .flatMap(Collection::stream)
+                    .collect(Collectors.toList());
+            return new SysTenantKickOutUsersBO(tenantSysUserIds);
+        }
+        return new SysTenantKickOutUsersBO();
+    }
+
+    /*
+    ----------------------------------------------------------------
+                        私有方法 private methods
+    ----------------------------------------------------------------
+     */
+
+    /**
+     * 根据租户主键ID,确定租户IDs
+     * @return map[主键ID, 租户ID]
+     */
+    private Map<Long, Long> determineTenantIdsByPrimaryKeys(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyMap();
+        }
+        List<SysTenantBO> sysTenantInfos = sysTenantService.listByIds(ids, false);
+        if (CollUtil.isEmpty(sysTenantInfos)) {
+            return Collections.emptyMap();
+        }
+        return sysTenantInfos.stream().collect(Collectors.toMap(SysTenantBO::getId, SysTenantBO::getTenantId, StreamFunction.ignoredThrowingMerger()));
+    }
+
+}

+ 82 - 82
src/main/java/cc/uncarbon/module/sys/model/interior/UserDeptContainer.java

@@ -1,82 +1,82 @@
-package cc.uncarbon.module.sys.model.interior;
-
-import cc.uncarbon.module.sys.entity.SysDeptEntity;
-import cn.hutool.core.collection.CollUtil;
-import lombok.Getter;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.stream.Collectors;
-
-/**
- * 用户关联部门容器
- */
-@Getter
-public class UserDeptContainer {
-
-    /**
-     * 直接关联的部门IDs
-     * 一般只有0或1个元素
-     */
-    private final List<Long> relatedDeptIds;
-
-    /**
-     * 直接关联的部门实例集合
-     * 一般只有0或1个元素
-     */
-    private final List<SysDeptEntity> relatedDepts;
-
-    /**
-     * 可见的部门IDs
-     */
-    private List<Long> visibleDeptIds;
-
-    /**
-     * 可见的部门实例集合
-     */
-    private List<SysDeptEntity> visibleDepts;
-
-
-    public UserDeptContainer(List<Long> relatedDeptIds, List<SysDeptEntity> relatedDepts) {
-        this.relatedDeptIds = relatedDeptIds;
-        this.relatedDepts = relatedDepts;
-        this.visibleDeptIds = relatedDeptIds;
-        this.visibleDepts = relatedDepts;
-    }
-
-    /**
-     * 用户是否有实际关联的部门
-     */
-    public boolean hasRelatedDepts() {
-        return CollUtil.isNotEmpty(relatedDeptIds) && CollUtil.isNotEmpty(relatedDepts);
-    }
-
-    /**
-     * 用户主要关联的部门,默认取第一个元素
-     * @return null or 部门实例
-     */
-    public SysDeptEntity primaryRelatedDept() {
-        return CollUtil.getFirst(relatedDepts);
-    }
-
-    /**
-     * 用户是否有实际可见的部门
-     * 也可视为部门的数据权限范围
-     */
-    public boolean hasVisibleDepts() {
-        return CollUtil.isNotEmpty(visibleDeptIds) && CollUtil.isNotEmpty(visibleDepts);
-    }
-
-    /**
-     * 更新可见的部门
-     */
-    public void updateVisibleDepts(List<SysDeptEntity> visibleDepts) {
-        if (CollUtil.isEmpty(visibleDepts)) {
-            this.visibleDeptIds = Collections.emptyList();
-            this.visibleDepts = Collections.emptyList();
-        } else {
-            this.visibleDeptIds = visibleDepts.stream().map(SysDeptEntity::getId).collect(Collectors.toList());
-            this.visibleDepts = visibleDepts;
-        }
-    }
-}
+package cc.uncarbon.module.sys.model.interior;
+
+import cc.uncarbon.module.sys.entity.SysDeptEntity;
+import cn.hutool.core.collection.CollUtil;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 用户关联部门容器
+ */
+@Getter
+public class UserDeptContainer {
+
+    /**
+     * 直接关联的部门IDs
+     * 一般只有0或1个元素
+     */
+    private final List<Long> relatedDeptIds;
+
+    /**
+     * 直接关联的部门实例集合
+     * 一般只有0或1个元素
+     */
+    private final List<SysDeptEntity> relatedDepts;
+
+    /**
+     * 可见的部门IDs
+     */
+    private List<Long> visibleDeptIds;
+
+    /**
+     * 可见的部门实例集合
+     */
+    private List<SysDeptEntity> visibleDepts;
+
+
+    public UserDeptContainer(List<Long> relatedDeptIds, List<SysDeptEntity> relatedDepts) {
+        this.relatedDeptIds = relatedDeptIds;
+        this.relatedDepts = relatedDepts;
+        this.visibleDeptIds = relatedDeptIds;
+        this.visibleDepts = relatedDepts;
+    }
+
+    /**
+     * 用户是否有实际关联的部门
+     */
+    public boolean hasRelatedDepts() {
+        return CollUtil.isNotEmpty(relatedDeptIds) && CollUtil.isNotEmpty(relatedDepts);
+    }
+
+    /**
+     * 用户主要关联的部门,默认取第一个元素
+     * @return null or 部门实例
+     */
+    public SysDeptEntity primaryRelatedDept() {
+        return CollUtil.getFirst(relatedDepts);
+    }
+
+    /**
+     * 用户是否有实际可见的部门
+     * 也可视为部门的数据权限范围
+     */
+    public boolean hasVisibleDepts() {
+        return CollUtil.isNotEmpty(visibleDeptIds) && CollUtil.isNotEmpty(visibleDepts);
+    }
+
+    /**
+     * 更新可见的部门
+     */
+    public void updateVisibleDepts(List<SysDeptEntity> visibleDepts) {
+        if (CollUtil.isEmpty(visibleDepts)) {
+            this.visibleDeptIds = Collections.emptyList();
+            this.visibleDepts = Collections.emptyList();
+        } else {
+            this.visibleDeptIds = visibleDepts.stream().map(SysDeptEntity::getId).collect(Collectors.toList());
+            this.visibleDepts = visibleDepts;
+        }
+    }
+}

+ 48 - 48
src/main/java/cc/uncarbon/module/sys/model/interior/UserRoleContainer.java

@@ -1,48 +1,48 @@
-package cc.uncarbon.module.sys.model.interior;
-
-import cc.uncarbon.module.sys.entity.SysRoleEntity;
-import lombok.Getter;
-
-import java.util.List;
-import java.util.Set;
-
-/**
- * 用户关联角色容器
- */
-@Getter
-public class UserRoleContainer {
-
-    /**
-     * 直接关联的角色IDs
-     */
-    private final Set<Long> relatedRoleIds;
-
-    /**
-     * 直接关联的角色实例集合
-     */
-    private final List<SysRoleEntity> relatedRoles;
-
-    /**
-     * 为超级管理员
-     */
-    private final boolean superAdmin;
-
-    /**
-     * 为租户管理员
-     */
-    private final boolean tenantAdmin;
-
-    /**
-     * 非级管理员or租户管理员
-     */
-    private final boolean notAnyAdmin;
-
-
-    public UserRoleContainer(Set<Long> relatedRoleIds, List<SysRoleEntity> relatedRoles) {
-        this.relatedRoleIds = relatedRoleIds;
-        this.relatedRoles = relatedRoles;
-        this.superAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isSuperAdmin);
-        this.tenantAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isTenantAdmin);
-        this.notAnyAdmin = !this.superAdmin && !this.tenantAdmin;
-    }
-}
+package cc.uncarbon.module.sys.model.interior;
+
+import cc.uncarbon.module.sys.entity.SysRoleEntity;
+import lombok.Getter;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 用户关联角色容器
+ */
+@Getter
+public class UserRoleContainer {
+
+    /**
+     * 直接关联的角色IDs
+     */
+    private final Set<Long> relatedRoleIds;
+
+    /**
+     * 直接关联的角色实例集合
+     */
+    private final List<SysRoleEntity> relatedRoles;
+
+    /**
+     * 为超级管理员
+     */
+    private final boolean superAdmin;
+
+    /**
+     * 为租户管理员
+     */
+    private final boolean tenantAdmin;
+
+    /**
+     * 非级管理员or租户管理员
+     */
+    private final boolean notAnyAdmin;
+
+
+    public UserRoleContainer(Set<Long> relatedRoleIds, List<SysRoleEntity> relatedRoles) {
+        this.relatedRoleIds = relatedRoleIds;
+        this.relatedRoles = relatedRoles;
+        this.superAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isSuperAdmin);
+        this.tenantAdmin = relatedRoles.stream().anyMatch(SysRoleEntity::isTenantAdmin);
+        this.notAnyAdmin = !this.superAdmin && !this.tenantAdmin;
+    }
+}

+ 2 - 2
src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysDeptDTO.java

@@ -2,14 +2,14 @@ package cc.uncarbon.module.sys.model.request;
 
 import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 3 - 3
src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysMenuDTO.java

@@ -3,15 +3,15 @@ package cc.uncarbon.module.sys.model.request;
 import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
 import cc.uncarbon.module.sys.enums.SysMenuTypeEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 2 - 2
src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysParamDTO.java

@@ -1,14 +1,14 @@
 package cc.uncarbon.module.sys.model.request;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 2 - 2
src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysRoleDTO.java

@@ -2,14 +2,14 @@ package cc.uncarbon.module.sys.model.request;
 
 import cc.uncarbon.module.sys.constant.SysConstant;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 import java.util.Objects;
 

+ 4 - 4
src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertOrUpdateSysUserDTO.java

@@ -6,16 +6,16 @@ import cc.uncarbon.framework.core.exception.BusinessException;
 import cc.uncarbon.module.sys.enums.SysUserStatusEnum;
 import cn.hutool.core.text.CharSequenceUtil;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Pattern;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 import java.util.Objects;
 

+ 1 - 2
src/main/java/cc/uncarbon/module/sys/model/request/AdminInsertSysTenantDTO.java

@@ -2,14 +2,13 @@ package cc.uncarbon.module.sys.model.request;
 
 import cc.uncarbon.framework.core.constant.HelioConstant;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.*;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.*;
-
 
 /**
  * 后台管理-新增系统租户

+ 2 - 2
src/main/java/cc/uncarbon/module/sys/model/request/AdminResetSysUserPasswordDTO.java

@@ -1,14 +1,14 @@
 package cc.uncarbon.module.sys.model.request;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 0 - 5
src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictClassifiedInsertOrUpdateDTO.java

@@ -11,7 +11,6 @@ import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 import lombok.experimental.SuperBuilder;
 
-import java.io.Serial;
 import java.io.Serializable;
 
 
@@ -25,10 +24,6 @@ import java.io.Serializable;
 @Data
 public class AdminSysDataDictClassifiedInsertOrUpdateDTO implements Serializable {
 
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "主键ID", hidden = true, title = "仅更新时使用")
     private Long id;
 

+ 0 - 5
src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictClassifiedListDTO.java

@@ -7,7 +7,6 @@ import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 import lombok.experimental.SuperBuilder;
 
-import java.io.Serial;
 import java.io.Serializable;
 
 
@@ -21,10 +20,6 @@ import java.io.Serializable;
 @Data
 public class AdminSysDataDictClassifiedListDTO implements Serializable {
 
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "分类编码")
     private String code;
 

+ 0 - 5
src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictItemInsertOrUpdateDTO.java

@@ -11,7 +11,6 @@ import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 import lombok.experimental.SuperBuilder;
 
-import java.io.Serial;
 import java.io.Serializable;
 
 
@@ -25,10 +24,6 @@ import java.io.Serializable;
 @Data
 public class AdminSysDataDictItemInsertOrUpdateDTO implements Serializable {
 
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "主键ID", hidden = true, title = "仅更新时使用")
     private Long id;
 

+ 0 - 5
src/main/java/cc/uncarbon/module/sys/model/request/AdminSysDataDictItemListDTO.java

@@ -7,7 +7,6 @@ import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 import lombok.experimental.SuperBuilder;
 
-import java.io.Serial;
 import java.io.Serializable;
 
 
@@ -21,10 +20,6 @@ import java.io.Serializable;
 @Data
 public class AdminSysDataDictItemListDTO implements Serializable {
 
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "所属分类ID", hidden = true)
     private Long classifiedId;
 

+ 2 - 2
src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateCurrentSysUserPasswordDTO.java

@@ -1,14 +1,14 @@
 package cc.uncarbon.module.sys.model.request;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 3 - 3
src/main/java/cc/uncarbon/module/sys/model/request/AdminUpdateSysTenantDTO.java

@@ -2,15 +2,15 @@ package cc.uncarbon.module.sys.model.request;
 
 import cc.uncarbon.framework.core.enums.EnabledStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 3 - 3
src/main/java/cc/uncarbon/module/sys/model/request/SysUserLoginDTO.java

@@ -1,15 +1,15 @@
 package cc.uncarbon.module.sys.model.request;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import lombok.experimental.Accessors;
 
-import jakarta.validation.constraints.NotBlank;
-import jakarta.validation.constraints.NotNull;
-import jakarta.validation.constraints.Size;
 import java.io.Serializable;
 
 

+ 53 - 53
src/main/java/cc/uncarbon/module/sys/model/response/IPLocationBO.java

@@ -1,53 +1,53 @@
-package cc.uncarbon.module.sys.model.response;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-import java.io.Serializable;
-
-/**
- * IP地址属地
- * 首用于系统日志
- */
-@Accessors(chain = true)
-@AllArgsConstructor
-@NoArgsConstructor
-@Data
-public class IPLocationBO implements Serializable {
-
-    @Schema(description = "国家或地区名")
-    private String regionName;
-
-    @Schema(description = "省级行政区名")
-    private String provinceName;
-
-    @Schema(description = "市级行政区名")
-    private String cityName;
-
-    @Schema(description = "县级行政区名")
-    private String districtName;
-
-    /**
-     * 未知属地
-     */
-    public static IPLocationBO unknown() {
-        return new IPLocationBO("未知", null, null, null);
-    }
-
-    /**
-     * 内网地址
-     */
-    public static IPLocationBO intranet() {
-        return new IPLocationBO("内网", null, null, null);
-    }
-
-    /**
-     * 位于中国内地
-     */
-    public static IPLocationBO inChina() {
-        return new IPLocationBO("中国", null, null, null);
-    }
-}
+package cc.uncarbon.module.sys.model.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+
+/**
+ * IP地址属地
+ * 首用于系统日志
+ */
+@Accessors(chain = true)
+@AllArgsConstructor
+@NoArgsConstructor
+@Data
+public class IPLocationBO implements Serializable {
+
+    @Schema(description = "国家或地区名")
+    private String regionName;
+
+    @Schema(description = "省级行政区名")
+    private String provinceName;
+
+    @Schema(description = "市级行政区名")
+    private String cityName;
+
+    @Schema(description = "县级行政区名")
+    private String districtName;
+
+    /**
+     * 未知属地
+     */
+    public static IPLocationBO unknown() {
+        return new IPLocationBO("未知", null, null, null);
+    }
+
+    /**
+     * 内网地址
+     */
+    public static IPLocationBO intranet() {
+        return new IPLocationBO("内网", null, null, null);
+    }
+
+    /**
+     * 位于中国内地
+     */
+    public static IPLocationBO inChina() {
+        return new IPLocationBO("中国", null, null, null);
+    }
+}

+ 0 - 5
src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictClassifiedBO.java

@@ -11,7 +11,6 @@ import lombok.experimental.Accessors;
 import lombok.experimental.SuperBuilder;
 import org.springframework.format.annotation.DateTimeFormat;
 
-import java.io.Serial;
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
@@ -26,10 +25,6 @@ import java.time.LocalDateTime;
 @Data
 public class SysDataDictClassifiedBO implements Serializable {
 
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "主键ID", hidden = true, title = "仅更新时使用")
     private Long id;
 

+ 0 - 5
src/main/java/cc/uncarbon/module/sys/model/response/SysDataDictItemBO.java

@@ -11,7 +11,6 @@ import lombok.experimental.Accessors;
 import lombok.experimental.SuperBuilder;
 import org.springframework.format.annotation.DateTimeFormat;
 
-import java.io.Serial;
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
@@ -26,10 +25,6 @@ import java.time.LocalDateTime;
 @Data
 public class SysDataDictItemBO implements Serializable {
 
-    @Serial
-    private static final long serialVersionUID = 1L;
-
-
     @Schema(description = "主键ID", hidden = true, title = "仅更新时使用")
     private Long id;
 

+ 26 - 26
src/main/java/cc/uncarbon/module/sys/model/response/SysTenantKickOutUsersBO.java

@@ -1,26 +1,26 @@
-package cc.uncarbon.module.sys.model.response;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Getter;
-
-import java.util.Collections;
-import java.util.List;
-
-/**
- * 系统租户-需强制登出用户 BO
- * 同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险
- */
-@Getter
-public class SysTenantKickOutUsersBO {
-
-    @Schema(description = "后台用户IDs")
-    private final List<Long> sysUserIds;
-
-    public SysTenantKickOutUsersBO() {
-        this.sysUserIds = Collections.emptyList();
-    }
-
-    public SysTenantKickOutUsersBO(List<Long> sysUserIds) {
-        this.sysUserIds = sysUserIds;
-    }
-}
+package cc.uncarbon.module.sys.model.response;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Getter;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 系统租户-需强制登出用户 BO
+ * 同一时间大量登出,会操作大量Redis键,可能存在缓存雪崩的风险
+ */
+@Getter
+public class SysTenantKickOutUsersBO {
+
+    @Schema(description = "后台用户IDs")
+    private final List<Long> sysUserIds;
+
+    public SysTenantKickOutUsersBO() {
+        this.sysUserIds = Collections.emptyList();
+    }
+
+    public SysTenantKickOutUsersBO(List<Long> sysUserIds) {
+        this.sysUserIds = sysUserIds;
+    }
+}

+ 1 - 1
src/main/java/cc/uncarbon/module/sys/service/SysDataDictService.java

@@ -97,7 +97,7 @@ public class SysDataDictService {
     @Transactional(rollbackFor = Exception.class)
     public void adminDeleteClassified(Collection<Long> ids) {
         log.info("[后台管理-删除数据字典分类] >> 入参={}", ids);
-        sysDataDictClassifiedMapper.deleteBatchIds(ids);
+        sysDataDictClassifiedMapper.deleteByIds(ids);
     }
 
     /**

+ 1 - 1
src/main/java/cc/uncarbon/module/sys/service/SysDeptService.java

@@ -115,7 +115,7 @@ public class SysDeptService {
     @Transactional(rollbackFor = Exception.class)
     public void adminDelete(Collection<Long> ids) {
         log.info("[后台管理-删除部门] >> 入参={}", ids);
-        sysDeptMapper.deleteBatchIds(ids);
+        sysDeptMapper.deleteByIds(ids);
     }
 
     /**

+ 1 - 1
src/main/java/cc/uncarbon/module/sys/service/SysMenuService.java

@@ -137,7 +137,7 @@ public class SysMenuService {
     @Transactional(rollbackFor = Exception.class)
     public void adminDelete(Collection<Long> ids) {
         log.info("[后台管理-删除后台菜单] >> 入参={}", ids);
-        sysMenuMapper.deleteBatchIds(ids);
+        sysMenuMapper.deleteByIds(ids);
     }
 
     /**

+ 1 - 1
src/main/java/cc/uncarbon/module/sys/service/SysParamService.java

@@ -119,7 +119,7 @@ public class SysParamService {
     @Transactional(rollbackFor = Exception.class)
     public void adminDelete(Collection<Long> ids) {
         log.info("[后台管理-删除系统参数] >> 入参={}", ids);
-        sysParamMapper.deleteBatchIds(ids);
+        sysParamMapper.deleteByIds(ids);
     }
 
     /**

+ 1 - 1
src/main/java/cc/uncarbon/module/sys/service/SysRoleService.java

@@ -135,7 +135,7 @@ public class SysRoleService {
     public void adminDelete(Collection<Long> ids) {
         log.info("[后台管理-删除后台角色] >> 入参={}", ids);
         preDeleteCheck(ids);
-        sysRoleMapper.deleteBatchIds(ids);
+        sysRoleMapper.deleteByIds(ids);
     }
 
     /**

+ 1 - 1
src/main/java/cc/uncarbon/module/sys/service/SysTenantService.java

@@ -132,7 +132,7 @@ public class SysTenantService {
     @Transactional(rollbackFor = Exception.class)
     public void adminDelete(Collection<Long> ids) {
         log.info("[后台管理-删除系统租户] >> 入参={}", ids);
-        sysTenantMapper.deleteBatchIds(ids);
+        sysTenantMapper.deleteByIds(ids);
     }
 
     /**

+ 2 - 2
src/main/java/cc/uncarbon/module/sys/service/SysUserService.java

@@ -29,13 +29,13 @@ import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import jakarta.annotation.PostConstruct;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import jakarta.annotation.PostConstruct;
 import java.math.BigInteger;
 import java.time.LocalDateTime;
 import java.util.*;
@@ -197,7 +197,7 @@ public class SysUserService {
     public void adminDelete(Collection<Long> ids) {
         log.info("[后台管理-删除后台用户] >> 入参={}", ids);
         preDeleteCheck(ids);
-        sysUserMapper.deleteBatchIds(ids);
+        sysUserMapper.deleteByIds(ids);
     }
 
     /**

+ 24 - 24
src/main/resources/i18n/messages_en_US.properties

@@ -1,24 +1,24 @@
-#-- Web 全局异常处理国际化消息 - 美式英语
-GLOBAL__NO_LOGIN=No login
-GLOBAL__PERMISSION_NOT_MATCH=Permission not match
-GLOBAL__ROLE_NOT_MATCH=Role not match
-GLOBAL__NOT_FOUND=Page not found
-GLOBAL__UNACCEPTABLE_PARAMETERS=Unacceptable parameters
-GLOBAL__METHOD_NOT_ALLOWED=Method not allowed
-GLOBAL__INTERNAL_ERROR=Please try again later
-
-#-- SysErrorEnum
-SysErrorEnum.INVALID_ID=Invalid id
-SysErrorEnum.INCORRECT_PIN_OR_PWD=Incorrect username or password
-SysErrorEnum.BANNED_USER=Banned user
-SysErrorEnum.INVALID_TENANT=Invalid tenant
-SysErrorEnum.DISABLED_TENANT=Disabled tenant
-SysErrorEnum.INCORRECT_OLD_PASSWORD=Old password is incorrect
-SysErrorEnum.NO_ROLE_AVAILABLE_FOR_CURRENT_USER=No role available for current user
-SysErrorEnum.NO_MENU_AVAILABLE_FOR_CURRENT_ROLE=No menu available for current role
-SysErrorEnum.UUID_CANNOT_BE_BLANK=UUID cannot be blank
-
-#-- OssErrorEnum
-OssErrorEnum.INVALID_ID=Invalid id
-OssErrorEnum.FILE_UPLOAD_FAILED=File upload failed, please contact administrator
-OssErrorEnum.FILE_DOWNLOAD_FAILED=File download failed, please contact administrator
+#-- Web 全局异常处理国际化消息 - 美式英语
+GLOBAL__NO_LOGIN=No login
+GLOBAL__PERMISSION_NOT_MATCH=Permission not match
+GLOBAL__ROLE_NOT_MATCH=Role not match
+GLOBAL__NOT_FOUND=Page not found
+GLOBAL__UNACCEPTABLE_PARAMETERS=Unacceptable parameters
+GLOBAL__METHOD_NOT_ALLOWED=Method not allowed
+GLOBAL__INTERNAL_ERROR=Please try again later
+
+#-- SysErrorEnum
+SysErrorEnum.INVALID_ID=Invalid id
+SysErrorEnum.INCORRECT_PIN_OR_PWD=Incorrect username or password
+SysErrorEnum.BANNED_USER=Banned user
+SysErrorEnum.INVALID_TENANT=Invalid tenant
+SysErrorEnum.DISABLED_TENANT=Disabled tenant
+SysErrorEnum.INCORRECT_OLD_PASSWORD=Old password is incorrect
+SysErrorEnum.NO_ROLE_AVAILABLE_FOR_CURRENT_USER=No role available for current user
+SysErrorEnum.NO_MENU_AVAILABLE_FOR_CURRENT_ROLE=No menu available for current role
+SysErrorEnum.UUID_CANNOT_BE_BLANK=UUID cannot be blank
+
+#-- OssErrorEnum
+OssErrorEnum.INVALID_ID=Invalid id
+OssErrorEnum.FILE_UPLOAD_FAILED=File upload failed, please contact administrator
+OssErrorEnum.FILE_DOWNLOAD_FAILED=File download failed, please contact administrator

+ 13 - 13
src/main/resources/i18n/messages_zh_CN.properties

@@ -1,13 +1,13 @@
-#-- Web 全局异常处理国际化消息 - 中文简体
-GLOBAL__NO_LOGIN=请您先登录
-GLOBAL__PERMISSION_NOT_MATCH=您权限不足
-GLOBAL__ROLE_NOT_MATCH=您与要求角色不符
-GLOBAL__NOT_FOUND=你迷路啦
-GLOBAL__UNACCEPTABLE_PARAMETERS=错误参数格式或值
-GLOBAL__METHOD_NOT_ALLOWED=错误的请求方式
-GLOBAL__INTERNAL_ERROR=请稍后再试
-
-#-- SysErrorEnum 不指定则使用原有定义的label作为提示消息
-# SysErrorEnum.INCORRECT_PIN_OR_PWD=你账号或者密码是不是有啥子输错了噢?
-
-#-- OssErrorEnum
+#-- Web 全局异常处理国际化消息 - 中文简体
+GLOBAL__NO_LOGIN=请您先登录
+GLOBAL__PERMISSION_NOT_MATCH=您权限不足
+GLOBAL__ROLE_NOT_MATCH=您与要求角色不符
+GLOBAL__NOT_FOUND=你迷路啦
+GLOBAL__UNACCEPTABLE_PARAMETERS=错误参数格式或值
+GLOBAL__METHOD_NOT_ALLOWED=错误的请求方式
+GLOBAL__INTERNAL_ERROR=请稍后再试
+
+#-- SysErrorEnum 不指定则使用原有定义的label作为提示消息
+# SysErrorEnum.INCORRECT_PIN_OR_PWD=你账号或者密码是不是有啥子输错了噢?
+
+#-- OssErrorEnum