Browse Source

update 代码优化更新

Yue 10 months ago
parent
commit
bdb5f88382
100 changed files with 2125 additions and 560 deletions
  1. 4 5
      SERVER/VberAdminPlusV3/.script/docker/docker-compose.yml
  2. 1 1
      SERVER/VberAdminPlusV3/.script/run/DF_VberServer.run.xml
  3. 7 6
      SERVER/VberAdminPlusV3/.script/sql/admin.sql
  4. 28 33
      SERVER/VberAdminPlusV3/pom.xml
  5. 2 1
      SERVER/VberAdminPlusV3/vber-admin/Dockerfile
  6. 8 7
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/AuthController.java
  7. 28 7
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/CaptchaController.java
  8. 5 0
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/domain/vo/CaptchaVo.java
  9. 16 12
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/SysLoginService.java
  10. 2 2
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/EmailAuthStrategy.java
  11. 2 2
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/PasswordAuthStrategy.java
  12. 2 2
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/SmsAuthStrategy.java
  13. 2 2
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/SocialAuthStrategy.java
  14. 25 4
      SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/XcxAuthStrategy.java
  15. 8 1
      SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application-dev.yml
  16. 7 0
      SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application-prod.yml
  17. 16 14
      SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application.yml
  18. 1 1
      SERVER/VberAdminPlusV3/vber-admin/src/main/resources/logback-plus.xml
  19. 10 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/ThreadPoolConfig.java
  20. 4 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/ValidatorConfig.java
  21. 18 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/CacheNames.java
  22. 0 4
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/Constants.java
  23. 14 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/SystemConstants.java
  24. 0 10
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/TenantConstants.java
  25. 41 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/DictDataDTO.java
  26. 41 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/DictTypeDTO.java
  27. 37 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/OrgDTO.java
  28. 47 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/PostDTO.java
  29. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/RoleDTO.java
  30. 34 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessDeleteEvent.java
  31. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessTaskEvent.java
  32. 6 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/model/LoginUser.java
  33. 2 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/model/PasswordLoginBody.java
  34. 2 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/model/RegisterBody.java
  35. 4 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/DeviceType.java
  36. 148 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/FormatsType.java
  37. 0 30
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/TenantStatus.java
  38. 5 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/UserType.java
  39. 20 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/DictService.java
  40. 18 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/OrgService.java
  41. 28 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/PermissionService.java
  42. 10 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/PostService.java
  43. 4 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/RoleService.java
  44. 8 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/UserService.java
  45. 203 67
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/DateUtils.java
  46. 85 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/NetUtils.java
  47. 61 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ObjectUtils.java
  48. 81 22
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ServletUtils.java
  49. 45 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/StringUtils.java
  50. 0 11
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/Threads.java
  51. 44 7
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ip/AddressUtils.java
  52. 15 31
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ip/RegionUtils.java
  53. 1 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/sql/SqlUtil.java
  54. 40 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/dicts/DictPattern.java
  55. 55 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/dicts/DictPatternValidator.java
  56. 50 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/enumd/EnumPattern.java
  57. 37 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/enumd/EnumPatternValidator.java
  58. 3 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/json/config/JacksonConfig.java
  59. 31 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/json/handler/CustomDateDeserializer.java
  60. 8 4
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/annotation/Sensitive.java
  61. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/core/SensitiveService.java
  62. 2 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/core/SensitiveStrategy.java
  63. 2 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/handler/SensitiveHandler.java
  64. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/config/SpringDocConfig.java
  65. 2 4
      SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/core/EncryptorManager.java
  66. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/filter/EncryptResponseBodyWrapper.java
  67. 12 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/interceptor/MybatisDecryptInterceptor.java
  68. 11 9
      SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/utils/EncryptUtils.java
  69. 2 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/pom.xml
  70. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/annotation/ExcelEnumFormat.java
  71. 24 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/annotation/ExcelNotation.java
  72. 27 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/annotation/ExcelRequired.java
  73. 6 6
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/convert/ExcelBigNumberConvert.java
  74. 7 7
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/convert/ExcelDictConvert.java
  75. 8 8
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/convert/ExcelEnumConvert.java
  76. 33 13
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/CellMergeStrategy.java
  77. 8 8
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/DefaultExcelListener.java
  78. 9 9
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/DropDownOptions.java
  79. 43 38
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelDownHandler.java
  80. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelListener.java
  81. 135 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/handler/DataWriteHandler.java
  82. 39 35
      SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/utils/ExcelUtil.java
  83. 5 6
      SERVER/VberAdminPlusV3/vber-common/vber-common-log/src/main/java/com/vber/common/log/aspect/LogAspect.java
  84. 8 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-mail/src/main/java/com/vber/common/mail/config/properties/MailProperties.java
  85. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionAspect.java
  86. 11 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/config/MybatisPlusConfig.java
  87. 4 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/core/page/PageQuery.java
  88. 18 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/core/page/TableDataInfo.java
  89. 6 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/enums/DataScopeType.java
  90. 3 4
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/InjectionMetaObjectHandler.java
  91. 5 4
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/MybatisExceptionHandler.java
  92. 197 39
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/PlusDataPermissionHandler.java
  93. 28 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/PlusPostInitTableInfoHandler.java
  94. 4 4
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
  95. 2 2
      SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/resources/common-mybatis.yml
  96. 3 3
      SERVER/VberAdminPlusV3/vber-common/vber-common-oss/pom.xml
  97. 9 13
      SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/core/OssClient.java
  98. 1 1
      SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/enums/AccessPolicyType.java
  99. 1 0
      SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/manager/CaffeineCacheDecorator.java
  100. 18 22
      SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/manager/PlusSpringCacheManager.java

+ 4 - 5
SERVER/VberAdminPlusV3/.script/docker/docker-compose.yml

@@ -1,8 +1,6 @@
-version: '3'
-
 services:
   #  mysql:
-  #    image: mysql:8.0.33
+  #    image: mysql:8.0.42
   #    container_name: mysql
   #    environment:
   #      # 时区上海
@@ -50,7 +48,7 @@ services:
   #    network_mode: "host"
   #
   #  redis:
-  #    image: redis:6.2.12
+  #    image: redis:7.2.8
   #    container_name: redis
   #    ports:
   #      - "6389:6379"
@@ -67,7 +65,8 @@ services:
   #    network_mode: "host"
 
   minio:
-    image: minio/minio:RELEASE.2023-04-13T03-08-07Z
+    # minio 最后一个未阉割版本 不能再进行升级 在往上的版本功能被阉割
+    image: minio/minio:RELEASE.2025-04-22T22-12-26Z
     container_name: minio
     ports:
       # api 端口

+ 1 - 1
SERVER/VberAdminPlusV3/.script/run/DF_VberServer.run.xml

@@ -1,5 +1,5 @@
 <component name="ProjectRunConfigurationManager">
-    <configuration default="false" name="DF_VberServer" type="docker-deploy" factoryName="dockerfile">
+    <configuration default="false" name="DF_VberServer" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
         <deployment type="dockerfile">
             <settings>
                 <option name="imageTag" value="vber/vber-server:3.0.0"/>

+ 7 - 6
SERVER/VberAdminPlusV3/.script/sql/admin.sql

@@ -204,10 +204,10 @@ CREATE TABLE sys_oper_log
     oper_url       VARCHAR(255)  DEFAULT '' COMMENT '请求URL',
     oper_ip        VARCHAR(128)  DEFAULT '' COMMENT '主机地址',
     oper_location  VARCHAR(255)  DEFAULT '' COMMENT '操作地点',
-    oper_param     VARCHAR(2000) DEFAULT '' COMMENT '请求参数',
-    json_result    VARCHAR(2000) DEFAULT '' COMMENT '返回参数',
+    oper_param     VARCHAR(4000) DEFAULT '' COMMENT '请求参数',
+    json_result    VARCHAR(4000) DEFAULT '' COMMENT '返回参数',
     status         INT(1)        DEFAULT 0 COMMENT '操作状态(0正常 1异常)',
-    error_msg      VARCHAR(2000) DEFAULT '' COMMENT '错误消息',
+    error_msg      VARCHAR(4000) DEFAULT '' COMMENT '错误消息',
     oper_time      DATETIME COMMENT '操作时间',
     cost_time      BIGINT(20)    DEFAULT 0 COMMENT '消耗时间',
     PRIMARY KEY (oper_id),
@@ -348,6 +348,7 @@ CREATE TABLE sys_oss
     original_name VARCHAR(255) NOT NULL DEFAULT '' COMMENT '原名',
     file_suffix   VARCHAR(10)  NOT NULL DEFAULT '' COMMENT '文件后缀名',
     url           VARCHAR(500) NOT NULL COMMENT 'URL地址',
+    ext1          TEXT                  default NULL COMMENT '扩展字段',
     object_id     VARCHAR(50) COMMENT '文件标识',
     service       VARCHAR(20)  NOT NULL DEFAULT 'minio' COMMENT '服务商',
     create_org    BIGINT(20)            DEFAULT NULL COMMENT '创建组织机构',
@@ -430,9 +431,9 @@ CREATE TABLE sys_social
     nick_name          VARCHAR(30)  DEFAULT '' COMMENT '用户昵称',
     email              VARCHAR(255) DEFAULT '' COMMENT '用户邮箱',
     avatar             VARCHAR(500) DEFAULT '' COMMENT '头像地址',
-    access_token       VARCHAR(255) NOT NULL COMMENT '用户的授权令牌',
+    access_token       VARCHAR(2000) NOT NULL COMMENT '用户的授权令牌',
     expire_in          INT          DEFAULT NULL COMMENT '用户的授权令牌的有效期,部分平台可能没有',
-    refresh_token      VARCHAR(255) DEFAULT NULL COMMENT '刷新令牌,部分平台可能没有',
+    refresh_token      VARCHAR(2000) DEFAULT NULL COMMENT '刷新令牌,部分平台可能没有',
     access_code        VARCHAR(255) DEFAULT NULL COMMENT '平台的授权信息,部分平台可能没有',
     union_id           VARCHAR(255) DEFAULT NULL COMMENT '用户的 unionid',
     scope              VARCHAR(255) DEFAULT NULL COMMENT '授予的权限,部分平台可能没有',
@@ -529,7 +530,7 @@ CREATE TABLE sys_tenant
     tenant_id         VARCHAR(20) NOT NULL COMMENT '租户编号',
     contact_user_name VARCHAR(20) COMMENT '联系人',
     contact_phone     VARCHAR(20) COMMENT '联系电话',
-    company_name      VARCHAR(50) COMMENT '企业名称',
+    company_name      VARCHAR(30) COMMENT '企业名称',
     license_number    VARCHAR(30) COMMENT '统一社会信用代码',
     address           VARCHAR(200) COMMENT '地址',
     intro             VARCHAR(200) COMMENT '企业简介',

+ 28 - 33
SERVER/VberAdminPlusV3/pom.xml

@@ -22,50 +22,51 @@
         <java.version>17</java.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
-        <spring-boot.version>3.3.5</spring-boot.version>
-        <spring-boot-admin.version>3.3.4</spring-boot-admin.version>
+        <spring-boot.version>3.4.6</spring-boot.version>
+        <spring-boot-admin.version>3.4.7</spring-boot-admin.version>
+        <springdoc.version>2.8.8</springdoc.version>
         <mybatis.version>3.5.16</mybatis.version>
-        <mybatis-plus.version>3.5.9</mybatis-plus.version>
+        <mybatis-plus.version>3.5.12</mybatis-plus.version>
         <dynamic-ds.version>4.3.1</dynamic-ds.version>
         <shardingsphere.version>5.5.0</shardingsphere.version>
-        <springdoc.version>2.6.0</springdoc.version>
         <therapi-javadoc.version>0.15.0</therapi-javadoc.version>
-        <redisson.version>3.37.0</redisson.version>
+        <redisson.version>3.45.1</redisson.version>
         <xxl-job.version>2.4.1</xxl-job.version>
         <poi.version>5.2.3</poi.version>
-        <easyexcel.version>4.0.3</easyexcel.version>
+        <fastexcel.version>1.2.0</fastexcel.version>
         <velocity.version>2.3</velocity.version>
-        <satoken.version>1.39.0</satoken.version>
+        <satoken.version>1.40.0</satoken.version>
         <p6spy.version>3.9.1</p6spy.version>
-        <hutool.version>5.8.31</hutool.version>
+        <hutool.version>5.8.35</hutool.version>
         <lock4j.version>2.2.7</lock4j.version>
-        <mapstruct-plus.version>1.4.5</mapstruct-plus.version>
+        <mapstruct-plus.version>1.4.8</mapstruct-plus.version>
         <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
-        <lombok.version>1.18.34</lombok.version>
-        <bouncycastle.version>1.76</bouncycastle.version>
-        <justauth.version>1.16.6</justauth.version>
+        <lombok.version>1.18.36</lombok.version>
+        <bouncycastle.version>1.80</bouncycastle.version>
+        <justauth.version>1.16.7</justauth.version>
         <!-- 离线IP地址定位库 -->
         <ip2region.version>2.7.0</ip2region.version>
 
         <!-- OSS 配置 -->
         <aws.sdk.version>2.28.22</aws.sdk.version>
-        <aws.crt.version>0.31.3</aws.crt.version>
         <!-- SMS 配置 -->
-        <sms4j.version>3.3.3</sms4j.version>
+        <sms4j.version>3.3.4</sms4j.version>
         <!-- 限制框架中的fastjson版本 -->
         <fastjson.version>1.2.83</fastjson.version>
         <!-- 面向运行时的D-ORM依赖 -->
-        <anyline.version>8.7.2-20241022</anyline.version>
+        <anyline.version>8.7.2-20250101</anyline.version>
 
         <!-- 插件版本 -->
         <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
         <maven-war-plugin.version>3.2.2</maven-war-plugin.version>
-        <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
+        <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>
         <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
         <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
 
         <!--工作流配置-->
         <flowable.version>7.0.1</flowable.version>
+        <!-- 打包默认跳过测试 -->
+        <skipTests>true</skipTests>
     </properties>
 
     <profiles>
@@ -243,19 +244,18 @@
                 <artifactId>s3</artifactId>
                 <version>${aws.sdk.version}</version>
             </dependency>
-            <!-- 使用AWS基于 CRT 的 S3 客户端 -->
-            <dependency>
-                <groupId>software.amazon.awssdk.crt</groupId>
-                <artifactId>aws-crt</artifactId>
-                <version>${aws.crt.version}</version>
-            </dependency>
             <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->
             <dependency>
                 <groupId>software.amazon.awssdk</groupId>
                 <artifactId>s3-transfer-manager</artifactId>
                 <version>${aws.sdk.version}</version>
             </dependency>
-
+            <!-- 将基于 Netty 的 HTTP 客户端 -->
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>netty-nio-client</artifactId>
+                <version>${aws.sdk.version}</version>
+            </dependency>
             <dependency>
                 <groupId>de.codecentric</groupId>
                 <artifactId>spring-boot-admin-starter-server</artifactId>
@@ -290,18 +290,13 @@
                 <version>${poi.version}</version>
             </dependency>
             <dependency>
-                <groupId>com.alibaba</groupId>
-                <artifactId>easyexcel</artifactId>
-                <version>${easyexcel.version}</version>
-                <exclusions>
-                    <exclusion>
-                        <groupId>org.apache.poi</groupId>
-                        <artifactId>poi-ooxml-schemas</artifactId>
-                    </exclusion>
-                </exclusions>
+                <groupId>cn.idev.excel</groupId>
+                <artifactId>fastexcel</artifactId>
+                <version>${fastexcel.version}</version>
             </dependency>
 
 
+
             <!--redisson-->
             <dependency>
                 <groupId>org.redisson</groupId>
@@ -381,7 +376,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>${maven-compiler-plugin.verison}</version>
+                <version>${maven-compiler-plugin.version}</version>
                 <configuration>
                     <source>${java.version}</source>
                     <target>${java.version}</target>

+ 2 - 1
SERVER/VberAdminPlusV3/vber-admin/Dockerfile

@@ -1,5 +1,6 @@
 # 贝尔实验室 Spring 官方推荐镜像 JDK下载地址 https://bell-sw.com/pages/downloads/
-FROM bellsoft/liberica-openjdk-debian:17.0.11-cds
+FROM bellsoft/liberica-openjdk-rocky:17.0.15-cds
+#FROM bellsoft/liberica-openjdk-rocky:21.0.7-cds
 #FROM openjdk:17.0.2-oraclelinux8
 
 LABEL maintainer="IwbY"

+ 8 - 7
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/AuthController.java

@@ -6,7 +6,7 @@ import cn.hutool.core.codec.Base64;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.vber.common.core.config.VbConfig;
-import com.vber.common.core.constant.UserConstants;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.domain.R;
 import com.vber.common.core.domain.model.LoginBody;
 import com.vber.common.core.domain.model.RegisterBody;
@@ -18,6 +18,7 @@ import com.vber.common.satoken.utils.LoginHelper;
 import com.vber.common.social.config.properties.SocialLoginConfigProperties;
 import com.vber.common.social.config.properties.SocialProperties;
 import com.vber.common.social.utils.SocialUtils;
+import com.vber.common.sse.dto.SseMessageDto;
 import com.vber.common.sse.utils.SseMessageUtils;
 import com.vber.common.tenant.helper.TenantHelper;
 import com.vber.system.domain.bo.SysTenantBo;
@@ -97,7 +98,7 @@ public class AuthController {
         if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
             log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
             return R.fail(MessageUtils.message("auth.grant.type.error"));
-        } else if (!UserConstants.NORMAL.equals(client.getStatus())) {
+        } else if (!SystemConstants.NORMAL.equals(client.getStatus())) {
             return R.fail(MessageUtils.message("auth.grant.type.blocked"));
         }
         // 校验租户
@@ -107,10 +108,10 @@ public class AuthController {
 
         Long userId = LoginHelper.getUserId();
         scheduledExecutorService.schedule(() -> {
-//            SseMessageDto dto = new SseMessageDto();
-//            dto.setMessage("欢迎登录" + vbConfig.getName());
-//            dto.setUserIds(List.of(userId));
-            SseMessageUtils.sendMessage(userId, "欢迎登录" + vbConfig.getName());
+            SseMessageDto dto = new SseMessageDto();
+            dto.setUserIds(List.of(userId));
+            dto.setMessage("欢迎登录" + vbConfig.getName());
+            SseMessageUtils.publishMessage(dto);
         }, 3, TimeUnit.SECONDS);
         return R.ok(loginVo);
     }
@@ -230,7 +231,7 @@ public class AuthController {
         }
         // 根据域名进行筛选
         List<TenantListVo> list = StreamUtils.filter(voList, vo ->
-                StringUtils.equals(vo.getDomain(), host));
+                StringUtils.equalsIgnoreCase(vo.getDomain(), host));
         result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
         return R.ok(result);
     }

+ 28 - 7
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/CaptchaController.java

@@ -8,6 +8,7 @@ import cn.hutool.core.util.RandomUtil;
 import com.vber.common.core.constant.Constants;
 import com.vber.common.core.constant.GlobalConstants;
 import com.vber.common.core.domain.R;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.core.utils.reflect.ReflectUtils;
@@ -79,12 +80,21 @@ public class CaptchaController {
      *
      * @param email 邮箱
      */
-    @RateLimiter(key = "#email", time = 60, count = 1)
     @GetMapping("/resource/email/code")
     public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
         if (!mailProperties.getEnabled()) {
             return R.fail("当前系统没有开启邮箱功能!");
         }
+        SpringUtils.getAopProxy(this).emailCodeImpl(email);
+        return R.ok();
+    }
+
+    /**
+     * 邮箱验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(key = "#email", time = 60, count = 1)
+    public void emailCodeImpl(String email) {
         String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
         String code = RandomUtil.randomNumbers(4);
         RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
@@ -92,22 +102,31 @@ public class CaptchaController {
             MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
         } catch (Exception e) {
             log.error("验证码短信发送异常 => {}", e.getMessage());
-            return R.fail(e.getMessage());
+            throw new ServiceException(e.getMessage());
         }
-        return R.ok();
     }
 
+
     /**
      * 生成验证码
      */
-    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
     @GetMapping("/auth/code")
     public R<CaptchaVo> getCode() {
-        CaptchaVo captchaVo = new CaptchaVo();
         boolean captchaEnabled = captchaProperties.getEnable();
         if (!captchaEnabled) {
+            CaptchaVo captchaVo = new CaptchaVo();
+            captchaVo.setCaptchaEnabled(false);
             return R.ok(captchaVo);
         }
+        return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
+    }
+
+    /**
+     * 生成验证码
+     * 独立方法避免验证码关闭之后仍然走限流
+     */
+    @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
+    public CaptchaVo getCodeImpl() {
         // 保存验证码信息
         String uuid = IdUtil.simpleUUID();
         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
@@ -119,17 +138,19 @@ public class CaptchaController {
         AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
         captcha.setGenerator(codeGenerator);
         captcha.createCode();
-        String code = captcha.getCode();
         // 如果是数学验证码,使用SpEL表达式处理验证码结果
+        String code = captcha.getCode();
         if (isMath) {
             ExpressionParser parser = new SpelExpressionParser();
             Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
             code = exp.getValue(String.class);
         }
         RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
+        CaptchaVo captchaVo = new CaptchaVo();
         captchaVo.setUuid(uuid);
         captchaVo.setImg(captcha.getImageBase64());
-        return R.ok(captchaVo);
+        return captchaVo;
     }
 
+
 }

+ 5 - 0
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/domain/vo/CaptchaVo.java

@@ -11,6 +11,11 @@ import lombok.Data;
 public class CaptchaVo {
 
 
+    /**
+     * 是否开启验证码
+     */
+    private Boolean captchaEnabled = true;
+
     private String uuid;
 
     /**

+ 16 - 12
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/SysLoginService.java

@@ -9,11 +9,12 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.lock.annotation.Lock4j;
 import com.vber.common.core.constant.Constants;
 import com.vber.common.core.constant.GlobalConstants;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.constant.TenantConstants;
+import com.vber.common.core.domain.dto.PostDTO;
 import com.vber.common.core.domain.dto.RoleDTO;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.enums.LoginType;
-import com.vber.common.core.enums.TenantStatus;
 import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.exception.user.UserException;
 import com.vber.common.core.utils.*;
@@ -53,6 +54,7 @@ public class SysLoginService {
     private final ISysPermissionService permissionService;
     private final ISysSocialService sysSocialService;
     private final ISysRoleService roleService;
+    private final ISysPostService postService;
     private final SysUserMapper userMapper;
     private final ISysOrgService orgService;
     @Value("${user.password.maxRetryCount}")
@@ -64,7 +66,6 @@ public class SysLoginService {
      * 绑定第三方用户
      *
      * @param authUserData 授权响应实体
-     * @return 统一响应实体
      */
     @Lock4j
     public void socialRegister(AuthUser authUserData) {
@@ -105,7 +106,7 @@ public class SysLoginService {
      */
     public void logout() {
         try {
-            LoginUser loginUser = LoginHelper.getLoginUser();
+            LoginUser  loginUser = LoginHelper.getLoginUser();
             if (ObjectUtil.isNull(loginUser)) {
                 return;
             }
@@ -141,14 +142,15 @@ public class SysLoginService {
      */
     public LoginUser buildLoginUser(SysUserVo user) {
         LoginUser loginUser = new LoginUser();
+        Long userId = user.getUserId();
         loginUser.setTenantId(user.getTenantId());
-        loginUser.setUserId(user.getUserId());
+        loginUser.setUserId(userId);
         loginUser.setOrgId(user.getOrgId());
         loginUser.setUsername(user.getUserName());
         loginUser.setNickname(user.getNickName());
         loginUser.setUserType(user.getUserType());
-        loginUser.setMenuPermission(permissionService.getMenuPermission(user.getUserId()));
-        loginUser.setRolePermission(permissionService.getRolePermission(user.getUserId()));
+        loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
+        loginUser.setRolePermission(permissionService.getRolePermission(userId));
         loginUser.setOrgName(ObjectUtil.isNull(user.getOrg()) ? "" : user.getOrg().getOrgName());
         if (ObjectUtil.isNotNull(user.getOrgId())) {
             Opt<SysOrgVo> orgOpt = Opt.of(user.getOrgId()).map(orgService::selectOrgById);
@@ -161,9 +163,11 @@ public class SysLoginService {
                 org = orgService.selectOrgById(user.getOrgId());
             }
             loginUser.setOrgName(ObjectUtil.isNull(org) ? "" : org.getOrgName());
-            List<SysRoleVo> roles = roleService.selectRolesByUserId(user.getUserId());
-            loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
         });
+        List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
+        List<SysPostVo> posts = postService.selectPostsByUserId(userId);
+        loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
+        loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
         return loginUser;
     }
 
@@ -224,17 +228,17 @@ public class SysLoginService {
         if (!TenantHelper.isEnable()) {
             return;
         }
-        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
-            return;
-        }
         if (StringUtils.isBlank(tenantId)) {
             throw new TenantException("tenant.number.not.blank");
         }
+        if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
+            return;
+        }
         SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
         if (ObjectUtil.isNull(tenant)) {
             log.info("登录租户:{} 不存在.", tenantId);
             throw new TenantException("tenant.not.exists");
-        } else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
+        } else if (SystemConstants.DISABLE.equals(tenant.getStatus())) {
             log.info("登录租户:{} 已被停用.", tenantId);
             throw new TenantException("tenant.blocked");
         } else if (ObjectUtil.isNotNull(tenant.getExpireTime())

+ 2 - 2
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/EmailAuthStrategy.java

@@ -4,10 +4,10 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.vber.common.core.constant.Constants;
 import com.vber.common.core.constant.GlobalConstants;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.domain.model.EmailLoginBody;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.enums.LoginType;
-import com.vber.common.core.enums.UserStatus;
 import com.vber.common.core.exception.user.CaptchaExpireException;
 import com.vber.common.core.exception.user.UserException;
 import com.vber.common.core.utils.MessageUtils;
@@ -73,7 +73,7 @@ public class EmailAuthStrategy extends AuthStrategy {
             if (ObjectUtil.isNull(user)) {
                 log.info("登录用户:邮箱 {} 不存在.", email);
                 throw new UserException("user.not.exists", email);
-            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
                 log.info("登录用户:邮箱 {} 已被停用.", email);
                 throw new UserException("user.blocked", email);
             }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/PasswordAuthStrategy.java

@@ -6,10 +6,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.vber.common.core.config.VbConfig;
 import com.vber.common.core.constant.Constants;
 import com.vber.common.core.constant.GlobalConstants;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.domain.model.PasswordLoginBody;
 import com.vber.common.core.enums.LoginType;
-import com.vber.common.core.enums.UserStatus;
 import com.vber.common.core.exception.user.CaptchaException;
 import com.vber.common.core.exception.user.CaptchaExpireException;
 import com.vber.common.core.exception.user.UserException;
@@ -101,7 +101,7 @@ public class PasswordAuthStrategy extends AuthStrategy {
             if (ObjectUtil.isNull(user)) {
                 log.info("登录用户:用户名 {} 不存在.", username);
                 throw new UserException("user.not.exists", username);
-            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
                 log.info("登录用户:用户名 {} 已被停用.", username);
                 throw new UserException("user.blocked", username);
             }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/SmsAuthStrategy.java

@@ -4,10 +4,10 @@ import cn.hutool.core.util.ObjectUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.vber.common.core.constant.Constants;
 import com.vber.common.core.constant.GlobalConstants;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.domain.model.SmsLoginBody;
 import com.vber.common.core.enums.LoginType;
-import com.vber.common.core.enums.UserStatus;
 import com.vber.common.core.exception.user.CaptchaExpireException;
 import com.vber.common.core.exception.user.UserException;
 import com.vber.common.core.utils.MessageUtils;
@@ -75,7 +75,7 @@ public class SmsAuthStrategy extends AuthStrategy {
             if (ObjectUtil.isNull(user)) {
                 log.info("登录用户:手机号码 {} 不存在.", phonenumber);
                 throw new UserException("user.not.exists", phonenumber);
-            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
                 log.info("登录用户:手机号码 {} 已被停用.", phonenumber);
                 throw new UserException("user.blocked", phonenumber);
             }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/SocialAuthStrategy.java

@@ -2,9 +2,9 @@ package com.vber.web.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.domain.model.SocialLoginBody;
-import com.vber.common.core.enums.UserStatus;
 import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.exception.user.UserException;
 import com.vber.common.core.utils.StreamUtils;
@@ -91,7 +91,7 @@ public class SocialAuthStrategy extends AuthStrategy {
             if (ObjectUtil.isNull(user)) {
                 log.info("登录用户:ID {} 不存在.", userId);
                 throw new UserException("user.not.exists", "");
-            } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+            } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
                 log.info("登录用户:ID {} 已被停用.", userId);
                 throw new UserException("user.blocked", "");
             }

+ 25 - 4
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/service/impl/XcxAuthStrategy.java

@@ -1,9 +1,10 @@
 package com.vber.web.service.impl;
 
 import cn.hutool.core.util.ObjectUtil;
+import com.vber.common.core.constant.SystemConstants;
 import com.vber.common.core.domain.model.XcxLoginBody;
 import com.vber.common.core.domain.model.XcxLoginUser;
-import com.vber.common.core.enums.UserStatus;
+import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.utils.ValidatorUtils;
 import com.vber.common.json.utils.JsonUtils;
 import com.vber.system.domain.vo.SysClientVo;
@@ -13,6 +14,13 @@ import com.vber.web.service.IAuthStrategy;
 import com.vber.web.service.SysLoginService;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.config.AuthConfig;
+import me.zhyd.oauth.model.AuthCallback;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthToken;
+import me.zhyd.oauth.model.AuthUser;
+import me.zhyd.oauth.request.AuthRequest;
+import me.zhyd.oauth.request.AuthWechatMiniProgramRequest;
 import org.springframework.stereotype.Service;
 
 /**
@@ -36,9 +44,22 @@ public class XcxAuthStrategy extends AuthStrategy {
         // 多个小程序识别使用
         String appid = loginBody.getAppid();
 
-        // todo 以下自行实现
         // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
-        String openid = "";
+        AuthRequest authRequest = new AuthWechatMiniProgramRequest(AuthConfig.builder()
+                .clientId(appid).clientSecret("自行填写密钥 可根据不同appid填入不同密钥")
+                .ignoreCheckRedirectUri(true).ignoreCheckState(true).build());
+        AuthCallback authCallback = new AuthCallback();
+        authCallback.setCode(xcxCode);
+        AuthResponse<AuthUser> resp = authRequest.login(authCallback);
+        String openid, unionId;
+        if (resp.ok()) {
+            AuthToken token = resp.getData().getToken();
+            openid = token.getOpenId();
+            // 微信小程序只有关联到微信开放平台下之后才能获取到 unionId,因此unionId不一定能返回。
+            unionId = token.getUnionId();
+        } else {
+            throw new ServiceException(resp.getMsg());
+        }
         // 框架登录不限制从什么表查询 只要最终构建出 LoginUser 即可
         SysUserVo user = loadUserByOpenid(openid);
 
@@ -65,7 +86,7 @@ public class XcxAuthStrategy extends AuthStrategy {
         if (ObjectUtil.isNull(user)) {
             log.info("登录用户:{} 不存在.", openid);
             // todo 用户不存在 业务逻辑自行实现
-        } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
+        } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
             log.info("登录用户:{} 已被停用.", openid);
             // todo 用户已被停用 业务逻辑自行实现
         }

+ 8 - 1
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application-dev.yml

@@ -98,7 +98,7 @@ redisson:
 --- # 监控中心配置
 spring.boot.admin.client:
   # 增加客户端开关
-  enabled: true
+  enabled: false
   url: http://localhost:6075
   instance:
     service-host-type: IP
@@ -253,3 +253,10 @@ justauth:
       client-id: 10**********6
       client-secret: 1f7d08**********5b7**********29e
       redirect-uri: ${justauth.address}/social-callback?source=gitlab
+    gitea:
+      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+      # gitea 服务器地址
+      server-url: https://demo.gitea.com
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitea

+ 7 - 0
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application-prod.yml

@@ -241,3 +241,10 @@ justauth:
       client-id: 10**********6
       client-secret: 1f7d08**********5b7**********29e
       redirect-uri: ${justauth.address}/social-callback?source=gitlab
+    gitea:
+      # 前端改动 https://gitee.com/JavaLionLi/plus-ui/pulls/204
+      # gitea 服务器地址
+      server-url: https://demo.gitea.com
+      client-id: 10**********6
+      client-secret: 1f7d08**********5b7**********29e
+      redirect-uri: ${justauth.address}/social-callback?source=gitea

+ 16 - 14
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application.yml

@@ -13,8 +13,8 @@ vb-msg:
   notice-router-path: /system/notice
 # 验证码配置
 captcha:
+  # 是否启用验证码校验
   enable: false
-  # 页面 <参数设置> 可开启关闭 验证码校验
   # 验证码类型 math 数组计算 char 字符验证
   type: MATH
   # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
@@ -51,6 +51,8 @@ logging:
   level:
     com.vap: @logging.level@
     org.springframework: warn
+    org.mybatis.spring.mapper: error
+    org.apache.fury: warn
   config: classpath:logback-plus.xml
 
 # 用户配置
@@ -145,8 +147,9 @@ tenant:
 # MyBatisPlus配置
 # https://baomidou.com/config/
 mybatis-plus:
-  # 不支持多包, 如有需要可在注解配置 或 提升扫包等级
-  # 例如 com.**.**.mapper
+  # 自定义配置 是否全局开启逻辑删除 关闭后 所有逻辑删除功能将失效
+  enableLogicDelete: true
+  # 多包名使用 例如 com.vber.**.mapper,org.xxx.**.mapper
   mapperPackage: com.vber.**.mapper
   # 对应的 XML 文件位置
   mapperLocations: classpath*:mapper/**/*Mapper.xml
@@ -190,9 +193,6 @@ springdoc:
   api-docs:
     # 是否开启接口文档
     enabled: true
-  #  swagger-ui:
-  #    # 持久化认证数据
-  #    persistAuthorization: true
   info:
     # 标题
     title: '标题:${vber.name}多租户管理系统_接口文档'
@@ -214,14 +214,16 @@ springdoc:
         name: ${sa-token.token-name}
   #这里定义了两个分组,可定义多个,也可以不定义
   group-configs:
-    - group: 1.演示模块
-      packages-to-scan: org.dromara.demo
-    - group: 2.通用模块
-      packages-to-scan: org.dromara.web
-    - group: 3.系统模块
-      packages-to-scan: org.dromara.system
-    - group: 4.代码生成模块
-      packages-to-scan: org.dromara.generator
+#    - group: 1.演示模块
+#      packages-to-scan: com.vber.demo
+    - group: 1.通用模块
+      packages-to-scan: com.vber.web
+    - group: 2.系统模块
+      packages-to-scan: com.vber.system
+    - group: 3.代码生成模块
+      packages-to-scan: com.vber.generator
+    - group: 4.工作流模块
+      packages-to-scan: com.vber.workflow
 
 # 防止XSS攻击
 xss:

+ 1 - 1
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/logback-plus.xml

@@ -2,7 +2,7 @@
 <configuration>
     <property name="log.path" value="./vber/logs"/>
     <property name="console.log.pattern"
-              value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
+              value="%cyan(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/>
     <property name="log.pattern" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/>
 
     <!-- 控制台输出 -->

+ 10 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/ThreadPoolConfig.java

@@ -1,6 +1,7 @@
 package com.vber.common.core.config;
 
 import com.vber.common.core.config.properties.ThreadPoolProperties;
+import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.core.utils.Threads;
 import jakarta.annotation.PreDestroy;
 import lombok.extern.slf4j.Slf4j;
@@ -9,6 +10,7 @@ import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
+import org.springframework.core.task.VirtualThreadTaskExecutor;
 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
 import java.util.concurrent.ScheduledExecutorService;
@@ -49,8 +51,15 @@ public class ThreadPoolConfig {
      */
     @Bean(name = "scheduledExecutorService")
     protected ScheduledExecutorService scheduledExecutorService() {
+        // daemon 必须为 true
+        BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder().daemon(true);
+        if (SpringUtils.isVirtual()) {
+            builder.namingPattern("virtual-schedule-pool-%d").wrappedFactory(new VirtualThreadTaskExecutor().getVirtualThreadFactory());
+        } else {
+            builder.namingPattern("schedule-pool-%d");
+        }
         ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(core,
-                new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
+                builder.build(),
                 new ThreadPoolExecutor.CallerRunsPolicy()) {
             @Override
             protected void afterExecute(Runnable r, Throwable t) {

+ 4 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/config/ValidatorConfig.java

@@ -3,6 +3,7 @@ package com.vber.common.core.config;
 import jakarta.validation.Validator;
 import org.hibernate.validator.HibernateValidator;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration;
 import org.springframework.context.MessageSource;
 import org.springframework.context.annotation.Bean;
 import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
@@ -14,11 +15,11 @@ import java.util.Properties;
  *
  * @author Iwb
  */
-@AutoConfiguration
+@AutoConfiguration(before = ValidationAutoConfiguration.class)
 public class ValidatorConfig {
 
     /**
-     * 配置校验框架 快速返回模式
+     * 配置校验框架 快速失败模式
      */
     @Bean
     public Validator validator(MessageSource messageSource) {
@@ -28,7 +29,7 @@ public class ValidatorConfig {
             // 设置使用 HibernateValidator 校验器
             factoryBean.setProviderClass(HibernateValidator.class);
             Properties properties = new Properties();
-            // 设置 快速异常返回
+            // 设置快速失败模式(fail-fast),即校验过程中一旦遇到失败,立即停止并返回错误
             properties.setProperty("hibernate.validator.fail_fast", "true");
             factoryBean.setValidationProperties(properties);
             // 加载配置

+ 18 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/CacheNames.java

@@ -3,13 +3,13 @@ package com.vber.common.core.constant;
 /**
  * 缓存组名称常量
  * <p>
- * key 格式为 cacheNames#ttl#maxIdleTime#maxSize
+ * key 格式为 cacheNames#ttl#maxIdleTime#maxSize#local
  * <p>
  * ttl 过期时间 如果设置为0则不过期 默认为0
  * maxIdleTime 最大空闲时间 根据LRU算法清理空闲数据 如果设置为0则不检测 默认为0
  * maxSize 组最大长度 根据LRU算法清理溢出数据 如果设置为0则无限长 默认为0
  * <p>
- * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500
+ * 例子: test#60s、test#0#60s、test#0#1m#1000、test#1h#0#500、test#1h#0#500#0
  *
  * @author Iwb
  */
@@ -30,6 +30,11 @@ public interface CacheNames {
      */
     String SYS_DICT = "sys_dict";
 
+    /**
+     * 数据字典类型
+     */
+    String SYS_DICT_TYPE = "sys_dict_type";
+
     /**
      * 租户
      */
@@ -58,6 +63,17 @@ public interface CacheNames {
      */
     String SYS_ORG = "sys_org#30d";
 
+    /**
+     * 角色自定义权限
+     */
+    String SYS_ROLE_CUSTOM = "sys_role_custom#30d";
+
+    /**
+     * 部门及以下权限
+     */
+    String SYS_ORG_AND_CHILD = "sys_org_and_child#30d";
+
+
     /**
      * OSS内容
      */

+ 0 - 4
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/Constants.java

@@ -67,10 +67,6 @@ public interface Constants {
      */
     Integer CAPTCHA_EXPIRATION = 2;
 
-    /**
-     * 令牌
-     */
-    String TOKEN = "token";
 
     /**
      * 顶级组织机构id

+ 14 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/UserConstants.java → SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/SystemConstants.java

@@ -5,7 +5,7 @@ package com.vber.common.core.constant;
  *
  * @author Iwb
  */
-public interface UserConstants {
+public interface SystemConstants {
 
     /**
      * 平台内系统用户的唯一标志
@@ -17,6 +17,10 @@ public interface UserConstants {
      */
     String NORMAL = "0";
 
+    /**
+     * 异常状态
+     */
+    String DISABLE = "1";
     /**
      * 异常状态
      */
@@ -150,4 +154,13 @@ public interface UserConstants {
     Long SUPER_ADMIN_ID = 1L;
     Long SUPER_SYSTEM_ID = 2L;
 
+    /**
+     * 根组织机构祖级列表
+     */
+    String ROOT_ORG_ANCESTORS = "0";
+
+    /**
+     * 默认组织机构 ID
+     */
+    Long DEFAULT_ORG_ID = 100L;
 }

+ 0 - 10
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/constant/TenantConstants.java

@@ -7,16 +7,6 @@ package com.vber.common.core.constant;
  */
 public interface TenantConstants {
 
-    /**
-     * 租户正常状态
-     */
-    String NORMAL = "0";
-
-    /**
-     * 租户封禁状态
-     */
-    String DISABLE = "1";
-
     /**
      * 超级管理员ID
      */

+ 41 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/DictDataDTO.java

@@ -0,0 +1,41 @@
+package com.vber.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典数据DTO
+ *
+ * @author Iwb
+ */
+@Data
+@NoArgsConstructor
+public class DictDataDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典标签
+     */
+    private String dictLabel;
+
+    /**
+     * 字典键值
+     */
+    private String dictValue;
+
+    /**
+     * 是否默认(Y是 N否)
+     */
+    private String isDefault;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 41 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/DictTypeDTO.java

@@ -0,0 +1,41 @@
+package com.vber.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 字典类型DTO
+ *
+ * @author Iwb
+ */
+@Data
+@NoArgsConstructor
+public class DictTypeDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 字典主键
+     */
+    private Long dictId;
+
+    /**
+     * 字典名称
+     */
+    private String dictName;
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 37 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/OrgDTO.java

@@ -0,0 +1,37 @@
+package com.vber.common.core.domain.dto;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 部门
+ *
+ * @author Iwb
+ */
+
+@Data
+@NoArgsConstructor
+public class OrgDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 父部门ID
+     */
+    private Long parentId;
+
+    /**
+     * 部门名称
+     */
+    private String deptName;
+
+}

+ 47 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/PostDTO.java

@@ -0,0 +1,47 @@
+package com.vber.common.core.domain.dto;
+
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 岗位
+ *
+ * @author Iwb
+ */
+@Data
+@NoArgsConstructor
+public class PostDTO implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 岗位ID
+     */
+    private Long postId;
+
+    /**
+     * 部门id
+     */
+    private Long orgId;
+
+    /**
+     * 岗位编码
+     */
+    private String postCode;
+
+    /**
+     * 岗位名称
+     */
+    private String postName;
+
+    /**
+     * 岗位类别编码
+     */
+    private String postCategory;
+
+}

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/dto/RoleDTO.java

@@ -34,7 +34,7 @@ public class RoleDTO implements Serializable {
     private String roleKey;
 
     /**
-     * 数据范围(1:所有数据权限;2:自定义数据权限;3:本组织机构数据权限;4:本组织机构及以下数据权限;5:仅本人数据权限)
+     * 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
      */
     private String dataScope;
 

+ 34 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessDeleteEvent.java

@@ -0,0 +1,34 @@
+package com.vber.common.core.domain.event;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 删除流程监听
+ *
+ * @author Iwb
+ */
+@Data
+public class ProcessDeleteEvent implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 流程定义编码
+     */
+    private String flowCode;
+
+    /**
+     * 业务id
+     */
+    private String businessId;
+
+}

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/event/ProcessTaskEvent.java

@@ -6,7 +6,7 @@ import java.io.Serial;
 import java.io.Serializable;
 
 /**
- * 总体流程监听
+ * 流程创建任务监听
  *
  * @author Yue
  */

+ 6 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/model/LoginUser.java

@@ -1,6 +1,7 @@
 package com.vber.common.core.domain.model;
 
 import com.vber.common.core.domain.dto.RoleDTO;
+import com.vber.common.core.domain.dto.PostDTO;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
@@ -116,6 +117,11 @@ public class LoginUser implements Serializable {
      */
     private Long roleId;
 
+    /**
+     * 岗位对象
+     */
+    private List<PostDTO> posts;
+
     /**
      * 客户端
      */

+ 2 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/model/PasswordLoginBody.java

@@ -5,7 +5,6 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import org.hibernate.validator.constraints.Length;
 
-import static com.vber.common.core.constant.UserConstants.*;
 
 /**
  * 密码登录对象
@@ -20,14 +19,14 @@ public class PasswordLoginBody extends LoginBody {
      * 用户名
      */
     @NotBlank(message = "{user.username.not.blank}")
-    @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
     private String username;
 
     /**
      * 用户密码
      */
     @NotBlank(message = "{user.password.not.blank}")
-    @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
     private String password;
 
 }

+ 2 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/domain/model/RegisterBody.java

@@ -5,7 +5,6 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 import org.hibernate.validator.constraints.Length;
 
-import static com.vber.common.core.constant.UserConstants.*;
 
 /**
  * 用户注册对象
@@ -20,14 +19,14 @@ public class RegisterBody extends LoginBody {
      * 用户名
      */
     @NotBlank(message = "{user.username.not.blank}")
-    @Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
+    @Length(min = 2, max = 30, message = "{user.username.length.valid}")
     private String username;
 
     /**
      * 用户密码
      */
     @NotBlank(message = "{user.password.not.blank}")
-    @Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
+    @Length(min = 5, max = 30, message = "{user.password.length.valid}")
     private String password;
 
     private String userType;

+ 4 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/DeviceType.java

@@ -29,9 +29,12 @@ public enum DeviceType {
     XCX("xcx"),
 
     /**
-     * social第三方端
+     * 第三方社交登录平台
      */
     SOCIAL("social");
 
+    /**
+     * 设备标识
+     */
     private final String device;
 }

+ 148 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/FormatsType.java

@@ -0,0 +1,148 @@
+package com.vber.common.core.enums;
+
+
+
+import com.vber.common.core.utils.StringUtils;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/*
+ * 日期格式
+ * "yyyy":4位数的年份,例如:2023年表示为"2023"。
+ * "yy":2位数的年份,例如:2023年表示为"23"。
+ * "MM":2位数的月份,取值范围为01到12,例如:7月表示为"07"。
+ * "M":不带前导零的月份,取值范围为1到12,例如:7月表示为"7"。
+ * "dd":2位数的日期,取值范围为01到31,例如:22日表示为"22"。
+ * "d":不带前导零的日期,取值范围为1到31,例如:22日表示为"22"。
+ * "EEEE":星期的全名,例如:星期三表示为"Wednesday"。
+ * "E":星期的缩写,例如:星期三表示为"Wed"。
+ * "DDD" 或 "D":一年中的第几天,取值范围为001到366,例如:第200天表示为"200"。
+ * 时间格式
+ * "HH":24小时制的小时数,取值范围为00到23,例如:下午5点表示为"17"。
+ * "hh":12小时制的小时数,取值范围为01到12,例如:下午5点表示为"05"。
+ * "mm":分钟数,取值范围为00到59,例如:30分钟表示为"30"。
+ * "ss":秒数,取值范围为00到59,例如:45秒表示为"45"。
+ * "SSS":毫秒数,取值范围为000到999,例如:123毫秒表示为"123"。
+ */
+
+/**
+ * 日期格式与时间格式枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum FormatsType {
+
+    /**
+     * 例如:2023年表示为"23"
+     */
+    YY("yy"),
+
+    /**
+     * 例如:2023年表示为"2023"
+     */
+    YYYY("yyyy"),
+
+    /**
+     * 例例如,2023年7月可以表示为 "2023-07"
+     */
+    YYYY_MM("yyyy-MM"),
+
+    /**
+     * 例如,日期 "2023年7月22日" 可以表示为 "2023-07-22"
+     */
+    YYYY_MM_DD("yyyy-MM-dd"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023-07-22 15:30"
+     */
+    YYYY_MM_DD_HH_MM("yyyy-MM-dd HH:mm"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023-07-22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SS("yyyy-MM-dd HH:mm:ss"),
+
+    /**
+     * 例如:下午3点30分45秒,表示为 "15:30:45"
+     */
+    HH_MM_SS("HH:mm:ss"),
+
+    /**
+     * 例例如,2023年7月可以表示为 "2023/07"
+     */
+    YYYY_MM_SLASH("yyyy/MM"),
+
+    /**
+     * 例如,日期 "2023年7月22日" 可以表示为 "2023/07/22"
+     */
+    YYYY_MM_DD_SLASH("yyyy/MM/dd"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SLASH("yyyy/MM/dd HH:mm"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023/07/22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SS_SLASH("yyyy/MM/dd HH:mm:ss"),
+
+    /**
+     * 例例如,2023年7月可以表示为 "2023.07"
+     */
+    YYYY_MM_DOT("yyyy.MM"),
+
+    /**
+     * 例如,日期 "2023年7月22日" 可以表示为 "2023.07.22"
+     */
+    YYYY_MM_DD_DOT("yyyy.MM.dd"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分",则可以表示为 "2023.07.22 15:30"
+     */
+    YYYY_MM_DD_HH_MM_DOT("yyyy.MM.dd HH:mm"),
+
+    /**
+     * 例如,当前时间如果是 "2023年7月22日下午3点30分45秒",则可以表示为 "2023.07.22 15:30:45"
+     */
+    YYYY_MM_DD_HH_MM_SS_DOT("yyyy.MM.dd HH:mm:ss"),
+
+    /**
+     * 例如,2023年7月可以表示为 "202307"
+     */
+    YYYYMM("yyyyMM"),
+
+    /**
+     * 例如,2023年7月22日可以表示为 "20230722"
+     */
+    YYYYMMDD("yyyyMMdd"),
+
+    /**
+     * 例如,2023年7月22日下午3点可以表示为 "2023072215"
+     */
+    YYYYMMDDHH("yyyyMMddHH"),
+
+    /**
+     * 例如,2023年7月22日下午3点30分可以表示为 "202307221530"
+     */
+    YYYYMMDDHHMM("yyyyMMddHHmm"),
+
+    /**
+     * 例如,2023年7月22日下午3点30分45秒可以表示为 "20230722153045"
+     */
+    YYYYMMDDHHMMSS("yyyyMMddHHmmss");
+
+    /**
+     * 时间格式
+     */
+    private final String timeFormat;
+
+    public static FormatsType getFormatsType(String str) {
+        for (FormatsType value : values()) {
+            if (StringUtils.contains(str, value.getTimeFormat())) {
+                return value;
+            }
+        }
+        throw new RuntimeException("'FormatsType' not found By " + str);
+    }
+}

+ 0 - 30
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/TenantStatus.java

@@ -1,30 +0,0 @@
-package com.vber.common.core.enums;
-
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 用户状态
- *
- * @author iwb
- */
-@Getter
-@AllArgsConstructor
-public enum TenantStatus {
-    /**
-     * 正常
-     */
-    OK("0", "正常"),
-    /**
-     * 停用
-     */
-    DISABLE("1", "停用"),
-    /**
-     * 删除
-     */
-    DELETED("2", "删除");
-
-    private final String code;
-    private final String info;
-
-}

+ 5 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/enums/UserType.java

@@ -15,15 +15,18 @@ import lombok.Getter;
 public enum UserType {
 
     /**
-     * pc端
+     * 后台系统用户
      */
     SYS_USER("sys_user"),
 
     /**
-     * app端
+     * 移动客户端用户
      */
     APP_USER("app_user");
 
+    /**
+     * 用户类型标识(用于 token、权限识别等)
+     */
     private final String userType;
 
     public static UserType getUserType(String str) {

+ 20 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/DictService.java

@@ -1,5 +1,9 @@
 package com.vber.common.core.service;
 
+import com.vber.common.core.domain.dto.DictDataDTO;
+import com.vber.common.core.domain.dto.DictTypeDTO;
+
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -64,4 +68,20 @@ public interface DictService {
      */
     Map<String, String> getAllDictByDictType(String dictType);
 
+    /**
+     * 根据字典类型查询详细信息
+     *
+     * @param dictType 字典类型
+     * @return 字典类型详细信息
+     */
+    DictTypeDTO getDictType(String dictType);
+
+    /**
+     * 根据字典类型查询字典数据列表
+     * @param dictType 字典类型
+     * @return 字典数据列表
+     */
+    List<DictDataDTO> getDictData(String dictType);
+
+
 }

+ 18 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/OrgService.java

@@ -1,5 +1,9 @@
 package com.vber.common.core.service;
 
+import com.vber.common.core.domain.dto.OrgDTO;
+
+import java.util.List;
+
 /**
  * 通用 组织机构服务
  *
@@ -15,4 +19,18 @@ public interface OrgService {
      */
     String selectOrgNameByIds(String orgIds);
 
+    /**
+     * 根据组织机构ID查询组织机构负责人
+     *
+     * @param orgId 组织机构ID,用于指定需要查询的组织机构
+     * @return 返回该组织机构的负责人ID
+     */
+    Long selectOrgLeaderById(Long orgId);
+
+    /**
+     * 查询组织机构
+     *
+     * @return 组织机构列表
+     */
+    List<OrgDTO> selectOrgsByList();
 }

+ 28 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/PermissionService.java

@@ -0,0 +1,28 @@
+package com.vber.common.core.service;
+
+import java.util.Set;
+
+/**
+ * 用户权限处理
+ *
+ * @author Lion Li
+ */
+public interface PermissionService {
+
+    /**
+     * 获取角色数据权限
+     *
+     * @param userId  用户id
+     * @return 角色权限信息
+     */
+    Set<String> getRolePermission(Long userId);
+
+    /**
+     * 获取菜单数据权限
+     *
+     * @param userId  用户id
+     * @return 菜单权限信息
+     */
+    Set<String> getMenuPermission(Long userId);
+
+}

+ 10 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/PostService.java

@@ -0,0 +1,10 @@
+package com.vber.common.core.service;
+
+/**
+ * 通用 岗位服务
+ *
+ * @author Iwb
+ */
+public interface PostService  {
+
+}

+ 4 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/RoleService.java

@@ -0,0 +1,4 @@
+package com.vber.common.core.service;
+
+public interface RoleService {
+}

+ 8 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/service/UserService.java

@@ -67,4 +67,12 @@ public interface UserService {
      * @return 用户
      */
     List<UserDTO> selectUsersByOrgIds(List<Long> orgIds);
+
+    /**
+     * 通过岗位ID查询用户
+     *
+     * @param postIds 岗位ids
+     * @return 用户
+     */
+    List<UserDTO> selectUsersByPostIds(List<Long> postIds);
 }

+ 203 - 67
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/DateUtils.java

@@ -1,7 +1,7 @@
 package com.vber.common.core.utils;
 
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
+import com.vber.common.core.enums.FormatsType;
+import com.vber.common.core.exception.ServiceException;
 import org.apache.commons.lang3.time.DateFormatUtils;
 
 import java.lang.management.ManagementFactory;
@@ -9,94 +9,149 @@ import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.time.*;
 import java.util.Date;
+import java.util.concurrent.TimeUnit;
 
 /**
  * 时间工具类
  *
  * @author Iwb
  */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
-
-    public static final String YYYY = "yyyy";
-
-    public static final String YYYY_MM = "yyyy-MM";
-
-    public static final String YYYY_MM_DD = "yyyy-MM-dd";
-
-    public static final String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
-
-    public static final String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
-
     private static final String[] PARSE_PATTERNS = {
             "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
             "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
             "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
 
+    @Deprecated
+    private DateUtils() {
+    }
+
     /**
-     * 获取当前Date型日期
+     * 获取当前日期和时间
      *
-     * @return Date() 当前日期
+     * @return 当前日期和时间的 Date 对象表示
      */
     public static Date getNowDate() {
         return new Date();
     }
 
     /**
-     * 获取当前日期, 默认格式为yyyy-MM-dd
+     * 获取当前日期的字符串表示,格式为YYYY-MM-DD
      *
-     * @return String
+     * @return 当前日期的字符串表示
      */
     public static String getDate() {
-        return dateTimeNow(YYYY_MM_DD);
+        return dateTimeNow(FormatsType.YYYY_MM_DD);
+    }
+
+    /**
+     * 获取当前日期的字符串表示,格式为yyyyMMdd
+     *
+     * @return 当前日期的字符串表示
+     */
+    public static String getCurrentDate() {
+        return DateFormatUtils.format(new Date(), FormatsType.YYYYMMDD.getTimeFormat());
     }
 
+    /**
+     * 获取当前日期的路径格式字符串,格式为"yyyy/MM/dd"
+     *
+     * @return 当前日期的路径格式字符串
+     */
+    public static String datePath() {
+        Date now = new Date();
+        return DateFormatUtils.format(now, FormatsType.YYYY_MM_DD_SLASH.getTimeFormat());
+    }
+
+    /**
+     * 获取当前时间的字符串表示,格式为YYYY-MM-DD HH:MM:SS
+     *
+     * @return 当前时间的字符串表示
+     */
     public static String getTime() {
-        return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+        return dateTimeNow(FormatsType.YYYY_MM_DD_HH_MM_SS);
     }
 
-    public static String dateTimeNow() {
-        return dateTimeNow(YYYYMMDDHHMMSS);
+    /**
+     * 获取当前时间的字符串表示,格式为 "HH:MM:SS"
+     *
+     * @return 当前时间的字符串表示,格式为 "HH:MM:SS"
+     */
+    public static String getTimeWithHourMinuteSecond() {
+        return dateTimeNow(FormatsType.HH_MM_SS);
     }
 
-    public static String dateTimeNow(final String format) {
-        return parseDateToStr(format, new Date());
+    /**
+     * 获取当前日期和时间的字符串表示,格式为YYYYMMDDHHMMSS
+     *
+     * @return 当前日期和时间的字符串表示
+     */
+    public static String dateTimeNow() {
+        return dateTimeNow(FormatsType.YYYYMMDDHHMMSS);
     }
 
-    public static String dateTime(final Date date) {
-        return parseDateToStr(YYYY_MM_DD, date);
+    /**
+     * 获取当前日期和时间的指定格式的字符串表示
+     *
+     * @param format 日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
+     * @return 当前日期和时间的字符串表示
+     */
+    public static String dateTimeNow(final FormatsType format) {
+        return parseDateToStr(format, new Date());
     }
 
-    public static String parseDateToStr(final String format, final Date date) {
-        return new SimpleDateFormat(format).format(date);
+    /**
+     * 将指定日期格式化为 YYYY-MM-DD 格式的字符串
+     *
+     * @param date 要格式化的日期对象
+     * @return 格式化后的日期字符串
+     */
+    public static String formatDate(final Date date) {
+        return parseDateToStr(FormatsType.YYYY_MM_DD, date);
     }
 
-    public static Date dateTime(final String format, final String ts) {
-        try {
-            return new SimpleDateFormat(format).parse(ts);
-        } catch (ParseException e) {
-            throw new RuntimeException(e);
-        }
+    /**
+     * 将指定日期格式化为 YYYY-MM-DD HH:MM:SS 格式的字符串
+     *
+     * @param date 要格式化的日期对象
+     * @return 格式化后的日期时间字符串
+     */
+    public static String formatDateTime(final Date date) {
+        return parseDateToStr(FormatsType.YYYY_MM_DD_HH_MM_SS, date);
     }
 
     /**
-     * 日期路径 即年/月/日 如2018/08/08
+     * 将指定日期按照指定格式进行格式化
+     *
+     * @param format 要使用的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
+     * @param date   要格式化的日期对象
+     * @return 格式化后的日期时间字符串
      */
-    public static String datePath() {
-        Date now = new Date();
-        return DateFormatUtils.format(now, "yyyy/MM/dd");
+    public static String parseDateToStr(final FormatsType format, final Date date) {
+        return new SimpleDateFormat(format.getTimeFormat()).format(date);
     }
 
     /**
-     * 日期路径 即年/月/日 如20180808
+     * 将指定格式的日期时间字符串转换为 Date 对象
+     *
+     * @param format 要解析的日期时间格式,例如"YYYY-MM-DD HH:MM:SS"
+     * @param ts     要解析的日期时间字符串
+     * @return 解析后的 Date 对象
+     * @throws RuntimeException 如果解析过程中发生异常
      */
-    public static String dateTime() {
-        Date now = new Date();
-        return DateFormatUtils.format(now, "yyyyMMdd");
+    public static Date parseDateTime(final FormatsType format, final String ts) {
+        try {
+            return new SimpleDateFormat(format.getTimeFormat()).parse(ts);
+        } catch (ParseException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     /**
-     * 日期型字符串转化为日期 格式
+     * 将对象转换为日期对象
+     *
+     * @param str 要转换的对象,通常是字符串
+     * @return 转换后的日期对象,如果转换失败或输入为null,则返回null
      */
     public static Date parseDate(Object str) {
         if (str == null) {
@@ -111,6 +166,8 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
 
     /**
      * 获取服务器启动时间
+     *
+     * @return 服务器启动时间的 Date 对象表示
      */
     public static Date getServerStartDate() {
         long time = ManagementFactory.getRuntimeMXBean().getStartTime();
@@ -118,35 +175,79 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     }
 
     /**
-     * 计算相差天数
+     * 计算两个时间之间的时间差,并以指定单位返回(绝对值)
+     *
+     * @param start 起始时间
+     * @param end   结束时间
+     * @param unit  所需返回的时间单位(DAYS、HOURS、MINUTES、SECONDS、MILLISECONDS、MICROSECONDS、NANOSECONDS)
+     * @return 时间差的绝对值,以指定单位表示
      */
-    public static int differentDaysByMillisecond(Date date1, Date date2) {
-        return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24)));
+    public static long difference(Date start, Date end, TimeUnit unit) {
+        // 计算时间差,单位为毫秒,取绝对值避免负数
+        long diffInMillis = Math.abs(end.getTime() - start.getTime());
+
+        // 根据目标单位转换时间差
+        return switch (unit) {
+            case DAYS -> diffInMillis / TimeUnit.DAYS.toMillis(1);
+            case HOURS -> diffInMillis / TimeUnit.HOURS.toMillis(1);
+            case MINUTES -> diffInMillis / TimeUnit.MINUTES.toMillis(1);
+            case SECONDS -> diffInMillis / TimeUnit.SECONDS.toMillis(1);
+            case MILLISECONDS -> diffInMillis;
+            case MICROSECONDS -> TimeUnit.MILLISECONDS.toMicros(diffInMillis);
+            case NANOSECONDS -> TimeUnit.MILLISECONDS.toNanos(diffInMillis);
+        };
     }
 
     /**
-     * 计算两个时间差
+     * 计算两个日期之间的时间差,并以天、小时和分钟的格式返回
+     *
+     * @param endDate 结束日期
+     * @param nowDate 当前日期
+     * @return 表示时间差的字符串,格式为"天 小时 分钟"
      */
     public static String getDatePoor(Date endDate, Date nowDate) {
-        long nd = 1000 * 24 * 60 * 60;
-        long nh = 1000 * 60 * 60;
-        long nm = 1000 * 60;
-        // long ns = 1000;
-        // 获得两个时间的毫秒时间差异
-        long diff = endDate.getTime() - nowDate.getTime();
-        // 计算差多少天
-        long day = diff / nd;
-        // 计算差多少小时
-        long hour = diff % nd / nh;
-        // 计算差多少分钟
-        long min = diff % nd % nh / nm;
-        // 计算差多少秒//输出结果
-        // long sec = diff % nd % nh % nm / ns;
-        return day + "天" + hour + "小时" + min + "分钟";
-    }
-
-    /**
-     * 增加 LocalDateTime ==> Date
+        long diffInMillis = endDate.getTime() - nowDate.getTime();
+        long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
+        long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
+        long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
+        return String.format("%d天 %d小时 %d分钟", day, hour, min);
+    }
+
+    /**
+     * 计算两个时间点的差值(天、小时、分钟、秒),当值为0时不显示该单位
+     *
+     * @param endDate 结束时间
+     * @param nowDate 当前时间
+     * @return 时间差字符串,格式为 "x天 x小时 x分钟 x秒",若为 0 则不显示
+     */
+    public static String getTimeDifference(Date endDate, Date nowDate) {
+        long diffInMillis = endDate.getTime() - nowDate.getTime();
+        long day = TimeUnit.MILLISECONDS.toDays(diffInMillis);
+        long hour = TimeUnit.MILLISECONDS.toHours(diffInMillis) % 24;
+        long min = TimeUnit.MILLISECONDS.toMinutes(diffInMillis) % 60;
+        long sec = TimeUnit.MILLISECONDS.toSeconds(diffInMillis) % 60;
+        // 构建时间差字符串,条件是值不为0才显示
+        StringBuilder result = new StringBuilder();
+        if (day > 0) {
+            result.append(String.format("%d天 ", day));
+        }
+        if (hour > 0) {
+            result.append(String.format("%d小时 ", hour));
+        }
+        if (min > 0) {
+            result.append(String.format("%d分钟 ", min));
+        }
+        if (sec > 0) {
+            result.append(String.format("%d秒", sec));
+        }
+        return result.length() > 0 ? result.toString().trim() : "0秒";
+    }
+
+    /**
+     * 将 LocalDateTime 对象转换为 Date 对象
+     *
+     * @param temporalAccessor 要转换的 LocalDateTime 对象
+     * @return 转换后的 Date 对象
      */
     public static Date toDate(LocalDateTime temporalAccessor) {
         ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
@@ -154,11 +255,46 @@ public class DateUtils extends org.apache.commons.lang3.time.DateUtils {
     }
 
     /**
-     * 增加 LocalDate ==> Date
+     * 将 LocalDate 对象转换为 Date 对象
+     *
+     * @param temporalAccessor 要转换的 LocalDate 对象
+     * @return 转换后的 Date 对象
      */
     public static Date toDate(LocalDate temporalAccessor) {
         LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
         ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
         return Date.from(zdt.toInstant());
     }
+
+    /**
+     * 校验日期范围
+     *
+     * @param startDate 开始日期
+     * @param endDate   结束日期
+     * @param maxValue  最大时间跨度的限制值
+     * @param unit      时间跨度的单位,可选择 "DAYS"、"HOURS" 或 "MINUTES"
+     */
+    public static void validateDateRange(Date startDate, Date endDate, int maxValue, TimeUnit unit) {
+        // 校验结束日期不能早于开始日期
+        if (endDate.before(startDate)) {
+            throw new ServiceException("结束日期不能早于开始日期");
+        }
+
+        // 计算时间跨度
+        long diffInMillis = endDate.getTime() - startDate.getTime();
+
+        // 根据单位转换时间跨度
+        long diff = switch (unit) {
+            case DAYS -> TimeUnit.MILLISECONDS.toDays(diffInMillis);
+            case HOURS -> TimeUnit.MILLISECONDS.toHours(diffInMillis);
+            case MINUTES -> TimeUnit.MILLISECONDS.toMinutes(diffInMillis);
+            default -> throw new IllegalArgumentException("不支持的时间单位");
+        };
+
+        // 校验时间跨度不超过最大限制
+        if (diff > maxValue) {
+            throw new ServiceException("最大时间跨度为 " + maxValue + " " + unit.toString().toLowerCase());
+        }
+    }
+
 }

+ 85 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/NetUtils.java

@@ -0,0 +1,85 @@
+package com.vber.common.core.utils;
+
+
+import cn.hutool.core.lang.PatternPool;
+import cn.hutool.core.net.NetUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import com.vber.common.core.utils.regex.RegexUtils;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * 增强网络相关工具类
+ *
+ * @author 秋辞未寒
+ */
+@Slf4j
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class NetUtils extends NetUtil {
+
+    /**
+     * 判断是否为IPv6地址
+     *
+     * @param ip IP地址
+     * @return 是否为IPv6地址
+     */
+    public static boolean isIPv6(String ip) {
+        try {
+            // 判断是否为IPv6地址
+            return InetAddress.getByName(ip) instanceof Inet6Address;
+        } catch (UnknownHostException e) {
+            return false;
+        }
+    }
+
+    /**
+     * 判断IPv6地址是否为内网地址
+     * <br><br>
+     * 以下地址将归类为本地地址,如有业务场景有需要,请根据需求自行处理:
+     * <pre>
+     * 通配符地址 0:0:0:0:0:0:0:0
+     * 链路本地地址 fe80::/10
+     * 唯一本地地址 fec0::/10
+     * 环回地址 ::1
+     * </pre>
+     *
+     * @param ip IP地址
+     * @return 是否为内网地址
+     */
+    public static boolean isInnerIPv6(String ip) {
+        try {
+            // 判断是否为IPv6地址
+            if (InetAddress.getByName(ip) instanceof Inet6Address inet6Address) {
+                // isAnyLocalAddress 判断是否为通配符地址,通常不会将其视为内网地址,根据业务场景自行处理判断
+                // isLinkLocalAddress 判断是否为链路本地地址,通常不算内网地址,是否划分归属于内网需要根据业务场景自行处理判断
+                // isLoopbackAddress 判断是否为环回地址,与IPv4的 127.0.0.1 同理,用于表示本机
+                // isSiteLocalAddress 判断是否为本地站点地址,IPv6唯一本地地址(Unique Local Addresses,简称ULA)
+                if (inet6Address.isAnyLocalAddress()
+                        || inet6Address.isLinkLocalAddress()
+                        || inet6Address.isLoopbackAddress()
+                        || inet6Address.isSiteLocalAddress()) {
+                    return true;
+                }
+            }
+        } catch (UnknownHostException e) {
+            // 注意,isInnerIPv6方法和isIPv6方法的适用范围不同,所以此处不能忽略其异常信息。
+            throw new IllegalArgumentException("Invalid IPv6 address!", e);
+        }
+        return false;
+    }
+
+    /**
+     * 判断是否为IPv4地址
+     *
+     * @param ip IP地址
+     * @return 是否为IPv4地址
+     */
+    public static boolean isIPv4(String ip) {
+        return RegexUtils.isMatch(PatternPool.IPV4, ip);
+    }
+
+}

+ 61 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ObjectUtils.java

@@ -0,0 +1,61 @@
+package com.vber.common.core.utils;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+import java.util.function.Function;
+
+/**
+ * 对象工具类
+ *
+ * @author 秋辞未寒
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class ObjectUtils extends ObjectUtil {
+
+    /**
+     * 如果对象不为空,则获取对象中的某个字段 ObjectUtils.notNullGetter(user, User::getName);
+     *
+     * @param obj 对象
+     * @param func 获取方法
+     * @return 对象字段
+     */
+    public static <T, E> E notNullGetter(T obj, Function<T, E> func) {
+        if (isNotNull(obj) && isNotNull(func)) {
+            return func.apply(obj);
+        }
+        return null;
+    }
+
+    /**
+     * 如果对象不为空,则获取对象中的某个字段,否则返回默认值
+     *
+     * @param obj          对象
+     * @param func         获取方法
+     * @param defaultValue 默认值
+     * @return 对象字段
+     */
+    public static <T, E> E notNullGetter(T obj, Function<T, E> func, E defaultValue) {
+        if (isNotNull(obj) && isNotNull(func)) {
+            return func.apply(obj);
+        }
+        return defaultValue;
+    }
+
+    /**
+     * 如果值不为空,则返回值,否则返回默认值
+     *
+     * @param obj          对象
+     * @param defaultValue 默认值
+     * @return 对象字段
+     */
+    public static <T> T notNull(T obj, T defaultValue) {
+        if (isNotNull(obj)) {
+            return obj;
+        }
+        return defaultValue;
+    }
+
+}
+

+ 81 - 22
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ServletUtils.java

@@ -25,7 +25,7 @@ import java.util.HashMap;
 import java.util.Map;
 
 /**
- * 客户端工具类
+ * 客户端工具类,提供获取请求参数、响应处理、头部信息等常用操作
  *
  * @author Iwb
  */
@@ -33,52 +33,73 @@ import java.util.Map;
 public class ServletUtils extends JakartaServletUtil {
 
     /**
-     * 获取String参数
+     * 获取指定名称的 String 类型的请求参数
+     *
+     * @param name 参数名
+     * @return 参数值
      */
     public static String getParameter(String name) {
         return getRequest().getParameter(name);
     }
 
     /**
-     * 获取String参数
+     * 获取指定名称的 String 类型的请求参数,若参数不存在,则返回默认值
+     *
+     * @param name         参数名
+     * @param defaultValue 默认值
+     * @return 参数值或默认值
      */
     public static String getParameter(String name, String defaultValue) {
         return Convert.toStr(getRequest().getParameter(name), defaultValue);
     }
 
     /**
-     * 获取Integer参数
+     * 获取指定名称的 Integer 类型的请求参数
+     *
+     * @param name 参数名
+     * @return 参数值
      */
     public static Integer getParameterToInt(String name) {
         return Convert.toInt(getRequest().getParameter(name));
     }
 
     /**
-     * 获取Integer参数
+     * 获取指定名称的 Integer 类型的请求参数,若参数不存在,则返回默认值
+     *
+     * @param name         参数名
+     * @param defaultValue 默认值
+     * @return 参数值或默认值
      */
     public static Integer getParameterToInt(String name, Integer defaultValue) {
         return Convert.toInt(getRequest().getParameter(name), defaultValue);
     }
 
     /**
-     * 获取Boolean参数
+     * 获取指定名称的 Boolean 类型的请求参数
+     *
+     * @param name 参数名
+     * @return 参数值
      */
     public static Boolean getParameterToBool(String name) {
         return Convert.toBool(getRequest().getParameter(name));
     }
 
     /**
-     * 获取Boolean参数
+     * 获取指定名称的 Boolean 类型的请求参数,若参数不存在,则返回默认值
+     *
+     * @param name         参数名
+     * @param defaultValue 默认值
+     * @return 参数值或默认值
      */
     public static Boolean getParameterToBool(String name, Boolean defaultValue) {
         return Convert.toBool(getRequest().getParameter(name), defaultValue);
     }
 
     /**
-     * 获得所有请求参数
+     * 获取所有请求参数(以 Map 的形式返回)
      *
      * @param request 请求对象{@link ServletRequest}
-     * @return Map
+     * @return 请求参数的 Map,键为参数名,值为参数值数组
      */
     public static Map<String, String[]> getParams(ServletRequest request) {
         final Map<String, String[]> map = request.getParameterMap();
@@ -86,10 +107,10 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 获得所有请求参数
+     * 获取所有请求参数(以 Map 的形式返回,值为字符串形式的拼接)
      *
      * @param request 请求对象{@link ServletRequest}
-     * @return Map
+     * @return 请求参数的 Map,键为参数名,值为拼接后的字符串
      */
     public static Map<String, String> getParamMap(ServletRequest request) {
         Map<String, String> params = new HashMap<>();
@@ -100,7 +121,9 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 获取request
+     * 获取当前 HTTP 请求对象
+     *
+     * @return 当前 HTTP 请求对象
      */
     public static HttpServletRequest getRequest() {
         try {
@@ -111,7 +134,9 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 获取response
+     * 获取当前 HTTP 响应对象
+     *
+     * @return 当前 HTTP 响应对象
      */
     public static HttpServletResponse getResponse() {
         try {
@@ -122,12 +147,25 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 获取session
+     * 获取当前请求的 HttpSession 对象
+     * <p>
+     * 如果当前请求已经关联了一个会话(即已经存在有效的 session ID),
+     * 则返回该会话对象;如果没有关联会话,则会创建一个新的会话对象并返回。
+     * <p>
+     * HttpSession 用于存储会话级别的数据,如用户登录信息、购物车内容等,
+     * 可以在多个请求之间共享会话数据
+     *
+     * @return 当前请求的 HttpSession 对象
      */
     public static HttpSession getSession() {
         return getRequest().getSession();
     }
 
+    /**
+     * 获取当前请求的请求属性
+     *
+     * @return {@link ServletRequestAttributes} 请求属性对象
+     */
     public static ServletRequestAttributes getRequestAttributes() {
         try {
             RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
@@ -137,6 +175,13 @@ public class ServletUtils extends JakartaServletUtil {
         }
     }
 
+    /**
+     * 获取指定请求头的值,如果头部为空则返回空字符串
+     *
+     * @param request 请求对象
+     * @param name    头部名称
+     * @return 头部值
+     */
     public static String getHeader(HttpServletRequest request, String name) {
         String value = request.getHeader(name);
         if (StringUtils.isEmpty(value)) {
@@ -145,6 +190,12 @@ public class ServletUtils extends JakartaServletUtil {
         return urlDecode(value);
     }
 
+    /**
+     * 获取所有请求头的 Map,键为头部名称,值为头部值
+     *
+     * @param request 请求对象
+     * @return 请求头的 Map
+     */
     public static Map<String, String> getHeaders(HttpServletRequest request) {
         Map<String, String> map = new LinkedCaseInsensitiveMap<>();
         Enumeration<String> enumeration = request.getHeaderNames();
@@ -159,7 +210,7 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 将字符串渲染到客户端
+     * 将字符串渲染到客户端(以 JSON 格式返回)
      *
      * @param response 渲染对象
      * @param string   待渲染的字符串
@@ -176,37 +227,45 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 是否是Ajax异步请求
+     * 判断当前请求是否为 Ajax 异步请求
      *
-     * @param request
+     * @param request 请求对象
+     * @return 是否为 Ajax 请求
      */
     public static boolean isAjaxRequest(HttpServletRequest request) {
 
+        // 判断 Accept 头部是否包含 application/json
         String accept = request.getHeader("accept");
         if (accept != null && accept.contains(MediaType.APPLICATION_JSON_VALUE)) {
             return true;
         }
-
+        // 判断 Accept 头部是否包含 application/json
         String xRequestedWith = request.getHeader("X-Requested-With");
         if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) {
             return true;
         }
-
+        // 判断 Accept 头部是否包含 application/json
         String uri = request.getRequestURI();
         if (StringUtils.equalsAnyIgnoreCase(uri, ".json", ".xml")) {
             return true;
         }
-
+        // 判断 Accept 头部是否包含 application/json
         String ajax = request.getParameter("__ajax");
         return StringUtils.equalsAnyIgnoreCase(ajax, "json", "xml");
     }
 
+
+    /**
+     * 获取客户端 IP 地址
+     *
+     * @return 客户端 IP 地址
+     */
     public static String getClientIP() {
         return getClientIP(getRequest());
     }
 
     /**
-     * 内容编码
+     * 内容进行 URL 编码
      *
      * @param str 内容
      * @return 编码后的内容
@@ -216,7 +275,7 @@ public class ServletUtils extends JakartaServletUtil {
     }
 
     /**
-     * 内容解
+     * 对内容进行 URL 编
      *
      * @param str 内容
      * @return 解码后的内容

+ 45 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/StringUtils.java

@@ -4,10 +4,9 @@ import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Validator;
 import cn.hutool.core.util.StrUtil;
-import lombok.AccessLevel;
-import lombok.NoArgsConstructor;
 import org.springframework.util.AntPathMatcher;
 
+import java.nio.charset.Charset;
 import java.util.*;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -17,13 +16,15 @@ import java.util.stream.Collectors;
  *
  * @author Iwb
  */
-@NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class StringUtils extends org.apache.commons.lang3.StringUtils {
 
     public static final String SEPARATOR = ",";
 
     public static final String SLASH = "/";
 
+    @Deprecated
+    private StringUtils() {
+    }
     /**
      * 获取参数不为空值
      *
@@ -337,7 +338,48 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
                 .stream()
                 .filter(Objects::nonNull)
                 .map(mapper)
+                .filter(Objects::nonNull)
                 .collect(Collectors.toList());
     }
 
+    /**
+     * 不区分大小写检查 CharSequence 是否以指定的前缀开头。
+     *
+     * @param str     要检查的 CharSequence 可能为 null
+     * @param prefixs 要查找的前缀可能为 null
+     * @return 是否包含
+     */
+    public static boolean startWithAnyIgnoreCase(CharSequence str, CharSequence... prefixs) {
+        // 判断是否是以指定字符串开头
+        for (CharSequence prefix : prefixs) {
+            if (StringUtils.startsWithIgnoreCase(str, prefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 将字符串从源字符集转换为目标字符集
+     *
+     * @param input       原始字符串
+     * @param fromCharset 源字符集
+     * @param toCharset   目标字符集
+     * @return 转换后的字符串
+     */
+    public static String convert(String input, Charset fromCharset, Charset toCharset) {
+        if (isBlank(input)) {
+            return input;
+        }
+        try {
+            // 从源字符集获取字节
+            byte[] bytes = input.getBytes(fromCharset);
+            // 使用目标字符集解码
+            return new String(bytes, toCharset);
+        } catch (Exception e) {
+            return input;
+        }
+    }
+
+
 }

+ 0 - 11
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/Threads.java

@@ -15,17 +15,6 @@ import java.util.concurrent.*;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class Threads {
 
-    /**
-     * sleep等待,单位为毫秒
-     */
-    public static void sleep(long milliseconds) {
-        try {
-            Thread.sleep(milliseconds);
-        } catch (InterruptedException e) {
-            return;
-        }
-    }
-
     /**
      * 停止线程池
      * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.

+ 44 - 7
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ip/AddressUtils.java

@@ -1,7 +1,7 @@
 package com.vber.common.core.utils.ip;
 
-import cn.hutool.core.net.NetUtil;
 import cn.hutool.http.HtmlUtil;
+import com.vber.common.core.utils.NetUtils;
 import com.vber.common.core.utils.StringUtils;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
@@ -16,18 +16,55 @@ import lombok.extern.slf4j.Slf4j;
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public class AddressUtils {
 
+    // 未知IP
+    public static final String UNKNOWN_IP = "XX XX";
+    // 内网地址
+    public static final String LOCAL_ADDRESS = "内网IP";
     // 未知地址
-    public static final String UNKNOWN = "XX XX";
+    public static final String UNKNOWN_ADDRESS = "未知";
 
     public static String getRealAddressByIP(String ip) {
-        if (StringUtils.isBlank(ip)) {
-            return UNKNOWN;
+        // 处理空串并过滤HTML标签
+        ip = HtmlUtil.cleanHtmlTag(StringUtils.blankToDefault(ip,""));
+        // 判断是否为IPv4
+        if (NetUtils.isIPv4(ip)) {
+            return resolverIPv4Region(ip);
         }
+        // 判断是否为IPv6
+        if (NetUtils.isIPv6(ip)) {
+            return resolverIPv6Region(ip);
+        }
+        // 如果不是IPv4或IPv6,则返回未知IP
+        return UNKNOWN_IP;
+    }
+
+    /**
+     * 根据IPv4地址查询IP归属行政区域
+     * @param ip ipv4地址
+     * @return 归属行政区域
+     */
+    private static String resolverIPv4Region(String ip){
         // 内网不查询
-        ip = StringUtils.contains(ip, "0:0:0:0:0:0:0:1") ? "127.0.0.1" : HtmlUtil.cleanHtmlTag(ip);
-        if (NetUtil.isInnerIP(ip)) {
-            return "内网IP";
+        if (NetUtils.isInnerIP(ip)) {
+            return LOCAL_ADDRESS;
         }
         return RegionUtils.getCityInfo(ip);
     }
+
+    /**
+     * 根据IPv6地址查询IP归属行政区域
+     * @param ip ipv6地址
+     * @return 归属行政区域
+     */
+    private static String resolverIPv6Region(String ip){
+        // 内网不查询
+        if (NetUtils.isInnerIPv6(ip)) {
+            return LOCAL_ADDRESS;
+        }
+        log.warn("ip2region不支持IPV6地址解析:{}", ip);
+        // 不支持IPv6,不再进行没有必要的IP地址信息的解析,直接返回
+        // 如有需要,可自行实现IPv6地址信息解析逻辑,并在这里返回
+        return UNKNOWN_ADDRESS;
+    }
+
 }

+ 15 - 31
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/ip/RegionUtils.java

@@ -1,49 +1,34 @@
 package com.vber.common.core.utils.ip;
 
-import cn.hutool.core.io.FileUtil;
-import cn.hutool.core.io.resource.ClassPathResource;
-import cn.hutool.core.util.ObjectUtil;
-import com.vber.common.core.exception.ServiceException;
-import com.vber.common.core.utils.file.FileUtils;
+import cn.hutool.core.io.resource.NoResourceException;
+import cn.hutool.core.io.resource.ResourceUtil;
 import lombok.extern.slf4j.Slf4j;
+import com.vber.common.core.exception.ServiceException;
+import com.vber.common.core.utils.StringUtils;
 import org.lionsoul.ip2region.xdb.Searcher;
 
-import java.io.File;
-
 /**
  * 根据ip地址定位工具类,离线方式
  * 参考地址:<a href="https://gitee.com/lionsoul/ip2region/tree/master/binding/java">集成 ip2region 实现离线IP地址定位库</a>
  *
- * @author lishuyan
+ * @author Iwb
  */
 @Slf4j
 public class RegionUtils {
 
+    // IP地址库文件名称
+    public static final String IP_XDB_FILENAME = "ip2region.xdb";
+
     private static final Searcher SEARCHER;
 
     static {
-        String fileName = "/ip2region.xdb";
-        File existFile = FileUtils.file(FileUtil.getTmpDir() + FileUtil.FILE_SEPARATOR + fileName);
-        if (!FileUtils.exist(existFile)) {
-            ClassPathResource fileStream = new ClassPathResource(fileName);
-            if (ObjectUtil.isEmpty(fileStream.getStream())) {
-                throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
-            }
-            FileUtils.writeFromStream(fileStream.getStream(), existFile);
-        }
-
-        String dbPath = existFile.getPath();
-
-        // 1、从 dbPath 加载整个 xdb 到内存。
-        byte[] cBuff;
-        try {
-            cBuff = Searcher.loadContentFromFile(dbPath);
-        } catch (Exception e) {
-            throw new ServiceException("RegionUtils初始化失败,原因:从ip2region.xdb文件加载内容失败!" + e.getMessage());
-        }
-        // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。
         try {
-            SEARCHER = Searcher.newWithBuffer(cBuff);
+            // 1、将 ip2region 数据库文件 xdb 从 ClassPath 加载到内存。
+            // 2、基于加载到内存的 xdb 数据创建一个 Searcher 查询对象。
+            SEARCHER = Searcher.newWithBuffer(ResourceUtil.readBytes(IP_XDB_FILENAME));
+            log.info("RegionUtils初始化成功,加载IP地址库数据成功!");
+        } catch (NoResourceException e) {
+            throw new ServiceException("RegionUtils初始化失败,原因:IP地址库数据不存在!");
         } catch (Exception e) {
             throw new ServiceException("RegionUtils初始化失败,原因:" + e.getMessage());
         }
@@ -54,9 +39,8 @@ public class RegionUtils {
      */
     public static String getCityInfo(String ip) {
         try {
-            ip = ip.trim();
             // 3、执行查询
-            String region = SEARCHER.search(ip);
+            String region = SEARCHER.search(StringUtils.trim(ip));
             return region.replace("0|", "").replace("|0", "");
         } catch (Exception e) {
             log.error("IP地址离线获取城市异常 {}", ip);

+ 1 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/utils/sql/SqlUtil.java

@@ -15,8 +15,7 @@ public class SqlUtil {
     /**
      * 定义常用的 sql关键字
      */
-    public static String SQL_REGEX = "and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
-   
+    public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
     /**
      * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
      */

+ 40 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/dicts/DictPattern.java

@@ -0,0 +1,40 @@
+package com.vber.common.core.validate.dicts;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 字典项校验注解
+ *
+ * @author Iwb
+ */
+@Constraint(validatedBy = DictPatternValidator.class)
+@Target({ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface DictPattern {
+
+    /**
+     * 字典类型,如 "sys_user_sex"
+     */
+    String dictType();
+
+    /**
+     * 分隔符
+     */
+    String separator();
+
+    /**
+     * 默认校验失败提示信息
+     */
+    String message() default "字典值无效";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+}

+ 55 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/dicts/DictPatternValidator.java

@@ -0,0 +1,55 @@
+package com.vber.common.core.validate.dicts;
+
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+import com.vber.common.core.service.DictService;
+import com.vber.common.core.utils.SpringUtils;
+import com.vber.common.core.utils.StringUtils;
+
+/**
+ * 自定义字典值校验器
+ *
+ * @author Iwb
+ */
+public class DictPatternValidator implements ConstraintValidator<DictPattern, String> {
+
+    /**
+     * 字典类型
+     */
+    private String dictType;
+
+    /**
+     * 分隔符
+     */
+    private String separator = ",";
+
+    /**
+     * 初始化校验器,提取注解上的字典类型
+     *
+     * @param annotation 注解实例
+     */
+    @Override
+    public void initialize(DictPattern annotation) {
+        this.dictType = annotation.dictType();
+        if (StringUtils.isNotBlank(annotation.separator())) {
+            this.separator = annotation.separator();
+        }
+    }
+
+    /**
+     * 校验字段值是否为指定字典类型中的合法值
+     *
+     * @param value   被校验的字段值
+     * @param context 校验上下文(可用于构建错误信息)
+     * @return true 表示校验通过(合法字典值),false 表示不通过
+     */
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext context) {
+        if (StringUtils.isBlank(dictType) || StringUtils.isBlank(value)) {
+            return false;
+        }
+        String dictLabel = SpringUtils.getBean(DictService.class).getDictLabel(dictType, value, separator);
+        return StringUtils.isNotBlank(dictLabel);
+    }
+
+}

+ 50 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/enumd/EnumPattern.java

@@ -0,0 +1,50 @@
+package com.vber.common.core.validate.enumd;
+
+import jakarta.validation.Constraint;
+import jakarta.validation.Payload;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * 自定义枚举校验
+ *
+ * @author Iwb
+ */
+@Documented
+@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
+@Retention(RUNTIME)
+@Repeatable(EnumPattern.List.class) // 允许在同一元素上多次使用该注解
+@Constraint(validatedBy = {EnumPatternValidator.class})
+public @interface EnumPattern {
+
+    /**
+     * 需要校验的枚举类型
+     */
+    Class<? extends Enum<?>> type();
+
+    /**
+     * 枚举类型校验值字段名称
+     * 需确保该字段实现了 getter 方法
+     */
+    String fieldName();
+
+    String message() default "输入值不在枚举范围内";
+
+    Class<?>[] groups() default {};
+
+    Class<? extends Payload>[] payload() default {};
+
+    @Documented
+    @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
+    @Retention(RUNTIME)
+    @interface List {
+        EnumPattern[] value();
+    }
+
+}

+ 37 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/core/validate/enumd/EnumPatternValidator.java

@@ -0,0 +1,37 @@
+package com.vber.common.core.validate.enumd;
+
+import com.vber.common.core.utils.StringUtils;
+import com.vber.common.core.utils.reflect.ReflectUtils;
+import jakarta.validation.ConstraintValidator;
+import jakarta.validation.ConstraintValidatorContext;
+
+
+/**
+ * 自定义枚举校验注解实现
+ *
+ * @author Iwb
+ */
+public class EnumPatternValidator implements ConstraintValidator<EnumPattern, String> {
+
+    private EnumPattern annotation;
+
+    @Override
+    public void initialize(EnumPattern annotation) {
+        ConstraintValidator.super.initialize(annotation);
+        this.annotation = annotation;
+    }
+
+    @Override
+    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
+        if (StringUtils.isNotBlank(value)) {
+            String fieldName = annotation.fieldName();
+            for (Object e : annotation.type().getEnumConstants()) {
+                if (value.equals(ReflectUtils.invokeGetter(e, fieldName))) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+}

+ 3 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/json/config/JacksonConfig.java

@@ -5,6 +5,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
 import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
 import com.vber.common.json.handler.BigNumberSerializer;
+import com.vber.common.json.handler.CustomDateDeserializer;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
@@ -15,6 +16,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Date;
 import java.util.TimeZone;
 
 /**
@@ -38,6 +40,7 @@ public class JacksonConfig {
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
             javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
             javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
+            javaTimeModule.addDeserializer(Date.class, new CustomDateDeserializer());
             builder.modules(javaTimeModule);
             builder.timeZone(TimeZone.getDefault());
             log.info("初始化 jackson 配置");

+ 31 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/json/handler/CustomDateDeserializer.java

@@ -0,0 +1,31 @@
+package com.vber.common.json.handler;
+
+import cn.hutool.core.date.DateUtil;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+
+import java.io.IOException;
+import java.util.Date;
+
+/**
+ * 自定义 Date 类型反序列化处理器(支持多种格式)
+ *
+ * @author AprilWind
+ */
+public class CustomDateDeserializer extends JsonDeserializer<Date> {
+
+    /**
+     * 反序列化逻辑:将字符串转换为 Date 对象
+     *
+     * @param p    JSON 解析器,用于获取字符串值
+     * @param ctxt 上下文环境(可用于获取更多配置)
+     * @return 转换后的 Date 对象,若为空字符串返回 null
+     * @throws IOException 当字符串格式非法或转换失败时抛出
+     */
+    @Override
+    public Date deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
+        return DateUtil.parse(p.getText());
+    }
+
+}

+ 8 - 4
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/annotation/Sensitive.java

@@ -21,8 +21,12 @@ import java.lang.annotation.Target;
 @JsonSerialize(using = SensitiveHandler.class)
 public @interface Sensitive {
     SensitiveStrategy strategy();
-
-    String roleKey() default "";
-
-    String perms() default "";
+    /**
+     * 角色标识符 多个角色满足一个即可
+     */
+    String[] roleKey()default {};
+    /**
+     * 权限标识符 多个权限满足一个即可
+     */
+    String[] perms()default {};
 }

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/core/SensitiveService.java

@@ -13,6 +13,6 @@ public interface SensitiveService {
     /**
      * 是否脱敏
      */
-    boolean isSensitive(String roleKey, String perms);
+    boolean isSensitive(String[] roleKey, String[] perms);
 
 }

+ 2 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/core/SensitiveStrategy.java

@@ -80,12 +80,12 @@ public enum SensitiveStrategy {
     FIRST_MASK(DesensitizedUtil::firstMask),
 
     /**
-     * 清空为null
+     * 清空为""
      */
     CLEAR(s -> DesensitizedUtil.clear()),
 
     /**
-     * 清空为""
+     * 清空为null
      */
     CLEAR_TO_NULL(s -> DesensitizedUtil.clearToNull());
 

+ 2 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-core/src/main/java/com/vber/common/sensitive/handler/SensitiveHandler.java

@@ -26,8 +26,8 @@ import java.util.Objects;
 public class SensitiveHandler extends JsonSerializer<String> implements ContextualSerializer {
 
     private SensitiveStrategy strategy;
-    private String roleKey;
-    private String perms;
+    private String[] roleKey;
+    private String[] perms;
 
     @Override
     public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-doc/src/main/java/com/vber/common/doc/config/SpringDocConfig.java

@@ -30,7 +30,7 @@ import java.util.Optional;
 import java.util.Set;
 
 /**
- * Swagger 文档配置
+ * 接口文档配置
  *
  * @author Iwb
  */

+ 2 - 4
SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/core/EncryptorManager.java

@@ -3,6 +3,7 @@ package com.vber.common.encrypt.core;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.ReflectUtil;
+import com.vber.common.core.utils.ObjectUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.encrypt.annotation.EncryptField;
 import lombok.NoArgsConstructor;
@@ -56,10 +57,7 @@ public class EncryptorManager {
      * 获取类加密字段缓存
      */
     public Set<Field> getFieldCache(Class<?> sourceClazz) {
-        if (ObjectUtil.isNotNull(fieldCache)) {
-            return fieldCache.get(sourceClazz);
-        }
-        return null;
+        return ObjectUtils.notNullGetter(fieldCache, f -> f.get(sourceClazz));
     }
 
     /**

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/filter/EncryptResponseBodyWrapper.java

@@ -80,9 +80,9 @@ public class EncryptResponseBodyWrapper extends HttpServletResponseWrapper {
 
         // 设置响应头
         servletResponse.addHeader("Access-Control-Expose-Headers", headerFlag);
-        servletResponse.setHeader(headerFlag, encryptPassword);
         servletResponse.setHeader("Access-Control-Allow-Origin", "*");
         servletResponse.setHeader("Access-Control-Allow-Methods", "*");
+        servletResponse.setHeader(headerFlag, encryptPassword);
         servletResponse.setCharacterEncoding(StandardCharsets.UTF_8.toString());
 
         // 获取原始内容

+ 12 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/interceptor/MybatisDecryptInterceptor.java

@@ -12,6 +12,7 @@ import com.vber.common.encrypt.enumd.EncodeType;
 import com.vber.common.encrypt.properties.EncryptorProperties;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.ibatis.executor.parameter.ParameterHandler;
 import org.apache.ibatis.executor.resultset.ResultSetHandler;
 import org.apache.ibatis.plugin.*;
 
@@ -39,6 +40,17 @@ public class MybatisDecryptInterceptor implements Interceptor {
 
     @Override
     public Object intercept(Invocation invocation) throws Throwable {
+        // 开始进行参数解密
+        ResultSetHandler resultSetHandler = (ResultSetHandler) invocation.getTarget();
+        Field parameterHandlerField = resultSetHandler.getClass().getDeclaredField("parameterHandler");
+        parameterHandlerField.setAccessible(true);
+        Object target = parameterHandlerField.get(resultSetHandler);
+        if (target instanceof ParameterHandler parameterHandler) {
+            Object parameterObject = parameterHandler.getParameterObject();
+            if (ObjectUtil.isNotNull(parameterObject) && !(parameterObject instanceof String)) {
+                decryptHandler(parameterObject);
+            }
+        }
         // 获取执行mysql执行结果
         Object result = invocation.proceed();
         if (result == null) {

+ 11 - 9
SERVER/VberAdminPlusV3/vber-common/vber-common-encrypt/src/main/java/com/vber/common/encrypt/utils/EncryptUtils.java

@@ -16,13 +16,15 @@ import java.util.Map;
 /**
  * 安全相关工具类
  *
- * @author 老马
+ * @author Iwb
  */
 public class EncryptUtils {
+
     /**
      * 公钥
      */
     public static final String PUBLIC_KEY = "publicKey";
+
     /**
      * 私钥
      */
@@ -51,7 +53,7 @@ public class EncryptUtils {
     /**
      * AES加密
      *
-     * @param data     待密数据
+     * @param data     待密数据
      * @param password 秘钥字符串
      * @return 加密后字符串, 采用Base64编码
      */
@@ -70,7 +72,7 @@ public class EncryptUtils {
     /**
      * AES加密
      *
-     * @param data     待密数据
+     * @param data     待密数据
      * @param password 秘钥字符串
      * @return 加密后字符串, 采用Hex编码
      */
@@ -106,7 +108,7 @@ public class EncryptUtils {
     }
 
     /**
-     * sm4加密
+     * SM4加密(Base64编码)
      *
      * @param data     待加密数据
      * @param password 秘钥字符串
@@ -125,11 +127,11 @@ public class EncryptUtils {
     }
 
     /**
-     * sm4加密
+     * SM4加密(Hex编码)
      *
      * @param data     待加密数据
      * @param password 秘钥字符串
-     * @return 加密后字符串, 采用Base64编码
+     * @return 加密后字符串, 采用Hex编码
      */
     public static String encryptBySm4Hex(String data, String password) {
         if (StrUtil.isBlank(password)) {
@@ -146,7 +148,7 @@ public class EncryptUtils {
     /**
      * sm4解密
      *
-     * @param data     待解密数据
+     * @param data     待解密数据(可以是Base64或Hex编码)
      * @param password 秘钥字符串
      * @return 解密后字符串
      */
@@ -208,7 +210,7 @@ public class EncryptUtils {
     /**
      * sm2私钥解密
      *
-     * @param data       待密数据
+     * @param data       待密数据
      * @param privateKey 私钥
      * @return 解密后字符串
      */
@@ -266,7 +268,7 @@ public class EncryptUtils {
     /**
      * rsa私钥解密
      *
-     * @param data       待密数据
+     * @param data       待密数据
      * @param privateKey 私钥
      * @return 解密后字符串
      */

+ 2 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/pom.xml

@@ -20,10 +20,9 @@
         </dependency>
 
         <dependency>
-            <groupId>com.alibaba</groupId>
-            <artifactId>easyexcel</artifactId>
+            <groupId>cn.idev.excel</groupId>
+            <artifactId>fastexcel</artifactId>
         </dependency>
-        
     </dependencies>
 
 </project>

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/annotation/ExcelEnumFormat.java

@@ -5,7 +5,7 @@ import java.lang.annotation.*;
 /**
  * 枚举格式化
  *
- * @author Liang
+ * @author Iwb
  */
 @Target({ElementType.FIELD})
 @Retention(RetentionPolicy.RUNTIME)

+ 24 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/annotation/ExcelNotation.java

@@ -0,0 +1,24 @@
+package com.vber.common.excel.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 批注
+ * @author Iwb
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelNotation {
+
+    /**
+     * col index
+     */
+    int index() default -1;
+    /**
+     * 批注内容
+     */
+    String value() default "";
+}

+ 27 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/annotation/ExcelRequired.java

@@ -0,0 +1,27 @@
+package com.vber.common.excel.annotation;
+
+
+import org.apache.poi.ss.usermodel.IndexedColors;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 是否必填
+ * @author Iwb
+ */
+@Target({ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcelRequired {
+
+    /**
+     * col index
+     */
+    int index() default -1;
+    /**
+     * 字体颜色
+     */
+    IndexedColors fontColor() default IndexedColors.RED;
+}

+ 6 - 6
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/convert/ExcelBigNumberConvert.java

@@ -2,12 +2,12 @@ package com.vber.common.excel.convert;
 
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
 import lombok.extern.slf4j.Slf4j;
 
 import java.math.BigDecimal;

+ 7 - 7
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/convert/ExcelDictConvert.java

@@ -3,18 +3,18 @@ package com.vber.common.excel.convert;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
 import com.vber.common.core.service.DictService;
 import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.excel.annotation.ExcelDictFormat;
 import com.vber.common.excel.utils.ExcelUtil;
-import lombok.extern.slf4j.Slf4j;
 
 import java.lang.reflect.Field;
 

+ 8 - 8
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/convert/ExcelEnumConvert.java

@@ -3,15 +3,15 @@ package com.vber.common.excel.convert;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.convert.Convert;
 import cn.hutool.core.util.ObjectUtil;
-import com.alibaba.excel.converters.Converter;
-import com.alibaba.excel.enums.CellDataTypeEnum;
-import com.alibaba.excel.metadata.GlobalConfiguration;
-import com.alibaba.excel.metadata.data.ReadCellData;
-import com.alibaba.excel.metadata.data.WriteCellData;
-import com.alibaba.excel.metadata.property.ExcelContentProperty;
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import lombok.extern.slf4j.Slf4j;
 import com.vber.common.core.utils.reflect.ReflectUtils;
 import com.vber.common.excel.annotation.ExcelEnumFormat;
-import lombok.extern.slf4j.Slf4j;
 
 import java.lang.reflect.Field;
 import java.util.HashMap;
@@ -20,7 +20,7 @@ import java.util.Map;
 /**
  * 枚举格式化转换处理
  *
- * @author Liang
+ * @author Iwb
  */
 @Slf4j
 public class ExcelEnumConvert implements Converter<Object> {

+ 33 - 13
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/CellMergeStrategy.java

@@ -3,11 +3,11 @@ package com.vber.common.excel.core;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ReflectUtil;
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.excel.annotation.ExcelProperty;
-import com.alibaba.excel.metadata.Head;
-import com.alibaba.excel.write.merge.AbstractMergeStrategy;
-import com.vber.common.core.utils.reflect.ReflectUtils;
-import com.vber.common.excel.annotation.CellMerge;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.metadata.Head;
+import cn.idev.excel.write.handler.WorkbookWriteHandler;
+import cn.idev.excel.write.handler.context.WorkbookWriteHandlerContext;
+import cn.idev.excel.write.merge.AbstractMergeStrategy;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.SneakyThrows;
@@ -15,6 +15,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.poi.ss.usermodel.Cell;
 import org.apache.poi.ss.usermodel.Sheet;
 import org.apache.poi.ss.util.CellRangeAddress;
+import com.vber.common.core.utils.reflect.ReflectUtils;
+import com.vber.common.excel.annotation.CellMerge;
 
 import java.lang.reflect.Field;
 import java.util.*;
@@ -25,7 +27,7 @@ import java.util.*;
  * @author Iwb
  */
 @Slf4j
-public class CellMergeStrategy extends AbstractMergeStrategy {
+public class CellMergeStrategy extends AbstractMergeStrategy implements WorkbookWriteHandler {
 
     private final List<CellRangeAddress> cellList;
     private final boolean hasTitle;
@@ -40,17 +42,28 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
 
     @Override
     protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
-        // judge the list is not null
-        if (CollUtil.isNotEmpty(cellList)) {
-            // the judge is necessary
-            if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == 0) {
-                for (CellRangeAddress item : cellList) {
-                    sheet.addMergedRegion(item);
+        //单元格写入了,遍历合并区域,如果该Cell在区域内,但非首行,则清空
+        final int rowIndex = cell.getRowIndex();
+        if (CollUtil.isNotEmpty(cellList)){
+            for (CellRangeAddress cellAddresses : cellList) {
+                final int firstRow = cellAddresses.getFirstRow();
+                if (cellAddresses.isInRange(cell) && rowIndex != firstRow){
+                    cell.setBlank();
                 }
             }
         }
     }
 
+    @Override
+    public void afterWorkbookDispose(final WorkbookWriteHandlerContext context) {
+        //当前表格写完后,统一写入
+        if (CollUtil.isNotEmpty(cellList)){
+            for (CellRangeAddress item : cellList) {
+                context.getWriteContext().writeSheetHolder().getSheet().addMergedRegion(item);
+            }
+        }
+    }
+
     @SneakyThrows
     private List<CellRangeAddress> handle(List<?> list, boolean hasTitle) {
         List<CellRangeAddress> cellList = new ArrayList<>();
@@ -92,13 +105,20 @@ public class CellMergeStrategy extends AbstractMergeStrategy {
                         // 空值跳过不合并
                         continue;
                     }
+
                     if (!cellValue.equals(val)) {
                         if ((i - repeatCell.getCurrent() > 1)) {
                             cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
                         }
                         map.put(field, new RepeatCell(val, i));
                     } else if (i == list.size() - 1) {
-                        if (i > repeatCell.getCurrent() && isMerge(list, i, field)) {
+                        if (!isMerge(list, i, field)) {
+                            // 如果最后一行不能合并,检查之前的数据是否需要合并
+                            if (i - repeatCell.getCurrent() > 1) {
+                                cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex - 1, colNum, colNum));
+                            }
+                        } else if (i > repeatCell.getCurrent()) {
+                            // 如果最后一行可以合并,则直接合并到最后
                             cellList.add(new CellRangeAddress(repeatCell.getCurrent() + rowIndex, i + rowIndex, colNum, colNum));
                         }
                     } else if (!isMerge(list, i, field)) {

+ 8 - 8
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/DefaultExcelListener.java

@@ -1,17 +1,17 @@
 package com.vber.common.excel.core;
 
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.excel.context.AnalysisContext;
-import com.alibaba.excel.event.AnalysisEventListener;
-import com.alibaba.excel.exception.ExcelAnalysisException;
-import com.alibaba.excel.exception.ExcelDataConvertException;
-import com.vber.common.core.utils.StreamUtils;
-import com.vber.common.core.utils.ValidatorUtils;
-import com.vber.common.json.utils.JsonUtils;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import cn.idev.excel.exception.ExcelAnalysisException;
+import cn.idev.excel.exception.ExcelDataConvertException;
 import jakarta.validation.ConstraintViolation;
 import jakarta.validation.ConstraintViolationException;
 import lombok.NoArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import com.vber.common.core.utils.StreamUtils;
+import com.vber.common.core.utils.ValidatorUtils;
+import com.vber.common.json.utils.JsonUtils;
 
 import java.util.Map;
 import java.util.Set;
@@ -59,7 +59,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
             Integer rowIndex = excelDataConvertException.getRowIndex();
             Integer columnIndex = excelDataConvertException.getColumnIndex();
             errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
-                    rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
+                rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
             if (log.isDebugEnabled()) {
                 log.error(errMsg);
             }

+ 9 - 9
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/DropDownOptions.java

@@ -1,10 +1,10 @@
 package com.vber.common.excel.core;
 
 import cn.hutool.core.util.StrUtil;
-import com.vber.common.core.exception.ServiceException;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import com.vber.common.core.exception.ServiceException;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -24,10 +24,6 @@ import java.util.stream.Collectors;
 @NoArgsConstructor
 @SuppressWarnings("unused")
 public class DropDownOptions {
-    /**
-     * 分隔符
-     */
-    private static final String DELIMITER = "_";
     /**
      * 一级下拉所在列index,从0开始算
      */
@@ -45,6 +41,10 @@ public class DropDownOptions {
      * <p>以每一个一级选项值为Key,每个一级选项对应的二级数据为Value</p>
      */
     private Map<String, List<String>> nextOptions = new HashMap<>();
+    /**
+     * 分隔符
+     */
+    private static final String DELIMITER = "_";
 
     /**
      * 创建只有一级的下拉选
@@ -114,15 +114,15 @@ public class DropDownOptions {
         // 先创建父类的下拉
         parentLinkSonOptions.setIndex(parentIndex);
         parentLinkSonOptions.setOptions(
-                parentList.stream()
-                        .map(howToBuildEveryOption)
-                        .collect(Collectors.toList())
+            parentList.stream()
+                .map(howToBuildEveryOption)
+                .collect(Collectors.toList())
         );
         // 提取父-子级联下拉
         Map<String, List<String>> sonOptions = new HashMap<>();
         // 父级依据自己的ID分组
         Map<Number, List<T>> parentGroupByIdMap =
-                parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
+            parentList.stream().collect(Collectors.groupingBy(parentHowToGetIdFunction));
         // 遍历每个子集,提取到Map中
         sonList.forEach(everySon -> {
             if (parentGroupByIdMap.containsKey(sonHowToGetParentIdFunction.apply(everySon))) {

+ 43 - 38
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelDownHandler.java

@@ -5,12 +5,18 @@ import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.EnumUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
-import com.alibaba.excel.metadata.FieldCache;
-import com.alibaba.excel.metadata.FieldWrapper;
-import com.alibaba.excel.util.ClassUtils;
-import com.alibaba.excel.write.handler.SheetWriteHandler;
-import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
-import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
+import cn.idev.excel.metadata.FieldCache;
+import cn.idev.excel.metadata.FieldWrapper;
+import cn.idev.excel.util.ClassUtils;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.holder.WriteWorkbookHolder;
+import com.vber.common.core.domain.dto.DictDataDTO;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.util.CellRangeAddressList;
+import org.apache.poi.ss.util.WorkbookUtil;
+import org.apache.poi.xssf.usermodel.XSSFDataValidation;
 import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.service.DictService;
 import com.vber.common.core.utils.SpringUtils;
@@ -18,11 +24,6 @@ import com.vber.common.core.utils.StreamUtils;
 import com.vber.common.core.utils.StringUtils;
 import com.vber.common.excel.annotation.ExcelDictFormat;
 import com.vber.common.excel.annotation.ExcelEnumFormat;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.poi.ss.usermodel.*;
-import org.apache.poi.ss.util.CellRangeAddressList;
-import org.apache.poi.ss.util.WorkbookUtil;
-import org.apache.poi.xssf.usermodel.XSSFDataValidation;
 
 import java.lang.reflect.Field;
 import java.util.*;
@@ -104,7 +105,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                     // 如果传递了字典名,则依据字典建立下拉
                     Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
                             .orElseThrow(() -> new ServiceException(String.format("字典 %s 不存在", dictType)))
-                            .values();
+                        .values();
                     options = new ArrayList<>(values);
                 } else if (StringUtils.isNotBlank(converterExp)) {
                     // 如果指定了确切的值,则直接解析确切的值
@@ -175,7 +176,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
         List<String> firstOptions = options.getOptions();
         Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
 
-        // 采用按行填充数据的方式,避免EasyExcel出现数据无法写入的问题
+        // 采用按行填充数据的方式,避免出现数据无法写入的问题
         // Attempting to write a row in the range that is already written to disk
 
         // 使用ArrayList记载数据,防止乱序
@@ -185,20 +186,19 @@ public class ExcelDownHandler implements SheetWriteHandler {
         for (int columnIndex = 0; columnIndex < firstOptions.size(); columnIndex++) {
             String columnName = firstOptions.get(columnIndex);
             firstRow.createCell(columnIndex)
-                    .setCellValue(columnName);
+                .setCellValue(columnName);
             columnNames.add(columnName);
         }
 
-
         // 创建名称管理器
         Name name = workbook.createName();
         // 设置名称管理器的别名
         name.setNameName(linkedOptionsSheetName);
         // 以横向第一行创建一级下拉拼接引用位置
         String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
-                linkedOptionsSheetName,
-                getExcelColumnName(0),
-                getExcelColumnName(firstOptions.size())
+            linkedOptionsSheetName,
+            getExcelColumnName(0),
+            getExcelColumnName(firstOptions.size())
         );
         // 设置名称管理器的引用位置
         name.setRefersToFormula(firstOptionsFunction);
@@ -218,12 +218,12 @@ public class ExcelDownHandler implements SheetWriteHandler {
             sonName.setNameName(thisFirstOptionsValue);
             // 以第二行该列数据拼接引用位置
             String sonFunction = String.format("%s!$%s$2:$%s$%d",
-                    linkedOptionsSheetName,
-                    firstOptionsColumnName,
-                    firstOptionsColumnName,
-                    // 二级选项存在则设置为(选项个数+1)行,否则设置为2行
-                    Math.max(Optional.ofNullable(secoundOptionsMap.get(thisFirstOptionsValue))
-                            .orElseGet(ArrayList::new).size(), 1) + 1
+                linkedOptionsSheetName,
+                firstOptionsColumnName,
+                firstOptionsColumnName,
+                // 二级选项存在则设置为(选项个数+1)行,否则设置为2行
+                Math.max(Optional.ofNullable(secoundOptionsMap.get(thisFirstOptionsValue))
+                    .orElseGet(ArrayList::new).size(), 1) + 1
             );
             // 设置名称管理器的引用位置
             sonName.setRefersToFormula(sonFunction);
@@ -237,6 +237,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
             }
         }
+
         // 将二级数据处理为按行区分
         Map<Integer, List<String>> columnValueMap = new HashMap<>();
         int currentRow = 1;
@@ -266,6 +267,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 currentRow = -1;
             }
         }
+
         // 填充第二级选项数据
         columnValueMap.forEach((rowIndex, rowValues) -> {
             Row row = linkedOptionsDataSheet.createRow(rowIndex);
@@ -274,10 +276,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
                 // 填充位置的部分不渲染
                 if (StrUtil.isNotBlank(rowValue)) {
                     row.createCell(columnIndex)
-                            .setCellValue(rowValue);
+                        .setCellValue(rowValue);
                 }
             }
         });
+
         currentLinkedOptionsSheetIndex++;
     }
 
@@ -289,9 +292,11 @@ public class ExcelDownHandler implements SheetWriteHandler {
      * @param value    下拉选可选值
      */
     private void dropDownWithSheet(DataValidationHelper helper, Workbook workbook, Sheet sheet, Integer celIndex, List<String> value) {
+        //由于poi的写出相关问题,超过100个会被临时写进硬盘,导致后续内存合并会出Attempting to write a row[] in the range [] that is already written to disk
+        String tmpOptionsSheetName = OPTIONS_SHEET_NAME + "_" + currentOptionsColumnIndex;
         // 创建下拉数据表
-        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)))
-                .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(OPTIONS_SHEET_NAME)));
+        Sheet simpleDataSheet = Optional.ofNullable(workbook.getSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)))
+            .orElseGet(() -> workbook.createSheet(WorkbookUtil.createSafeSheetName(tmpOptionsSheetName)));
         // 将下拉表隐藏
         workbook.setSheetHidden(workbook.getSheetIndex(simpleDataSheet), true);
         // 完善纵向的一级选项数据表
@@ -299,10 +304,10 @@ public class ExcelDownHandler implements SheetWriteHandler {
             int finalI = i;
             // 获取每一选项行,如果没有则创建
             Row row = Optional.ofNullable(simpleDataSheet.getRow(i))
-                    .orElseGet(() -> simpleDataSheet.createRow(finalI));
-            // 获取本级选项对应的选项列,如果没有则创建
-            Cell cell = Optional.ofNullable(row.getCell(currentOptionsColumnIndex))
-                    .orElseGet(() -> row.createCell(currentOptionsColumnIndex));
+                .orElseGet(() -> simpleDataSheet.createRow(finalI));
+            // 获取本级选项对应的选项列,如果没有则创建。上述采用多个sheet,默认索引为1列
+            Cell cell = Optional.ofNullable(row.getCell(0))
+                .orElseGet(() -> row.createCell(0));
             // 设置值
             cell.setCellValue(value.get(i));
         }
@@ -310,14 +315,14 @@ public class ExcelDownHandler implements SheetWriteHandler {
         // 创建名称管理器
         Name name = workbook.createName();
         // 设置名称管理器的别名
-        String nameName = String.format("%s_%d", OPTIONS_SHEET_NAME, celIndex);
+        String nameName = String.format("%s_%d", tmpOptionsSheetName, celIndex);
         name.setNameName(nameName);
         // 以纵向第一列创建一级下拉拼接引用位置
         String function = String.format("%s!$%s$1:$%s$%d",
-                OPTIONS_SHEET_NAME,
-                getExcelColumnName(currentOptionsColumnIndex),
-                getExcelColumnName(currentOptionsColumnIndex),
-                value.size());
+            tmpOptionsSheetName,
+            getExcelColumnName(0),
+            getExcelColumnName(0),
+            value.size());
         // 设置名称管理器的引用位置
         name.setRefersToFormula(function);
         // 设置数据校验为序列模式,引用的是名称管理器中的别名
@@ -387,8 +392,8 @@ public class ExcelDownHandler implements SheetWriteHandler {
         int thisCircleColumnIndex = columnIndex % 26;
         // 26一循环的次数大于0,则视为栏名至少两位
         String columnPrefix = columnCircleCount == 0
-                ? StrUtil.EMPTY
-                : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
+            ? StrUtil.EMPTY
+            : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
         // 从26一循环内取对应的栏位名
         String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
         // 将二者拼接即为最终的栏位名

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/core/ExcelListener.java

@@ -1,6 +1,6 @@
 package com.vber.common.excel.core;
 
-import com.alibaba.excel.read.listener.ReadListener;
+import cn.idev.excel.read.listener.ReadListener;
 
 /**
  * Excel 导入监听

+ 135 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/handler/DataWriteHandler.java

@@ -0,0 +1,135 @@
+package com.vber.common.excel.handler;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.idev.excel.metadata.data.DataFormatData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.util.StyleUtil;
+import cn.idev.excel.write.handler.CellWriteHandler;
+import cn.idev.excel.write.handler.SheetWriteHandler;
+import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
+import cn.idev.excel.write.metadata.holder.WriteSheetHolder;
+import cn.idev.excel.write.metadata.style.WriteCellStyle;
+import cn.idev.excel.write.metadata.style.WriteFont;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
+import org.apache.poi.xssf.usermodel.XSSFRichTextString;
+import com.vber.common.core.utils.reflect.ReflectUtils;
+import com.vber.common.excel.annotation.ExcelNotation;
+import com.vber.common.excel.annotation.ExcelRequired;
+
+import java.lang.reflect.Field;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 批注、必填
+ *
+ * @author Iwb
+ */
+public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
+
+    /**
+     * 批注
+     */
+    private final Map<Integer, String> notationMap;
+
+    /**
+     * 头列字体颜色
+     */
+    private final Map<Integer, Short> headColumnMap;
+
+
+    public DataWriteHandler(Class<?> clazz) {
+        notationMap = getNotationMap(clazz);
+        headColumnMap = getRequiredMap(clazz);
+    }
+
+    @Override
+    public void afterCellDispose(CellWriteHandlerContext context) {
+        if (CollUtil.isEmpty(notationMap) && CollUtil.isEmpty(headColumnMap)) {
+            return;
+        }
+        WriteCellData<?> cellData = context.getFirstCellData();
+        WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
+
+        DataFormatData dataFormatData = new DataFormatData();
+        // 单元格设置为文本格式
+        dataFormatData.setIndex((short) 49);
+        writeCellStyle.setDataFormatData(dataFormatData);
+
+        if (context.getHead()) {
+            Cell cell = context.getCell();
+            WriteSheetHolder writeSheetHolder = context.getWriteSheetHolder();
+            Sheet sheet = writeSheetHolder.getSheet();
+            Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
+            Drawing<?> drawing = sheet.createDrawingPatriarch();
+            // 设置标题字体样式
+            WriteFont headWriteFont = new WriteFont();
+            // 加粗
+            headWriteFont.setBold(true);
+            if (CollUtil.isNotEmpty(headColumnMap) && headColumnMap.containsKey(cell.getColumnIndex())) {
+                // 设置字体颜色
+                headWriteFont.setColor(headColumnMap.get(cell.getColumnIndex()));
+            }
+            writeCellStyle.setWriteFont(headWriteFont);
+            CellStyle cellStyle = StyleUtil.buildCellStyle(workbook, null, writeCellStyle);
+            cell.setCellStyle(cellStyle);
+
+            if (CollUtil.isNotEmpty(notationMap) && notationMap.containsKey(cell.getColumnIndex())) {
+                // 批注内容
+                String notationContext = notationMap.get(cell.getColumnIndex());
+                // 创建绘图对象
+                Comment comment = drawing.createCellComment(new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), 0, (short) 5, 5));
+                comment.setString(new XSSFRichTextString(notationContext));
+                cell.setCellComment(comment);
+            }
+        }
+    }
+
+    /**
+     * 获取必填列
+     */
+    private static Map<Integer, Short> getRequiredMap(Class<?> clazz) {
+        Map<Integer, Short> requiredMap = new HashMap<>();
+        Field[] fields = clazz.getDeclaredFields();
+        // 检查 fields 数组是否为空
+        if (fields.length == 0) {
+            return requiredMap;
+        }
+        Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
+
+        for (int i = 0; i < filteredFields.length; i++) {
+            Field field = filteredFields[i];
+            if (!field.isAnnotationPresent(ExcelRequired.class)) {
+                continue;
+            }
+            ExcelRequired excelRequired = field.getAnnotation(ExcelRequired.class);
+            int columnIndex =  excelRequired.index() == -1 ? i : excelRequired.index();
+            requiredMap.put(columnIndex, excelRequired.fontColor().getIndex());
+        }
+        return requiredMap;
+    }
+
+    /**
+     * 获取批注
+     */
+    private static Map<Integer, String> getNotationMap(Class<?> clazz) {
+        Map<Integer, String> notationMap = new HashMap<>();
+        Field[] fields = clazz.getDeclaredFields();
+        // 检查 fields 数组是否为空
+        if (fields.length == 0) {
+            return notationMap;
+        }
+        Field[] filteredFields = ReflectUtils.getFields(clazz, field -> !"serialVersionUID".equals(field.getName()));
+        for (int i = 0; i < filteredFields.length; i++) {
+            Field field = filteredFields[i];
+            if (!field.isAnnotationPresent(ExcelNotation.class)) {
+                continue;
+            }
+            ExcelNotation excelNotation = field.getAnnotation(ExcelNotation.class);
+            int columnIndex =  excelNotation.index() == -1 ? i : excelNotation.index();
+            notationMap.put(columnIndex, excelNotation.value());
+        }
+        return notationMap;
+    }
+}

+ 39 - 35
SERVER/VberAdminPlusV3/vber-common/vber-common-excel/src/main/java/com/vber/common/excel/utils/ExcelUtil.java

@@ -3,21 +3,22 @@ package com.vber.common.excel.utils;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.io.resource.ClassPathResource;
 import cn.hutool.core.util.IdUtil;
-import com.alibaba.excel.EasyExcel;
-import com.alibaba.excel.ExcelWriter;
-import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
-import com.alibaba.excel.write.metadata.WriteSheet;
-import com.alibaba.excel.write.metadata.fill.FillConfig;
-import com.alibaba.excel.write.metadata.fill.FillWrapper;
-import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
-import com.vber.common.core.utils.StringUtils;
-import com.vber.common.core.utils.file.FileUtils;
-import com.vber.common.excel.convert.ExcelBigNumberConvert;
-import com.vber.common.excel.core.*;
+import cn.idev.excel.FastExcel;
+import cn.idev.excel.ExcelWriter;
+import cn.idev.excel.write.builder.ExcelWriterSheetBuilder;
+import cn.idev.excel.write.metadata.WriteSheet;
+import cn.idev.excel.write.metadata.fill.FillConfig;
+import cn.idev.excel.write.metadata.fill.FillWrapper;
+import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.http.HttpServletResponse;
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
+import com.vber.common.core.utils.StringUtils;
+import com.vber.common.core.utils.file.FileUtils;
+import com.vber.common.excel.convert.ExcelBigNumberConvert;
+import com.vber.common.excel.core.*;
+import com.vber.common.excel.handler.DataWriteHandler;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -42,7 +43,7 @@ public class ExcelUtil {
      * @return 转换后集合
      */
     public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
-        return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
+        return FastExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
     }
 
 
@@ -56,7 +57,7 @@ public class ExcelUtil {
      */
     public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
         DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
-        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        FastExcel.read(is, clazz, listener).sheet().doRead();
         return listener.getExcelResult();
     }
 
@@ -69,7 +70,7 @@ public class ExcelUtil {
      * @return 转换后集合
      */
     public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
-        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        FastExcel.read(is, clazz, listener).sheet().doRead();
         return listener.getExcelResult();
     }
 
@@ -185,12 +186,13 @@ public class ExcelUtil {
      */
     public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
                                        OutputStream os, List<DropDownOptions> options) {
-        ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
+        ExcelWriterSheetBuilder builder = FastExcel.write(os, clazz)
                 .autoCloseStream(false)
                 // 自动适配
                 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                 // 大数值自动转换 防止失真
                 .registerConverter(new ExcelBigNumberConvert())
+                .registerWriteHandler(new DataWriteHandler(clazz))
                 .sheet(sheetName);
         if (merge) {
             // 合并处理器
@@ -211,8 +213,11 @@ public class ExcelUtil {
      * @param data         模板需要的数据
      * @param response     响应体
      */
-    public static void exportTemplate(List<Object> data, String filename, String templatePath, HttpServletResponse response) {
+    public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
         try {
+            if (CollUtil.isEmpty(data)) {
+                throw new IllegalArgumentException("数据为空");
+            }
             resetResponse(filename, response);
             ServletOutputStream os = response.getOutputStream();
             exportTemplate(data, templatePath, os);
@@ -230,21 +235,20 @@ public class ExcelUtil {
      * @param data         模板需要的数据
      * @param os           输出流
      */
-    public static void exportTemplate(List<Object> data, String templatePath, OutputStream os) {
+    public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
         ClassPathResource templateResource = new ClassPathResource(templatePath);
-        ExcelWriter excelWriter = EasyExcel.write(os)
+        ExcelWriter excelWriter = FastExcel.write(os)
                 .withTemplate(templateResource.getStream())
                 .autoCloseStream(false)
                 // 大数值自动转换 防止失真
                 .registerConverter(new ExcelBigNumberConvert())
+                .registerWriteHandler(new DataWriteHandler(data.get(0).getClass()))
                 .build();
-        WriteSheet writeSheet = EasyExcel.writerSheet().build();
-        if (CollUtil.isEmpty(data)) {
-            throw new IllegalArgumentException("数据为空");
-        }
+        WriteSheet writeSheet = FastExcel.writerSheet().build();
+        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
         // 单表多数据导出 模板格式为 {.属性}
-        for (Object d : data) {
-            excelWriter.fill(d, writeSheet);
+        for (T d : data) {
+            excelWriter.fill(d, fillConfig, writeSheet);
         }
         excelWriter.finish();
     }
@@ -261,6 +265,9 @@ public class ExcelUtil {
      */
     public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
         try {
+            if (CollUtil.isEmpty(data)) {
+                throw new IllegalArgumentException("数据为空");
+            }
             resetResponse(filename, response);
             ServletOutputStream os = response.getOutputStream();
             exportTemplateMultiList(data, templatePath, os);
@@ -281,6 +288,9 @@ public class ExcelUtil {
      */
     public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
         try {
+            if (CollUtil.isEmpty(data)) {
+                throw new IllegalArgumentException("数据为空");
+            }
             resetResponse(filename, response);
             ServletOutputStream os = response.getOutputStream();
             exportTemplateMultiSheet(data, templatePath, os);
@@ -300,16 +310,13 @@ public class ExcelUtil {
      */
     public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
         ClassPathResource templateResource = new ClassPathResource(templatePath);
-        ExcelWriter excelWriter = EasyExcel.write(os)
+        ExcelWriter excelWriter = FastExcel.write(os)
                 .withTemplate(templateResource.getStream())
                 .autoCloseStream(false)
                 // 大数值自动转换 防止失真
                 .registerConverter(new ExcelBigNumberConvert())
                 .build();
-        WriteSheet writeSheet = EasyExcel.writerSheet().build();
-        if (CollUtil.isEmpty(data)) {
-            throw new IllegalArgumentException("数据为空");
-        }
+        WriteSheet writeSheet = FastExcel.writerSheet().build();
         for (Map.Entry<String, Object> map : data.entrySet()) {
             // 设置列表后续还有数据
             FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
@@ -317,7 +324,7 @@ public class ExcelUtil {
                 // 多表导出必须使用 FillWrapper
                 excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
             } else {
-                excelWriter.fill(map.getValue(), writeSheet);
+                excelWriter.fill(map.getValue(), fillConfig, writeSheet);
             }
         }
         excelWriter.finish();
@@ -334,17 +341,14 @@ public class ExcelUtil {
      */
     public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
         ClassPathResource templateResource = new ClassPathResource(templatePath);
-        ExcelWriter excelWriter = EasyExcel.write(os)
+        ExcelWriter excelWriter = FastExcel.write(os)
                 .withTemplate(templateResource.getStream())
                 .autoCloseStream(false)
                 // 大数值自动转换 防止失真
                 .registerConverter(new ExcelBigNumberConvert())
                 .build();
-        if (CollUtil.isEmpty(data)) {
-            throw new IllegalArgumentException("数据为空");
-        }
         for (int i = 0; i < data.size(); i++) {
-            WriteSheet writeSheet = EasyExcel.writerSheet(i).build();
+            WriteSheet writeSheet = FastExcel.writerSheet(i).build();
             for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
                 // 设置列表后续还有数据
                 FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();

+ 5 - 6
SERVER/VberAdminPlusV3/vber-common/vber-common-log/src/main/java/com/vber/common/log/aspect/LogAspect.java

@@ -100,7 +100,7 @@ public class LogAspect {
 
             if (e != null) {
                 operLog.setStatus(BusinessStatus.FAIL.ordinal());
-                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 2000));
+                operLog.setErrorMsg(StringUtils.substring(e.getMessage(), 0, 3800));
             }
             // 设置方法名称
             String className = joinPoint.getTarget().getClass().getName();
@@ -113,13 +113,12 @@ public class LogAspect {
             // 设置消耗时间
             StopWatch stopWatch = TIME_THREADLOCAL.get();
             stopWatch.stop();
-            operLog.setCostTime(stopWatch.getTime());
+            operLog.setCostTime(stopWatch.getDuration().toMillis());
             // 发布事件保存数据库
             SpringUtils.context().publishEvent(operLog);
         } catch (Exception exp) {
             // 记录本地异常日志
             log.error("异常信息:{}", exp.getMessage());
-            exp.printStackTrace();
         } finally {
             TIME_THREADLOCAL.remove();
         }
@@ -146,7 +145,7 @@ public class LogAspect {
         }
         // 是否需要保存response,参数和值
         if (log.isSaveResponseData() && ObjectUtil.isNotNull(jsonResult)) {
-            operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 2000));
+            operLog.setJsonResult(StringUtils.substring(JsonUtils.toJsonString(jsonResult), 0, 3800));
         }
     }
 
@@ -161,11 +160,11 @@ public class LogAspect {
         String requestMethod = operLog.getRequestMethod();
         if (MapUtil.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) {
             String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames);
-            operLog.setOperParam(StringUtils.substring(params, 0, 2000));
+            operLog.setOperParam(StringUtils.substring(params, 0, 3800));
         } else {
             MapUtil.removeAny(paramsMap, EXCLUDE_PROPERTIES);
             MapUtil.removeAny(paramsMap, excludeParamNames);
-            operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 2000));
+            operLog.setOperParam(StringUtils.substring(JsonUtils.toJsonString(paramsMap), 0, 3800));
         }
     }
 

+ 8 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-mail/src/main/java/com/vber/common/mail/config/properties/MailProperties.java

@@ -43,7 +43,14 @@ public class MailProperties {
     private String pass;
 
     /**
-     * 发送方,遵循RFC-822标准
+     * 发送方,遵循RFC-822标准<br>
+     * 发件人可以是以下形式:
+     *
+     * <pre>
+     * 1. user@xxx.xx
+     * 2. name &lt;user@xxx.xx&gt;
+     * </pre>
+
      */
     private String from;
 

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/aspect/DataPermissionAspect.java

@@ -13,7 +13,7 @@ import org.aspectj.lang.annotation.Before;
 /**
  * 数据权限处理
  *
- * @author Lion Li
+ * @author Iwb
  */
 @Slf4j
 @Aspect

+ 11 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/config/MybatisPlusConfig.java

@@ -2,6 +2,7 @@ package com.vber.common.mybatis.config;
 
 import cn.hutool.core.net.NetUtil;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
 import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
 import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
@@ -13,6 +14,7 @@ import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.mybatis.aspect.DataPermissionAspect;
 import com.vber.common.mybatis.handler.InjectionMetaObjectHandler;
 import com.vber.common.mybatis.handler.MybatisExceptionHandler;
+import com.vber.common.mybatis.handler.PlusPostInitTableInfoHandler;
 import com.vber.common.mybatis.interceptor.PlusDataPermissionInterceptor;
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.beans.BeansException;
@@ -52,7 +54,7 @@ public class MybatisPlusConfig {
      * 数据权限拦截器
      */
     public PlusDataPermissionInterceptor dataPermissionInterceptor() {
-        return new PlusDataPermissionInterceptor();
+        return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage"));
     }
 
     /**
@@ -105,6 +107,14 @@ public class MybatisPlusConfig {
         return new MybatisExceptionHandler();
     }
 
+    /**
+     * 初始化表对象处理器
+     */
+    @Bean
+    public PostInitTableInfoHandler postInitTableInfoHandler() {
+        return new PlusPostInitTableInfoHandler();
+    }
+
 
     /**
      * PaginationInnerInterceptor 分页插件,自动识别数据库类型

+ 4 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/core/page/PageQuery.java

@@ -121,4 +121,8 @@ public class PageQuery implements Serializable {
         return (pageNum - 1) * pageSize;
     }
 
+    public PageQuery(Integer pageSize, Integer pageNum) {
+        this.pageSize = pageSize;
+        this.pageNum = pageNum;
+    }
 }

+ 18 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/core/page/TableDataInfo.java

@@ -1,5 +1,6 @@
 package com.vber.common.mybatis.core.page;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.hutool.http.HttpStatus;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import lombok.Data;
@@ -50,6 +51,8 @@ public class TableDataInfo<T> implements Serializable {
     public TableDataInfo(List<T> list, long total) {
         this.rows = list;
         this.total = total;
+        this.code = HttpStatus.HTTP_OK;
+        this.msg = "查询成功";
     }
 
     /**
@@ -86,4 +89,19 @@ public class TableDataInfo<T> implements Serializable {
         return rspData;
     }
 
+
+    /**
+     * 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
+     *
+     * @param list 原始数据列表(全部数据)
+     * @param page 分页参数对象(包含当前页码、每页大小等)
+     * @return 构造好的分页结果 TableDataInfo<T>
+     */
+    public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
+        if (CollUtil.isEmpty(list)) {
+            return TableDataInfo.build();
+        }
+        List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
+        return new TableDataInfo<>(pageList, list.size());
+    }
 }

+ 6 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/enums/DataScopeType.java

@@ -47,7 +47,12 @@ public enum DataScopeType {
     /**
      * 仅本人数据权限
      */
-    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
+    SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "),
+
+    /**
+     * 组织机构及以下或本人数据权限
+     */
+    ORG_AND_CHILD_OR_SELF("6", " #{#orgName} IN ( #{@sdss.getOrgAndChild( #user.orgId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 ");
 
     private final String code;
 

+ 3 - 4
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/InjectionMetaObjectHandler.java

@@ -5,6 +5,7 @@ import cn.hutool.http.HttpStatus;
 import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.exception.ServiceException;
+import com.vber.common.core.utils.ObjectUtils;
 import com.vber.common.mybatis.core.domain.BaseEntity;
 import com.vber.common.satoken.utils.LoginHelper;
 import lombok.extern.slf4j.Slf4j;
@@ -30,8 +31,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
         try {
             if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
                 // 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间
-                Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime())
-                        ? baseEntity.getCreateTime() : new Date();
+                Date current = ObjectUtils.notNull(baseEntity.getCreateTime(), new Date());
                 baseEntity.setCreateTime(current);
                 baseEntity.setUpdateTime(current);
                 if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
@@ -41,8 +41,7 @@ public class InjectionMetaObjectHandler implements MetaObjectHandler {
                         //  填充创建人、更新人和创建部门信息
                         baseEntity.setCreateBy(userId);
                         baseEntity.setUpdateBy(userId);
-                        baseEntity.setCreateOrg(ObjectUtil.isNotNull(baseEntity.getCreateOrg())
-                                ? baseEntity.getCreateOrg() : loginUser.getOrgId());
+                        baseEntity.setCreateOrg(ObjectUtils.notNull(baseEntity.getCreateOrg(), loginUser.getOrgId()));
                     }
                 }
             } else {

+ 5 - 4
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/MybatisExceptionHandler.java

@@ -1,5 +1,6 @@
 package com.vber.common.mybatis.handler;
 
+import cn.hutool.http.HttpStatus;
 import com.vber.common.core.domain.R;
 import com.vber.common.core.utils.StringUtils;
 import jakarta.servlet.http.HttpServletRequest;
@@ -25,7 +26,7 @@ public class MybatisExceptionHandler {
     public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage());
-        return R.fail("数据库中已存在该记录,请联系管理员确认");
+        return R.fail(HttpStatus.HTTP_CONFLICT,"数据库中已存在该记录,请联系管理员确认");
     }
 
     /**
@@ -35,12 +36,12 @@ public class MybatisExceptionHandler {
     public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) {
         String requestURI = request.getRequestURI();
         String message = e.getMessage();
-        if (StringUtils.contains("CannotFindDataSourceException", message)) {
+        if (StringUtils.contains(message,"CannotFindDataSourceException")) {
             log.error("请求地址'{}', 未找到数据源", requestURI);
-            return R.fail("未找到数据源,请联系管理员确认");
+            return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
         }
         log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
-        return R.fail(message);
+        return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message);
     }
 
 }

+ 197 - 39
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/PlusDataPermissionHandler.java

@@ -3,6 +3,14 @@ package com.vber.common.mybatis.handler;
 import cn.hutool.core.annotation.AnnotationUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.JSQLParserException;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
+import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
+import net.sf.jsqlparser.parser.CCJSqlParserUtil;
+import org.apache.ibatis.io.Resources;
 import com.vber.common.core.domain.dto.RoleDTO;
 import com.vber.common.core.domain.model.LoginUser;
 import com.vber.common.core.exception.ServiceException;
@@ -14,13 +22,6 @@ import com.vber.common.mybatis.annotation.DataPermission;
 import com.vber.common.mybatis.enums.DataScopeType;
 import com.vber.common.mybatis.helper.DataPermissionHelper;
 import com.vber.common.satoken.utils.LoginHelper;
-import lombok.extern.slf4j.Slf4j;
-import net.sf.jsqlparser.JSQLParserException;
-import net.sf.jsqlparser.expression.Expression;
-import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
-import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
-import net.sf.jsqlparser.parser.CCJSqlParserUtil;
-import org.apache.ibatis.io.Resources;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.expression.BeanFactoryResolver;
 import org.springframework.core.io.Resource;
@@ -28,19 +29,14 @@ import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
 import org.springframework.core.io.support.ResourcePatternResolver;
 import org.springframework.core.type.ClassMetadata;
 import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
-import org.springframework.expression.BeanResolver;
-import org.springframework.expression.ExpressionParser;
-import org.springframework.expression.ParserContext;
+import org.springframework.expression.*;
 import org.springframework.expression.common.TemplateParserContext;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
 import org.springframework.expression.spel.support.StandardEvaluationContext;
 import org.springframework.util.ClassUtils;
 
 import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 
@@ -52,6 +48,10 @@ import java.util.function.Function;
 @Slf4j
 public class PlusDataPermissionHandler {
 
+    /**
+     * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行)
+     */
+    private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
 
     /**
      * spel 解析器
@@ -63,8 +63,14 @@ public class PlusDataPermissionHandler {
      */
     private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
 
-
-
+    /**
+     * 构造方法,扫描指定包下的 Mapper 类并初始化缓存
+     *
+     * @param mapperPackage Mapper 类所在的包路径
+     */
+    public PlusDataPermissionHandler(String mapperPackage) {
+        scanMapperClasses(mapperPackage);
+    }
 
     /**
      * 获取数据过滤条件的 SQL 片段
@@ -77,7 +83,7 @@ public class PlusDataPermissionHandler {
     public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
         try {
             // 获取数据权限配置
-            DataPermission dataPermission = DataPermissionHelper.getPermission();
+            DataPermission dataPermission = getDataPermission(mappedStatementId);
             // 获取当前登录用户信息
             LoginUser currentUser = DataPermissionHelper.getVariable("user");
             if (ObjectUtil.isNull(currentUser)) {
@@ -123,10 +129,33 @@ public class PlusDataPermissionHandler {
             joinStr = " " + dataPermission.joinStr() + " ";
         }
         LoginUser user = DataPermissionHelper.getVariable("user");
-        StandardEvaluationContext context = new StandardEvaluationContext();
+        Object defaultValue = "-1";
+        NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue);
+        context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue));
         context.setBeanResolver(beanResolver);
         DataPermissionHelper.getContext().forEach(context::setVariable);
         Set<String> conditions = new HashSet<>();
+        // 优先设置变量
+        List<String> keys = new ArrayList<>();
+        Map<DataColumn, Boolean> ignoreMap = new HashMap<>();
+        for (DataColumn dataColumn : dataPermission.value()) {
+            if (dataColumn.key().length != dataColumn.value().length) {
+                throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
+            }
+            // 包含权限标识符 这直接跳过
+            if (StringUtils.isNotBlank(dataColumn.permission()) &&
+                    CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
+            ) {
+                ignoreMap.put(dataColumn, Boolean.TRUE);
+                continue;
+            }
+            // 设置注解变量 key 为表达式变量 value 为变量值
+            for (int i = 0; i < dataColumn.key().length; i++) {
+                context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
+            }
+            keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList());
+        }
+
         for (RoleDTO role : user.getRoles()) {
             user.setRoleId(role.getRoleId());
             // 获取角色权限泛型
@@ -136,33 +165,25 @@ public class PlusDataPermissionHandler {
             }
             // 全部数据权限直接返回
             if (type == DataScopeType.ALL) {
-                return "";
+                return StringUtils.EMPTY;
             }
             boolean isSuccess = false;
             for (DataColumn dataColumn : dataPermission.value()) {
-                if (dataColumn.key().length != dataColumn.value().length) {
-                    throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
-                }
-                // 不包含 key 变量 则不处理
-                if (!StringUtils.containsAny(type.getSqlTemplate(),
-                        Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
-                )) {
-                    continue;
-                }
                 // 包含权限标识符 这直接跳过
-                if (StringUtils.isNotBlank(dataColumn.permission()) &&
-                        CollUtil.contains(user.getMenuPermission(), dataColumn.permission())
-                ) {
+                if (ignoreMap.containsKey(dataColumn)) {
+                    // 修复多角色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4
                     conditions.add(joinStr + " 1 = 1 ");
                     isSuccess = true;
                     continue;
                 }
-                // 设置注解变量 key 为表达式变量 value 为变量值
-                for (int i = 0; i < dataColumn.key().length; i++) {
-                    context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
+                // 不包含 key 变量 则不处理
+                if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) {
+                    continue;
+                }
+                // 当前注解不满足模板 不处理
+                if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) {
+                    continue;
                 }
-
-
                 // 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
                 String sql = DataPermissionHelper.ignore(() ->
                         parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
@@ -181,18 +202,155 @@ public class PlusDataPermissionHandler {
             String sql = StreamUtils.join(conditions, Function.identity(), "");
             return sql.substring(joinStr.length());
         }
-        return "";
+        return StringUtils.EMPTY;
     }
 
+    /**
+     * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类
+     *
+     * @param mapperPackage Mapper 类所在的包路径
+     */
+    private void scanMapperClasses(String mapperPackage) {
+        // 创建资源解析器和元数据读取工厂
+        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+        CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
+        // 将 Mapper 包路径按分隔符拆分为数组
+        String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
+        String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
+        try {
+            for (String packagePattern : packagePatternArray) {
+                // 将包路径转换为资源路径
+                String path = ClassUtils.convertClassNameToResourcePath(packagePattern);
+                // 获取指定路径下的所有 .class 文件资源
+                Resource[] resources = resolver.getResources(classpath + path + "/*.class");
+                for (Resource resource : resources) {
+                    // 获取资源的类元数据
+                    ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata();
+                    // 获取资源对应的类对象
+                    Class<?> clazz = Resources.classForName(classMetadata.getClassName());
+                    // 查找类中的特定注解
+                    findAnnotation(clazz);
+                }
+            }
+        } catch (Exception e) {
+            log.error("初始化数据安全缓存时出错:{}", e.getMessage());
+        }
+    }
 
+    /**
+     * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中
+     *
+     * @param clazz 要查找的类
+     */
+    private void findAnnotation(Class<?> clazz) {
+        DataPermission dataPermission;
+        for (Method method : clazz.getMethods()) {
+            if (method.isDefault() || method.isVarArgs()) {
+                continue;
+            }
+            String mappedStatementId = clazz.getName() + "." + method.getName();
+            if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
+                dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
+                dataPermissionCacheMap.put(mappedStatementId, dataPermission);
+            }
+        }
+        if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
+            dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
+            dataPermissionCacheMap.put(clazz.getName(), dataPermission);
+        }
+    }
 
+    /**
+     * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象
+     *
+     * @param mapperId 映射语句 ID
+     * @return DataPermission 注解对象,如果不存在则返回 null
+     */
+    public DataPermission getDataPermission(String mapperId) {
+        // 检查上下文中是否包含映射语句 ID 对应的 DataPermission 注解对象
+        if (DataPermissionHelper.getPermission() != null) {
+            return DataPermissionHelper.getPermission();
+        }
+        // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象
+        if (dataPermissionCacheMap.containsKey(mapperId)) {
+            return dataPermissionCacheMap.get(mapperId);
+        }
+        // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找
+        String clazzName = mapperId.substring(0, mapperId.lastIndexOf("."));
+        if (dataPermissionCacheMap.containsKey(clazzName)) {
+            return dataPermissionCacheMap.get(clazzName);
+        }
+        return null;
+    }
 
     /**
      * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象
      *
+     * @param mapperId 映射语句 ID
      * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true
      */
-    public boolean invalid() {
-        return DataPermissionHelper.getPermission() == null;
+    public boolean invalid(String mapperId) {
+        return getDataPermission(mapperId) == null;
     }
+
+    /**
+     * 对所有null变量找不到的变量返回默认值
+     */
+    @AllArgsConstructor
+    private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext {
+
+        private final Object defaultValue;
+
+        @Override
+        public Object lookupVariable(String name) {
+            Object obj = super.lookupVariable(name);
+            // 如果读取到的值是 null,则返回默认值
+            if (obj == null) {
+                return defaultValue;
+            }
+            return obj;
+        }
+
+    }
+
+    /**
+     * 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器
+     */
+    @AllArgsConstructor
+    private static class NullSafePropertyAccessor implements PropertyAccessor {
+
+        private final PropertyAccessor delegate;
+        private final Object defaultValue;
+
+        @Override
+        public Class<?>[] getSpecificTargetClasses() {
+            return delegate.getSpecificTargetClasses();
+        }
+
+        @Override
+        public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
+            return delegate.canRead(context, target, name);
+        }
+
+        @Override
+        public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
+            TypedValue value = delegate.read(context, target, name);
+            // 如果读取到的值是 null,则返回默认值
+            if (value.getValue() == null) {
+                return new TypedValue(defaultValue);
+            }
+            return value;
+        }
+
+        @Override
+        public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
+            return delegate.canWrite(context, target, name);
+        }
+
+        @Override
+        public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
+            delegate.write(context, target, name, newValue);
+        }
+    }
+
 }

+ 28 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/handler/PlusPostInitTableInfoHandler.java

@@ -0,0 +1,28 @@
+package com.vber.common.mybatis.handler;
+
+
+import cn.hutool.core.convert.Convert;
+import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import org.apache.ibatis.session.Configuration;
+import com.vber.common.core.utils.SpringUtils;
+import com.vber.common.core.utils.reflect.ReflectUtils;
+
+/**
+ * 修改表信息初始化方式
+ * 目前用于全局修改是否使用逻辑删除
+ *
+ * @author Iwb
+ */
+public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler {
+
+    @Override
+    public void postTableInfo(TableInfo tableInfo, Configuration configuration) {
+        String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true");
+        // 只有关闭时 统一设置false 为true时mp自动判断不处理
+        if (!Convert.toBool(flag)) {
+            ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false);
+        }
+    }
+
+}

+ 4 - 4
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/java/com/vber/common/mybatis/interceptor/PlusDataPermissionInterceptor.java

@@ -39,8 +39,8 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
     /**
      * 构造函数,初始化 PlusDataPermissionHandler 实例
      */
-    public PlusDataPermissionInterceptor() {
-        this.dataPermissionHandler = new PlusDataPermissionHandler();
+    public PlusDataPermissionInterceptor(String mapperPackage) {
+        this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage);
     }
 
     /**
@@ -61,7 +61,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
             return;
         }
         // 检查是否缺少有效的数据权限注解
-        if (dataPermissionHandler.invalid()) {
+        if (dataPermissionHandler.invalid(ms.getId())) {
             return;
         }
         // 解析 sql 分配对应方法
@@ -89,7 +89,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
                 return;
             }
             // 检查是否缺少有效的数据权限注解
-            if (dataPermissionHandler.invalid()) {
+            if (dataPermissionHandler.invalid(ms.getId())) {
                 return;
             }
             PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();

+ 2 - 2
SERVER/VberAdminPlusV3/vber-common/vber-common-mybatis/src/main/resources/common-mybatis.yml

@@ -24,8 +24,8 @@ mybatis-plus:
       # 主键类型
       # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
       idType: AUTO
-      # 逻辑已删除值(框架表均使用此值 禁止随意修改)
-      logicDeleteValue: 2
+      # 逻辑已删除值(可按需求随意修改)
+      logicDeleteValue: 1
       # 逻辑未删除值
       logicNotDeleteValue: 0
       insertStrategy: NOT_NULL

+ 3 - 3
SERVER/VberAdminPlusV3/vber-common/vber-common-oss/pom.xml

@@ -57,10 +57,10 @@
             </exclusions>
         </dependency>
 
-        <!-- 使用AWS基于 CRT 的 S3 客户端 -->
+        <!-- 使用AWS基于  Netty 的 HTTP 客户端 -->
         <dependency>
-            <groupId>software.amazon.awssdk.crt</groupId>
-            <artifactId>aws-crt</artifactId>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>netty-nio-client</artifactId>
         </dependency>
 
         <!-- 基于 AWS CRT 的 S3 客户端的性能增强的 S3 传输管理器 -->

+ 9 - 13
SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/core/OssClient.java

@@ -9,7 +9,7 @@ import com.vber.common.core.utils.StringUtils;
 import com.vber.common.core.utils.file.FileUtils;
 import com.vber.common.oss.constant.OssConstant;
 import com.vber.common.oss.entity.UploadResult;
-import com.vber.common.oss.enumd.AccessPolicyType;
+import com.vber.common.oss.enums.AccessPolicyType;
 import com.vber.common.oss.exception.OssException;
 import com.vber.common.oss.file.VbFile;
 import com.vber.common.oss.properties.OssProperties;
@@ -23,9 +23,8 @@ import software.amazon.awssdk.core.async.BlockingInputStreamAsyncRequestBody;
 import software.amazon.awssdk.regions.Region;
 import software.amazon.awssdk.services.s3.S3AsyncClient;
 import software.amazon.awssdk.services.s3.S3Configuration;
-import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
+import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
 import software.amazon.awssdk.services.s3.model.GetObjectResponse;
-import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
 import software.amazon.awssdk.services.s3.presigner.S3Presigner;
 import software.amazon.awssdk.transfer.s3.S3TransferManager;
 import software.amazon.awssdk.transfer.s3.model.*;
@@ -88,17 +87,14 @@ public class OssClient {
                 //MinIO 使用 HTTPS 限制使用域名访问,站点填域名。需要启用路径样式访问
                 boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
                 // 创建AWS基于 CRT 的 S3 客户端
-                client = S3AsyncClient.crtBuilder()
+                // 创建AWS基于 Netty 的 S3 客户端
+                client = S3AsyncClient.builder()
                         .credentialsProvider(credentialsProvider)
                         .endpointOverride(URI.create(getEndpoint()))
                         .region(of())
-                        .targetThroughputInGbps(20.0)
-                        .minimumPartSizeInBytes(10 * 1025 * 1024L)
-                        .checksumValidationEnabled(false)
                         .forcePathStyle(isStyle)
-                        .httpConfiguration(S3CrtHttpConfiguration.builder()
-                                .connectionTimeout(Duration.ofSeconds(60)) // 设置连接超时
-                                .build())
+                        .httpClient(NettyNioAsyncHttpClient.builder()
+                                .connectionTimeout(Duration.ofSeconds(60)).build())
                         .build();
                 //AWS基于 CRT 的 S3 AsyncClient 实例用作 S3 传输管理器的底层客户端
                 transferManager = S3TransferManager.builder().s3Client(client).build();
@@ -641,12 +637,12 @@ public class OssClient {
      * 获取私有URL链接
      *
      * @param objectKey 对象KEY
-     * @param second    授权时间
+     * @param expiredTime 链接授权到期时间
      */
-    public String getPrivateUrl(String objectKey, Integer second) {
+    public String getPrivateUrl(String objectKey, Duration expiredTime) {
         // 使用 AWS S3 预签名 URL 的生成器 获取对象的预签名 URL
         URL url = presigner.presignGetObject(
-                        x -> x.signatureDuration(Duration.ofSeconds(second))
+                      x -> x.signatureDuration(expiredTime)
                                 .getObjectRequest(
                                         y -> y.bucket(properties.getBucketName())
                                                 .key(objectKey)

+ 1 - 1
SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/enumd/AccessPolicyType.java → SERVER/VberAdminPlusV3/vber-common/vber-common-oss/src/main/java/com/vber/common/oss/enums/AccessPolicyType.java

@@ -1,4 +1,4 @@
-package com.vber.common.oss.enumd;
+package com.vber.common.oss.enums;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;

+ 1 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/manager/CaffeineCacheDecorator.java

@@ -78,6 +78,7 @@ public class CaffeineCacheDecorator implements Cache {
 
     @Override
     public void clear() {
+        CAFFEINE.invalidateAll();
         cache.clear();
     }
 

+ 18 - 22
SERVER/VberAdminPlusV3/vber-common/vber-common-redis/src/main/java/com/vber/common/redis/manager/PlusSpringCacheManager.java

@@ -1,18 +1,3 @@
-/**
- * Copyright (c) 2013-2021 Nikita Koksharov
- * <p>
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * <p>
- * http://www.apache.org/licenses/LICENSE-2.0
- * <p>
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
 package com.vber.common.redis.manager;
 
 import com.vber.common.redis.utils.RedisUtils;
@@ -39,7 +24,8 @@ import java.util.concurrent.ConcurrentMap;
  * 修改 RedissonSpringCacheManager 源码
  * 重写 cacheName 处理方法 支持多参数
  *
- * @author Nikita Koksharov
+ * @author Iwb
+ *
  */
 @SuppressWarnings("unchecked")
 public class PlusSpringCacheManager implements CacheManager {
@@ -144,18 +130,25 @@ public class PlusSpringCacheManager implements CacheManager {
         if (array.length > 3) {
             config.setMaxSize(Integer.parseInt(array[3]));
         }
+        int local = 1;
+        if (array.length > 4) {
+            local = Integer.parseInt(array[4]);
+        }
 
         if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
-            return createMap(name, config);
+            return createMap(name, config, local);
         }
 
-        return createMapCache(name, config);
+        return createMapCache(name, config, local);
     }
 
-    private Cache createMap(String name, CacheConfig config) {
+    private Cache createMap(String name, CacheConfig config, int local) {
         RMap<Object, Object> map = RedisUtils.getClient().getMap(name);
 
-        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, allowNullValues));
+        Cache cache = new RedissonCache(map, allowNullValues);
+        if (local == 1) {
+            cache = new CaffeineCacheDecorator(name, cache);
+        }
         if (transactionAware) {
             cache = new TransactionAwareCacheDecorator(cache);
         }
@@ -166,10 +159,13 @@ public class PlusSpringCacheManager implements CacheManager {
         return cache;
     }
 
-    private Cache createMapCache(String name, CacheConfig config) {
+    private Cache createMapCache(String name, CacheConfig config, int local) {
         RMapCache<Object, Object> map = RedisUtils.getClient().getMapCache(name);
 
-        Cache cache = new CaffeineCacheDecorator(name, new RedissonCache(map, config, allowNullValues));
+        Cache cache = new RedissonCache(map, config, allowNullValues);
+        if (local == 1) {
+            cache = new CaffeineCacheDecorator(name, cache);
+        }
         if (transactionAware) {
             cache = new TransactionAwareCacheDecorator(cache);
         }

Some files were not shown because too many files changed in this diff