Ver Fonte

build 报告模块拆分

mamingxu há 1 mês atrás
commit
8051a273cf
100 ficheiros alterados com 7056 adições e 0 exclusões
  1. 2 0
      .gitattributes
  2. 33 0
      .gitignore
  3. 19 0
      .mvn/wrapper/maven-wrapper.properties
  4. 417 0
      easier-common/easier-common-bom/pom.xml
  5. 100 0
      easier-common/easier-common-core/pom.xml
  6. 19 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/annotation/NoRespWrap.java
  7. 19 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/EasierCommonProperties.java
  8. 34 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/Ip2RegionConfiguration.java
  9. 81 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/JacksonConfiguration.java
  10. 25 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/MessageSourceConfiguration.java
  11. 15 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/RestTemplateConfiguration.java
  12. 151 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/CacheConstants.java
  13. 152 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/CommonConstants.java
  14. 26 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/PaginationConstants.java
  15. 236 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/SecurityConstants.java
  16. 66 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/ServiceNameConstants.java
  17. 26 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/CheckedException.java
  18. 33 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/EasierBizException.java
  19. 103 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/ErrorCodes.java
  20. 22 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/TenantBrokerExceptionWrapper.java
  21. 13 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/ValidateCodeException.java
  22. 38 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/factory/YamlPropertySourceFactory.java
  23. 61 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/jackson/EasierJavaTimeModule.java
  24. 37 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/sensitive/Sensitive.java
  25. 95 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/sensitive/SensitiveSerialize.java
  26. 55 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/sensitive/SensitiveTypeEnum.java
  27. 95 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ClassUtils.java
  28. 168 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/DesensitizedUtils.java
  29. 246 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/EntityUtils.java
  30. 194 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ImageUtils.java
  31. 74 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/Ip2Region.java
  32. 21 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/KeyStrResolver.java
  33. 103 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/MathUtils.java
  34. 52 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/MsgUtils.java
  35. 94 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/RespResult.java
  36. 303 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/RetOps.java
  37. 106 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/SpringContextHolder.java
  38. 43 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ThreadLocalKit.java
  39. 30 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ValidGroup.java
  40. 237 0
      easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/WebUtils.java
  41. 7 0
      easier-common/easier-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  42. 13 0
      easier-common/easier-common-core/src/main/resources/banner.txt
  43. 109 0
      easier-common/easier-common-core/src/main/resources/i18n/messages_zh_CN.properties
  44. BIN
      easier-common/easier-common-core/src/main/resources/xdb/ip2region.xdb
  45. 62 0
      easier-common/easier-common-data/pom.xml
  46. 28 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/application/EasierApplicationConfigProperties.java
  47. 57 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/application/EasierApplicationHandler.java
  48. 274 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/DefaultRedisCacheWriter.java
  49. 72 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisAutoCacheManager.java
  50. 91 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisCacheAutoConfiguration.java
  51. 22 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisCacheManagerConfiguration.java
  52. 20 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisMessageConfiguration.java
  53. 30 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisTemplateConfiguration.java
  54. 72 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScope.java
  55. 30 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeFuncEnum.java
  56. 13 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeHandle.java
  57. 87 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeInnerInterceptor.java
  58. 7 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeInterceptor.java
  59. 21 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeSqlInjector.java
  60. 45 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeTypeEnum.java
  61. 82 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/EasierBaseMapper.java
  62. 106 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/EasierDefaultDatascopeHandle.java
  63. 28 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectCountByScope.java
  64. 26 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectListByScope.java
  65. 26 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectPageByScope.java
  66. 40 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectRealById.java
  67. 49 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/handler/JsonLongArrayTypeHandler.java
  68. 56 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/handler/JsonStringArrayTypeHandler.java
  69. 180 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/DruidSqlLogFilter.java
  70. 26 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/EasierMybatisProperties.java
  71. 174 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/MybatisPlusConfiguration.java
  72. 102 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/MybatisPlusMetaObjectHandler.java
  73. 101 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/DictResolver.java
  74. 94 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/ParamResolver.java
  75. 96 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/SqlFilterArgumentResolver.java
  76. 30 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/TenantKeyStrResolver.java
  77. 28 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierFeignTenantInterceptor.java
  78. 28 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierTenantConfigProperties.java
  79. 22 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierTenantConfiguration.java
  80. 68 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierTenantHandler.java
  81. 146 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantBroker.java
  82. 72 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantContextHolder.java
  83. 61 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantContextHolderFilter.java
  84. 32 0
      easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantRequestInterceptor.java
  85. 10 0
      easier-common/easier-common-data/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  86. 43 0
      easier-common/easier-common-domain/pom.xml
  87. 49 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierCommonEntity.java
  88. 27 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierDelEntity.java
  89. 24 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierMongoEntity.java
  90. 52 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierPaginationEntity.java
  91. 33 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierRemovableEntity.java
  92. 33 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonAttachmentItemModel.java
  93. 25 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonContentItemModel.java
  94. 21 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonExtraInfoItemModel.java
  95. 23 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonImageModel.java
  96. 42 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLocation.java
  97. 90 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLogicFineRuleModel.java
  98. 115 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLogicModel.java
  99. 70 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLogicRuleItem.java
  100. 22 0
      easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonSettingItemModel.java

+ 2 - 0
.gitattributes

@@ -0,0 +1,2 @@
+/mvnw text eol=lf
+*.cmd text eol=crlf

+ 33 - 0
.gitignore

@@ -0,0 +1,33 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**/target/
+!**/src/test/**/target/
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+!**/src/main/**/build/
+!**/src/test/**/build/
+
+### VS Code ###
+.vscode/

+ 19 - 0
.mvn/wrapper/maven-wrapper.properties

@@ -0,0 +1,19 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+wrapperVersion=3.3.2
+distributionType=only-script
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip

+ 417 - 0
easier-common/easier-common-bom/pom.xml

@@ -0,0 +1,417 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.yaoyicloud</groupId>
+        <artifactId>easier-common</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>easier-common-bom</artifactId>
+    <packaging>pom</packaging>
+
+    <properties>
+        <easier.version>1.0.0</easier.version>
+        <mybatis-plus.version>3.5.4</mybatis-plus.version>
+        <mybatis-plus-join.version>1.4.6</mybatis-plus-join.version>
+        <dynamic-ds.version>4.2.0</dynamic-ds.version>
+        <druid.version>1.2.20</druid.version>
+        <hutool.version>5.8.22</hutool.version>
+        <mysql.connector.version>8.0.33</mysql.connector.version>
+        <mp.weixin.version>4.4.0</mp.weixin.version>
+        <ijpay.version>2.8.0</ijpay.version>
+        <groovy.version>3.0.3</groovy.version>
+        <javax.version>4.0.1</javax.version>
+        <jsoup.version>1.13.1</jsoup.version>
+        <aviator.version>5.3.3</aviator.version>
+        <flowable.version>6.8.0</flowable.version>
+        <security.oauth.version>2.5.2.RELEASE</security.oauth.version>
+        <fastjson.version>1.2.83</fastjson.version>
+        <xxl.job.version>2.3.0</xxl.job.version>
+        <aliyun.version>3.0.52.ALL</aliyun.version>
+        <aws.version>1.12.261</aws.version>
+        <javers.version>6.10.0</javers.version>
+        <seata.version>1.6.1</seata.version>
+        <asm.version>7.1</asm.version>
+        <log4j2.version>2.17.1</log4j2.version>
+        <javaformat.plugin.version>0.0.23</javaformat.plugin.version>
+        <docker.plugin.version>0.33.0</docker.plugin.version>
+        <cloud.plugin.version>2.0.0</cloud.plugin.version>
+        <sentinel.version>1.8.4</sentinel.version>
+        <bribric.qcc.version>1.1.0.5</bribric.qcc.version>
+        <bribric.wfq.version>1.0.0.1</bribric.wfq.version>
+        <bribric.qxb.version>1.1.0.6</bribric.qxb.version>
+        <bribric.allinpay.version>1.0.0.2</bribric.allinpay.version>
+        <bribric.netocr.version>1.1.0.1</bribric.netocr.version>
+        <tencloud-spring-boot-starter.version>1.1.5</tencloud-spring-boot-starter.version>
+        <itextpdf.version>7.2.5</itextpdf.version>
+        <itext-asian.version>7.2.5</itext-asian.version>
+        <common-compress.version>1.26.0</common-compress.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <!--common dependencies-->
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-core</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-data</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-datasource</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-domain</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-feign</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-gateway</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-gray</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-idempotent</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-job</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-log</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-office</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-oss</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-seata</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-security</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-sentinel</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-sequence</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-websocket</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.common</groupId>
+                <artifactId>easier-common-xss</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+
+            <!--api dependencies-->
+            <dependency>
+                <groupId>com.yaoyicloud.easier.workspace</groupId>
+                <artifactId>easier-workspace-api</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.filerepo</groupId>
+                <artifactId>easier-filerepo-api</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.warehouse</groupId>
+                <artifactId>easier-warehouse-api</artifactId>
+                <version>${easier.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.yaoyicloud.easier.quartz</groupId>
+                <artifactId>easier-quartz-api</artifactId>
+                <version>1.0.0</version>
+            </dependency>
+            <!--toolkit dependencies-->
+            <dependency>
+                <groupId>com.bribric.springframework.boot</groupId>
+                <artifactId>netocr-spring-boot-starter</artifactId>
+                <version>${bribric.netocr.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.bribric.springframework.boot</groupId>
+                <artifactId>qcc-spring-boot-starter</artifactId>
+                <version>${bribric.qcc.version}</version>
+            </dependency>
+            <!--微风企-->
+            <dependency>
+                <groupId>com.bribric.springframework.boot</groupId>
+                <artifactId>wfq-spring-boot-starter</artifactId>
+                <version>${bribric.wfq.version}</version>
+            </dependency>
+            <!-- 启信宝 -->
+            <dependency>
+                <groupId>com.bribric.springframework.boot</groupId>
+                <artifactId>qxb-spring-boot-starter</artifactId>
+                <version>${bribric.qxb.version}</version>
+            </dependency>
+            <!-- 通联支付 -->
+            <dependency>
+                <groupId>com.bribric.springframework.boot</groupId>
+                <artifactId>allinpay-spring-boot-starter</artifactId>
+                <version>${bribric.allinpay.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.bribric.springframework.boot</groupId>
+                <artifactId>tencloud-spring-boot-starter</artifactId>
+                <version>${tencloud-spring-boot-starter.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>com.squareup.okio</groupId>
+                        <artifactId>okio</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+            <!--asm-->
+            <dependency>
+                <groupId>org.ow2.asm</groupId>
+                <artifactId>asm</artifactId>
+                <version>${asm.version}</version>
+            </dependency>
+            <!--  seata kryo 序列化-->
+            <dependency>
+                <groupId>io.seata</groupId>
+                <artifactId>seata-serializer-kryo</artifactId>
+                <version>${seata.version}</version>
+            </dependency>
+            <!--mybatis plus extension,包含了mybatis plus core-->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-extension</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+            <!--mybatis-->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>${dynamic-ds.version}</version>
+            </dependency>
+            <!-- 连表查询依赖	-->
+            <dependency>
+                <groupId>com.github.yulichang</groupId>
+                <artifactId>mybatis-plus-join-boot-starter</artifactId>
+                <version>${mybatis-plus-join.version}</version>
+            </dependency>
+            <!-- 连表查询依赖	-->
+            <dependency>
+                <groupId>com.github.yulichang</groupId>
+                <artifactId>mybatis-plus-join-annotation</artifactId>
+                <version>${mybatis-plus-join.version}</version>
+            </dependency>
+            <!-- druid 连接池 -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+            <!--mysql 驱动-->
+            <dependency>
+                <groupId>com.mysql</groupId>
+                <artifactId>mysql-connector-j</artifactId>
+                <version>${mysql.connector.version}</version>
+            </dependency>
+            <!--fastjson-->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+            <!-- 对象对比工具-->
+            <dependency>
+                <groupId>org.javers</groupId>
+                <artifactId>javers-core</artifactId>
+                <version>${javers.version}</version>
+            </dependency>
+            <!--微信依赖-->
+            <dependency>
+                <groupId>com.github.binarywang</groupId>
+                <artifactId>weixin-java-mp</artifactId>
+                <version>${mp.weixin.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.binarywang</groupId>
+                <artifactId>weixin-java-cp</artifactId>
+                <version>${mp.weixin.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.binarywang</groupId>
+                <artifactId>weixin-java-common</artifactId>
+                <version>${mp.weixin.version}</version>
+            </dependency>
+            <!--计算引擎-->
+            <dependency>
+                <groupId>com.googlecode.aviator</groupId>
+                <artifactId>aviator</artifactId>
+                <version>${aviator.version}</version>
+            </dependency>
+            <!--工作流依赖-->
+            <dependency>
+                <groupId>org.flowable</groupId>
+                <artifactId>flowable-spring-boot-starter-process</artifactId>
+                <version>${flowable.version}</version>
+            </dependency>
+            <!--支付相关SDK-->
+            <dependency>
+                <groupId>com.github.javen205</groupId>
+                <artifactId>IJPay-WxPay</artifactId>
+                <version>${ijpay.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.github.javen205</groupId>
+                <artifactId>IJPay-AliPay</artifactId>
+                <version>${ijpay.version}</version>
+            </dependency>
+            <!--pdf-->
+            <dependency>
+                <groupId>com.itextpdf</groupId>
+                <artifactId>itextpdf</artifactId>
+                <version>${itextpdf.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>com.itextpdf</groupId>
+                <artifactId>itext-asian</artifactId>
+                <version>${itext-asian.version}</version>
+            </dependency>
+            <!--定义groovy 版本-->
+            <dependency>
+                <groupId>org.codehaus.groovy</groupId>
+                <artifactId>groovy</artifactId>
+                <version>${groovy.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>javax.servlet-api</artifactId>
+                <version>${javax.version}</version>
+            </dependency>
+            <!--稳定版本,替代spring security bom内置-->
+            <dependency>
+                <groupId>org.springframework.security.oauth</groupId>
+                <artifactId>spring-security-oauth2</artifactId>
+                <version>${security.oauth.version}</version>
+            </dependency>
+            <!--jsoup html 解析组件-->
+            <dependency>
+                <groupId>org.jsoup</groupId>
+                <artifactId>jsoup</artifactId>
+                <version>${jsoup.version}</version>
+            </dependency>
+            <!--  指定 log4j 版本-->
+            <dependency>
+                <groupId>org.apache.logging.log4j</groupId>
+                <artifactId>log4j-to-slf4j</artifactId>
+                <version>${log4j2.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.logging.log4j</groupId>
+                <artifactId>log4j-bom</artifactId>
+                <version>${log4j2.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <!--hutool bom-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-bom</artifactId>
+                <version>${hutool.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-core</artifactId>
+                <version>${sentinel.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-web-servlet</artifactId>
+                <version>${sentinel.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-transport-simple-http</artifactId>
+                <version>${sentinel.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-parameter-flow-control</artifactId>
+                <version>${sentinel.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>com.alibaba.csp</groupId>
+                <artifactId>sentinel-api-gateway-adapter-common</artifactId>
+                <version>${sentinel.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.commons</groupId>
+                <artifactId>commons-compress</artifactId>
+                <version>${common-compress.version}</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+
+</project>

+ 100 - 0
easier-common/easier-common-core/pom.xml

@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.yaoyicloud</groupId>
+        <artifactId>easier-common</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>easier-common-core</artifactId>
+    <packaging>jar</packaging>
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <!--common-domain-->
+        <dependency>
+            <groupId>com.yaoyicloud</groupId>
+            <artifactId>easier-common-domain</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <!--hutool-->
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-json</artifactId>
+            <version>5.8.22</version>
+        </dependency>
+        <!--mvc 相关配置-->
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-webmvc</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!--server-api-->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+        </dependency>
+        <!--hibernate-validator-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!--json模块-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-json</artifactId>
+        </dependency>
+        <!--TTL-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>transmittable-thread-local</artifactId>
+            <version>2.12.6</version>
+<!--            <version>${ttl.version}</version>-->
+        </dependency>
+        <!--ip归属(离线)-->
+        <dependency>
+            <groupId>org.lionsoul</groupId>
+            <artifactId>ip2region</artifactId>
+            <version>2.6.6</version>
+        </dependency>
+        <!--common-io-->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.11.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>pdfbox</artifactId>
+            <version>3.0.4</version>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.github.jai-imageio</groupId>
+            <artifactId>jai-imageio-core</artifactId>
+            <version>1.4.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.github.jai-imageio</groupId>
+            <artifactId>jai-imageio-jpeg2000</artifactId>
+            <version>1.3.0</version>
+        </dependency>
+
+        <!-- Optional for you ; just to avoid the same error with JBIG2 images -->
+        <dependency>
+            <groupId>org.apache.pdfbox</groupId>
+            <artifactId>jbig2-imageio</artifactId>
+            <version>3.0.4</version>
+        </dependency>
+
+    </dependencies>
+</project>

+ 19 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/annotation/NoRespWrap.java

@@ -0,0 +1,19 @@
+package com.yaoyicloud.easier.common.core.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * 用于标识响应不包装
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-07-27 22:49
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface NoRespWrap {}

+ 19 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/EasierCommonProperties.java

@@ -0,0 +1,19 @@
+package com.yaoyicloud.easier.common.core.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import lombok.Data;
+
+/**
+ * 通用配置文件
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-14 00:04
+ */
+@Data
+@ConfigurationProperties(prefix = "easier.common")
+public class EasierCommonProperties {
+
+    private String ip2RegionPath;
+}

+ 34 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/Ip2RegionConfiguration.java

@@ -0,0 +1,34 @@
+package com.yaoyicloud.easier.common.core.config;
+
+import javax.annotation.Resource;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import com.yaoyicloud.easier.common.core.util.Ip2Region;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * @author jimmy
+ * @version 1.0.0
+ * @date 12/29/23 11:12
+ */
+@Slf4j
+@Configuration
+@ConditionalOnClass(EasierCommonProperties.class)
+public class Ip2RegionConfiguration {
+
+    @Resource
+    private EasierCommonProperties properties;
+
+    @Bean
+    public Ip2Region ip2Region() {
+        String path = properties.getIp2RegionPath();
+        log.info("Ip2Region(path:{}) Init Start", path);
+        Ip2Region ip2Region = new Ip2Region(path);
+        log.info("Ip2Region(path:{}) Init End", path);
+        return ip2Region;
+    }
+}

+ 81 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/JacksonConfiguration.java

@@ -0,0 +1,81 @@
+package com.yaoyicloud.easier.common.core.config;
+
+import java.nio.charset.StandardCharsets;
+import java.time.format.DateTimeFormatter;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
+import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
+import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
+import org.springframework.boot.web.servlet.filter.OrderedCharacterEncodingFilter;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.yaoyicloud.easier.common.core.jackson.EasierJavaTimeModule;
+
+import cn.hutool.core.date.DatePattern;
+
+@Configuration
+@ConditionalOnClass(ObjectMapper.class)
+@AutoConfigureBefore(JacksonAutoConfiguration.class)
+@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
+public class JacksonConfiguration implements WebMvcConfigurer {
+
+    private static final String ASIA_SHANGHAI = "Asia/Shanghai";
+
+    @Bean
+    @ConditionalOnMissingBean
+    public Jackson2ObjectMapperBuilderCustomizer customizer() {
+        return builder -> {
+            builder.locale(Locale.CHINA);
+            builder.timeZone(TimeZone.getTimeZone(ASIA_SHANGHAI));
+            builder.simpleDateFormat(DatePattern.NORM_DATETIME_PATTERN);
+            builder.serializerByType(Long.class, ToStringSerializer.instance);
+            builder.modules(new EasierJavaTimeModule());
+        };
+    }
+
+    /**
+     * 增加GET请求参数中时间类型转换 {@link EasierJavaTimeModule}
+     * <ul>
+     * <li>HH:mm:ss -> LocalTime</li>
+     * <li>yyyy-MM-dd -> LocalDate</li>
+     * <li>yyyy-MM-dd HH:mm:ss -> LocalDateTime</li>
+     * </ul>
+     * 
+     * @param registry FormatterRegistry
+     */
+    @Override
+    public void addFormatters(FormatterRegistry registry) {
+        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
+        registrar.setTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN));
+        registrar.setDateFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN));
+        registrar.setDateTimeFormatter(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
+        registrar.registerFormatters(registry);
+    }
+
+    /**
+     * 避免form 提交 context-type 不规范中文乱码
+     * 
+     * @return Filter
+     */
+    @Bean
+    public OrderedCharacterEncodingFilter characterEncodingFilter() {
+        OrderedCharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
+        filter.setEncoding(StandardCharsets.UTF_8.name());
+        filter.setForceEncoding(true);
+        filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
+        return filter;
+    }
+
+}

+ 25 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/MessageSourceConfiguration.java

@@ -0,0 +1,25 @@
+package com.yaoyicloud.easier.common.core.config;
+
+import org.springframework.context.MessageSource;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.support.ReloadableResourceBundleMessageSource;
+
+/**
+ * 国际化配置
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-11 13:25
+ */
+@Configuration
+public class MessageSourceConfiguration {
+
+    @Bean
+    public MessageSource easierMessageSource() {
+        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
+        messageSource.setBasename("classpath:i18n/messages");
+        return messageSource;
+    }
+
+}

+ 15 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/config/RestTemplateConfiguration.java

@@ -0,0 +1,15 @@
+package com.yaoyicloud.easier.common.core.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class RestTemplateConfiguration {
+
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+
+}

+ 151 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/CacheConstants.java

@@ -0,0 +1,151 @@
+package com.yaoyicloud.easier.common.core.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 缓存常量
+ *
+ * <p>
+ * 全局缓存,在缓存名称上加上该前缀表示该缓存不区分租户,比如:
+ * {@code @Cacheable(value = CacheConstants.GLOBALLY+CacheConstants.MENU_DETAILS, key = "#roleId  + '_menu'", unless = "#result == null")}
+ * </p>
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-19 11:20
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public final class CacheConstants {
+
+    /**
+     * 全局缓存,在缓存名称上加上该前缀表示该缓存不区分租户
+     */
+    public static final String GLOBALLY = "gl:";
+
+    /**
+     * 验证码前缀
+     */
+    public static final String DEFAULT_CODE_KEY = "DEFAULT_CODE_KEY:";
+
+    /**
+     * 菜单信息缓存
+     */
+    public static final String MENU_DETAILS = "menu_details";
+
+    /**
+     * 移动端菜单信息缓存
+     */
+    public static final String APP_MENU_DETAILS = "app_menu_details";
+
+    /**
+     * 用户信息缓存
+     */
+    public static final String USER_DETAILS = "user_details";
+
+    /**
+     * 移动端用户信息缓存
+     */
+    public static final String MEMBER_DETAILS = "member_details";
+
+    /**
+     * 角色信息缓存
+     */
+    public static final String ROLE_DETAILS = "role_details";
+
+    /**
+     * 字典信息缓存
+     */
+    public static final String DICT_DETAILS = "dict_details";
+
+    /**
+     * 商品分类标签
+     */
+    public static final String MALL_CATE_DETAILS = "mall_cate_details";
+
+    /**
+     * 商品规格
+     */
+    public static final String MALL_PROD_SPEC_DETAILS = "mall_prod_spec_details";
+
+    /**
+     * 商品详情
+     */
+    public static final String MALL_PROD_DETAILS = "mall_prod_details";
+
+    /**
+     * 项目标签详情
+     */
+    public static final String PROJ_TAG_DETAILS = "proj_tag_details";
+
+    /**
+     * oauth 客户端信息
+     */
+    public static final String CLIENT_DETAILS_KEY = "easier_oauth:client:details";
+
+    /**
+     * spring boot admin 事件key
+     */
+    public static final String EVENT_KEY = GLOBALLY + "event_key";
+
+    /**
+     * 路由存放
+     */
+    public static final String ROUTE_KEY = GLOBALLY + "gateway_route_key";
+
+    /**
+     * 内存reload 时间
+     */
+    public static final String ROUTE_JVM_RELOAD_TOPIC = "gateway_jvm_route_reload_topic";
+
+    /**
+     * redis 重新加载 路由信息
+     */
+    public static final String ROUTE_REDIS_RELOAD_TOPIC = "upms_redis_route_reload_topic";
+
+    /**
+     * redis 重新加载客户端信息
+     */
+    public static final String CLIENT_REDIS_RELOAD_TOPIC = "upms_redis_client_reload_topic";
+
+    /**
+     * 公众号 reload
+     */
+    public static final String MP_REDIS_RELOAD_TOPIC = "mp_redis_reload_topic";
+
+    /**
+     * 支付 reload 事件
+     */
+    public static final String PAY_REDIS_RELOAD_TOPIC = "pay_redis_reload_topic";
+
+    /**
+     * 参数缓存
+     */
+    public static final String PARAMS_DETAILS = "params_details";
+
+    /**
+     * 租户缓存 (不区分租户)
+     */
+    public static final String TENANT_DETAILS = GLOBALLY + "tenant_details";
+
+    /**
+     * i18n缓存 (不区分租户)
+     */
+    public static final String I18N_DETAILS = GLOBALLY + "i18n_details";
+
+    /**
+     * 客户端配置缓存
+     */
+    public static final String CLIENT_FLAG = "client_config_flag";
+
+    /**
+     * 登录错误次数
+     */
+    public static final String LOGIN_ERROR_TIMES = "login_error_times";
+
+    /**
+     * oauth 缓存前缀
+     */
+    public static final String PROJECT_OAUTH_ACCESS = "token::access_token";
+
+}

+ 152 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/CommonConstants.java

@@ -0,0 +1,152 @@
+package com.yaoyicloud.easier.common.core.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 通用常量
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-19 11:25
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public final class CommonConstants {
+
+    /**
+     * header 中租户ID
+     */
+    public static final String TENANT_ID_HEADER = "X-Tenant-Id";
+
+    /**
+     * header 中版本信息
+     */
+    public static final String VERSION = "X-Version";
+
+    /**
+     * 端类型头信息
+     */
+    public static final String TERMINAL_TYPE_HEADER = "X-Terminal-Type";
+
+    /**
+     * header 中应用ID, 可空,默认为1,锋信易
+     */
+    public static final String APPLICATION_ID_HEADER = "X-Application-Id";
+
+    /**
+     * 租户ID
+     */
+    public static final Long TENANT_ID_1 = 1L;
+
+    /**
+     * FXY APP ID
+     */
+    public static final Long APPLICATION_ID_1 = 1L;
+
+    /**
+     * 成功标记
+     */
+    public static final String SUCCESS = "2000";
+
+    /**
+     * 默认成功信息
+     */
+    public static final String SUCCESS_MSG = "成功";
+
+    /**
+     * 失败标记
+     */
+    public static final String FAIL = "5000";
+
+    /**
+     * 默认失败信息
+     */
+    public static final String FAIL_MSG = "失败";
+
+    /**
+     * 删除
+     */
+    public static final String STATUS_DEL = "1";
+
+    /**
+     * 正常
+     */
+    public static final String STATUS_NORMAL = "0";
+
+    /**
+     * 锁定
+     */
+    public static final String STATUS_LOCK = "9";
+
+    /**
+     * 菜单树根节点
+     */
+    public static final Long DEF_TREE_ROOT_ID = -1L;
+
+    /**
+     * 编码
+     */
+    public static final String UTF8 = "UTF-8";
+
+    /**
+     * 前端工程名
+     */
+    public static final String FRONT_END_PROJECT = "easier-ui";
+
+    /**
+     * 移动端工程名
+     */
+    public static final String UNI_END_PROJECT = "easier-app";
+
+    /**
+     * 后端工程名
+     */
+    public static final String BACK_END_PROJECT = "easier";
+
+    /**
+     * 公共参数
+     */
+    public static final String EASIER_PUBLIC_PARAM_KEY = "EASIER_PUBLIC_PARAM_KEY";
+
+    /**
+     * 默认存储bucket
+     */
+    public static final String BUCKET_NAME = "freerr";
+
+    /**
+     * 滑块验证码
+     */
+    public static final String IMAGE_CODE_TYPE = "blockPuzzle";
+
+    /**
+     * 验证码开关
+     */
+    public static final String CAPTCHA_FLAG = "captcha_flag";
+
+    /**
+     * 密码传输是否加密
+     */
+    public static final String ENC_FLAG = "enc_flag";
+
+    /**
+     * 客户端允许同时在线数量
+     */
+    public static final String ONLINE_QUANTITY = "online_quantity";
+
+    /**
+     * 请求开始时间
+     */
+    public static final String REQUEST_START_TIME = "REQUEST-START-TIME";
+
+    /**
+     * json 映射器
+     */
+    public static final String JSON_TYPE_HANDLER_MAPPING =
+        "typeHandler=com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler";
+
+    /**
+     * 匿名
+     */
+    public static final String ANONYMOUS_NAME = "anonymous";
+
+}

+ 26 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/PaginationConstants.java

@@ -0,0 +1,26 @@
+package com.yaoyicloud.easier.common.core.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 分页常量
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-19 11:25
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public final class PaginationConstants {
+
+    /**
+     * 当前页
+     */
+    public static final String CURRENT = "current";
+
+    /**
+     * 每页大小
+     */
+    public static final String SIZE = "size";
+
+}

+ 236 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/SecurityConstants.java

@@ -0,0 +1,236 @@
+package com.yaoyicloud.easier.common.core.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 安全常量
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-19 11:22
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public final class SecurityConstants {
+
+    /**
+     * 启动时是否检查Inner注解安全性
+     */
+    public static final boolean INNER_CHECK = true;
+
+    /**
+     * 刷新
+     */
+    public static final String REFRESH_TOKEN = "refresh_token";
+
+    /**
+     * 验证码有效期
+     */
+    public static final int CODE_TIME = 60;
+
+    /**
+     * 验证码长度
+     */
+    public static final String CODE_SIZE = "6";
+
+    /**
+     * 角色前缀
+     */
+    public static final String ROLE = "ROLE_";
+
+    /**
+     * 前缀
+     */
+    public static final String EASIER_PREFIX = "easier_";
+
+    /**
+     * token 相关前缀
+     */
+    public static final String TOKEN_PREFIX = "token:";
+
+    /**
+     * oauth 相关前缀
+     */
+    public static final String OAUTH_PREFIX = "oauth:";
+
+    /**
+     * 授权码模式code key 前缀
+     */
+    public static final String OAUTH_CODE_PREFIX = "oauth:code:";
+
+    /**
+     * 项目的license
+     */
+    public static final String EASIER_LICENSE = "https://easier.yaoyicloud.com";
+
+    /**
+     * 内部
+     */
+    public static final String FROM_IN = "Y";
+
+    /**
+     * 标志
+     */
+    public static final String FROM = "from";
+
+    /**
+     * 请求header
+     */
+    public static final String HEADER_FROM_IN = FROM + "=" + FROM_IN;
+
+    /**
+     * OAUTH URL
+     */
+    public static final String OAUTH_TOKEN_URL = "/oauth2/token";
+
+    /**
+     * 移动端授权
+     */
+    public static final String GRANT_MOBILE = "mobile";
+
+    /**
+     * 客户类型
+     */
+    public static final String CLIENT_TYPE_HEADER = "X-Client-Type";
+
+    /**
+     * QQ获取token
+     */
+    public static final String QQ_AUTHORIZATION_CODE_URL = "https://graph.qq.com/oauth2.0/token?grant_type="
+        + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
+
+    /**
+     * 微信获取OPENID
+     */
+    public static final String WX_AUTHORIZATION_CODE_URL = "https://api.weixin.qq.com/sns/oauth2/access_token"
+        + "?appid=%s&secret=%s&code=%s&grant_type=authorization_code";
+
+    /**
+     * 微信小程序OPENID
+     */
+    public static final String MINI_APP_AUTHORIZATION_CODE_URL =
+        "https://api.weixin.qq.com/sns/jscode2session" + "?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code";
+
+    /**
+     * 码云获取token
+     */
+    public static final String GITEE_AUTHORIZATION_CODE_URL = "https://gitee.com/oauth/token?grant_type="
+        + "authorization_code&code=%S&client_id=%s&redirect_uri=" + "%s&client_secret=%s";
+
+    /**
+     * 开源中国获取token
+     */
+    public static final String OSC_AUTHORIZATION_CODE_URL = "https://www.oschina.net/action/openapi/token";
+
+    /**
+     * QQ获取用户信息
+     */
+    public static final String QQ_USER_INFO_URL = "https://graph.qq.com/oauth2.0/me?access_token=%s";
+
+    /**
+     * 码云获取用户信息
+     */
+    public static final String GITEE_USER_INFO_URL = "https://gitee.com/api/v5/user?access_token=%s";
+
+    /**
+     * 开源中国用户信息
+     */
+    public static final String OSC_USER_INFO_URL =
+        "https://www.oschina.net/action/openapi/user?access_token=%s&dataType=json";
+
+    /**
+     * 钉钉获取 token
+     */
+    public static final String DING_OLD_GET_TOKEN = "https://oapi.dingtalk.com/gettoken";
+
+    /**
+     * 钉钉同步部门列表
+     */
+    public static final String DING_OLD_DEPT_URL = "https://oapi.dingtalk.com/topapi/v2/department/listsub";
+
+    /**
+     * 钉钉部门用户id列表
+     */
+    public static final String DING_DEPT_USERIDS_URL = "https://oapi.dingtalk.com/topapi/user/listid";
+
+    /**
+     * 钉钉用户详情
+     */
+    public static final String DING_USER_INFO_URL = "https://oapi.dingtalk.com/topapi/v2/user/get";
+
+    /**
+     * {bcrypt} 加密的特征码
+     */
+    public static final String BCRYPT = "{bcrypt}";
+
+    /**
+     * 客户端模式
+     */
+    public static final String CLIENT_CREDENTIALS = "client_credentials";
+
+    /**
+     * 客户端编号
+     */
+    public static final String CLIENT_ID = "client_id";
+
+    /**
+     * 客户端唯一令牌
+     */
+    public static final String CLIENT_RECREATE = "recreate_flag";
+
+    /**
+     * 用户ID字段
+     */
+    public static final String DETAILS_USER_ID = "user_id";
+
+    /**
+     * 用户名
+     */
+    public static final String DETAILS_USERNAME = "username";
+
+    /**
+     * 姓名
+     */
+    public static final String NAME = "name";
+
+    /**
+     * 协议字段
+     */
+    public static final String DETAILS_LICENSE = "license";
+
+    /**
+     * 激活字段 兼容外围系统接入
+     */
+    public static final String ACTIVE = "active";
+
+    /**
+     * AES 加密
+     */
+    public static final String AES = "aes";
+
+    /**
+     * 授权码模式confirm
+     */
+    public static final String CUSTOM_CONSENT_PAGE_URI = "/token/confirm_access";
+
+    /**
+     * {noop} 加密的特征码
+     */
+    public static final String NOOP = "{noop}";
+
+    /**
+     * 短信登录 参数名称
+     */
+    public static final String SMS_PARAMETER_NAME = "mobile";
+
+    /**
+     * 手机号登录
+     */
+    public static final String APP = "mobile";
+
+    /**
+     * 用户信息
+     */
+    public static final String DETAILS_USER = "user_info";
+
+}

+ 66 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/constant/ServiceNameConstants.java

@@ -0,0 +1,66 @@
+package com.yaoyicloud.easier.common.core.constant;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 服务名称
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-18 17:18
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public final class ServiceNameConstants {
+
+    /**
+     * AUTH 中心
+     */
+    public static final String AUTH_SERVICE = "easier-auth";
+
+    /**
+     * WORKSPACE 中心
+     */
+    public static final String WORKSPACE_SERVICE = "easier-workspace-biz";
+
+    /**
+     * FILEREPO 中心
+     */
+    public static final String FILEREPO_SERVICE = "easier-filerepo-biz";
+
+    /**
+     * WAREHOUSE 中心
+     */
+    public static final String WAREHOUSE_SERVICE = "easier-warehouse-biz";
+
+    /**
+     * QRTZ 中心
+     */
+    public static final String QRTZ_SERVICE = "easier-quartz-biz";
+
+    /**
+     * CHECKOUT 中心
+     */
+    public static final String CHECKOUT_SERVICE = "easier-checkout-biz";
+
+    /**
+     * MEMBERSHIP 中心
+     */
+    public static final String MEMBERSHIP_SERVER = "easier-membership-biz";
+
+    /**
+     * 流程引擎
+     */
+    public static final String FLOW_ENGINE_SERVER = "easier-flow-engine-biz";
+
+    /**
+     * 流程工单
+     */
+    public static final String FLOW_TASK_SERVER = "easier-flow-task-biz";
+
+    /**
+     * REPORT 中心
+     */
+    public static final String REPORT_SERVER = "easier-report-biz";
+
+}

+ 26 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/CheckedException.java

@@ -0,0 +1,26 @@
+package com.yaoyicloud.easier.common.core.exception;
+
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+public class CheckedException extends RuntimeException {
+
+    private static final long serialVersionUID = 1L;
+
+    public CheckedException(String message) {
+        super(message);
+    }
+
+    public CheckedException(Throwable cause) {
+        super(cause);
+    }
+
+    public CheckedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public CheckedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+}

+ 33 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/EasierBizException.java

@@ -0,0 +1,33 @@
+package com.yaoyicloud.easier.common.core.exception;
+
+import lombok.Getter;
+
+/**
+ * 业务异常
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-10-09 19:32
+ */
+@Getter
+public class EasierBizException extends RuntimeException {
+
+    private static final long serialVersionUID = 1254338260038180257L;
+
+    private final String errorCode;
+    private final String errorMsgId;
+
+    protected String serviceName;
+    protected Object[] params;
+
+    public EasierBizException(String serviceName, String errorCode, String errorMsgId, Object[] params) {
+        this(serviceName, errorCode, errorMsgId);
+        this.params = params;
+    }
+
+    public EasierBizException(String serviceName, String errorCode, String errorMsgId) {
+        this.serviceName = serviceName;
+        this.errorCode = errorCode;
+        this.errorMsgId = errorMsgId;
+    }
+}

+ 103 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/ErrorCodes.java

@@ -0,0 +1,103 @@
+package com.yaoyicloud.easier.common.core.exception;
+
+@SuppressWarnings("checkstyle:InterfaceIsType")
+public interface ErrorCodes {
+
+    /**
+     * 系统编码错误
+     */
+    String SYS_PARAM_CONFIG_ERROR = "sys.param.config.error";
+
+    /**
+     * 系统内置参数不能删除
+     */
+    String SYS_PARAM_DELETE_SYSTEM = "sys.param.delete.system";
+
+    /**
+     * 用户已存在
+     */
+    String SYS_USER_USERNAME_EXISTING = "sys.user.username.existing";
+
+    /**
+     * 用户已存在
+     */
+    String SYS_USER_PHONE_EXISTING = "sys.user.phone.existing";
+
+    /**
+     * 用户原密码错误,修改失败
+     */
+    String SYS_USER_UPDATE_PASSWORDERROR = "sys.user.update.passwordError";
+
+    /**
+     * 用户信息为空
+     */
+    String SYS_USER_USERINFO_EMPTY = "sys.user.userInfo.empty";
+
+    /**
+     * 获取当前用户信息失败
+     */
+    String SYS_USER_QUERY_ERROR = "sys.user.query.error";
+
+    String SYS_USER_IMPORT_SUCCEED = "sys.user.import.succeed";
+
+    /**
+     * 部门名称不存在
+     */
+    String SYS_DEPT_DEPTNAME_INEXISTENCE = "sys.dept.deptName.inexistence";
+
+    /**
+     * 岗位名称不存在
+     */
+    String SYS_POST_POSTNAME_INEXISTENCE = "sys.post.postName.inexistence";
+
+    /**
+     * 岗位名称或编码已经存在
+     */
+    String SYS_POST_NAMEORCODE_EXISTING = "sys.post.nameOrCode.existing";
+
+    /**
+     * 角色名称不存在
+     */
+    String SYS_ROLE_ROLENAME_INEXISTENCE = "sys.role.roleName.inexistence";
+
+    /**
+     * 角色名或角色编码已经存在
+     */
+    String SYS_ROLE_NAMEORCODE_EXISTING = "sys.role.nameOrCode.existing";
+
+    /**
+     * 菜单存在下级节点 删除失败
+     */
+    String SYS_MENU_DELETE_EXISTING = "sys.menu.delete.existing";
+
+    /**
+     * 系统内置字典不允许删除
+     */
+    String SYS_DICT_DELETE_SYSTEM = "sys.dict.delete.system";
+
+    /**
+     * 系统内置字典不能修改
+     */
+    String SYS_DICT_UPDATE_SYSTEM = "sys.dict.update.system";
+
+    /**
+     * 验证码发送频繁
+     */
+    String SYS_APP_SMS_OFTEN = "sys.app.sms.often";
+
+    /**
+     * 手机号未注册
+     */
+    String SYS_APP_PHONE_UNREGISTERED = "sys.app.phone.unregistered";
+
+    /**
+     * 企微调用接口错误
+     */
+    String SYS_CONNECT_CP_DEPT_SYNC_ERROR = "sys.connect.cp.dept.sync.error";
+
+    /**
+     * 企微调用接口错误
+     */
+    String SYS_CONNECT_CP_USER_SYNC_ERROR = "sys.connect.cp.user.sync.error";
+
+}

+ 22 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/TenantBrokerExceptionWrapper.java

@@ -0,0 +1,22 @@
+package com.yaoyicloud.easier.common.core.exception;
+
+/**
+ * 租户封装异常
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/02/05 22:31
+ */
+public class TenantBrokerExceptionWrapper extends RuntimeException {
+
+    private static final long serialVersionUID = 7334032689572847358L;
+
+    public TenantBrokerExceptionWrapper(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public TenantBrokerExceptionWrapper(Throwable cause) {
+        super(cause);
+    }
+
+}

+ 13 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/exception/ValidateCodeException.java

@@ -0,0 +1,13 @@
+package com.yaoyicloud.easier.common.core.exception;
+
+public class ValidateCodeException extends RuntimeException {
+
+    private static final long serialVersionUID = -7285211528095468156L;
+
+    public ValidateCodeException() {}
+
+    public ValidateCodeException(String msg) {
+        super(msg);
+    }
+
+}

+ 38 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/factory/YamlPropertySourceFactory.java

@@ -0,0 +1,38 @@
+package com.yaoyicloud.easier.common.core.factory;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.EncodedResource;
+import org.springframework.core.io.support.PropertySourceFactory;
+import org.springframework.lang.Nullable;
+
+public class YamlPropertySourceFactory implements PropertySourceFactory {
+
+    @Override
+    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
+        Properties propertiesFromYaml = loadYamlIntoProperties(resource);
+        String sourceName = name != null ? name : resource.getResource().getFilename();
+        return new PropertiesPropertySource(sourceName, propertiesFromYaml);
+    }
+
+    private Properties loadYamlIntoProperties(EncodedResource resource) throws FileNotFoundException {
+        try {
+            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+            factory.setResources(resource.getResource());
+            factory.afterPropertiesSet();
+            return factory.getObject();
+        } catch (IllegalStateException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof FileNotFoundException) {
+                throw (FileNotFoundException) e.getCause();
+            }
+            throw e;
+        }
+    }
+
+}

+ 61 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/jackson/EasierJavaTimeModule.java

@@ -0,0 +1,61 @@
+package com.yaoyicloud.easier.common.core.jackson;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+
+import com.fasterxml.jackson.databind.module.SimpleModule;
+import com.fasterxml.jackson.datatype.jsr310.PackageVersion;
+import com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+
+import cn.hutool.core.date.DatePattern;
+
+public class EasierJavaTimeModule extends SimpleModule {
+
+    private static final long serialVersionUID = -1066306931365176877L;
+
+    /**
+     * 指定序列化规则
+     */
+    public EasierJavaTimeModule() {
+        super(PackageVersion.VERSION);
+
+        // ======================= 时间序列化规则 ===============================
+        // yyyy-MM-dd HH:mm:ss
+        this.addSerializer(LocalDateTime.class,
+            new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
+        // yyyy-MM-dd
+        this.addSerializer(LocalDate.class,
+            new LocalDateSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
+        // HH:mm:ss
+        this.addSerializer(LocalTime.class,
+            new LocalTimeSerializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
+
+        // Instant 类型序列化
+        this.addSerializer(Instant.class, InstantSerializer.INSTANCE);
+
+        // ======================= 时间反序列化规则 ==============================
+        // yyyy-MM-dd HH:mm:ss
+        this.addDeserializer(LocalDateTime.class,
+            new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN)));
+        // yyyy-MM-dd
+        this.addDeserializer(LocalDate.class,
+            new LocalDateDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_DATE_PATTERN)));
+        // HH:mm:ss
+        this.addDeserializer(LocalTime.class,
+            new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DatePattern.NORM_TIME_PATTERN)));
+        // Instant 反序列化
+        this.addDeserializer(Instant.class, InstantDeserializer.INSTANT);
+
+    }
+
+}

+ 37 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/sensitive/Sensitive.java

@@ -0,0 +1,37 @@
+package com.yaoyicloud.easier.common.core.sensitive;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+@JacksonAnnotationsInside
+@JsonSerialize(using = SensitiveSerialize.class)
+public @interface Sensitive {
+
+    /**
+     * 脱敏数据类型, 非Customer时, 将忽略 refixNoMaskLen 和 suffixNoMaskLen 和 maskStr
+     */
+    SensitiveTypeEnum type() default SensitiveTypeEnum.CUSTOMER;
+
+    /**
+     * 前置不需要打码的长度
+     */
+    int prefixNoMaskLen() default 0;
+
+    /**
+     * 后置不需要打码的长度
+     */
+    int suffixNoMaskLen() default 0;
+
+    /**
+     * 用什么打码
+     */
+    String maskStr() default "*";
+
+}

+ 95 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/sensitive/SensitiveSerialize.java

@@ -0,0 +1,95 @@
+package com.yaoyicloud.easier.common.core.sensitive;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.BeanProperty;
+import com.fasterxml.jackson.databind.JsonMappingException;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.ContextualSerializer;
+import com.yaoyicloud.easier.common.core.util.DesensitizedUtils;
+
+import cn.hutool.core.util.DesensitizedUtil;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@AllArgsConstructor
+public class SensitiveSerialize extends JsonSerializer<String> implements ContextualSerializer {
+
+    private SensitiveTypeEnum type;
+
+    private Integer prefixNoMaskLen;
+
+    private Integer suffixNoMaskLen;
+
+    private String maskStr;
+
+    @Override
+    public void serialize(final String origin, final JsonGenerator jsonGenerator,
+        final SerializerProvider serializerProvider) throws IOException {
+        switch (type) {
+            case CHINESE_NAME:
+                jsonGenerator.writeString(DesensitizedUtils.chineseName(origin));
+                break;
+            case ID_CARD:
+                jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin));
+                break;
+            case FIXED_PHONE:
+                jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin));
+                break;
+            case MOBILE_PHONE:
+                jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin));
+                break;
+            case ADDRESS:
+                jsonGenerator.writeString(DesensitizedUtils.address(origin));
+                break;
+            case EMAIL:
+                jsonGenerator.writeString(DesensitizedUtils.email(origin));
+                break;
+            case BANK_CARD:
+                jsonGenerator.writeString(DesensitizedUtils.bankCard(origin));
+                break;
+            case PASSWORD:
+                jsonGenerator.writeString(DesensitizedUtils.password(origin));
+                break;
+            case KEY:
+                jsonGenerator.writeString(DesensitizedUtils.key(origin));
+                break;
+            case IPV4:
+                jsonGenerator.writeString(DesensitizedUtils.ipv4(origin));
+                break;
+            case CAR_LICENSE:
+                jsonGenerator.writeString(DesensitizedUtil.carLicense(origin));
+                break;
+            case CUSTOMER:
+                jsonGenerator
+                    .writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr));
+                break;
+            default:
+                throw new IllegalArgumentException("Unknow sensitive type enum " + type);
+        }
+    }
+
+    @Override
+    public JsonSerializer<?> createContextual(final SerializerProvider serializerProvider,
+        final BeanProperty beanProperty) throws JsonMappingException {
+        if (beanProperty != null) {
+            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {
+                Sensitive sensitive = beanProperty.getAnnotation(Sensitive.class);
+                if (sensitive == null) {
+                    sensitive = beanProperty.getContextAnnotation(Sensitive.class);
+                }
+                if (sensitive != null) {
+                    return new SensitiveSerialize(sensitive.type(), sensitive.prefixNoMaskLen(),
+                        sensitive.suffixNoMaskLen(), sensitive.maskStr());
+                }
+            }
+            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
+        }
+        return serializerProvider.findNullValueSerializer(null);
+    }
+
+}

+ 55 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/sensitive/SensitiveTypeEnum.java

@@ -0,0 +1,55 @@
+package com.yaoyicloud.easier.common.core.sensitive;
+
+public enum SensitiveTypeEnum {
+
+    /**
+     * 自定义
+     */
+    CUSTOMER,
+    /**
+     * 用户名, 刘*华, 徐*
+     */
+    CHINESE_NAME,
+    /**
+     * 身份证号, 110110********1234
+     */
+    ID_CARD,
+    /**
+     * 座机号, ****1234
+     */
+    FIXED_PHONE,
+    /**
+     * 手机号, 176****1234
+     */
+    MOBILE_PHONE,
+    /**
+     * 地址, 北京********
+     */
+    ADDRESS,
+    /**
+     * 电子邮件, s*****o@xx.com
+     */
+    EMAIL,
+    /**
+     * 银行卡, 622202************1234
+     */
+    BANK_CARD,
+    /**
+     * 密码, 永远是 ******, 与长度无关
+     */
+    PASSWORD,
+    /**
+     * 密钥, 【密钥】密钥除了最后三位其他都是***, 与长度无关
+     */
+    KEY,
+    /**
+     * IPV4 类型 113.123.198.176 主机部分打码 113.123.*.123
+     */
+    IPV4,
+
+    /**
+     * 中国大陆车牌,包含普通车辆、新能源车辆
+     */
+    CAR_LICENSE
+
+}

+ 95 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ClassUtils.java

@@ -0,0 +1,95 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import org.springframework.core.BridgeMethodResolver;
+import org.springframework.core.DefaultParameterNameDiscoverer;
+import org.springframework.core.MethodParameter;
+import org.springframework.core.ParameterNameDiscoverer;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.SynthesizingMethodParameter;
+import org.springframework.web.method.HandlerMethod;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class ClassUtils extends org.springframework.util.ClassUtils {
+
+    @SuppressWarnings("checkstyle:MemberName")
+    private final ParameterNameDiscoverer PARAMETERNAMEDISCOVERER = new DefaultParameterNameDiscoverer();
+
+    /**
+     * 获取方法参数信息
+     * 
+     * @param constructor 构造器
+     * @param parameterIndex 参数序号
+     * @return {MethodParameter}
+     */
+    public MethodParameter getMethodParameter(Constructor<?> constructor, int parameterIndex) {
+        MethodParameter methodParameter = new SynthesizingMethodParameter(constructor, parameterIndex);
+        methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
+        return methodParameter;
+    }
+
+    /**
+     * 获取方法参数信息
+     * 
+     * @param method 方法
+     * @param parameterIndex 参数序号
+     * @return {MethodParameter}
+     */
+    public MethodParameter getMethodParameter(Method method, int parameterIndex) {
+        MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
+        methodParameter.initParameterNameDiscovery(PARAMETERNAMEDISCOVERER);
+        return methodParameter;
+    }
+
+    /**
+     * 获取Annotation
+     * 
+     * @param method Method
+     * @param annotationType 注解类
+     * @param <A> 泛型标记
+     * @return {Annotation}
+     */
+    public <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
+        Class<?> targetClass = method.getDeclaringClass();
+        // The method may be on an interface, but we need attributes from the target
+        // class.
+        // If the target class is null, the method will be unchanged.
+        Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
+        // If we are dealing with method with generic parameters, find the original
+        // method.
+        specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
+        // 先找方法,再找方法上的类
+        A annotation = AnnotatedElementUtils.findMergedAnnotation(specificMethod, annotationType);
+
+        if (null != annotation) {
+            return annotation;
+        }
+        // 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
+        return AnnotatedElementUtils.findMergedAnnotation(specificMethod.getDeclaringClass(), annotationType);
+    }
+
+    /**
+     * 获取Annotation
+     * 
+     * @param handlerMethod HandlerMethod
+     * @param annotationType 注解类
+     * @param <A> 泛型标记
+     * @return {Annotation}
+     */
+    public <A extends Annotation> A getAnnotation(HandlerMethod handlerMethod, Class<A> annotationType) {
+        // 先找方法,再找方法上的类
+        A annotation = handlerMethod.getMethodAnnotation(annotationType);
+        if (null != annotation) {
+            return annotation;
+        }
+        // 获取类上面的Annotation,可能包含组合注解,故采用spring的工具类
+        Class<?> beanType = handlerMethod.getBeanType();
+        return AnnotatedElementUtils.findMergedAnnotation(beanType, annotationType);
+    }
+
+}

+ 168 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/DesensitizedUtils.java

@@ -0,0 +1,168 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import cn.hutool.core.util.StrUtil;
+
+/**
+ * 脱敏工具类
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-11 13:27
+ */
+public class DesensitizedUtils {
+
+    /**
+     * 对字符串进行脱敏操作
+     *
+     * @param origin 原始字符串
+     * @param prefixNoMaskLen 左侧需要保留几位明文字段
+     * @param suffixNoMaskLen 右侧需要保留几位明文字段
+     * @param maskStr 用于遮罩的字符串, 如'*'
+     * @return 脱敏后结果
+     */
+    public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) {
+        if (origin == null) {
+            return null;
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0, n = origin.length(); i < n; i++) {
+            if (i < prefixNoMaskLen) {
+                sb.append(origin.charAt(i));
+                continue;
+            }
+            if (i > (n - suffixNoMaskLen - 1)) {
+                sb.append(origin.charAt(i));
+                continue;
+            }
+            sb.append(maskStr);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 【中文姓名】只显示最后一个汉字,其他隐藏为星号,比如:**梦
+     *
+     * @param fullName 姓名
+     * @return 结果
+     */
+    public static String chineseName(String fullName) {
+        if (fullName == null) {
+            return null;
+        }
+        return desValue(fullName, 0, 1, "*");
+    }
+
+    /**
+     * 【身份证号】显示前六位, 四位,其他隐藏。共计18位或者15位,比如:340304*******1234
+     *
+     * @param id 身份证号码
+     * @return 结果
+     */
+    public static String idCardNum(String id) {
+        return desValue(id, 6, 4, "*");
+    }
+
+    /**
+     * 【固定电话】后四位,其他隐藏,比如 ****1234
+     *
+     * @param num 固定电话
+     * @return 结果
+     */
+    public static String fixedPhone(String num) {
+        return desValue(num, 0, 4, "*");
+    }
+
+    /**
+     * 【手机号码】前三位,后四位,其他隐藏,比如135****6810
+     *
+     * @param num 手机号码
+     * @return 结果
+     */
+    public static String mobilePhone(String num) {
+        return desValue(num, 3, 4, "*");
+    }
+
+    /**
+     * 【地址】只显示到地区,不显示详细地址,比如:北京市海淀区****
+     *
+     * @param address 地址
+     * @return 结果
+     */
+    public static String address(String address) {
+        return desValue(address, 6, 0, "*");
+    }
+
+    /**
+     * 【电子邮箱 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
+     *
+     * @param email 电子邮箱
+     * @return 结果
+     */
+    public static String email(String email) {
+        if (email == null) {
+            return null;
+        }
+        int index = StrUtil.indexOf(email, '@');
+        if (index <= 1) {
+            return email;
+        }
+        String preEmail = desValue(email.substring(0, index), 1, 0, "*");
+        return preEmail + email.substring(index);
+
+    }
+
+    /**
+     * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:622260**********1234
+     *
+     * @param cardNum 银行卡号
+     * @return 结果
+     */
+    public static String bankCard(String cardNum) {
+        return desValue(cardNum, 6, 4, "*");
+    }
+
+    /**
+     * 【密码】密码的全部字符都用*代替,比如:******
+     *
+     * @param password 密码
+     * @return 结果
+     */
+    public static String password(String password) {
+        if (password == null) {
+            return null;
+        }
+        return "******";
+    }
+
+    /**
+     * 【密钥】密钥除了最后三位,全部都用*代替,比如:***xdS 脱敏后长度为6,如果明文长度不足三位,则按实际长度显示,剩余位置补*
+     *
+     * @param key 密钥
+     * @return 结果
+     */
+    @SuppressWarnings("checkstyle:ReturnCount")
+    public static String key(String key) {
+        if (key == null) {
+            return null;
+        }
+        int viewLength = 6;
+        StringBuilder tmpKey = new StringBuilder(desValue(key, 0, 3, "*"));
+        if (tmpKey.length() > viewLength) {
+            return tmpKey.substring(tmpKey.length() - viewLength);
+        } else if (tmpKey.length() < viewLength) {
+            int buffLength = viewLength - tmpKey.length();
+            for (int i = 0; i < buffLength; i++) {
+                tmpKey.insert(0, "*");
+            }
+            return tmpKey.toString();
+        } else {
+            return tmpKey.toString();
+        }
+    }
+
+    public static String ipv4(String origin) {
+        return StrUtil.subBefore(origin, '.', true) + ".*";
+    }
+
+}

+ 246 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/EntityUtils.java

@@ -0,0 +1,246 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+
+/**
+ * <p>
+ * EntityUtils工具类用于基于Lambda表达式实现类型转换,具有如下优点:
+ * </p>
+ * <p>
+ * 1. 实现对象转对象;集合转集合;分页对象转分页对象
+ * </p>
+ * <p>
+ * 2. 实体类转Vo、实体类转DTO等都能应用此工具类
+ * </p>
+ * <p>
+ * 3. 转换参数均为不可变类型,业务更加安全
+ * </p>
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-12-06 14:23
+ */
+public final class EntityUtils {
+    private EntityUtils() {}
+
+    /**
+     * 将对象集合按照一定规则映射后收集为另一种形式的集合
+     *
+     * @param <R> 最终结果的泛型
+     * @param <S> 原始集合元素的类泛型
+     * @param <T> 转换后元素的中间状态泛型
+     * @param <A> 最终结果收集器泛型
+     * @param source 最原始的集合实例
+     * @param action 转换规则
+     * @param collector 收集器的类型
+     * @return 变换后存储新元素的集合实例
+     */
+    public static <R, S, T, A> R collectCommon(final Collection<S> source, Function<? super S, ? extends T> action,
+        Collector<? super T, A, R> collector) {
+        Objects.requireNonNull(source);
+        Objects.requireNonNull(collector);
+        return source.stream().map(action).collect(collector);
+    }
+
+    /**
+     * 将对象集合按照一定规则映射后收集为另一种形式的集合
+     *
+     * @param <S> 原始集合元素的类泛型
+     * @param <T> 转换后元素的中间状态泛型
+     * @param source 最原始的集合实例
+     * @param action 转换规则
+     * @return 变换后存储新元素的集合实例
+     */
+    @SafeVarargs
+    public static <S, T> Set<T> collectSet(Function<? super S, ? extends T> action, final S... source) {
+        Objects.requireNonNull(source);
+        return collectSet(Arrays.asList(source), action);
+    }
+
+    /**
+     * 将对象集合按照一定规则映射后收集为另一种形式的集合
+     *
+     * @param <S> 原始集合元素的类泛型
+     * @param <T> 转换后元素的中间状态泛型
+     * @param source 最原始的集合实例
+     * @param action 转换规则
+     * @return 变换后存储新元素的集合实例
+     */
+    public static <S, T> Set<T> collectSet(final Collection<S> source, Function<? super S, ? extends T> action) {
+        Objects.requireNonNull(source);
+        return source.stream().map(action).collect(Collectors.toSet());
+    }
+
+    /**
+     * 将对象集合按照一定规则映射后收集为List集合
+     *
+     * @param <S> 原始集合元素的类泛型
+     * @param source 最原始的集合实例
+     * @param action 转换规则
+     * @return 变换后存储新元素的集合实例
+     */
+    public static <S> List<? extends S> collectList(final Collection<S> source,
+        Function<? super S, ? extends S> action) {
+        return collectCommon(source, action, Collectors.toList());
+    }
+
+    /**
+     * 将对象以一种类型转换成另一种类型
+     *
+     * @param <T> 源数据类型
+     * @param <R> 变换后数据类型
+     * @param source 源List集合
+     * @param action 映射Lmabda表达式
+     * @return 变换后的类型,如果source为null,则返回null
+     */
+    public static <T, R> R toObj(final T source, final Function<? super T, ? extends R> action) {
+        Objects.requireNonNull(action);
+        return Optional.ofNullable(source).map(action).orElse(null);
+    }
+
+    /**
+     * <p>
+     * 将{@code List}集合换成另一种类型
+     * </p>
+     * 
+     * <pre>
+     * public class User {
+     *     private Long userId;
+     *     private String userName;
+     *     private String sex;
+     * }
+     * </pre>
+     * <p>
+     * 通过方法引用获得任意列组成的新{@code List}集合
+     * </p>
+     * 
+     * <pre>
+     *     List&lt;Long&gt; userIds = EntityUtils.toList(list,User::getUserId)
+     * </pre>
+     * <p>
+     * 在{@code User}类中添加有如下构造器
+     * </p>
+     * 
+     * <pre>
+     * public User(User user) {
+     *     if (user != null) {
+     *         this.userId = user.userId;
+     *         this.userName = user.userName;
+     *         this.sex = user.sex;
+     *     }
+     * }
+     * </pre>
+     * 
+     * <pre>
+     * public class UserVo extends User {
+     *     private String deptName;
+     *
+     *     public UserVo(User user) {
+     *         super(user);
+     *     }
+     * }
+     * </pre>
+     * 
+     * 通过如下代码可实现DO 转 VO
+     * 
+     * <pre>
+     *     List&lt;Long&gt; userVos = EntityUtils.toList(list,UserVo::new)
+     * </pre>
+     *
+     * @param <T> 源数据类型
+     * @param <R> 变换后数据类型
+     * @param source 源List集合
+     * @param action 映射Lmabda表达式
+     * @return 变换后的类型集合,如果source为null,则返回空集合
+     */
+    public static <T, R> List<R> toList(final Collection<T> source, final Function<? super T, ? extends R> action) {
+        Objects.requireNonNull(action);
+        if (Objects.nonNull(source)) {
+            return source.stream().map(action).collect(Collectors.toList());
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * 将Array数组以一种类型转换成另一种类型
+     *
+     * @param <T> 源数据类型
+     * @param <R> 变换后数据类型
+     * @param source 源Array数组
+     * @param action 映射Lmabda表达式
+     * @return 变换后的类型集合,如果source为null,则返回空集合
+     */
+    @SuppressWarnings("unchecked")
+    public static <T, R> R[] toArray(final T[] source, final Function<? super T, ? extends R> action) {
+        Objects.requireNonNull(action);
+        if (Objects.nonNull(source)) {
+            return (R[]) Arrays.stream(source).map(action).toArray();
+        }
+        return (R[]) new ArrayList<R>().toArray();
+    }
+
+    /**
+     * 将IPaged对象以一种类型转换成另一种类型
+     *
+     * @param source 源Page
+     * @param action 转换规则
+     * @param <E> 源Page类型泛型
+     * @param <T> 源实体类
+     * @param <R> 目标Page类型泛型
+     * @return 变换后的分页类型
+     */
+    public static <E extends IPage<T>, T, R> IPage<R> toPage(E source, final Function<? super T, ? extends R> action) {
+        Objects.requireNonNull(source);
+        Objects.requireNonNull(action);
+        return source.convert(action);
+    }
+
+    /**
+     * 将集合转化成Map
+     *
+     * @param lists 集合实例
+     * @param keyAction key转换规则
+     * @param valueAction value转换规则
+     * @param <T> 集合实体类泛型
+     * @param <K> Key实体类泛型
+     * @param <V> Value实体类泛型
+     * @return Map实例
+     */
+    public static <T, K, V> Map<K, V> toMap(final Collection<T> lists, Function<? super T, ? extends K> keyAction,
+        Function<? super T, ? extends V> valueAction) {
+        Objects.requireNonNull(lists);
+        Objects.requireNonNull(keyAction);
+        Objects.requireNonNull(valueAction);
+        return lists.stream().collect(Collectors.toMap(keyAction, valueAction));
+    }
+
+    /**
+     * 将List集合以一种类型转换成Set集合
+     *
+     * @param <T> 源数据类型
+     * @param <R> 变换后数据类型
+     * @param source 源List集合
+     * @param action 映射Lambda表达式
+     * @return 变换后的类型集合,如果source为null,则返回空集合
+     */
+    public static <T, R> Set<R> toSet(final Collection<T> source, final Function<? super T, ? extends R> action) {
+        Objects.requireNonNull(action);
+        if (Objects.nonNull(source)) {
+            return source.stream().map(action).collect(Collectors.toSet());
+        }
+        return new HashSet<>();
+    }
+}

+ 194 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ImageUtils.java

@@ -0,0 +1,194 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import org.apache.pdfbox.Loader;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.rendering.PDFRenderer;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 图片处理类
+ */
+public class ImageUtils {
+    public static List<String> convertPdfToImages(String pdfUrl) throws Exception {
+        URL url = new URL(pdfUrl);
+        InputStream is = url.openStream();
+        PDDocument document = Loader.loadPDF(is.readAllBytes());
+        PDFRenderer renderer = new PDFRenderer(document);
+        List<String> images = new ArrayList<>();
+        for (int i = 0; i < document.getNumberOfPages(); i++) {
+            BufferedImage image = renderer.renderImageWithDPI(i, 150);
+            images.add(bufferedImageToBase64(image));
+        }
+        document.close();
+        return images;
+    }
+
+    /**
+     * BufferedImage转base64
+     *
+     * @param bufferedImage
+     * @return
+     */
+    public static String bufferedImageToBase64(BufferedImage bufferedImage) {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        try {
+            // 设置图片格式
+            ImageIO.write(bufferedImage, "jpg", stream);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        byte[] bytes = Base64.getEncoder().encode(stream.toByteArray());
+        String base64 = new String(bytes);
+        return base64;
+    }
+
+    public static String imgUrlToBase64(String imageUrl) {
+        try {
+            URL url = new URL(imageUrl);
+            InputStream is = url.openStream();
+            byte[] bytes = is.readAllBytes(); // 从流中读取所有字节
+            is.close();
+            return Base64.getEncoder().encodeToString(bytes);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static String getFileExtensionOrigin(String path) {
+        if (path != null && path.contains(".")) {
+            String ext = path.substring(path.lastIndexOf(".") + 1);
+            if (ext.indexOf("?") > 0) {
+                ext = ext.substring(0, ext.indexOf("?"));
+            }
+            return ext.toLowerCase();
+        }
+        return "";
+    }
+
+    public static String getFileExtension(String path) {
+        String ext = getFileExtensionOrigin(path);
+        if ("pdf".equals(ext)) {
+            return "tiff";
+        }
+        return ext;
+    }
+
+    public static List<String> urlToBase64(String url) {
+        // 常见的图片文件扩展名列表
+        try {
+            List<String> imageExtensions = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg");
+            String ext = getFileExtensionOrigin(url);
+            if (imageExtensions.contains(ext)) {
+                List<String> list = new ArrayList<>();
+                list.add(imgUrlToBase64(url));
+                return list;
+            } else if (ext.equals("pdf")) {
+                return convertPdfToImages(url);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        return null;
+    }
+
+    public static List<Map<String, String>> urlToBase64Map(String url) {
+        // 常见的图片文件扩展名列表
+        try {
+            List<String> imageExtensions = Arrays.asList("jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg");
+            String ext = getFileExtensionOrigin(url);
+            if (imageExtensions.contains(ext)) {
+                List<Map<String, String>> list = new ArrayList<>();
+                list.add(imgUrlToBase64Map(url));
+                return list;
+            } else if (ext.equals("pdf")) {
+                return convertPdfToImagesMap(url);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        return null;
+    }
+
+    public static List<Map<String, String>> convertPdfToImagesMap(String pdfUrl) throws Exception {
+        URL url = new URL(pdfUrl);
+        InputStream is = url.openStream();
+        PDDocument document = Loader.loadPDF(is.readAllBytes());
+        PDFRenderer renderer = new PDFRenderer(document);
+        List<Map<String, String>> images = new ArrayList<>();
+        for (int i = 0; i < document.getNumberOfPages(); i++) {
+            BufferedImage image = renderer.renderImageWithDPI(i, 150);
+            images.add(bufferedImageToBase64Map(image));
+        }
+        document.close();
+        return images;
+    }
+
+    public static Map<String, String> imgUrlToBase64Map(String imageUrl) {
+        double width = 456.5;
+        try {
+            URL url = new URL(imageUrl);
+            InputStream is = url.openStream();
+            byte[] bytes = is.readAllBytes(); // 从流中读取所有字节
+            is.close();
+            ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+            BufferedImage image = ImageIO.read(bis);
+            String height = getImgHeight(image, width);
+            bis.close();
+            return Map.of("img", Base64.getEncoder().encodeToString(bytes), "width", String.valueOf(width), "height",
+                height);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public static Map<String, String> bufferedImageToBase64Map(BufferedImage bufferedImage) {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        double width = 456.5;
+        try {
+            // 设置图片格式
+            ImageIO.write(bufferedImage, "jpg", stream);
+            String height = getImgHeight(bufferedImage, width);
+            byte[] bytes = Base64.getEncoder().encode(stream.toByteArray());
+            String base64 = new String(bytes);
+            return Map.of("img", base64, "width", String.valueOf(width), "height", height);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 计算图片自适应后的高度
+     *
+     * @param image
+     * @return
+     */
+    private static String getImgHeight(BufferedImage image, double width) {
+        int widthInput = image.getWidth();
+        int heightInput = image.getHeight();
+        double height = (heightInput * width) / widthInput;
+        if (height > 645) {
+            height = 645;
+            width = (widthInput * height) / heightInput;
+        }
+        DecimalFormat df = new DecimalFormat("#0.0");
+        return df.format(height);
+    }
+}

+ 74 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/Ip2Region.java

@@ -0,0 +1,74 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.util.concurrent.TimeUnit;
+
+import org.lionsoul.ip2region.xdb.Searcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Ip工具类
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-12-21 14:41
+ */
+public final class Ip2Region {
+
+    @SuppressWarnings("checkstyle:ConstantName")
+    private static final Logger log = LoggerFactory.getLogger(Ip2Region.class);
+
+    private static byte[] cBuff;
+
+    /**
+     * 从 dbPath 加载整个 xdb 到内存。
+     */
+    public Ip2Region(String xdbPath) {
+
+        try {
+            cBuff = Searcher.loadContentFromFile(xdbPath);
+        } catch (Exception e) {
+            log.warn("failed to load content", e);
+        }
+
+    }
+
+    /**
+     * 获取IP属地
+     * <p>
+     * 格式为:[province]·[city]
+     * </p>
+     *
+     * @param remoteIp 远程IP
+     * @return IP属地
+     */
+    public String toRegion(final String remoteIp) {
+
+        Searcher searcher;
+        try {
+            searcher = Searcher.newWithBuffer(cBuff);
+        } catch (Exception e) {
+            log.error("failed to create content cached searcher: %s\n", e);
+            return null;
+        }
+
+        try {
+            // 查询IP属地
+            long sTime = System.nanoTime();
+            String search = searcher.search(remoteIp);
+            long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime);
+            log.info("{region: {}, ioCount: {}, took: {} μs}", search, searcher.getIOCount(), cost);
+
+            // 封装为固定格式[province]·[city]
+            search = search.replace("|0", "").replace("0|", "");
+            String[] split = search.split("\\|");
+            return (split.length == 1) ? split[0]
+                : ((split.length == 2) ? split[0] + "·" + split[1] : split[1] + "·" + split[2]);
+
+        } catch (Exception e) {
+            log.error("获取IP属地失败", e);
+            throw new RuntimeException(e.getMessage());
+        }
+    }
+
+}

+ 21 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/KeyStrResolver.java

@@ -0,0 +1,21 @@
+package com.yaoyicloud.easier.common.core.util;
+
+public interface KeyStrResolver {
+
+    /**
+     * 字符串加工
+     *
+     * @param in 输入字符串
+     * @param split 分割符
+     * @return 输出字符串
+     */
+    String extract(String in, String split);
+
+    /**
+     * 字符串获取
+     *
+     * @return 模块返回字符串
+     */
+    String key();
+
+}

+ 103 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/MathUtils.java

@@ -0,0 +1,103 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.math.BigDecimal;
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+
+/**
+ * 数学计算处理类
+ */
+public class MathUtils {
+
+    /**
+     * 计算工具 比例、百分比
+     * @param numStr1
+     * @param numStr2
+     * @param isPercent
+     * @return
+     */
+    public static String numericPercent(String numStr1, String numStr2, boolean isPercent, boolean isRise) {
+        try {
+            double numerator = Double.parseDouble(numStr1.replaceAll("-", "0"));
+            double denominator = Double.parseDouble(numStr2.replaceAll("-", "0"));
+            if (denominator == 0) {
+                return "0";
+            }
+            double percentage;
+            if (isPercent) {
+                if (isRise) {
+                    percentage = (numerator / denominator - 1) * 100;
+                } else {
+                    percentage = (numerator / denominator) * 100;
+                }
+            } else {
+                if (isRise) {
+                    percentage = (numerator / denominator - 1);
+                } else {
+                    percentage = (numerator / denominator);
+                }
+            }
+            NumberFormat nf = NumberFormat.getInstance();
+            nf.setMaximumFractionDigits(2);
+            String returnValue;
+            if (isPercent) {
+                returnValue = nf.format(percentage) + "%";
+            } else {
+                returnValue = nf.format(percentage);
+            }
+            return returnValue;
+        } catch (NumberFormatException e) {
+            e.printStackTrace();
+            return "0";
+        }
+    }
+
+    /**
+     * 计算工具
+     * @param num1
+     * @param num2
+     * @param isPercent
+     * @param isRise
+     * @return
+     */
+    public static BigDecimal numericPercent(BigDecimal num1, BigDecimal num2, boolean isPercent, boolean isRise) {
+        num1 = num1 != null ? num1 : new BigDecimal(0);
+        num2 = num2 != null ? num2 : new BigDecimal(0);
+        String result = numericPercent(String.valueOf(num1), String.valueOf(num2), isPercent, isRise);
+        result = result.replaceAll("%", "").replaceAll(",", "");
+        return new BigDecimal(result);
+    }
+
+    /**
+     * 计算工具 计算百分比的平均值
+     * @param percent1
+     * @param percent2
+     * @param percent3
+     * @return
+     */
+    public static String numericPercentAvg(String percent1, String percent2, String percent3, boolean isPercent) {
+        // 校验空值
+        if (percent1 == null || percent2 == null || percent3 == null) {
+            return "0";
+        }
+
+        // 统一转换
+        double num1 = Double.parseDouble(percent1.trim().replaceAll("%", ""));
+        double num2 = Double.parseDouble(percent2.trim().replaceAll("%", ""));
+        double num3 = Double.parseDouble(percent3.trim().replaceAll("%", ""));
+
+        // 计算平均值
+        double average = (num1 + num2 + num3) / 3.0;
+
+        // 格式化输出
+        DecimalFormat df;
+        if (isPercent) {
+            df = new DecimalFormat("0.00%");
+            return df.format(average / 100.0);  // 转换为百分比格式
+        } else {
+            df = new DecimalFormat("0.00");
+            return df.format(average);  // 转换为比例
+        }
+    }
+
+}

+ 52 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/MsgUtils.java

@@ -0,0 +1,52 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.util.Locale;
+
+import org.springframework.context.MessageSource;
+
+import lombok.experimental.UtilityClass;
+
+/**
+ * 信息工具类
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-24 23:56
+ */
+@UtilityClass
+public class MsgUtils {
+
+    /**
+     * 通过code 获取中文错误信息
+     *
+     * @param code 状态码
+     * @return 信息
+     */
+    public String getMessage(String code) {
+        MessageSource messageSource = SpringContextHolder.getBean("easierMessageSource");
+        return messageSource.getMessage(code, null, Locale.CHINA);
+    }
+
+    /**
+     * 通过code 和参数获取中文错误信息
+     *
+     * @param code 状态码
+     * @return 信息
+     */
+    public String getMessage(String code, Object... objects) {
+        MessageSource messageSource = SpringContextHolder.getBean("easierMessageSource");
+        return messageSource.getMessage(code, objects, Locale.CHINA);
+    }
+
+    /**
+     * security 通过code 和参数获取中文错误信息
+     *
+     * @param code 状态码
+     * @return 信息
+     */
+    public String getSecurityMessage(String code, Object... objects) {
+        MessageSource messageSource = SpringContextHolder.getBean("securityMessageSource");
+        return messageSource.getMessage(code, objects, Locale.CHINA);
+    }
+
+}

+ 94 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/RespResult.java

@@ -0,0 +1,94 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.Objects;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.yaoyicloud.easier.common.core.constant.CommonConstants;
+
+import cn.hutool.core.date.DatePattern;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+/**
+ * 响应信息主体
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-11 13:26
+ */
+@SuppressWarnings("checkstyle:JavadocType")
+@Getter
+@Builder
+@ToString
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class RespResult<T> implements Serializable {
+
+    private static final long serialVersionUID = -2762015630844641571L;
+
+    @Setter
+    private String code;
+
+    @Setter
+    private String msg;
+
+    @Getter
+    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN)
+    private final LocalDateTime timestamp = LocalDateTime.now();
+
+    @Setter
+    private T data;
+
+    public static <T> RespResult<T> ok() {
+        return restResult(null, CommonConstants.SUCCESS, CommonConstants.SUCCESS_MSG);
+    }
+
+    public static <T> RespResult<T> ok(T data) {
+        return restResult(data, CommonConstants.SUCCESS, CommonConstants.SUCCESS_MSG);
+    }
+
+    public static <T> RespResult<T> ok(T data, String msg) {
+        return restResult(data, CommonConstants.SUCCESS, msg);
+    }
+
+    public static <T> RespResult<T> failed() {
+        return restResult(null, CommonConstants.FAIL, CommonConstants.FAIL_MSG);
+    }
+
+    public static <T> RespResult<T> failed(String msg) {
+        return restResult(null, CommonConstants.FAIL, msg);
+    }
+
+    public static <T> RespResult<T> failed(T data) {
+        return restResult(data, CommonConstants.FAIL, CommonConstants.FAIL_MSG);
+    }
+
+    public static <T> RespResult<T> failed(T data, String msg) {
+        return restResult(data, CommonConstants.FAIL, msg);
+    }
+
+    public static <T> RespResult<T> failed(String code, String msg) {
+        return restResult(null, code, msg);
+    }
+
+    static <T> RespResult<T> restResult(T data, String code, String msg) {
+        RespResult<T> apiResult = new RespResult<>();
+        apiResult.setCode(code);
+        apiResult.setData(data);
+        apiResult.setMsg(msg);
+        return apiResult;
+    }
+
+    public boolean isOk() {
+        return Objects.equals(this.code, CommonConstants.SUCCESS);
+    }
+
+}

+ 303 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/RetOps.java

@@ -0,0 +1,303 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import com.yaoyicloud.easier.common.core.constant.CommonConstants;
+
+import cn.hutool.core.util.ObjectUtil;
+
+/**
+ * 简化{@code RespResult<T>} 的访问操作,例子
+ * 
+ * <pre>
+ * RespResult<Integer> result = RespResult.ok(0);
+ * // 使用场景1: 链式操作: 断言然后消费
+ * RetOps.of(result).assertCode(-1, r -> new RuntimeException("error " + r.getCode()))
+ *     .assertDataNotEmpty(r -> new IllegalStateException("oops!")).useData(System.out::println);
+ *
+ * // 使用场景2: 读取原始值(data),这里返回的是Optional
+ * RetOps.of(result).getData().orElse(null);
+ *
+ * // 使用场景3: 类型转换
+ * RespResult<String> s = RetOps.of(result).assertDataNotNull(r -> new IllegalStateException("nani??"))
+ *     .map(i -> Integer.toHexString(i)).peek();
+ * </pre>
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-18 15:28
+ */
+@SuppressWarnings("checkstyle:JavadocType")
+public class RetOps<T> {
+
+    /**
+     * 状态码为成功
+     */
+    public static final Predicate<RespResult<?>> CODE_SUCCESS = r -> CommonConstants.SUCCESS == r.getCode();
+
+    /**
+     * 数据有值
+     */
+    public static final Predicate<RespResult<?>> HAS_DATA = r -> ObjectUtil.isNotEmpty(r.getData());
+
+    /**
+     * 数据有值,并且包含元素
+     */
+    public static final Predicate<RespResult<?>> HAS_ELEMENT = r -> ObjectUtil.isNotEmpty(r.getData());
+
+    /**
+     * 状态码为成功并且有值
+     */
+    public static final Predicate<RespResult<?>> DATA_AVAILABLE = CODE_SUCCESS.and(HAS_DATA);
+
+    private final RespResult<T> original;
+
+    // ~ 初始化
+    // ===================================================================================================
+
+    RetOps(RespResult<T> original) {
+        this.original = original;
+    }
+
+    public static <T> RetOps<T> of(RespResult<T> original) {
+        return new RetOps<>(Objects.requireNonNull(original));
+    }
+
+    // ~ 杂项方法
+    // ===================================================================================================
+
+    /**
+     * 观察原始值
+     *
+     * @return R
+     */
+    public RespResult<T> peek() {
+        return original;
+    }
+
+    /**
+     * 读取{@code code}的值
+     *
+     * @return 返回code的值
+     */
+    public String getCode() {
+        return original.getCode();
+    }
+
+    /**
+     * 读取{@code data}的值
+     *
+     * @return 返回 Optional 包装的data
+     */
+    public Optional<T> getData() {
+        return Optional.ofNullable(original.getData());
+    }
+
+    /**
+     * 有条件地读取{@code data}的值
+     *
+     * @param predicate 断言函数
+     * @return 返回 Optional 包装的data,如果断言失败返回empty
+     */
+    public Optional<T> getDataIf(Predicate<? super RespResult<?>> predicate) {
+        return predicate.test(original) ? getData() : Optional.empty();
+    }
+
+    /**
+     * 读取{@code msg}的值
+     *
+     * @return 返回Optional包装的 msg
+     */
+    public Optional<String> getMsg() {
+        return Optional.ofNullable(original.getMsg());
+    }
+
+    /**
+     * 对{@code code}的值进行相等性测试
+     *
+     * @param value 基准值
+     * @return 返回ture表示相等
+     */
+    public boolean codeEquals(String value) {
+        return Objects.equals(original.getCode(), value);
+    }
+
+    /**
+     * 对{@code code}的值进行相等性测试
+     *
+     * @param value 基准值
+     * @return 返回ture表示不相等
+     */
+    public boolean codeNotEquals(String value) {
+        return !codeEquals(value);
+    }
+
+    /**
+     * 是否成功
+     *
+     * @return 返回ture表示成功
+     * @see CommonConstants#SUCCESS
+     */
+    public boolean isSuccess() {
+        return codeEquals(CommonConstants.SUCCESS);
+    }
+
+    /**
+     * 是否失败
+     *
+     * @return 返回ture表示失败
+     */
+    public boolean notSuccess() {
+        return !isSuccess();
+    }
+
+    // ~ 链式操作
+    // ===================================================================================================
+
+    /**
+     * 断言{@code code}的值
+     *
+     * @param expect 预期的值
+     * @param func 用户函数,负责创建异常对象
+     * @param <Ex> 异常类型
+     * @return 返回实例,以便于继续进行链式操作
+     * @throws Ex 断言失败时抛出
+     */
+    public <Ex extends Exception> RetOps<T> assertCode(String expect,
+        Function<? super RespResult<T>, ? extends Ex> func) throws Ex {
+        if (codeNotEquals(expect)) {
+            throw func.apply(original);
+        }
+        return this;
+    }
+
+    /**
+     * 断言成功
+     *
+     * @param func 用户函数,负责创建异常对象
+     * @param <Ex> 异常类型
+     * @return 返回实例,以便于继续进行链式操作
+     * @throws Ex 断言失败时抛出
+     */
+    public <Ex extends Exception> RetOps<T> assertSuccess(Function<? super RespResult<T>, ? extends Ex> func)
+        throws Ex {
+        return assertCode(CommonConstants.SUCCESS, func);
+    }
+
+    /**
+     * 断言业务数据有值
+     *
+     * @param func 用户函数,负责创建异常对象
+     * @param <Ex> 异常类型
+     * @return 返回实例,以便于继续进行链式操作
+     * @throws Ex 断言失败时抛出
+     */
+    public <Ex extends Exception> RetOps<T> assertDataNotNull(Function<? super RespResult<T>, ? extends Ex> func)
+        throws Ex {
+        if (Objects.isNull(original.getData())) {
+            throw func.apply(original);
+        }
+        return this;
+    }
+
+    /**
+     * 断言业务数据有值,并且包含元素
+     *
+     * @param func 用户函数,负责创建异常对象
+     * @param <Ex> 异常类型
+     * @return 返回实例,以便于继续进行链式操作
+     * @throws Ex 断言失败时抛出
+     */
+    public <Ex extends Exception> RetOps<T> assertDataNotEmpty(Function<? super RespResult<T>, ? extends Ex> func)
+        throws Ex {
+        if (ObjectUtil.isEmpty(original.getData())) {
+            throw func.apply(original);
+        }
+        return this;
+    }
+
+    /**
+     * 对业务数据(data)转换
+     *
+     * @param mapper 业务数据转换函数
+     * @param <U> 数据类型
+     * @return 返回新实例,以便于继续进行链式操作
+     */
+    public <U> RetOps<U> map(Function<? super T, ? extends U> mapper) {
+        RespResult<U> result =
+            RespResult.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
+        return of(result);
+    }
+
+    /**
+     * 对业务数据(data)转换
+     *
+     * @param predicate 断言函数
+     * @param mapper 业务数据转换函数
+     * @param <U> 数据类型
+     * @return 返回新实例,以便于继续进行链式操作
+     * @see RetOps#CODE_SUCCESS
+     * @see RetOps#HAS_DATA
+     * @see RetOps#HAS_ELEMENT
+     * @see RetOps#DATA_AVAILABLE
+     */
+    public <U> RetOps<U> mapIf(Predicate<? super RespResult<T>> predicate, Function<? super T, ? extends U> mapper) {
+        RespResult<U> result =
+            RespResult.restResult(mapper.apply(original.getData()), original.getCode(), original.getMsg());
+        return of(result);
+    }
+
+    // ~ 数据消费
+    // ===================================================================================================
+
+    /**
+     * 消费数据,注意此方法保证数据可用
+     *
+     * @param consumer 消费函数
+     */
+    public void useData(Consumer<? super T> consumer) {
+        consumer.accept(original.getData());
+    }
+
+    /**
+     * 条件消费(错误代码匹配某个值)
+     *
+     * @param consumer 消费函数
+     * @param codes 错误代码集合,匹配任意一个则调用消费函数
+     */
+    public void useDataOnCode(Consumer<? super T> consumer, String... codes) {
+        useDataIf(o -> Arrays.stream(codes).filter(c -> original.getCode().equals(c)).findFirst().isPresent(),
+            consumer);
+    }
+
+    /**
+     * 条件消费(错误代码表示成功)
+     *
+     * @param consumer 消费函数
+     */
+    public void useDataIfSuccess(Consumer<? super T> consumer) {
+        useDataIf(CODE_SUCCESS, consumer);
+    }
+
+    /**
+     * 条件消费
+     *
+     * @param predicate 断言函数
+     * @param consumer 消费函数,断言函数返回{@code true}时被调用
+     * @see RetOps#CODE_SUCCESS
+     * @see RetOps#HAS_DATA
+     * @see RetOps#HAS_ELEMENT
+     * @see RetOps#DATA_AVAILABLE
+     */
+    public void useDataIf(Predicate<? super RespResult<T>> predicate, Consumer<? super T> consumer) {
+        if (predicate.test(original)) {
+            consumer.accept(original.getData());
+        }
+    }
+
+}

+ 106 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/SpringContextHolder.java

@@ -0,0 +1,106 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.util.Map;
+
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+@Lazy(false)
+public class SpringContextHolder implements BeanFactoryPostProcessor, ApplicationContextAware, DisposableBean {
+
+    private static ConfigurableListableBeanFactory beanFactory;
+
+    private static ApplicationContext applicationContext = null;
+
+    /**
+     * 取得存储在静态变量中的ApplicationContext.
+     */
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    /**
+     * BeanFactoryPostProcessor, 注入Context到静态变量中.
+     */
+    @Override
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
+        SpringContextHolder.beanFactory = factory;
+    }
+
+    /**
+     * 实现ApplicationContextAware接口, 注入Context到静态变量中.
+     */
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        SpringContextHolder.applicationContext = applicationContext;
+    }
+
+    public static ListableBeanFactory getBeanFactory() {
+        return null == beanFactory ? applicationContext : beanFactory;
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T getBean(String name) {
+        return (T) getBeanFactory().getBean(name);
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, Map<Bean名称,实现类></>
+     */
+    public static <T> Map<String, T> getBeansOfType(Class<T> type) {
+        return getBeanFactory().getBeansOfType(type);
+    }
+
+    /**
+     * 从静态变量applicationContext中取得Bean, 自动转型为所赋值对象的类型.
+     */
+    public static <T> T getBean(Class<T> requiredType) {
+        return getBeanFactory().getBean(requiredType);
+    }
+
+    /**
+     * 清除SpringContextHolder中的ApplicationContext为Null.
+     */
+    public static void clearHolder() {
+        if (log.isDebugEnabled()) {
+            log.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);
+        }
+        applicationContext = null;
+    }
+
+    /**
+     * 发布事件
+     * 
+     * @param event
+     */
+    public static void publishEvent(ApplicationEvent event) {
+        if (applicationContext == null) {
+            return;
+        }
+        applicationContext.publishEvent(event);
+    }
+
+    /**
+     * 实现DisposableBean接口, 在Context关闭时清理静态变量.
+     */
+    @Override
+    public void destroy() {
+        SpringContextHolder.clearHolder();
+    }
+
+}

+ 43 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ThreadLocalKit.java

@@ -0,0 +1,43 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * ThreadLocal组件
+ *
+ * @author jimmy
+ * @date 2022/05/11 14:38
+ */
+@SuppressWarnings("checkstyle:JavadocType")
+@Component
+public final class ThreadLocalKit<T> {
+
+    private final ThreadLocal<T> threadLocal = new ThreadLocal<>();
+
+    /**
+     * 获取当前线程的存的变量
+     *
+     * @return 线程公共变量
+     */
+    public T get() {
+        return threadLocal.get();
+    }
+
+    /**
+     * 设置当前线程的存的变量
+     *
+     * @param t 目标变量
+     */
+    public void set(T t) {
+        threadLocal.set(t);
+
+    }
+
+    /**
+     * 移除当前线程的存的变量
+     */
+    public void remove() {
+        threadLocal.remove();
+    }
+
+}

+ 30 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/ValidGroup.java

@@ -0,0 +1,30 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 校验类型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-11 13:27
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public final class ValidGroup {
+
+    /**
+     * 插入组
+     */
+    public interface Insert {
+
+    }
+
+    /**
+     * 编辑组
+     */
+    public interface Update {
+
+    }
+
+}

+ 237 - 0
easier-common/easier-common-core/src/main/java/com/yaoyicloud/easier/common/core/util/WebUtils.java

@@ -0,0 +1,237 @@
+package com.yaoyicloud.easier.common.core.util;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Optional;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.http.MediaType;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.context.request.RequestAttributes;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+import org.springframework.web.method.HandlerMethod;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * Miscellaneous utilities for web applications.
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-11 13:31
+ */
+@Slf4j
+@UtilityClass
+public class WebUtils extends org.springframework.web.util.WebUtils {
+
+    @SuppressWarnings("checkstyle:MemberName")
+    private final String BASIC_ = "Basic ";
+
+    @SuppressWarnings("checkstyle:MemberName")
+    private final String UNKNOWN = "unknown";
+
+    /**
+     * 判断是否ajax请求 spring ajax 返回含有 ResponseBody 或者 RestController注解
+     *
+     * @param handlerMethod HandlerMethod
+     * @return 是否ajax请求
+     */
+    public boolean isBody(HandlerMethod handlerMethod) {
+        ResponseBody responseBody = ClassUtils.getAnnotation(handlerMethod, ResponseBody.class);
+        return responseBody != null;
+    }
+
+    /**
+     * 读取cookie
+     *
+     * @param name cookie name
+     * @return cookie value
+     */
+    public String getCookieVal(String name) {
+        HttpServletRequest request = WebUtils.getRequest();
+        Assert.notNull(request, "request from RequestContextHolder is null");
+        return getCookieVal(request, name);
+    }
+
+    /**
+     * 读取cookie
+     *
+     * @param request HttpServletRequest
+     * @param name cookie name
+     * @return cookie value
+     */
+    public String getCookieVal(HttpServletRequest request, String name) {
+        Cookie cookie = getCookie(request, name);
+        return cookie != null ? cookie.getValue() : null;
+    }
+
+    /**
+     * 清除 某个指定的cookie
+     *
+     * @param response HttpServletResponse
+     * @param key cookie key
+     */
+    public void removeCookie(HttpServletResponse response, String key) {
+        setCookie(response, key, null, 0);
+    }
+
+    /**
+     * 设置cookie
+     *
+     * @param response HttpServletResponse
+     * @param name cookie name
+     * @param value cookie value
+     * @param maxAgeInSeconds maxage
+     */
+    public void setCookie(HttpServletResponse response, String name, String value, int maxAgeInSeconds) {
+        Cookie cookie = new Cookie(name, value);
+        cookie.setPath("/");
+        cookie.setMaxAge(maxAgeInSeconds);
+        cookie.setHttpOnly(true);
+        response.addCookie(cookie);
+    }
+
+    /**
+     * 获取 HttpServletRequest
+     *
+     * @return {HttpServletRequest}
+     */
+    public HttpServletRequest getRequest() {
+        try {
+            RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
+            return ((ServletRequestAttributes) requestAttributes).getRequest();
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 获取 HttpServletResponse
+     *
+     * @return {HttpServletResponse}
+     */
+    public HttpServletResponse getResponse() {
+        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
+    }
+
+    /**
+     * 返回json
+     *
+     * @param response HttpServletResponse
+     * @param result 结果对象
+     */
+    public void renderJson(HttpServletResponse response, Object result) {
+        renderJson(response, result, MediaType.APPLICATION_JSON_VALUE);
+    }
+
+    /**
+     * 返回json
+     *
+     * @param response HttpServletResponse
+     * @param result 结果对象
+     * @param contentType contentType
+     */
+    public void renderJson(HttpServletResponse response, Object result, String contentType) {
+        response.setCharacterEncoding("UTF-8");
+        response.setContentType(contentType);
+        try (PrintWriter out = response.getWriter()) {
+            out.append(JSONUtil.toJsonStr(result));
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 获取ip
+     *
+     * @return {String}
+     */
+    public String getIP() {
+        return getIP(WebUtils.getRequest());
+    }
+
+    /**
+     * 获取ip
+     *
+     * @param request HttpServletRequest
+     * @return {String}
+     */
+    public String getIP(HttpServletRequest request) {
+        Assert.notNull(request, "HttpServletRequest is null");
+        String ip = request.getHeader("X-Requested-For");
+        if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("X-Forwarded-For");
+        }
+        if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("Proxy-Client-IP");
+        }
+        if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("WL-Proxy-Client-IP");
+        }
+        if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_CLIENT_IP");
+        }
+        if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
+        }
+        if (StrUtil.isBlank(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
+            ip = request.getRemoteAddr();
+        }
+        return StrUtil.isBlank(ip) ? null : ip.split(",")[0];
+    }
+
+    /**
+     * 解析 client id
+     *
+     * @param header
+     * @param defVal
+     * @return 如果解析失败返回默认值
+     */
+    @SuppressWarnings("checkstyle:ReturnCount")
+    public String extractClientId(String header, final String defVal) {
+
+        if (header == null || !header.startsWith(BASIC_)) {
+            log.debug("请求头中client信息为空: {}", header);
+            return defVal;
+        }
+        byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
+        byte[] decoded;
+        try {
+            decoded = Base64.decode(base64Token);
+        } catch (IllegalArgumentException e) {
+            log.debug("Failed to decode basic authentication token: {}", header);
+            return defVal;
+        }
+
+        String token = new String(decoded, StandardCharsets.UTF_8);
+
+        int delim = token.indexOf(":");
+
+        if (delim == -1) {
+            log.debug("Invalid basic authentication token: {}", header);
+            return defVal;
+        }
+        return token.substring(0, delim);
+    }
+
+    /**
+     * 从请求头中解析 client id
+     *
+     * @param header 头信息
+     * @return 客户端ID
+     */
+    public Optional<String> extractClientId(String header) {
+        return Optional.ofNullable(extractClientId(header, null));
+    }
+
+}

+ 7 - 0
easier-common/easier-common-core/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,7 @@
+com.yaoyicloud.easier.common.core.config.JacksonConfiguration
+com.yaoyicloud.easier.common.core.config.MessageSourceConfiguration
+com.yaoyicloud.easier.common.core.config.RestTemplateConfiguration
+com.yaoyicloud.easier.common.core.util.SpringContextHolder
+com.yaoyicloud.easier.common.core.config.Ip2RegionConfiguration
+com.yaoyicloud.easier.common.core.util.ThreadLocalKit
+com.yaoyicloud.easier.common.core.config.EasierCommonProperties

+ 13 - 0
easier-common/easier-common-core/src/main/resources/banner.txt

@@ -0,0 +1,13 @@
+${AnsiColor.BRIGHT_YELLOW}
+========================================================================
+
+,--.   ,--.,---.   ,-----.,--.   ,--.,--.,--.   ,--.,--. ,--.,--.  ,--.
+ \  `.'  //  O  \ '  .-.  '\  `.'  / |  | \  `.'  / |  | |  ||  ,'.|  |
+  '.    /|  .-.  ||  | |  | '.    /  |  |  '.    /  |  | |  ||  |' '  |
+    |  | |  | |  |'  '-'  '   |  |   |  |    |  |   '  '-'  '|  | `   |
+    `--' `--' `--' `-----'    `--'   `--'    `--'    `-----' `--'  `--'
+
+Powered  by Spring Boot${spring-boot.formatted-version}
+Designed by Yaoyi Cloud
+========================================================================
+${AnsiColor.DEFAULT}

+ 109 - 0
easier-common/easier-common-core/src/main/resources/i18n/messages_zh_CN.properties

@@ -0,0 +1,109 @@
+# common error message
+com.data.get.error=\u8FD4\u56DE\u7ED3\u679C\u9519\u8BEF
+com.data.update.error=\u66F4\u65B0\u6570\u636E\u5931\u8D25
+com.data.violation.error=\u5F53\u524D\u72B6\u6001\u4E0D\u5141\u8BB8\u8FD9\u4E2A\u64CD\u4F5C
+com.data.expiry.error=\u670D\u52A1\u5DF2\u5230\u671F\uFF0C\u8BF7\u8054\u7CFB\u5BA2\u670D\u8FDB\u884C\u7EED\u8D39
+# workspace error message
+sys.tenant.del.plt.error=\u5E73\u53F0\u79DF\u6237\u4E0D\u80FD\u5220\u9664
+sys.tenant.lic.date.error=\u8D77\u6B62\u65F6\u95F4\u5012\u7F6E\uFF0C\u8BF7\u66F4\u6B63\u540E\u91CD\u8BD5
+sys.dept.dup.name.error=\u540C\u7EA7\u5B58\u5728\u91CD\u540D\u7684\u90E8\u95E8
+sys.user.username.not.exist=\u7528\u6237\u4E0D\u5B58\u5728
+sys.user.password.old.missing=\u539F\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
+sys.user.password.new.missing=\u65B0\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
+sys.user.password.wrong=\u5BC6\u7801\u8F93\u5165\u9519\u8BEF
+sys.user.password.new.diff=\u65B0\u5BC6\u7801\u4E24\u6B21\u4E0D\u60F3\u7B49
+sys.user.update.passwordError=\u539F\u5BC6\u7801\u9519\u8BEF\uFF0C\u4FEE\u6539\u5931\u8D25
+sys.user.query.error=\u83B7\u53D6\u5F53\u524D\u7528\u6237\u4FE1\u606F\u5931\u8D25
+sys.user.import.succeed=\u7528\u6237\u5BFC\u5165\u6210\u529F\uFF0C\u9ED8\u8BA4\u5BC6\u7801\u4E3A\u624B\u673A\u53F7
+sys.user.username.existing={0} \u7528\u6237\u540D\u5DF2\u5B58\u5728
+sys.user.phone.existing={0} \u624B\u673A\u53F7\u5DF2\u5B58\u5728
+sys.user.userInfo.empty={0} \u7528\u6237\u4FE1\u606F\u4E3A\u7A7A
+sys.dept.deptName.inexistence={0} \u90E8\u95E8\u540D\u79F0\u4E0D\u5B58\u5728
+sys.post.postName.inexistence={0} \u5C97\u4F4D\u540D\u79F0\u4E0D\u5B58\u5728
+sys.post.nameOrCode.existing={0} {1} \u5C97\u4F4D\u540D\u6216\u5C97\u4F4D\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728
+sys.role.roleName.inexistence={0} \u89D2\u8272\u540D\u79F0\u4E0D\u5B58\u5728
+sys.role.nameOrCode.existing={0} {1} \u89D2\u8272\u540D\u6216\u89D2\u8272\u7F16\u7801\u5DF2\u7ECF\u5B58\u5728
+sys.param.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u53C2\u6570\u4E0D\u80FD\u5220\u9664
+sys.param.config.error={0} \u7CFB\u7EDF\u53C2\u6570\u914D\u7F6E\u9519\u8BEF
+sys.menu.delete.existing=\u83DC\u5355\u542B\u6709\u4E0B\u7EA7\u4E0D\u80FD\u5220\u9664
+sys.app.sms.often=\u9A8C\u8BC1\u7801\u53D1\u9001\u8FC7\u9891\u7E41
+sys.app.phone.unregistered={0} \u624B\u673A\u53F7\u672A\u6CE8\u518C
+sys.dict.delete.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u5220\u9664
+sys.dict.update.system=\u7CFB\u7EDF\u5185\u7F6E\u5B57\u5178\u9879\u76EE\u4E0D\u80FD\u4FEE\u6539
+sys.connect.cp.dept.sync.error=\u83B7\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u90E8\u95E8\u5217\u8868\u5931\u8D25
+sys.connect.cp.user.sync.error=\u83B7\u53D6\u4F01\u4E1A\u5FAE\u4FE1\u7528\u6237\u5217\u8868\u5931\u8D25
+sys.tenant.permission.error=\u6CA1\u6709\u8BBF\u95EE\u8FD9\u4E2A\u79DF\u6237\u7684\u6743\u9650
+sys.tenant.not_existed.error=\u79DF\u6237\u53EF\u80FD\u4E0D\u5B58\u5728
+# filerepo error message
+cms.file.not.existed.error=\u6587\u4EF6\u4E0D\u5B58\u5728
+cms.file.dup.name.error=\u5B58\u5728\u76F8\u540C\u540D\u79F0\u7684\u6587\u4EF6
+cms.file.upload.error=\u6587\u4EF6\u4E0A\u4F20\u5931\u8D25
+cms.ent.bond.error=\u7ED1\u5B9A\u5173\u7CFB\u5DF2\u5B58\u5728\uFF0C\u8BF7\u52FF\u91CD\u590D\u7ED1\u5B9A
+cms.ent.bond.cap.error=\u53EF\u6DFB\u52A0\u4F01\u4E1A\u7528\u91CF\u4E0D\u8DB3
+cms.ent.bond.missing.error=\u4F01\u4E1A\u7ED1\u5B9A\u5173\u7CFB\u4E0D\u5B58\u5728
+cms.ent.risk.info.miss=\u672A\u83B7\u53D6\u5230\u4F01\u4E1A\u98CE\u63A7\u8FDC\u7AEF\u4FE1\u606F
+csm.ent.risk.info.resp.error=\u4F01\u4E1A\u98CE\u63A7\u4FE1\u606F\u8FDC\u7AEF\u54CD\u5E94\u5931\u8D25\uFF0C\u54CD\u5E94\u7801: {0}\uFF0C\u4FE1\u606F: {1}
+csm.ent.risk.inv.resp.error=\u4E0A\u4F20\u5931\u8D25\uFF08{0}\uFF09
+csm.ent.risk.inv.recog.error=\u8FDC\u7AEF\u53D1\u7968\u8BC6\u522B\u5931\u8D25
+csm.ent.risk.info.decl.period.error=\u5F53\u524D\u7533\u62A5\u5468\u671F\u4E3A\uFF1A{0}\u0020\u81F3 {1}\u0020\u0020\uFF0C\u8BF7\u8054\u7CFB\u9080\u8BF7\u65B9\u91CD\u53D1\u9080\u8BF7\uFF01
+csm.ent.risk.info.decl.key.not.match.error=\u7533\u62A5\u63D0\u53D6\u7801\u4E0D\u5339\u914D\uFF0C\u8BF7\u6838\u5B9E\uFF01
+cms.ent.risk.info.decl.not.existed.error=\u672A\u67E5\u8BE2\u5230\u6709\u6548\u7533\u62A5\u4FE1\u606F
+cms.ent.risk.info.decl.state.error=\u5F53\u524D\u7533\u62A5\u72B6\u6001\u4E3A\uFF1A{0}\uFF0C\u4E0D\u652F\u6301\u63D0\u4EA4\u6216\u4FDD\u5B58\u64CD\u4F5C
+cms.ent.risk.info.decl.getinfo.error=\u914D\u7F6E\u5F02\u5E38\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458
+cms.ent.risk.info.entname.error=\u8BF7\u68C0\u67E5\u4F01\u4E1A\u540D\u79F0\u4E0E\u7EDF\u4E00\u4FE1\u7528\u4EE3\u7801\u662F\u5426\u5339\u914D\uFF0C\u5386\u53F2\u8BB0\u5F55\u4E3A {0}\uFF0C\u5F53\u524D\u63D0\u4EA4\u4E3A {1}
+cms.ent.auth.link.address.error=\u6388\u6743\u94FE\u63A5\u5730\u5740\u83B7\u53D6\u5931\u8D25
+cms.ent.auth.notauth.error=\u4F01\u4E1A\u672A\u6388\u6743
+cms.email.send.fail.error=\u53D1\u9001\u5931\u8D25\uFF0C\u8BF7\u91CD\u8BD5
+cms.recharge.not.existed.error=\u5145\u503C\u4FE1\u606F\u4E0D\u5B58\u5728
+cms.recharge.expiry.error=\u6709\u6548\u671F\u8D85\u51FA\u9650\u5236
+cms.recharge.version.error=\u5145\u503C\u7248\u672C\u4E0D\u5339\u914D
+cms.report.rate.limiting.error=\u8BF7\u7A0D\u540E\u518D\u8BD5
+cms.ent.risk.info.standard.missing.error=\u8BF7\u914D\u7F6E\u670D\u52A1\u5546\u51C6\u5165\u6807\u51C6
+cms.ent.risk.info.standard.name.existing=\u6807\u51C6\u540D\u79F0\u5DF2\u5B58\u5728
+cms.ent.risk.info.standard.rule.check=\u6807\u51C6\u8BBE\u7F6E\u5173\u952E\u5B57\u4E0D\u5408\u6CD5\uFF0C{0}
+cms.ent.risk.info.ckt.missing.error=\u5BA1\u6838\u6570\u636E\u4E0D\u5B58\u5728
+cms.ent.risk.info.ckt.score.outofbounds.error=\u8BC4\u5206\u8D85\u51FA\u9650\u5236
+cms.ent.risk.info.ckt.financial.msg.missing=\u8BF7\u5148\u586B\u5199\u8D22\u52A1\u610F\u89C1
+cms.ent.risk.info.ckt.missing=\u5BA1\u6838\u6570\u636E\u4E0D\u5B58\u5728
+cms.ent.risk.info.ckt.state.error=\u4F01\u4E1A\u98CE\u9669\u4FE1\u606F\u5BA1\u6838\u72B6\u6001\u9519\u8BEF\uFF0C\u4E0D\u652F\u6301\u8BE5\u64CD\u4F5C
+cms.ent.risk.info.ckt.manual.score.limit=\u590D\u6838\u5F97\u5206\u8303\u56F4\u662F{0}-{1}
+cms.ent.risk.info.ckt.manual.msg.missing=\u590D\u6838\u539F\u56E0\u5FC5\u586B
+cms.ent.risk.info.ckt.item.manual.result.missing=\u8BF7\u5148\u5B8C\u6210\u5BA1\u6838\u9879\u7684\u590D\u6838
+cms.ent.risk.info.ckt.category.msg.missing=\u8BF7\u5148\u586B\u5199\u4E13\u4E1A\u610F\u89C1
+cms.pay.qrcode.validsign.error=\u9A8C\u7B7E\u5931\u8D25
+cms.pay.qrcode.error=\u83B7\u53D6\u4ED8\u6B3E\u4E8C\u7EF4\u7801\u5931\u8D25
+cms.pay.qrcode.haspaid.error=\u5DF2\u652F\u4ED8\u6210\u529F
+cms.pay.qrcode.notpaid.error=\u672A\u652F\u4ED8
+cms.pay.qrcode.queryresult.error=\u67E5\u8BE2\u7ED3\u679C\u5931\u8D25
+cms.review.item.config.sign.missing=\u7B7E\u540D\u4FE1\u606F\u5FC5\u586B
+cms.file.parser.format.error=\u6A21\u677F\u4E0D\u6B63\u786E
+# shoppingmall error message
+mall.cate.level.limit.error=\u5206\u7C7B\u6700\u591A\u80FD\u6DFB\u52A02\u7EA7\u54E6
+mall.cate.name.dump.error=\u5B58\u5728\u91CD\u540D\u7684\u5546\u54C1\u7684\u89C4\u683C
+# warehouse error message
+wh.state.error=\u5F53\u524D\u72B6\u6001\u4E0D\u652F\u6301\u64CD\u4F5C
+wh.deli.locked.error=\u7ED3\u7B97\u7269\u5DF2\u7ECF\u9501\u5B9A\uFF0C\u5982\u9700\u91CD\u65B0\u9501\u5B9A\uFF0C\u8BF7\u5148\u89E3\u9501
+wh.plugin.dup.name.error=\u5B58\u5728\u540C\u540D\u7684\u63D2\u4EF6
+wh.proj.tag.dup.name.error=\u5B58\u5728\u540C\u540D\u7684\u6807\u7B7E
+wh.sys.proj.tag.opt.error=\u7CFB\u7EDF\u5185\u5EFA\u6807\u7B7E\u4E0D\u80FD\u64CD\u4F5C
+wh.tmpl.state.error=\u5F53\u524D\u6A21\u677F\u72B6\u6001\uFF1A{0}\u5982\u679C\u7F16\u8F91\uFF0C\u8BF7\u5148\u4E2D\u6B62\u53D1\u5E03\uFF01
+wh.category.name.error=\u4E0D\u80FD\u4F7F\u7528\u8FD9\u4E2A\u5206\u7C7B\u540D\u79F0
+wh.category.name_not_existed.error=\u5206\u7C7B\u540D\u79F0\u4E0D\u5B58\u5728
+# membership error message
+ums.mem.info.empty={0} \u7528\u6237\u4FE1\u606F\u4E3A\u7A7A
+ums.mem.query.error=\u83B7\u53D6\u4F1A\u5458\u4FE1\u606F\u5F02\u5E38
+ums.role.import.error=\u5BFC\u5165\u89D2\u8272\u4FE1\u606F\u5931\u8D25 {0}
+ums.mem.import.error=\u5BFC\u5165\u89D2\u8272\u4FE1\u606F\u5931\u8D25 {0}
+usm.role.nameorcode.existing=\u89D2\u8272\u540D\u79F0\u6216\u8005\u7F16\u7801\u91CD\u590D
+ums.role.code.pattern.error=\u89D2\u8272Code\u683C\u5F0F\u5FC5\u987B\u4EE5"ROLE_",\u0020\u4E14\u53EA\u652F\u6301\u82F1\u6587\u5927\u5199\u548C\u6570\u5B57\u7EC4\u5408\uFF0C\u591A\u4E2A\u5355\u8BCD\u5EFA\u8BAE\u4F7F\u7528"_"\u9694\u5F00
+ums.verification.code.error={0} \u9A8C\u8BC1\u7801\u9519\u8BEF
+ums.phone.existed.error=\u624B\u673A\u53F7\u5DF2\u6CE8\u518C\uFF0C\u8BF7\u4F7F\u7528\u9A8C\u8BC1\u7801\u76F4\u63A5\u767B\u5F55
+ums.daily.bonus.existing=\u4F1A\u5458\u7B7E\u5230\u8BB0\u5F55\u5DF2\u7ECF\u5B58\u5728
+ums.verify.token.get.error=\u83B7\u53D6\u4EBA\u8EAB\u6838\u9A8C\u4EE4\u724C[ {0} ]\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5
+ums.verify.token.exp.error=\u4EBA\u8EAB\u6838\u9A8C\u4EE4\u724C\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u7533\u9886
+ums.verify.token.limit.error=\u6BCF\u5929\u6700\u591A\u83B7\u53D6{0}\u6B21\u6838\u8EAB\u4EE4\u724C,\u0020\u8BF7\u660E\u65E5\u518D\u8BD5
+ums.mem.ide.exp.error=\u4F1A\u5458\u8EAB\u4EFD\u8BC1\u4EF6\u6709\u6548\u671F\u4E0D\u8DB3
+# quartz error message
+qrz.job.not.existed.error=\u4EFB\u52A1\u4E0D\u5B58\u5728
+qrz.job.existed.error=\u4EFB\u52A1\u5DF2\u5B58\u5728
+qrz.job.not.running.error=\u65E0\u6B63\u5728\u8FD0\u884C\u7684\u4EFB\u52A1

BIN
easier-common/easier-common-core/src/main/resources/xdb/ip2region.xdb


+ 62 - 0
easier-common/easier-common-data/pom.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.yaoyicloud</groupId>
+        <artifactId>easier-common</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>easier-common-data</artifactId>
+    <packaging>jar</packaging>
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <!--core dependency-->
+        <dependency>
+            <groupId>com.yaoyicloud</groupId>
+            <artifactId>easier-common-core</artifactId>
+            <version>0.0.1-SNAPSHOT</version>
+        </dependency>
+        <!--mybatis plus-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>3.5.4</version>
+        </dependency>
+        <!-- 连表查询依赖	-->
+        <dependency>
+            <groupId>com.github.yulichang</groupId>
+            <artifactId>mybatis-plus-join-boot-starter</artifactId>
+<version>1.4.6</version>
+        </dependency>
+        <!-- druid 连接池 -->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+            <optional>true</optional>
+            <version>1.2.20</version>
+        </dependency>
+        <!--安全依赖获取上下文信息-->
+<!--        <dependency>-->
+<!--            <groupId>com.yaoyicloud</groupId>-->
+<!--            <artifactId>easier-common-security</artifactId>-->
+<!--            <optional>true</optional>-->
+<!--        </dependency>-->
+        <!--feign client-->
+<!--        <dependency>-->
+<!--            <groupId>com.yaoyicloud.easier.workspace</groupId>-->
+<!--            <artifactId>easier-workspace-api</artifactId>-->
+<!--        </dependency>-->
+        <!--缓存依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 28 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/application/EasierApplicationConfigProperties.java

@@ -0,0 +1,28 @@
+package com.yaoyicloud.easier.common.data.application;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.Data;
+
+@Data
+@RefreshScope
+@Configuration
+@ConfigurationProperties(prefix = "easier.application")
+public class EasierApplicationConfigProperties {
+
+    /**
+     * 维护租户列名称
+     */
+    private String column = "app_id";
+
+    /**
+     * 多租户的数据表集合
+     */
+    private List<String> tables = new ArrayList<>();
+
+}

+ 57 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/application/EasierApplicationHandler.java

@@ -0,0 +1,57 @@
+package com.yaoyicloud.easier.common.data.application;
+
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+import com.yaoyicloud.easier.common.core.constant.CommonConstants;
+import com.yaoyicloud.easier.common.data.tenant.TenantContextHolder;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+
+@Slf4j
+@RequiredArgsConstructor
+public class EasierApplicationHandler implements TenantLineHandler {
+
+    private final EasierApplicationConfigProperties properties;
+
+    /**
+     * 获取应用 ID 值表达式,只支持单个 ID 值
+     * <p>
+     * 
+     * @return 应用 ID 值表达式
+     */
+    @Override
+    public Expression getTenantId() {
+        Long id = TenantContextHolder.getApplicationId();
+        log.debug("当前应用为 >> {}", id);
+
+        if (id == null) {
+            return new LongValue(CommonConstants.APPLICATION_ID_1); // FXY is default
+        }
+        return new LongValue(id);
+    }
+
+    /**
+     * 获取应用字段名
+     * 
+     * @return 应用字段名
+     */
+    @Override
+    public String getTenantIdColumn() {
+        return properties.getColumn();
+    }
+
+    /**
+     * 根据表名判断是否忽略拼接应用条件
+     * <p>
+     * 
+     * @param tableName 表名
+     * @return 是否忽略, true:表示忽略,false:需要解析并拼接应用条件
+     */
+    @Override
+    public boolean ignoreTable(String tableName) {
+        return !properties.getTables().contains(tableName);
+    }
+
+}

+ 274 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/DefaultRedisCacheWriter.java

@@ -0,0 +1,274 @@
+package com.yaoyicloud.easier.common.data.cache;
+
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.springframework.dao.PessimisticLockingFailureException;
+import org.springframework.data.redis.cache.CacheStatistics;
+import org.springframework.data.redis.cache.CacheStatisticsCollector;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.data.redis.connection.RedisConnection;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStringCommands.SetOption;
+import org.springframework.data.redis.core.types.Expiration;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+
+/**
+ * {@link RedisCacheWriter} implementation capable of reading/writing binary data from/to Redis in {@literal standalone} and
+ * {@literal cluster} environments. Works upon a given {@link RedisConnectionFactory} to obtain the actual {@link RedisConnection}. <br />
+ * {@link DefaultRedisCacheWriter} can be used in {@link RedisCacheWriter#lockingRedisCacheWriter(RedisConnectionFactory) locking} or
+ * {@link RedisCacheWriter#nonLockingRedisCacheWriter(RedisConnectionFactory) non-locking} mode. While {@literal non-locking} aims for
+ * maximum performance it may result in overlapping, non atomic, command execution for operations spanning multiple Redis interactions like
+ * {@code putIfAbsent}. The {@literal locking} counterpart prevents command overlap by setting an explicit lock key and checking against
+ * presence of this key which leads to additional requests and potential command wait times.
+ *
+ * @author Christoph Strobl
+ * @author Mark Paluch
+ * @since 2.0
+ */
+class DefaultRedisCacheWriter implements RedisCacheWriter {
+
+    private final RedisConnectionFactory connectionFactory;
+
+    private final Duration sleepTime;
+
+    /**
+     * @param connectionFactory must not be {@literal null}.
+     */
+    DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory) {
+        this(connectionFactory, Duration.ZERO);
+    }
+
+    /**
+     * @param connectionFactory must not be {@literal null}.
+     * @param sleepTime sleep time between lock request attempts. Must not be {@literal null}. Use {@link Duration#ZERO} to disable locking.
+     */
+    private DefaultRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime) {
+
+        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
+        Assert.notNull(sleepTime, "SleepTime must not be null!");
+
+        this.connectionFactory = connectionFactory;
+        this.sleepTime = sleepTime;
+    }
+
+    @Override
+    public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
+
+        Assert.notNull(name, "Name must not be null!");
+        Assert.notNull(key, "Key must not be null!");
+        Assert.notNull(value, "Value must not be null!");
+
+        execute(name, connection -> {
+
+            if (shouldExpireWithin(ttl)) {
+                connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), SetOption.upsert());
+            } else {
+                connection.set(key, value);
+            }
+
+            return "OK";
+        });
+    }
+
+    @Override
+    public byte[] get(String name, byte[] key) {
+
+        Assert.notNull(name, "Name must not be null!");
+        Assert.notNull(key, "Key must not be null!");
+
+        return execute(name, connection -> connection.get(key));
+    }
+
+    @Override
+    public byte[] putIfAbsent(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
+
+        Assert.notNull(name, "Name must not be null!");
+        Assert.notNull(key, "Key must not be null!");
+        Assert.notNull(value, "Value must not be null!");
+
+        return execute(name, connection -> {
+
+            if (isLockingCacheWriter()) {
+                doLock(name, connection);
+            }
+
+            try {
+                if (Boolean.TRUE.equals(connection.setNX(key, value))) {
+
+                    if (shouldExpireWithin(ttl)) {
+                        connection.pExpire(key, ttl.toMillis());
+                    }
+                    return null;
+                }
+
+                return connection.get(key);
+            } finally {
+
+                if (isLockingCacheWriter()) {
+                    doUnlock(name, connection);
+                }
+            }
+        });
+    }
+
+    /**
+     * 删除,原来是删除指定的键,目前修改为既可以删除指定键的数据,也是可以删除某个前缀开始的所有数据
+     * 
+     * @param name
+     * @param key
+     */
+    @Override
+    public void remove(String name, byte[] key) {
+
+        Assert.notNull(name, "Name must not be null!");
+        Assert.notNull(key, "Key must not be null!");
+
+        execute(name, connection -> {
+            // 获取某个前缀所拥有的所有的键,某个前缀开头,后面肯定是*
+            Set<byte[]> keys = connection.keys(key);
+            int delNum = 0;
+            Assert.notNull(keys, "keys must not be null!");
+            for (byte[] keyByte : keys) {
+                delNum += connection.del(keyByte);
+            }
+            return delNum;
+        });
+    }
+
+    @Override
+    public void clean(String name, byte[] pattern) {
+
+        Assert.notNull(name, "Name must not be null!");
+        Assert.notNull(pattern, "Pattern must not be null!");
+
+        execute(name, connection -> {
+
+            boolean wasLocked = false;
+
+            try {
+
+                if (isLockingCacheWriter()) {
+                    doLock(name, connection);
+                    wasLocked = true;
+                }
+
+                byte[][] keys =
+                    Optional.ofNullable(connection.keys(pattern)).orElse(Collections.emptySet()).toArray(new byte[0][]);
+
+                if (keys.length > 0) {
+                    connection.del(keys);
+                }
+            } finally {
+
+                if (wasLocked && isLockingCacheWriter()) {
+                    doUnlock(name, connection);
+                }
+            }
+
+            return "OK";
+        });
+    }
+
+    @Override
+    public void clearStatistics(String s) {
+
+    }
+
+    @Override
+    public RedisCacheWriter withStatisticsCollector(CacheStatisticsCollector cacheStatisticsCollector) {
+        return null;
+    }
+
+    /**
+     * Explicitly set a write lock on a cache.
+     * 
+     * @param name the name of the cache to lock.
+     */
+    void lock(String name) {
+        execute(name, connection -> doLock(name, connection));
+    }
+
+    /**
+     * Explicitly remove a write lock from a cache.
+     * 
+     * @param name the name of the cache to unlock.
+     */
+    void unlock(String name) {
+        executeLockFree(connection -> doUnlock(name, connection));
+    }
+
+    private Boolean doLock(String name, RedisConnection connection) {
+        return connection.setNX(createCacheLockKey(name), new byte[0]);
+    }
+
+    private Long doUnlock(String name, RedisConnection connection) {
+        return connection.del(createCacheLockKey(name));
+    }
+
+    boolean doCheckLock(String name, RedisConnection connection) {
+        return connection.exists(createCacheLockKey(name));
+    }
+
+    /**
+     * @return {@literal true} if {@link RedisCacheWriter} uses locks.
+     */
+    private boolean isLockingCacheWriter() {
+        return !sleepTime.isZero() && !sleepTime.isNegative();
+    }
+
+    private <T> T execute(String name, Function<RedisConnection, T> callback) {
+
+        try (RedisConnection connection = connectionFactory.getConnection()) {
+            checkAndPotentiallyWaitUntilUnlocked(name, connection);
+            return callback.apply(connection);
+        }
+    }
+
+    private void executeLockFree(Consumer<RedisConnection> callback) {
+        try (RedisConnection connection = connectionFactory.getConnection()) {
+            callback.accept(connection);
+        }
+    }
+
+    private void checkAndPotentiallyWaitUntilUnlocked(String name, RedisConnection connection) {
+
+        if (!isLockingCacheWriter()) {
+            return;
+        }
+
+        try {
+            while (doCheckLock(name, connection)) {
+                Thread.sleep(sleepTime.toMillis());
+            }
+        } catch (InterruptedException ex) {
+
+            // Re-interrupt current thread, to allow other participants to react.
+            Thread.currentThread().interrupt();
+
+            throw new PessimisticLockingFailureException(
+                String.format("Interrupted while waiting to unlock cache %s", name), ex);
+        }
+    }
+
+    private static boolean shouldExpireWithin(@Nullable Duration ttl) {
+        return ttl != null && !ttl.isZero() && !ttl.isNegative();
+    }
+
+    private static byte[] createCacheLockKey(String name) {
+        return (name + "~lock").getBytes(StandardCharsets.UTF_8);
+    }
+
+    @Override
+    public CacheStatistics getCacheStatistics(String s) {
+        return null;
+    }
+
+}

+ 72 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisAutoCacheManager.java

@@ -0,0 +1,72 @@
+package com.yaoyicloud.easier.common.data.cache;
+
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+
+import org.springframework.boot.convert.DurationStyle;
+import org.springframework.cache.Cache;
+import org.springframework.data.redis.cache.RedisCache;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+import org.springframework.lang.Nullable;
+
+import com.yaoyicloud.easier.common.core.constant.CacheConstants;
+import com.yaoyicloud.easier.common.data.tenant.TenantContextHolder;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * redis 缓存管理器
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024-01-07 16:00
+ */
+@Slf4j
+public class RedisAutoCacheManager extends RedisCacheManager {
+
+    private static final String SPLIT_FLAG = "#";
+
+    private static final int CACHE_LENGTH = 2;
+
+    RedisAutoCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration,
+        Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
+        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
+    }
+
+    @Override
+    protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
+        if (StrUtil.isBlank(name) || !name.contains(SPLIT_FLAG)) {
+            return super.createRedisCache(name, cacheConfig);
+        }
+
+        String[] cacheArray = name.split(SPLIT_FLAG);
+        if (cacheArray.length < CACHE_LENGTH) {
+            return super.createRedisCache(name, cacheConfig);
+        }
+
+        if (cacheConfig != null) {
+            Duration duration = DurationStyle.detectAndParse(cacheArray[1], ChronoUnit.SECONDS);
+            cacheConfig = cacheConfig.entryTtl(duration);
+        }
+        return super.createRedisCache(cacheArray[0], cacheConfig);
+    }
+
+    /**
+     * 从上下文中获取租户ID,重写@Cacheable value 值
+     *
+     * @param name 缓存名称
+     * @return 缓存
+     */
+    @Override
+    public Cache getCache(String name) {
+        if (name.startsWith(CacheConstants.GLOBALLY)) {
+            return super.getCache(name);
+        }
+        return super.getCache(TenantContextHolder.getTenantId() + StrUtil.COLON + name);
+    }
+
+}

+ 91 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisCacheAutoConfiguration.java

@@ -0,0 +1,91 @@
+package com.yaoyicloud.easier.common.data.cache;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.cache.CacheProperties;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.lang.Nullable;
+
+@Configuration
+@AutoConfigureAfter({RedisAutoConfiguration.class})
+@ConditionalOnBean({RedisConnectionFactory.class})
+@EnableConfigurationProperties(CacheProperties.class)
+public class RedisCacheAutoConfiguration {
+
+    private final CacheProperties cacheProperties;
+
+    private final CacheManagerCustomizers customizerInvoker;
+
+    @Nullable
+    private final RedisCacheConfiguration redisCacheConfiguration;
+
+    RedisCacheAutoConfiguration(CacheProperties cacheProperties, CacheManagerCustomizers customizerInvoker,
+        ObjectProvider<RedisCacheConfiguration> redisCacheConfiguration) {
+        this.cacheProperties = cacheProperties;
+        this.customizerInvoker = customizerInvoker;
+        this.redisCacheConfiguration = redisCacheConfiguration.getIfAvailable();
+    }
+
+    @Bean
+    @Primary
+    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory, ResourceLoader resourceLoader) {
+        DefaultRedisCacheWriter redisCacheWriter = new DefaultRedisCacheWriter(connectionFactory);
+        RedisCacheConfiguration cacheConfiguration = this.determineConfiguration(resourceLoader.getClassLoader());
+        List<String> cacheNames = this.cacheProperties.getCacheNames();
+        Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
+        if (!cacheNames.isEmpty()) {
+            Map<String, RedisCacheConfiguration> cacheConfigMap = new LinkedHashMap<>(cacheNames.size());
+            cacheNames.forEach(it -> cacheConfigMap.put(it, cacheConfiguration));
+            initialCaches.putAll(cacheConfigMap);
+        }
+        RedisAutoCacheManager cacheManager =
+            new RedisAutoCacheManager(redisCacheWriter, cacheConfiguration, initialCaches, true);
+        cacheManager.setTransactionAware(false);
+        return this.customizerInvoker.customize(cacheManager);
+    }
+
+    private RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) {
+        if (this.redisCacheConfiguration != null) {
+            return this.redisCacheConfiguration;
+        } else {
+            CacheProperties.Redis redisProperties = this.cacheProperties.getRedis();
+            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
+            config = config.serializeValuesWith(RedisSerializationContext.SerializationPair
+                .fromSerializer(new JdkSerializationRedisSerializer(classLoader)));
+            if (redisProperties.getTimeToLive() != null) {
+                config = config.entryTtl(redisProperties.getTimeToLive());
+            }
+
+            if (redisProperties.getKeyPrefix() != null) {
+                config = config.prefixCacheNameWith(redisProperties.getKeyPrefix());
+            }
+
+            if (!redisProperties.isCacheNullValues()) {
+                config = config.disableCachingNullValues();
+            }
+
+            if (!redisProperties.isUseKeyPrefix()) {
+                config = config.disableKeyPrefix();
+            }
+
+            return config;
+        }
+    }
+
+}

+ 22 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisCacheManagerConfiguration.java

@@ -0,0 +1,22 @@
+package com.yaoyicloud.easier.common.data.cache;
+
+import java.util.List;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizer;
+import org.springframework.boot.autoconfigure.cache.CacheManagerCustomizers;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnMissingBean(CacheManagerCustomizers.class)
+public class RedisCacheManagerConfiguration {
+
+    @Bean
+    public CacheManagerCustomizers
+        cacheManagerCustomizers(ObjectProvider<List<CacheManagerCustomizer<?>>> customizers) {
+        return new CacheManagerCustomizers(customizers.getIfAvailable());
+    }
+
+}

+ 20 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisMessageConfiguration.java

@@ -0,0 +1,20 @@
+package com.yaoyicloud.easier.common.data.cache;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.listener.RedisMessageListenerContainer;
+
+@Configuration(proxyBeanMethods = false)
+public class RedisMessageConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean
+    public RedisMessageListenerContainer redisContainer(RedisConnectionFactory redisConnectionFactory) {
+        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
+        container.setConnectionFactory(redisConnectionFactory);
+        return container;
+    }
+
+}

+ 30 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/cache/RedisTemplateConfiguration.java

@@ -0,0 +1,30 @@
+package com.yaoyicloud.easier.common.data.cache;
+
+import org.springframework.boot.autoconfigure.AutoConfigureBefore;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.RedisSerializer;
+
+@EnableCaching
+@Configuration
+@AutoConfigureBefore(name = {"org.redisson.spring.starter.RedissonAutoConfiguration",
+    "org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration"})
+public class RedisTemplateConfiguration {
+
+    @Bean
+    @Primary
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setKeySerializer(RedisSerializer.string());
+        redisTemplate.setHashKeySerializer(RedisSerializer.string());
+        redisTemplate.setValueSerializer(RedisSerializer.java());
+        redisTemplate.setHashValueSerializer(RedisSerializer.java());
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        return redisTemplate;
+    }
+
+}

+ 72 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScope.java

@@ -0,0 +1,72 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class DataScope extends HashMap {
+
+    private static final long serialVersionUID = -4151504341212809077L;
+
+    /**
+     * 限制范围的字段名称
+     */
+    private String scopeDeptName = "dept_id";
+
+    /**
+     * 本人权限范围字段
+     */
+    private String scopeUserName = "created_by";
+
+    /**
+     * 具体的数据范围
+     */
+    private List<Long> deptList = new ArrayList<>();
+
+    /**
+     * 具体查询的用户数据权限范围
+     */
+    private String username;
+
+    /**
+     * 是否只查询本部门
+     */
+    private Boolean isOnly = false;
+
+    /**
+     * 函数名称,默认 SELECT * ;
+     *
+     * <ul>
+     * <li>COUNT(1)</li>
+     * </ul>
+     */
+    private DataScopeFuncEnum func = DataScopeFuncEnum.ALL;
+
+    /**
+     * of 获取实例
+     */
+    public static DataScope of() {
+        return new DataScope();
+    }
+
+    public DataScope deptIds(List<Long> deptIds) {
+        this.deptList = deptIds;
+        return this;
+    }
+
+    public DataScope only(boolean isOnly) {
+        this.isOnly = isOnly;
+        return this;
+    }
+
+    public DataScope func(DataScopeFuncEnum func) {
+        this.func = func;
+        return this;
+    }
+
+}

+ 30 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeFuncEnum.java

@@ -0,0 +1,30 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum DataScopeFuncEnum {
+
+    /**
+     * 查询全部数据 SELECT * FROM (originSql) temp_data_scope WHERE temp_data_scope.dept_id IN (1)
+     */
+    ALL("*", "全部"),
+
+    /**
+     * 查询函数COUNT SELECT COUNT(1) FROM (originSql) temp_data_scope WHERE temp_data_scope.dept_id IN (1)
+     */
+    COUNT("COUNT(1)", "自定义");
+
+    /**
+     * 类型
+     */
+    private final String type;
+
+    /**
+     * 描述
+     */
+    private final String description;
+
+}

+ 13 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeHandle.java

@@ -0,0 +1,13 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+public interface DataScopeHandle {
+
+    /**
+     * 计算用户数据权限
+     * 
+     * @param dataScope 数据权限设置
+     * @return 返回true表示无需进行数据过滤处理,返回false表示需要进行数据过滤
+     */
+    Boolean calcScope(DataScope dataScope);
+
+}

+ 87 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeInnerInterceptor.java

@@ -0,0 +1,87 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ibatis.executor.Executor;
+import org.apache.ibatis.mapping.BoundSql;
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.session.ResultHandler;
+import org.apache.ibatis.session.RowBounds;
+
+import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.Setter;
+
+public class DataScopeInnerInterceptor implements DataScopeInterceptor {
+
+    @Setter
+    private DataScopeHandle dataScopeHandle;
+
+    @SuppressWarnings("checkstyle:ReturnCount")
+    @Override
+    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
+        ResultHandler resultHandler, BoundSql boundSql) {
+        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
+
+        String originalSql = boundSql.getSql();
+        Object parameterObject = boundSql.getParameterObject();
+
+        // 查找参数中包含DataScope类型的参数
+        DataScope dataScope = findDataScopeObject(parameterObject);
+        if (dataScope == null) {
+            return;
+        }
+
+        // 返回true 不拦截直接返回原始 SQL (只针对 * 查询)
+        if (dataScopeHandle.calcScope(dataScope) && DataScopeFuncEnum.ALL.equals(dataScope.getFunc())) {
+            return;
+        }
+
+        // 返回true 不拦截直接返回原始 SQL (只针对 COUNT 查询)
+        if (dataScopeHandle.calcScope(dataScope) && DataScopeFuncEnum.COUNT.equals(dataScope.getFunc())) {
+            mpBs.sql(String.format("SELECT %s FROM (%s) temp_data_scope", dataScope.getFunc().getType(), originalSql));
+            return;
+        }
+
+        List<Long> deptIds = dataScope.getDeptList();
+
+        // 1.无数据权限限制,则直接返回 0 条数据
+        if (CollUtil.isEmpty(deptIds) && StrUtil.isBlank(dataScope.getUsername())) {
+            originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE 1 = 2",
+                dataScope.getFunc().getType(), originalSql);
+        } else if (StrUtil.isNotBlank(dataScope.getUsername())) { // 2.如果为本人权限则走下面
+            originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s = '%s'",
+                dataScope.getFunc().getType(), originalSql, dataScope.getScopeUserName(), dataScope.getUsername());
+        } else { // 3.都没有,则是其他权限,走下面
+            String join = CollectionUtil.join(deptIds, ",");
+            originalSql = String.format("SELECT %s FROM (%s) temp_data_scope WHERE temp_data_scope.%s IN (%s)",
+                dataScope.getFunc().getType(), originalSql, dataScope.getScopeDeptName(), join);
+        }
+
+        mpBs.sql(originalSql);
+    }
+
+    /**
+     * 查找参数是否包括DataScope对象
+     * 
+     * @param parameterObj 参数列表
+     * @return DataScope
+     */
+    private DataScope findDataScopeObject(Object parameterObj) {
+        if (parameterObj instanceof DataScope) {
+            return (DataScope) parameterObj;
+        } else if (parameterObj instanceof Map) {
+            for (Object val : ((Map<?, ?>) parameterObj).values()) {
+                if (val instanceof DataScope) {
+                    return (DataScope) val;
+                }
+            }
+        }
+        return null;
+    }
+
+}

+ 7 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeInterceptor.java

@@ -0,0 +1,7 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
+
+public interface DataScopeInterceptor extends InnerInterceptor {
+
+}

+ 21 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeSqlInjector.java

@@ -0,0 +1,21 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import java.util.List;
+
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+import com.github.yulichang.injector.MPJSqlInjector;
+
+public class DataScopeSqlInjector extends MPJSqlInjector {
+
+    @Override
+    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
+        List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
+        methodList.add(new SelectListByScope());
+        methodList.add(new SelectPageByScope());
+        methodList.add(new SelectCountByScope());
+        methodList.add(new SelectRealById());
+        return methodList;
+    }
+
+}

+ 45 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/DataScopeTypeEnum.java

@@ -0,0 +1,45 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum DataScopeTypeEnum {
+
+    /**
+     * 查询全部数据
+     */
+    ALL(0, "全部"),
+
+    /**
+     * 自定义
+     */
+    CUSTOM(1, "自定义"),
+
+    /**
+     * 本级及子级
+     */
+    OWN_CHILD_LEVEL(2, "本级及子级"),
+
+    /**
+     * 本级
+     */
+    OWN_LEVEL(3, "本级"),
+
+    /**
+     * 本人
+     */
+    SELF_LEVEL(4, "本人");
+
+    /**
+     * 类型
+     */
+    private final int type;
+
+    /**
+     * 描述
+     */
+    private final String description;
+
+}

+ 82 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/EasierBaseMapper.java

@@ -0,0 +1,82 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.ibatis.annotations.Param;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.Constants;
+import com.baomidou.mybatisplus.extension.toolkit.Db;
+import com.github.yulichang.base.MPJBaseMapper;
+
+/**
+ * mapper 扩展
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2023-12-13 21:46
+ */
+@SuppressWarnings("checkstyle:JavadocType")
+public interface EasierBaseMapper<T> extends MPJBaseMapper<T> {
+
+    /**
+     * 根据 entity 条件,查询全部记录
+     *
+     * @param queryWrapper 实体对象封装操作类(可以为 null)
+     * @param scope 数据权限范围
+     * @return List<T>
+     */
+    List<T> selectListByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope scope);
+
+    /**
+     * 根据 entity 条件,查询全部记录(并翻页)
+     *
+     * @param page 分页查询条件(可以为 RowBounds.DEFAULT)
+     * @param queryWrapper 实体对象封装操作类(可以为 null)
+     * @param scope 数据权限范围
+     * @return Page
+     */
+    <E extends IPage<T>> E selectPageByScope(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper,
+        DataScope scope);
+
+    /**
+     * 根据 Wrapper 条件,查询总记录数
+     *
+     * @param queryWrapper 实体对象封装操作类(可以为 null)
+     * @param scope 数据权限范围
+     * @return Integer
+     */
+    Long selectCountByScope(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper, DataScope scope);
+
+    /**
+     * 根据ID查询(忽略逻辑查询)
+     *
+     * @param id 主键ID
+     * @return {@link T } 实体对象
+     */
+    T selectRealById(Serializable id);
+
+    /**
+     * 批量保存
+     *
+     * @param entityList 实体列表
+     * @return boolean 是否保存成功
+     */
+    default <T> boolean saveBatch(Collection<T> entityList) {
+        return Db.saveBatch(entityList);
+    }
+
+    /**
+     * 批量更新
+     *
+     * @param entityList 实体列表
+     * @return boolean 是否更新成功
+     */
+    default <T> boolean updateBatchById(Collection<T> entityList) {
+        return Db.updateBatchById(entityList);
+    }
+
+}

+ 106 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/EasierDefaultDatascopeHandle.java

@@ -0,0 +1,106 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.springframework.security.core.GrantedAuthority;
+
+import com.yaoyicloud.easier.common.core.constant.SecurityConstants;
+import com.yaoyicloud.easier.common.core.util.RetOps;
+import com.yaoyicloud.easier.common.domain.enums.type.CommonType;
+import com.yaoyicloud.easier.common.security.service.EasierUser;
+import com.yaoyicloud.easier.common.security.util.SecurityUtils;
+import com.yaoyicloud.easier.workspace.api.entity.SysDept;
+import com.yaoyicloud.easier.workspace.api.entity.SysRole;
+import com.yaoyicloud.easier.workspace.api.feign.RemoteDataScopeService;
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * 默认data scope 判断处理器
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-12-02 18:41
+ */
+@RequiredArgsConstructor
+public class EasierDefaultDatascopeHandle implements DataScopeHandle {
+
+    private final RemoteDataScopeService dataScopeService;
+
+    /**
+     * 计算用户数据权限
+     *
+     * @param dataScope 数据权限范围
+     * @return 计算结果
+     */
+    @SuppressWarnings({"checkstyle:ReturnCount", "checkstyle:WhitespaceAround"})
+    @Override
+    public Boolean calcScope(DataScope dataScope) {
+        EasierUser user = SecurityUtils.getUser();
+        // toc 客户端不进行数据权限
+        if (CommonType.UserType.TO_C.equals(user.getUserType())) {
+            return true;
+        }
+
+        List<String> roleIdList = user.getAuthorities().stream().map(GrantedAuthority::getAuthority)
+            .filter(authority -> authority.startsWith(SecurityConstants.ROLE))
+            .map(authority -> authority.split(StrUtil.UNDERLINE)[1]).collect(Collectors.toList());
+
+        List<Long> deptList = dataScope.getDeptList();
+
+        // 当前用户的角色为空 , 返回false
+        if (CollectionUtil.isEmpty(roleIdList)) {
+            return false;
+        }
+        // @formatter:off
+		SysRole role = RetOps.of(dataScopeService.getRoleList(roleIdList))
+				.getData()
+				.orElseGet(Collections::emptyList)
+				.stream()
+				.min(Comparator.comparingInt(r-> r.getDataScopeType().getCode())).orElse(null);
+		// @formatter:on
+        // 角色有可能已经删除了
+        if (role == null) {
+            return false;
+        }
+        CommonType.DataScopeType dsType = role.getDataScopeType();
+        // 查询全部
+        if (Objects.equals(dsType, CommonType.DataScopeType.ALL)) {
+            return true;
+        }
+        // 自定义
+        if (Objects.equals(dsType, CommonType.DataScopeType.CUSTOM) && ArrayUtil.isNotEmpty(role.getScopeIds())) {
+            Long[] scopeIds = role.getScopeIds();
+            deptList.addAll(List.of(scopeIds));
+        }
+        // 查询本级及其下级
+        if (Objects.equals(dsType, CommonType.DataScopeType.SET_AND_SUBSET)) {
+            // @formatter:off
+			List<Long> deptIdList = RetOps.of(dataScopeService.getDescendantList(user.getDeptId()))
+					.getData()
+					.orElseGet(Collections::emptyList)
+					.stream()
+					.map(SysDept::getDeptId).collect(Collectors.toList());
+			// @formatter:on
+            deptList.addAll(deptIdList);
+        }
+        // 只查询本级
+        if (Objects.equals(dsType, CommonType.DataScopeType.SET)) {
+            deptList.add(user.getDeptId());
+        }
+
+        // 只查询本人
+        if (Objects.equals(dsType, CommonType.DataScopeType.SELF)) {
+            dataScope.setUsername(user.getUsername());
+        }
+        return false;
+    }
+
+}

+ 28 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectCountByScope.java

@@ -0,0 +1,28 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlSource;
+
+import com.baomidou.mybatisplus.core.enums.SqlMethod;
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+
+public class SelectCountByScope extends AbstractMethod {
+
+    public SelectCountByScope() {
+        super("selectCountByScope");
+    }
+
+    @Override
+    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
+        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
+
+        String sql = String.format(sqlMethod.getSql(), this.sqlFirst(), this.sqlSelectColumns(tableInfo, true),
+            tableInfo.getTableName(), this.sqlWhereEntityWrapper(true, tableInfo), this.sqlOrderBy(tableInfo),
+            this.sqlComment());
+        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
+
+        return this.addSelectMappedStatementForOther(mapperClass, sqlSource, Long.class);
+    }
+
+}

+ 26 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectListByScope.java

@@ -0,0 +1,26 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlSource;
+
+import com.baomidou.mybatisplus.core.enums.SqlMethod;
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+
+public class SelectListByScope extends AbstractMethod {
+
+    public SelectListByScope() {
+        super("selectListByScope");
+    }
+
+    @Override
+    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
+        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
+        String sql = String.format(sqlMethod.getSql(), this.sqlFirst(), this.sqlSelectColumns(tableInfo, true),
+            tableInfo.getTableName(), this.sqlWhereEntityWrapper(true, tableInfo), this.sqlOrderBy(tableInfo),
+            this.sqlComment());
+        SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
+        return this.addSelectMappedStatementForTable(mapperClass, sqlSource, tableInfo);
+    }
+
+}

+ 26 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectPageByScope.java

@@ -0,0 +1,26 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlSource;
+
+import com.baomidou.mybatisplus.core.enums.SqlMethod;
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+
+public class SelectPageByScope extends AbstractMethod {
+
+    public SelectPageByScope() {
+        super("selectPageByScope");
+    }
+
+    @Override
+    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
+        SqlMethod sqlMethod = SqlMethod.SELECT_PAGE;
+        String sql = String.format(sqlMethod.getSql(), this.sqlFirst(), this.sqlSelectColumns(tableInfo, true),
+            tableInfo.getTableName(), this.sqlWhereEntityWrapper(true, tableInfo), this.sqlOrderBy(tableInfo),
+            this.sqlComment());
+        SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass);
+        return this.addSelectMappedStatementForTable(mapperClass, sqlSource, tableInfo);
+    }
+
+}

+ 40 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/datascope/SelectRealById.java

@@ -0,0 +1,40 @@
+package com.yaoyicloud.easier.common.data.datascope;
+
+import org.apache.ibatis.mapping.MappedStatement;
+import org.apache.ibatis.mapping.SqlSource;
+import org.apache.ibatis.scripting.defaults.RawSqlSource;
+
+import com.baomidou.mybatisplus.core.injector.AbstractMethod;
+import com.baomidou.mybatisplus.core.metadata.TableInfo;
+
+/**
+ * 忽略逻辑查询
+ *
+ * @author jimmy
+ * @date 2024-08-19 17:52
+ */
+public class SelectRealById extends AbstractMethod {
+
+    public SelectRealById() {
+        super("selectRealById");
+    }
+
+    /**
+     * 注入自定义 MappedStatement
+     *
+     * @param mapperClass mapper 接口
+     * @param modelClass mapper 泛型
+     * @param tableInfo 数据库表反射信息
+     * @return MappedStatement
+     */
+    @Override
+    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
+
+        SqlSource sqlSource = new RawSqlSource(configuration,
+            String.format("SELECT %s FROM %s WHERE %s=#{%s}", sqlSelectColumns(tableInfo, false),
+                tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty()),
+            Object.class);
+
+        return this.addSelectMappedStatementForTable(mapperClass, "selectRealById", sqlSource, tableInfo);
+    }
+}

+ 49 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/handler/JsonLongArrayTypeHandler.java

@@ -0,0 +1,49 @@
+package com.yaoyicloud.easier.common.data.handler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.SneakyThrows;
+
+@MappedTypes(value = {Long[].class})
+@MappedJdbcTypes(value = JdbcType.VARCHAR)
+public class JsonLongArrayTypeHandler extends BaseTypeHandler<Long[]> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, Long[] parameter, JdbcType jdbcType)
+        throws SQLException {
+        ps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA));
+    }
+
+    @Override
+    @SneakyThrows
+    public Long[] getNullableResult(ResultSet rs, String columnName) {
+        String reString = rs.getString(columnName);
+        return Convert.toLongArray(reString);
+    }
+
+    @Override
+    @SneakyThrows
+    public Long[] getNullableResult(ResultSet rs, int columnIndex) {
+        String reString = rs.getString(columnIndex);
+        return Convert.toLongArray(reString);
+    }
+
+    @Override
+    @SneakyThrows
+    public Long[] getNullableResult(CallableStatement cs, int columnIndex) {
+        String reString = cs.getString(columnIndex);
+        return Convert.toLongArray(reString);
+    }
+
+}

+ 56 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/handler/JsonStringArrayTypeHandler.java

@@ -0,0 +1,56 @@
+package com.yaoyicloud.easier.common.data.handler;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.apache.ibatis.type.BaseTypeHandler;
+import org.apache.ibatis.type.JdbcType;
+import org.apache.ibatis.type.MappedJdbcTypes;
+import org.apache.ibatis.type.MappedTypes;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.SneakyThrows;
+
+/**
+ * Mybatis数组,符串互转
+ * <p>
+ * MappedJdbcTypes 数据库中的数据类型 MappedTypes java中的的数据类型
+ *
+ * @date 2019-11-20
+ */
+@MappedTypes(value = {String[].class})
+@MappedJdbcTypes(value = JdbcType.VARCHAR)
+public class JsonStringArrayTypeHandler extends BaseTypeHandler<String[]> {
+
+    @Override
+    public void setNonNullParameter(PreparedStatement ps, int i, String[] parameter, JdbcType jdbcType)
+        throws SQLException {
+        ps.setString(i, ArrayUtil.join(parameter, StrUtil.COMMA));
+    }
+
+    @Override
+    @SneakyThrows
+    public String[] getNullableResult(ResultSet rs, String columnName) {
+        String reString = rs.getString(columnName);
+        return Convert.toStrArray(reString);
+    }
+
+    @Override
+    @SneakyThrows
+    public String[] getNullableResult(ResultSet rs, int columnIndex) {
+        String reString = rs.getString(columnIndex);
+        return Convert.toStrArray(reString);
+    }
+
+    @Override
+    @SneakyThrows
+    public String[] getNullableResult(CallableStatement cs, int columnIndex) {
+        String reString = cs.getString(columnIndex);
+        return Convert.toStrArray(reString);
+    }
+
+}

+ 180 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/DruidSqlLogFilter.java

@@ -0,0 +1,180 @@
+package com.yaoyicloud.easier.common.data.mybatis;
+
+import java.sql.SQLException;
+import java.time.temporal.TemporalAccessor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.alibaba.druid.DbType;
+import com.alibaba.druid.filter.FilterChain;
+import com.alibaba.druid.filter.FilterEventAdapter;
+import com.alibaba.druid.proxy.jdbc.JdbcParameter;
+import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
+import com.alibaba.druid.proxy.jdbc.StatementProxy;
+import com.alibaba.druid.sql.SQLUtils;
+import com.alibaba.druid.sql.ast.SQLStatement;
+import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
+import com.alibaba.druid.stat.TableStat;
+import com.alibaba.druid.util.StringUtils;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@RequiredArgsConstructor
+public class DruidSqlLogFilter extends FilterEventAdapter {
+
+    private static final SQLUtils.FormatOption FORMAT_OPTION = new SQLUtils.FormatOption(false, false);
+
+    private final EasierMybatisProperties properties;
+
+    @Override
+    protected void statementExecuteBefore(StatementProxy statement, String sql) {
+        statement.setLastExecuteStartNano();
+    }
+
+    @Override
+    protected void statementExecuteBatchBefore(StatementProxy statement) {
+        statement.setLastExecuteStartNano();
+    }
+
+    @Override
+    protected void statementExecuteUpdateBefore(StatementProxy statement, String sql) {
+        statement.setLastExecuteStartNano();
+    }
+
+    @Override
+    protected void statementExecuteQueryBefore(StatementProxy statement, String sql) {
+        statement.setLastExecuteStartNano();
+    }
+
+    @Override
+    protected void statementExecuteAfter(StatementProxy statement, String sql, boolean firstResult) {
+        statement.setLastExecuteTimeNano();
+    }
+
+    @Override
+    protected void statementExecuteBatchAfter(StatementProxy statement, int[] result) {
+        statement.setLastExecuteTimeNano();
+    }
+
+    @Override
+    protected void statementExecuteQueryAfter(StatementProxy statement, String sql, ResultSetProxy resultSet) {
+        statement.setLastExecuteTimeNano();
+    }
+
+    @Override
+    protected void statementExecuteUpdateAfter(StatementProxy statement, String sql, int updateCount) {
+        statement.setLastExecuteTimeNano();
+    }
+
+    @SuppressWarnings("checkstyle:ReturnCount")
+    @Override
+    public void statement_close(FilterChain chain, StatementProxy statement) throws SQLException {
+        // 先调用父类关闭 statement
+        super.statement_close(chain, statement);
+        // 支持动态开启
+        if (!properties.isShowSql()) {
+            return;
+        }
+
+        // 是否开启调试
+        if (!log.isInfoEnabled()) {
+            return;
+        }
+
+        // 打印可执行的 sql
+        String sql = statement.getBatchSql();
+        // sql 为空直接返回
+        if (StringUtils.isEmpty(sql)) {
+            return;
+        }
+
+        String dbType = statement.getConnectionProxy().getDirectDataSource().getDbType();
+
+        // 判断表名是配置了匹配过滤
+        if (CollUtil.isNotEmpty(properties.getSkipTable())) {
+            List<String> skipTableList = properties.getSkipTable();
+
+            List<String> tableNameList = getTablesBydruid(sql, dbType);
+            if (tableNameList.stream().anyMatch(tableName -> StrUtil.containsAnyIgnoreCase(tableName,
+                ArrayUtil.toArray(skipTableList, String.class)))) {
+                return;
+            }
+        }
+
+        int parametersSize = statement.getParametersSize();
+        List<Object> parameters = new ArrayList<>(parametersSize);
+        for (int i = 0; i < parametersSize; ++i) {
+            // 转换参数,处理 java8 时间
+            parameters.add(getJdbcParameter(statement.getParameter(i)));
+        }
+        String formattedSql = SQLUtils.format(sql, DbType.of(dbType), parameters, FORMAT_OPTION);
+        printSql(formattedSql, statement);
+    }
+
+    private static Object getJdbcParameter(JdbcParameter jdbcParam) {
+        if (jdbcParam == null) {
+            return null;
+        }
+        Object value = jdbcParam.getValue();
+        // 处理 java8 时间
+        if (value instanceof TemporalAccessor) {
+            return value.toString();
+        }
+        return value;
+    }
+
+    private static void printSql(String sql, StatementProxy statement) {
+        // 打印 sql
+        String sqlLogger =
+            "\n\n======= Sql Logger ======================" + "\n{}" + "\n======= Sql Execute Time: {} =======\n";
+        log.info(sqlLogger, sql.trim(), format(statement.getLastExecuteTimeNano()));
+    }
+
+    /**
+     * 格式化执行时间,单位为 ms 和 s,保留三位小数
+     * 
+     * @param nanos 纳秒
+     * @return 格式化后的时间
+     */
+    private static String format(long nanos) {
+        if (nanos < 1) {
+            return "0ms";
+        }
+        double millis = (double) nanos / (1000 * 1000);
+        // 不够 1 ms,最小单位为 ms
+        if (millis > 1000) {
+            return String.format("%.3fs", millis / 1000);
+        } else {
+            return String.format("%.3fms", millis);
+        }
+    }
+
+    /**
+     * 从SQL中提取表名(sql中出现的所有表)
+     * 
+     * @param sql sql语句
+     * @param dbType dbType
+     * @return List<String>
+     */
+    public static List<String> getTablesBydruid(String sql, String dbType) {
+        List<String> result = new ArrayList<String>();
+        List<SQLStatement> stmtList = SQLUtils.parseStatements(sql, dbType);
+        for (SQLStatement stmt : stmtList) {
+            // 也可以用更精确的解析器,如MySqlSchemaStatVisitor
+            SchemaStatVisitor visitor = new SchemaStatVisitor();
+            stmt.accept(visitor);
+            Map<TableStat.Name, TableStat> tables = visitor.getTables();
+            for (TableStat.Name name : tables.keySet()) {
+                result.add(name.getName());
+            }
+        }
+        return result;
+    }
+
+}

+ 26 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/EasierMybatisProperties.java

@@ -0,0 +1,26 @@
+package com.yaoyicloud.easier.common.data.mybatis;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+
+import lombok.Data;
+
+@Data
+@RefreshScope
+@ConfigurationProperties("easier.mybatis")
+public class EasierMybatisProperties {
+
+    /**
+     * 是否打印可执行 sql
+     */
+    private boolean showSql = true;
+
+    /**
+     * 跳过表
+     */
+    private List<String> skipTable = new ArrayList<>();
+
+}

+ 174 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/MybatisPlusConfiguration.java

@@ -0,0 +1,174 @@
+package com.yaoyicloud.easier.common.data.mybatis;
+
+import java.util.List;
+import java.util.Properties;
+
+import javax.sql.DataSource;
+
+import org.apache.ibatis.mapping.DatabaseIdProvider;
+import org.apache.ibatis.mapping.VendorDatabaseIdProvider;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
+import com.yaoyicloud.easier.common.data.application.EasierApplicationConfigProperties;
+import com.yaoyicloud.easier.common.data.application.EasierApplicationHandler;
+import com.yaoyicloud.easier.common.data.datascope.DataScopeInnerInterceptor;
+import com.yaoyicloud.easier.common.data.datascope.DataScopeInterceptor;
+import com.yaoyicloud.easier.common.data.datascope.DataScopeSqlInjector;
+import com.yaoyicloud.easier.common.data.datascope.EasierDefaultDatascopeHandle;
+import com.yaoyicloud.easier.common.data.handler.JsonLongArrayTypeHandler;
+import com.yaoyicloud.easier.common.data.handler.JsonStringArrayTypeHandler;
+import com.yaoyicloud.easier.common.data.resolver.SqlFilterArgumentResolver;
+import com.yaoyicloud.easier.common.data.tenant.EasierTenantConfigProperties;
+import com.yaoyicloud.easier.common.data.tenant.EasierTenantHandler;
+import com.yaoyicloud.easier.common.security.service.EasierUser;
+import com.yaoyicloud.easier.workspace.api.feign.RemoteDataScopeService;
+
+@Configuration
+@ConditionalOnBean(DataSource.class)
+@AutoConfigureAfter(DataSourceAutoConfiguration.class)
+@EnableConfigurationProperties(EasierMybatisProperties.class)
+public class MybatisPlusConfiguration implements WebMvcConfigurer {
+
+    /**
+     * 增加请求参数解析器,对请求中的参数注入SQL 检查
+     *
+     * @param resolverList List<HandlerMethodArgumentResolver>
+     */
+    @Override
+    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolverList) {
+        resolverList.add(new SqlFilterArgumentResolver());
+    }
+
+    /**
+     * mybatis plus 拦截器配置
+     *
+     * @return EasierDefaultDataScopeHandle
+     */
+    @Bean
+    public MybatisPlusInterceptor mybatisPlusInterceptor(TenantLineInnerInterceptor tenantLineInnerInterceptor,
+        TenantLineInnerInterceptor applicationLineInnerInterceptor, DataScopeInterceptor dataScopeInterceptor,
+        OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor) {
+        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
+        // 乐观锁
+        interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor);
+        // 应用ID支持
+        interceptor.addInnerInterceptor(applicationLineInnerInterceptor);
+        // 注入多租户支持
+        interceptor.addInnerInterceptor(tenantLineInnerInterceptor);
+        // 数据权限
+        interceptor.addInnerInterceptor(dataScopeInterceptor);
+        // 分页支持
+        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
+        paginationInnerInterceptor.setMaxLimit(1000L);
+        interceptor.addInnerInterceptor(paginationInnerInterceptor);
+        return interceptor;
+    }
+
+    /**
+     * 创建租户维护处理器对象
+     *
+     * @return 处理后的租户维护处理器
+     */
+    @Bean
+    @ConditionalOnMissingBean(name = "tenantLineInnerInterceptor")
+    public TenantLineInnerInterceptor tenantLineInnerInterceptor(EasierTenantConfigProperties tenantConfigProperties) {
+        TenantLineInnerInterceptor tenantLineInnerInterceptor = new TenantLineInnerInterceptor();
+        tenantLineInnerInterceptor.setTenantLineHandler(new EasierTenantHandler(tenantConfigProperties));
+        return tenantLineInnerInterceptor;
+    }
+
+    /**
+     * 创建应用维护处理器对象
+     *
+     * @return 处理后的应用维护处理器
+     */
+    @Bean
+    @ConditionalOnMissingBean(name = "applicationLineInnerInterceptor")
+    public TenantLineInnerInterceptor
+        applicationLineInnerInterceptor(EasierApplicationConfigProperties applicationConfigProperties) {
+        TenantLineInnerInterceptor applicationLineInnerInterceptor = new TenantLineInnerInterceptor();
+        applicationLineInnerInterceptor.setTenantLineHandler(new EasierApplicationHandler(applicationConfigProperties));
+        return applicationLineInnerInterceptor;
+    }
+
+    /**
+     * 数据权限拦截器
+     *
+     * @return DataScopeInterceptor
+     */
+    @Bean
+    @ConditionalOnMissingBean
+    @ConditionalOnClass(EasierUser.class)
+    public DataScopeInterceptor dataScopeInterceptor(RemoteDataScopeService dataScopeService) {
+        DataScopeInnerInterceptor dataScopeInnerInterceptor = new DataScopeInnerInterceptor();
+        dataScopeInnerInterceptor.setDataScopeHandle(new EasierDefaultDatascopeHandle(dataScopeService));
+        return dataScopeInnerInterceptor;
+    }
+
+    @Bean
+    @Primary
+    @ConditionalOnBean(DataScopeInterceptor.class)
+    public DataScopeSqlInjector dataScopeSqlInjector() {
+        return new DataScopeSqlInjector();
+    }
+
+    @Bean
+    public DruidSqlLogFilter sqlLogFilter(EasierMybatisProperties properties) {
+        return new DruidSqlLogFilter(properties);
+    }
+
+    /**
+     * 审计字段自动填充
+     *
+     * @return {@link MetaObjectHandler}
+     */
+    @Bean
+    public MybatisPlusMetaObjectHandler mybatisPlusMetaObjectHandler() {
+        return new MybatisPlusMetaObjectHandler();
+    }
+
+    /**
+     * 数据库方言配置
+     *
+     * @return DatabaseIdProvider
+     */
+    @Bean
+    public DatabaseIdProvider databaseIdProvider() {
+        VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
+        Properties properties = new Properties();
+        properties.setProperty("SQL Server", "mssql");
+        databaseIdProvider.setProperties(properties);
+        return databaseIdProvider;
+    }
+
+    @Bean
+    public JsonStringArrayTypeHandler jsonStringArrayTypeHandler() {
+        return new JsonStringArrayTypeHandler();
+    }
+
+    @Bean
+    public JsonLongArrayTypeHandler jsonLongArrayTypeHandler() {
+        return new JsonLongArrayTypeHandler();
+    }
+
+    @Bean
+    public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
+        return new OptimisticLockerInnerInterceptor();
+    }
+
+}

+ 102 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/mybatis/MybatisPlusMetaObjectHandler.java

@@ -0,0 +1,102 @@
+package com.yaoyicloud.easier.common.data.mybatis;
+
+import java.nio.charset.Charset;
+import java.time.LocalDateTime;
+import java.util.Optional;
+
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.util.ClassUtils;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import com.yaoyicloud.easier.common.domain.enums.flag.CommonFlag;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * MybatisPlus 自动填充配置
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-10-28 11:47
+ */
+@Slf4j
+public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
+
+    @Override
+    public void insertFill(MetaObject metaObject) {
+        log.debug("mybatis plus start insert fill ....");
+        LocalDateTime now = LocalDateTime.now();
+
+        // 审计字段自动填充,覆盖用户输入
+        fillValIfNullByName("createdTime", now, metaObject, true);
+        fillValIfNullByName("modifiedTime", now, metaObject, true);
+        fillValIfNullByName("createdBy", getUserName(), metaObject, true);
+        fillValIfNullByName("modifiedBy", getUserName(), metaObject, true);
+        fillValIfNullByName("version", 0, metaObject, false);
+
+        // 删除标记自动填充
+        fillValIfNullByName("delFlag", CommonFlag.DelFlag.OK, metaObject, true);
+    }
+
+    @Override
+    public void updateFill(MetaObject metaObject) {
+        log.debug("mybatis plus start update fill ....");
+        fillValIfNullByName("modifiedTime", LocalDateTime.now(), metaObject, true);
+        fillValIfNullByName("modifiedBy", getUserName(), metaObject, true);
+    }
+
+    /**
+     * 填充值,先判断是否有手动设置,优先手动设置的值,例如:job必须手动设置
+     *
+     * @param fieldName 属性名
+     * @param fieldVal 属性值
+     * @param metaObject MetaObject
+     * @param isCover 是否覆盖原有值,避免更新操作手动入参
+     */
+    @SuppressWarnings("checkstyle:ReturnCount")
+    private static void fillValIfNullByName(String fieldName, Object fieldVal, MetaObject metaObject, boolean isCover) {
+        // 0. 如果填充值为空
+        if (fieldVal == null) {
+            return;
+        }
+        // 1. 没有 get 方法
+        if (!metaObject.hasSetter(fieldName)) {
+            return;
+        }
+        // 2. 如果用户有手动设置的值
+        Object userSetValue = metaObject.getValue(fieldName);
+        String setValueStr = StrUtil.str(userSetValue, Charset.defaultCharset());
+        if (StrUtil.isNotBlank(setValueStr) && !isCover) {
+            return;
+        }
+        // 3. field 类型相同时设置
+        Class<?> getterType = metaObject.getGetterType(fieldName);
+        if (ClassUtils.isAssignableValue(getterType, fieldVal)) {
+            metaObject.setValue(fieldName, fieldVal);
+        }
+    }
+
+    /**
+     * 获取 spring security 当前的用户名
+     *
+     * @return 当前用户名
+     */
+    private String getUserName() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+        // 匿名接口直接返回
+        if (authentication instanceof AnonymousAuthenticationToken) {
+            return "anonymous";
+        }
+
+        if (Optional.ofNullable(authentication).isPresent()) {
+            return authentication.getName();
+        }
+
+        return "anonymous";
+    }
+
+}

+ 101 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/DictResolver.java

@@ -0,0 +1,101 @@
+package com.yaoyicloud.easier.common.data.resolver;
+
+import java.util.List;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.yaoyicloud.easier.common.core.util.SpringContextHolder;
+import com.yaoyicloud.easier.workspace.api.entity.SysDictItem;
+import com.yaoyicloud.easier.workspace.api.feign.RemoteDictService;
+
+import cn.hutool.core.lang.Assert;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class DictResolver {
+
+    /**
+     * 根据字典类型获取所有字典项
+     * 
+     * @param type 字典类型
+     * @return 字典数据项集合
+     */
+    public List<SysDictItem> getDictItemsByType(String type) {
+        Assert.isTrue(StringUtils.isNotBlank(type), "参数不合法");
+
+        RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);
+
+        return remoteDictService.getDictByType(type).getData();
+    }
+
+    /**
+     * 根据字典类型以及字典项字典值获取字典标签
+     * 
+     * @param type 字典类型
+     * @param itemValue 字典项字典值
+     * @return 字典项标签值
+     */
+    public String getDictItemLabel(String type, String itemValue) {
+        Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法");
+
+        SysDictItem sysDictItem = getDictItemByItemValue(type, itemValue);
+
+        return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getLabel() : StringPool.EMPTY;
+    }
+
+    /**
+     * 根据字典类型以及字典标签获取字典值
+     * 
+     * @param type 字典类型
+     * @param itemLabel 字典数据标签
+     * @return 字典数据项值
+     */
+    public String getDictItemValue(String type, String itemLabel) {
+        Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法");
+
+        SysDictItem sysDictItem = getDictItemByItemLabel(type, itemLabel);
+
+        return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getItemValue() : StringPool.EMPTY;
+    }
+
+    /**
+     * 根据字典类型以及字典值获取字典项
+     * 
+     * @param type 字典类型
+     * @param itemValue 字典数据值
+     * @return 字典数据项
+     */
+    public SysDictItem getDictItemByItemValue(String type, String itemValue) {
+        Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法");
+
+        List<SysDictItem> dictItemList = getDictItemsByType(type);
+
+        if (CollectionUtils.isNotEmpty(dictItemList)) {
+            return dictItemList.stream().filter(item -> itemValue.equals(item.getItemValue())).findFirst().orElse(null);
+        }
+
+        return null;
+    }
+
+    /**
+     * 根据字典类型以及字典标签获取字典项
+     * 
+     * @param type 字典类型
+     * @param itemLabel 字典数据项标签
+     * @return 字典数据项
+     */
+    public SysDictItem getDictItemByItemLabel(String type, String itemLabel) {
+        Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法");
+
+        List<SysDictItem> dictItemList = getDictItemsByType(type);
+
+        if (CollectionUtils.isNotEmpty(dictItemList)) {
+            return dictItemList.stream().filter(item -> itemLabel.equals(item.getLabel())).findFirst().orElse(null);
+        }
+
+        return null;
+    }
+
+}

+ 94 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/ParamResolver.java

@@ -0,0 +1,94 @@
+package com.yaoyicloud.easier.common.data.resolver;
+
+import java.util.Map;
+import java.util.Objects;
+
+import com.yaoyicloud.easier.common.core.constant.SecurityConstants;
+import com.yaoyicloud.easier.common.core.util.SpringContextHolder;
+import com.yaoyicloud.easier.workspace.api.feign.RemoteParamService;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import lombok.experimental.UtilityClass;
+
+/**
+ * 参数解析器
+ *
+ * @author jimmy
+ * @version 1.2.0
+ * @date 2024/04/07 17:10
+ */
+@UtilityClass
+public class ParamResolver {
+
+    /**
+     * 根据多个key 查询value 配置 结果使用hutool 的maputil 进行包装处理 MapUtil.getBool(result,key)
+     *
+     * @param key key
+     * @return Map<String, Object>
+     */
+    public Map<String, Object> getMap(String... key) {
+        // 校验入参是否合法
+        if (Objects.isNull(key)) {
+            throw new IllegalArgumentException("参数不合法");
+        }
+
+        RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
+        return remoteParamService.getByKeys(key, SecurityConstants.FROM_IN).getData();
+    }
+
+    /**
+     * 根据key 查询value 配置
+     *
+     * @param key key
+     * @param defaultVal 默认值
+     * @return value
+     */
+    public Long getLong(String key, Long... defaultVal) {
+        return checkAndGet(key, Long.class, defaultVal);
+    }
+
+    /**
+     * 根据key 查询value 配置
+     *
+     * @param key key
+     * @param defaultVal 默认值
+     * @return value
+     */
+    public Integer getInt(String key, Integer... defaultVal) {
+        return checkAndGet(key, Integer.class, defaultVal);
+    }
+
+    /**
+     * 根据key 查询value 配置
+     *
+     * @param key key
+     * @param defaultVal 默认值
+     * @return value
+     */
+    public String getStr(String key, String... defaultVal) {
+        return checkAndGet(key, String.class, defaultVal);
+    }
+
+    private <T> T checkAndGet(String key, Class<T> clazz, T... defaultVal) {
+        // 校验入参是否合法
+        if (StrUtil.isBlank(key) || defaultVal.length > 1) {
+            throw new IllegalArgumentException("参数不合法");
+        }
+
+        RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
+
+        String result = remoteParamService.getByKey(key, SecurityConstants.FROM_IN).getData();
+
+        if (StrUtil.isNotBlank(result)) {
+            return Convert.convert(clazz, result);
+        }
+
+        if (defaultVal.length == 1) {
+            return Convert.convert(clazz, defaultVal.clone()[0]);
+
+        }
+        return null;
+    }
+
+}

+ 96 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/SqlFilterArgumentResolver.java

@@ -0,0 +1,96 @@
+package com.yaoyicloud.easier.common.data.resolver;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import com.baomidou.mybatisplus.core.metadata.OrderItem;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.yaoyicloud.easier.common.core.exception.CheckedException;
+
+import cn.hutool.core.comparator.CompareUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class SqlFilterArgumentResolver implements HandlerMethodArgumentResolver {
+
+    private static final String[] KEYWORDS = {"master", "truncate", "insert", "select", "delete", "update", "declare",
+        "alter", "drop", "sleep", "extractvalue", "concat"};
+
+    /**
+     * 判断Controller是否包含page 参数
+     * 
+     * @param parameter 参数
+     * @return 是否过滤
+     */
+    @Override
+    public boolean supportsParameter(MethodParameter parameter) {
+        return parameter.getParameterType().equals(Page.class);
+    }
+
+    /**
+     * @param parameter 入参集合
+     * @param mavContainer model 和 view
+     * @param webRequest web相关
+     * @param binderFactory 入参解析
+     * @return 检查后新的page对象
+     *         <p>
+     *         page 只支持查询 GET .如需解析POST获取请求报文体处理
+     */
+    @Override
+    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
+        NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
+
+        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
+
+        String ascs = request.getParameter("ascs");
+        String descs = request.getParameter("descs");
+
+        String current = request.getParameter("current");
+        String size = request.getParameter("size");
+
+        Page page = new Page();
+        if (StrUtil.isNotBlank(current)) {
+            // 如果current page 小于零 视为不合法数据
+            if (CompareUtil.compare(Long.parseLong(current), 0L) < 0) {
+                throw new CheckedException("current page error");
+            }
+            page.setCurrent(Long.parseLong(current));
+        }
+
+        if (StrUtil.isNotBlank(size)) {
+            page.setSize(Long.parseLong(size));
+        }
+
+        List<OrderItem> orderItemList = new ArrayList<>();
+        Optional.ofNullable(ascs).ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
+            .filter(sqlInjectPredicate()).map(OrderItem::asc).collect(Collectors.toList())));
+        Optional.ofNullable(descs).ifPresent(s -> orderItemList.addAll(Arrays.stream(s.split(StrUtil.COMMA))
+            .filter(sqlInjectPredicate()).map(OrderItem::desc).collect(Collectors.toList())));
+        page.addOrder(orderItemList);
+
+        return page;
+    }
+
+    /**
+     * 判断用户输入里面有没有关键字
+     * 
+     * @return Predicate
+     */
+    private Predicate<String> sqlInjectPredicate() {
+        return sql -> Arrays.stream(KEYWORDS).noneMatch(keyword -> StrUtil.containsIgnoreCase(sql, keyword));
+    }
+
+}

+ 30 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/resolver/TenantKeyStrResolver.java

@@ -0,0 +1,30 @@
+package com.yaoyicloud.easier.common.data.resolver;
+
+import com.yaoyicloud.easier.common.core.util.KeyStrResolver;
+import com.yaoyicloud.easier.common.data.tenant.TenantContextHolder;
+
+public class TenantKeyStrResolver implements KeyStrResolver {
+
+    /**
+     * 传入字符串增加 租户编号:in
+     * 
+     * @param in 输入字符串
+     * @param split 分割符
+     * @return
+     */
+    @Override
+    public String extract(String in, String split) {
+        return TenantContextHolder.getTenantId() + split + in;
+    }
+
+    /**
+     * 返回当前租户ID
+     * 
+     * @return
+     */
+    @Override
+    public String key() {
+        return String.valueOf(TenantContextHolder.getTenantId());
+    }
+
+}

+ 28 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierFeignTenantInterceptor.java

@@ -0,0 +1,28 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import com.yaoyicloud.easier.common.core.constant.CommonConstants;
+
+import feign.RequestInterceptor;
+import feign.RequestTemplate;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class EasierFeignTenantInterceptor implements RequestInterceptor {
+
+    @Override
+    public void apply(RequestTemplate requestTemplate) {
+        Long val = CommonConstants.APPLICATION_ID_1; // Default FXY
+        if (TenantContextHolder.getApplicationId() != null) {
+            val = TenantContextHolder.getApplicationId();
+        }
+
+        requestTemplate.header(CommonConstants.APPLICATION_ID_HEADER, val.toString());
+
+        if (TenantContextHolder.getTenantId() == null) {
+            log.debug("TTL 中的 租户ID为空,feign拦截器 >> 跳过");
+            return;
+        }
+        requestTemplate.header(CommonConstants.TENANT_ID_HEADER, TenantContextHolder.getTenantId().toString());
+    }
+
+}

+ 28 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierTenantConfigProperties.java

@@ -0,0 +1,28 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.context.annotation.Configuration;
+
+import lombok.Data;
+
+@Data
+@RefreshScope
+@Configuration
+@ConfigurationProperties(prefix = "easier.tenant")
+public class EasierTenantConfigProperties {
+
+    /**
+     * 维护租户列名称
+     */
+    private String column = "tenant_id";
+
+    /**
+     * 多租户的数据表集合
+     */
+    private List<String> tables = new ArrayList<>();
+
+}

+ 22 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierTenantConfiguration.java

@@ -0,0 +1,22 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+
+import feign.RequestInterceptor;
+
+@Configuration
+public class EasierTenantConfiguration {
+
+    @Bean
+    public RequestInterceptor easierFeignTenantInterceptor() {
+        return new EasierFeignTenantInterceptor();
+    }
+
+    @Bean
+    public ClientHttpRequestInterceptor easierTenantRequestInterceptor() {
+        return new TenantRequestInterceptor();
+    }
+
+}

+ 68 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/EasierTenantHandler.java

@@ -0,0 +1,68 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import net.sf.jsqlparser.expression.Expression;
+import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.NullValue;
+
+@Slf4j
+@RequiredArgsConstructor
+public class EasierTenantHandler implements TenantLineHandler {
+
+    private final EasierTenantConfigProperties properties;
+
+    /**
+     * 获取租户 ID 值表达式,只支持单个 ID 值
+     * <p>
+     * 
+     * @return 租户 ID 值表达式
+     */
+    @Override
+    public Expression getTenantId() {
+        Long tenantId = TenantContextHolder.getTenantId();
+        log.debug("当前租户为 >> {}", tenantId);
+
+        if (tenantId == null) {
+            return new NullValue();
+        }
+        return new LongValue(tenantId);
+    }
+
+    /**
+     * 获取租户字段名
+     * 
+     * @return 租户字段名
+     */
+    @Override
+    public String getTenantIdColumn() {
+        return properties.getColumn();
+    }
+
+    /**
+     * 根据表名判断是否忽略拼接多租户条件
+     * <p>
+     * 默认都要进行解析并拼接多租户条件
+     * 
+     * @param tableName 表名
+     * @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
+     */
+    @Override
+    public boolean ignoreTable(String tableName) {
+        // 判断是否跳过当前查询的租户过滤
+        if (TenantContextHolder.getTenantSkip()) {
+            return Boolean.TRUE;
+        }
+
+        Long tenantId = TenantContextHolder.getTenantId();
+        // 租户中ID 为空,查询全部,不进行过滤
+        if (tenantId == null) {
+            return Boolean.TRUE;
+        }
+
+        return !properties.getTables().contains(tableName);
+    }
+
+}

+ 146 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantBroker.java

@@ -0,0 +1,146 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import java.util.function.Supplier;
+
+import com.yaoyicloud.easier.common.core.exception.TenantBrokerExceptionWrapper;
+
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 租户运行时代理<br/>
+ * 这是一个工具类,用于切换租户运行时,保护租户ID上下文<br/>
+ * 下面这段代码演示问题所在
+ * 
+ * <pre>
+ * void methodA() {
+ *     // 因为某些特殊原因,需要手动指定租户
+ *     TenantContextHolder.setTenantId(1);
+ *     // do something ...
+ * }
+ * 
+ * void methodB() {
+ *     // 因为某些特殊原因,需要手动指定租户
+ *     TenantContextHolder.setTenantId(2);
+ *     methodA();
+ *     // 此时租户ID已经变成 1
+ *     // do something ...
+ * }
+ * </pre>
+ * 
+ * 嵌套设置租户ID会导致租户上下文难以维护,并且很难察觉,容易导致数据错乱。 推荐的写法:
+ * 
+ * <pre>
+ * void methodA() {
+ *     TenantBroker.RunAs(1, () -> {
+ *         // do something ...
+ *     });
+ * }
+ * 
+ * void methodB() {
+ *     TenantBroker.RunAs(2, () -> {
+ *         methodA();
+ *         // do something ...
+ *     });
+ * }
+ * </pre>
+ *
+ * @date 2020/6/12
+ * @since 3.9
+ */
+@Slf4j
+@UtilityClass
+public class TenantBroker {
+
+    @FunctionalInterface
+    public interface RunAs<T> {
+
+        /**
+         * 执行业务逻辑
+         *
+         * @param tenantId 租户ID
+         * @throws Exception 异常
+         */
+        void run(T tenantId) throws Exception;
+
+    }
+
+    @FunctionalInterface
+    public interface ApplyAs<T, R> {
+
+        /**
+         * 执行业务逻辑,返回一个值
+         *
+         * @param tenantId 租户ID
+         * @return 运行结果
+         * @throws Exception 异常
+         */
+        R apply(T tenantId) throws Exception;
+
+    }
+
+    /**
+     * 以某个租户的身份运行
+     *
+     * @param tenant 租户ID
+     * @param func function
+     */
+    public void runAs(Long tenant, RunAs<Long> func) {
+        final Long pre = TenantContextHolder.getTenantId();
+        try {
+            log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
+            TenantContextHolder.setTenantId(tenant);
+            func.run(tenant);
+        } catch (Exception e) {
+            throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
+        } finally {
+            log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
+            TenantContextHolder.setTenantId(pre);
+        }
+    }
+
+    /**
+     * 以某个租户的身份运行
+     *
+     * @param tenant 租户ID
+     * @param func function
+     * @param <T> 返回数据类型
+     * @return 运行结果
+     */
+    public <T> T applyAs(Long tenant, ApplyAs<Long, T> func) {
+        final Long pre = TenantContextHolder.getTenantId();
+        try {
+            log.trace("TenantBroker 切换租户{} -> {}", pre, tenant);
+            TenantContextHolder.setTenantId(tenant);
+            return func.apply(tenant);
+        } catch (Exception e) {
+            throw new TenantBrokerExceptionWrapper(e.getMessage(), e);
+        } finally {
+            log.trace("TenantBroker 还原租户{} <- {}", pre, tenant);
+            TenantContextHolder.setTenantId(pre);
+        }
+    }
+
+    /**
+     * 以某个租户的身份运行
+     *
+     * @param supplier supplier
+     * @param func function
+     */
+    public void runAs(Supplier<Long> supplier, RunAs<Long> func) {
+        runAs(supplier.get(), func);
+    }
+
+    /**
+     * 以某个租户的身份运行
+     *
+     * @param supplier supplier
+     * @param func function
+     * @param <T> 返回数据类型
+     * @return 运行结果
+     */
+    public <T> T applyAs(Supplier<Long> supplier, ApplyAs<Long, T> func) {
+        return applyAs(supplier.get(), func);
+    }
+
+}

+ 72 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantContextHolder.java

@@ -0,0 +1,72 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import com.alibaba.ttl.TransmittableThreadLocal;
+
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class TenantContextHolder {
+
+    @SuppressWarnings("checkstyle:MemberName")
+    private final ThreadLocal<Long> THREAD_LOCAL_TENANT = new TransmittableThreadLocal<>();
+
+    @SuppressWarnings("checkstyle:MemberName")
+    private final ThreadLocal<Boolean> THREAD_LOCAL_TENANT_SKIP_FLAG = new TransmittableThreadLocal<>();
+
+    @SuppressWarnings("checkstyle:MemberName")
+    private final ThreadLocal<Long> THREAD_LOCAL_APPLICATION = new TransmittableThreadLocal<>();
+
+    /**
+     * TTL 设置租户ID<br/>
+     * <b>谨慎使用此方法,避免嵌套调用。尽量使用 {@code TenantBroker} </b>
+     *
+     * @param tenantId 租户ID
+     * @see TenantBroker
+     */
+    public void setTenantId(Long tenantId) {
+        THREAD_LOCAL_TENANT.set(tenantId);
+    }
+
+    /**
+     * 设置是否过滤的标识
+     */
+    public void setTenantSkip() {
+        THREAD_LOCAL_TENANT_SKIP_FLAG.set(Boolean.TRUE);
+    }
+
+    /**
+     * 获取TTL中的租户ID
+     *
+     * @return 租户ID
+     */
+    public Long getTenantId() {
+        return THREAD_LOCAL_TENANT.get();
+    }
+
+    /**
+     * 获取是否跳过租户过滤的标识
+     *
+     * @return 过滤结果
+     */
+    public Boolean getTenantSkip() {
+        return THREAD_LOCAL_TENANT_SKIP_FLAG.get() != null && THREAD_LOCAL_TENANT_SKIP_FLAG.get();
+    }
+
+    public void clearTenant() {
+        THREAD_LOCAL_TENANT.remove();
+        THREAD_LOCAL_TENANT_SKIP_FLAG.remove();
+    }
+
+    public void setApplicationId(Long id) {
+        THREAD_LOCAL_APPLICATION.set(id);
+    }
+
+    public static Long getApplicationId() {
+        return THREAD_LOCAL_APPLICATION.get();
+    }
+
+    public void clearApplication() {
+        THREAD_LOCAL_APPLICATION.remove();
+    }
+
+}

+ 61 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantContextHolderFilter.java

@@ -0,0 +1,61 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.GenericFilterBean;
+
+import com.yaoyicloud.easier.common.core.constant.CommonConstants;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE)
+public class TenantContextHolderFilter extends GenericFilterBean {
+
+    private static final String UNDEFINED_STR = "undefined";
+
+    @Override
+    @SneakyThrows
+    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) {
+        HttpServletRequest request = (HttpServletRequest) servletRequest;
+        HttpServletResponse response = (HttpServletResponse) servletResponse;
+
+        TenantContextHolder.setApplicationId(
+            getParamId(request, CommonConstants.APPLICATION_ID_HEADER, CommonConstants.APPLICATION_ID_1));
+
+        log.debug("获取header中的应用ID为:{}", TenantContextHolder.getApplicationId());
+
+        TenantContextHolder
+            .setTenantId(getParamId(request, CommonConstants.TENANT_ID_HEADER, CommonConstants.TENANT_ID_1));
+
+        log.debug("获取header中的租户ID为:{}", TenantContextHolder.getTenantId());
+
+        filterChain.doFilter(request, response);
+
+        TenantContextHolder.clearTenant();
+        TenantContextHolder.clearApplication();
+    }
+
+    private long getParamId(HttpServletRequest request, String key, long defaultValue) {
+        String headerId = request.getHeader(key);
+        String paramId = request.getParameter(key);
+
+        if (StrUtil.isNotBlank(headerId) && !StrUtil.equals(UNDEFINED_STR, headerId)) {
+            return Long.parseLong(headerId);
+        } else if (StrUtil.isNotBlank(paramId) && !StrUtil.equals(UNDEFINED_STR, paramId)) {
+            return Long.parseLong(paramId);
+        } else {
+            return defaultValue;
+        }
+    }
+}

+ 32 - 0
easier-common/easier-common-data/src/main/java/com/yaoyicloud/easier/common/data/tenant/TenantRequestInterceptor.java

@@ -0,0 +1,32 @@
+package com.yaoyicloud.easier.common.data.tenant;
+
+import java.io.IOException;
+
+import org.springframework.http.HttpRequest;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+
+import com.yaoyicloud.easier.common.core.constant.CommonConstants;
+
+public class TenantRequestInterceptor implements ClientHttpRequestInterceptor {
+
+    @Override
+    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
+        throws IOException {
+
+        long val = CommonConstants.APPLICATION_ID_1; // Default FXY
+        if (TenantContextHolder.getApplicationId() != null) {
+            val = TenantContextHolder.getApplicationId();
+        }
+        request.getHeaders().set(CommonConstants.APPLICATION_ID_HEADER, String.valueOf(val));
+
+        if (TenantContextHolder.getTenantId() != null) {
+            request.getHeaders().set(CommonConstants.TENANT_ID_HEADER,
+                String.valueOf(TenantContextHolder.getTenantId()));
+        }
+
+        return execution.execute(request, body);
+    }
+
+}

+ 10 - 0
easier-common/easier-common-data/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1,10 @@
+com.yaoyicloud.easier.common.data.cache.RedisTemplateConfiguration
+com.yaoyicloud.easier.common.data.cache.RedisMessageConfiguration
+com.yaoyicloud.easier.common.data.cache.RedisCacheManagerConfiguration
+com.yaoyicloud.easier.common.data.cache.RedisCacheAutoConfiguration
+com.yaoyicloud.easier.common.data.tenant.EasierTenantConfigProperties
+com.yaoyicloud.easier.common.data.application.EasierApplicationConfigProperties
+com.yaoyicloud.easier.common.data.tenant.TenantContextHolderFilter
+com.yaoyicloud.easier.common.data.tenant.EasierTenantConfiguration
+com.yaoyicloud.easier.common.data.mybatis.MybatisPlusConfiguration
+com.yaoyicloud.easier.common.data.resolver.TenantKeyStrResolver

+ 43 - 0
easier-common/easier-common-domain/pom.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.yaoyicloud</groupId>
+        <artifactId>easier-common</artifactId>
+        <version>0.0.1-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>easier-common-domain</artifactId>
+    <packaging>jar</packaging>
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <!--mybatis plus-->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-extension</artifactId>
+            <version>3.5.4</version>
+        </dependency>
+        <!--mongo-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-mongodb</artifactId>
+        </dependency>
+        <!--validation-api-->
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-core</artifactId>
+            <version>5.8.22</version>
+        </dependency>
+    </dependencies>
+</project>

+ 49 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierCommonEntity.java

@@ -0,0 +1,49 @@
+package com.yaoyicloud.easier.common.domain;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 通用实体用来记录操作历史, 由管理员或者用户操作的实体从此继承
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 12/13/23 21:29
+ */
+@Data
+@NoArgsConstructor(access = AccessLevel.NONE)
+public abstract class EasierCommonEntity implements Serializable {
+
+    private static final long serialVersionUID = 7344139240780431717L;
+
+    /**
+     * 创建人
+     */
+    @TableField(fill = FieldFill.INSERT)
+    protected String createdBy;
+
+    /**
+     * 更新人
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    protected String modifiedBy;
+
+    /**
+     * 创建时间
+     */
+    @TableField(fill = FieldFill.INSERT)
+    protected LocalDateTime createdTime;
+
+    /**
+     * 更新时间
+     */
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    protected LocalDateTime modifiedTime;
+}

+ 27 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierDelEntity.java

@@ -0,0 +1,27 @@
+package com.yaoyicloud.easier.common.domain;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.NotNull;
+
+import com.yaoyicloud.easier.common.domain.enums.type.CommonType;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 删除实体
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/01/24 13:26
+ */
+@Data
+@NoArgsConstructor(access = AccessLevel.NONE)
+public abstract class EasierDelEntity implements Serializable {
+    private static final long serialVersionUID = -9031491888305921908L;
+
+    @NotNull(message = "删除类型必填")
+    protected CommonType.DeleteType deleteType;
+}

+ 24 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierMongoEntity.java

@@ -0,0 +1,24 @@
+package com.yaoyicloud.easier.common.domain;
+
+import java.io.Serializable;
+
+import org.springframework.data.mongodb.core.mapping.MongoId;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author jimmy
+ * @version 1.0.0
+ * @date 1/4/24 22:26
+ */
+@Data
+@NoArgsConstructor(access = AccessLevel.NONE)
+public abstract class EasierMongoEntity implements Serializable {
+
+    private static final long serialVersionUID = 3485834548943262185L;
+
+    @MongoId
+    protected String mongoId;
+}

+ 52 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierPaginationEntity.java

@@ -0,0 +1,52 @@
+package com.yaoyicloud.easier.common.domain;
+
+import java.io.Serializable;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotNull;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+
+import lombok.AccessLevel;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 分页查询实体
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 12/19/23 12:08
+ */
+@SuppressWarnings("checkstyle:JavadocType")
+@Data
+@NoArgsConstructor(access = AccessLevel.NONE)
+public abstract class EasierPaginationEntity<T> implements Serializable {
+
+    private static final long serialVersionUID = -4928914279527370524L;
+
+    /**
+     * 当前页
+     */
+    @NotNull(message = "页码必填")
+    @Min(value = 1L, message = "页码最小值为${value}")
+    protected Long current;
+
+    /**
+     * 每页记录数
+     */
+    @NotNull(message = "每页记录数必填")
+    @Min(value = 1L, message = "每页记录数最小值为${value}")
+    @Max(value = 100L, message = "每页记录数最大值为${value}")
+    protected Long size;
+
+    /**
+     * 生成分页实体
+     *
+     * @return 分页实体
+     */
+    public Page<T> getPage() {
+        return new Page<>(this.current, this.size);
+    }
+}

+ 33 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/EasierRemovableEntity.java

@@ -0,0 +1,33 @@
+package com.yaoyicloud.easier.common.domain;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.yaoyicloud.easier.common.domain.enums.flag.CommonFlag;
+
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+
+/**
+ * 通用实体,用于可能移出业务系统之外的实体继承
+ *
+ * @author dengjia
+ * @version 1.0.0
+ * @date 25/01/10 21:29
+ */
+@NoArgsConstructor(access = AccessLevel.NONE)
+public abstract class EasierRemovableEntity extends EasierCommonEntity {
+
+    /**
+     * 锁定标记, 表明写移出
+     */
+    @TableField(value = "lock_flag")
+    private CommonFlag.LockFlag lockFlag;
+
+    /**
+     * 删除标记,表明读写移出
+     */
+    @TableLogic
+    @TableField(fill = FieldFill.INSERT)
+    private CommonFlag.DelFlag delFlag;
+}

+ 33 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonAttachmentItemModel.java

@@ -0,0 +1,33 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+/**
+ * 通用附件项模型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/03/21 15:16
+ */
+@Data
+public final class CommonAttachmentItemModel implements Serializable {
+    private static final long serialVersionUID = 7332548757187843583L;
+
+    /**
+     * 附件名称
+     */
+    private String name;
+
+    /**
+     * 附件URL
+     */
+    private String url;
+
+    /**
+     * 缩略图URL
+     */
+    private String thumbnail;
+
+}

+ 25 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonContentItemModel.java

@@ -0,0 +1,25 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import com.yaoyicloud.easier.common.domain.enums.type.CommonType;
+
+import lombok.Data;
+
+/**
+ * 内容项模型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/03/21 15:23
+ */
+@Data
+public final class CommonContentItemModel implements Serializable {
+    private static final long serialVersionUID = 9172389512593386039L;
+
+    private CommonType.ContentItemType type;
+
+    private CommonImageModel image;
+
+    private String text;
+}

+ 21 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonExtraInfoItemModel.java

@@ -0,0 +1,21 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+/**
+ * 通用额外信息项模型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/03/21 15:12
+ */
+@Data
+public final class CommonExtraInfoItemModel implements Serializable {
+    private static final long serialVersionUID = -6473914546129846560L;
+
+    private String infoKey;
+
+    private Object infoVal;
+}

+ 23 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonImageModel.java

@@ -0,0 +1,23 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+/**
+ * 通用图片模型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/03/21 15:26
+ */
+@Data
+public class CommonImageModel implements Serializable {
+    private static final long serialVersionUID = 5745803368386861371L;
+
+    private String url;
+
+    private Integer width;
+
+    private Integer height;
+}

+ 42 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLocation.java

@@ -0,0 +1,42 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+/**
+ * 通用地点实体
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2022-10-15 20:03
+ */
+@Data
+public final class CommonLocation implements Serializable {
+    private static final long serialVersionUID = -8084107044975538320L;
+
+    /**
+     * 地点名称
+     */
+    private String site;
+
+    /**
+     * 经度
+     */
+    private Double longitude;
+
+    /**
+     * 纬度
+     */
+    private Double latitude;
+
+    /**
+     * 城市编码
+     */
+    private String cityCode;
+
+    /**
+     * 地址编码
+     */
+    private String addressCode;
+}

+ 90 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLogicFineRuleModel.java

@@ -0,0 +1,90 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+import java.util.Map;
+
+import com.yaoyicloud.easier.common.domain.enums.type.CommonType;
+
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 精细化评分规则项,可json序列化
+ *
+ */
+@Data
+@NoArgsConstructor
+public class CommonLogicFineRuleModel implements Serializable {
+    private static final long serialVersionUID = -6389402890130074848L;
+
+    /**
+     * 判断逻辑类型启用项
+     */
+    private String[] logicTypeEnable;
+
+    /**
+     * 字段值规则
+     */
+    private Map<String, ValueRule> logicToValueRuleMap;
+
+    /**
+     * 精细化评分规则项子项
+     */
+    private CommonLogicRuleItem[] ruleItems;
+
+    public Pair<Boolean, Long> calculate(String val, String itemType) {
+        if (ArrayUtil.isNotEmpty(ruleItems)) {
+            boolean isMatch = false;
+            for (CommonLogicRuleItem item : ruleItems) {
+                if (StrUtil.isNotBlank(itemType) && !StrUtil.equals(itemType, item.getItemType())) {
+                    // 规则项类型不匹配则跳过
+                    continue;
+                }
+                
+                Pair<Boolean, Long> result = item.calculate(val);
+                if (result.getKey()) {
+                    // 匹配成功,根据targetVal判断返回true还是false
+                    return new Pair<Boolean, Long>(result.getValue() > 0, result.getValue());
+                } else {
+                    //  匹配失败,根据targetVal判断返回true还是false
+                    if (result.getValue() <= 0) {
+                        isMatch = true;
+                    }
+                }
+            }
+
+            return new Pair<Boolean, Long>(isMatch, 0L);
+        }
+        // score 没有意义
+        return new Pair<Boolean, Long>(false, 0L);
+    }
+
+    /**
+     * 值规则
+     *
+     * @author snows
+     * @date 2025/04/25
+     */
+    @Data
+    public static class ValueRule implements Serializable {
+        private static final long serialVersionUID = 3193037987204367475L;
+
+        /**
+         * 数据类型
+         */
+        private String[] dataTypeEnable;
+
+        /**
+         * 单位类型
+         */
+        private CommonType.RuleUnitType unitType;
+
+        /**
+         * 小数点位数
+         */
+        private Integer decimalPlacesNumber;
+    }
+}

+ 115 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLogicModel.java

@@ -0,0 +1,115 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Map;
+
+import com.yaoyicloud.easier.common.domain.enums.type.CommonType;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 通用逻辑模型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/04/13 13:13
+ */
+@Data
+@NoArgsConstructor
+public class CommonLogicModel implements Serializable {
+    private static final long serialVersionUID = -6389402890130074848L;
+
+    /**
+     * 规则字段类型
+     */
+    private CommonType.LogicType logic;
+
+    /**
+     * 指标值
+     */
+    private String targetValue;
+
+    public static CommonLogicModel of(CommonType.LogicType logic, String targetValue) {
+        return new CommonLogicModel(logic, targetValue);
+    }
+
+    public static CommonLogicModel of(Map<String, Object> params) {
+        return new CommonLogicModel(Enum.valueOf(CommonType.LogicType.class, params.get("logic").toString()),
+            params.get("targetValue").toString());
+    }
+
+    protected CommonLogicModel(CommonType.LogicType logic, String targetValue) {
+        this.logic = logic;
+        this.targetValue = targetValue;
+    }
+
+    @SuppressWarnings({"checkstyle:MethodName", "checkstyle:ReturnCount"})
+    protected boolean calculate_internal(String value) {
+        switch (logic) {
+            case CONTAIN:
+                for (String val : value.split(StrUtil.COMMA)) {
+                    final String[] keywords = targetValue.split(StrUtil.COMMA);
+                    if (StrUtil.containsAny(val, keywords)) {
+                        return Boolean.TRUE;
+                    }
+                }
+                return Boolean.FALSE;
+            case NOT_CONTAIN:
+                boolean notContains = true;
+                for (String val : value.split(StrUtil.COMMA)) {
+                    final String[] notKeywords = targetValue.split(StrUtil.COMMA);
+                    if (StrUtil.containsAny(val, notKeywords)) {
+                        notContains = Boolean.FALSE;
+                    }
+                }
+                return notContains;
+            case EQ_ANY:
+                final String[] eqKeys = targetValue.split(StrUtil.COMMA);
+                return StrUtil.equalsAny(value, eqKeys);
+            case GT:
+                BigDecimal val4 = new BigDecimal(value);
+                BigDecimal target4 = new BigDecimal(targetValue);
+                return val4.compareTo(target4) > 0;
+            case GE:
+                BigDecimal val = new BigDecimal(value);
+                BigDecimal target = new BigDecimal(targetValue);
+                return val.compareTo(target) >= 0;
+            case NE:
+                return !targetValue.equals(value);
+            case EQ:
+                if (NumberUtil.isNumber(value)) {
+                    return new BigDecimal(value).compareTo(new BigDecimal(targetValue)) == 0;
+                }
+                return targetValue.equals(value);
+            case LT:
+                BigDecimal val1 = new BigDecimal(value);
+                BigDecimal target1 = new BigDecimal(targetValue);
+                return val1.compareTo(target1) < 0;
+            case LE:
+                BigDecimal val5 = new BigDecimal(value);
+                BigDecimal target5 = new BigDecimal(targetValue);
+                return val5.compareTo(target5) <= 0;
+            case BETWEEN:
+                final String[] btVals = targetValue.split(StrUtil.COMMA);
+                BigDecimal start = new BigDecimal(btVals[0]);
+                BigDecimal end = new BigDecimal(btVals[1]);
+                // 如果start>end,则调换顺序
+                if (start.compareTo(end) > 0) {
+                    BigDecimal temp = start;
+                    start = end;
+                    end = temp;
+                }
+                BigDecimal val3 = new BigDecimal(value);
+                return val3.compareTo(start) >= 0 && val3.compareTo(end) <= 0;
+            case OTHER:
+                // 其他逻辑直接处理为成功
+                return true;
+            default:
+                return false;
+        }
+    }
+}

+ 70 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonLogicRuleItem.java

@@ -0,0 +1,70 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import com.yaoyicloud.easier.common.domain.enums.type.CommonType;
+
+import cn.hutool.core.lang.Pair;
+import lombok.Data;
+
+/**
+ * 精细化评分规则项子项,可json序列化
+ *
+ * @author snows
+ * @date 2025/01/17
+ */
+@Data
+public class CommonLogicRuleItem implements Serializable {
+    private static final long serialVersionUID = -320800240067075225L;
+
+    /**
+     * 规则项类型
+     */
+    private String itemType = CommonType.ItemType.NULL.getType();
+
+    /**
+     * 规则字段类型
+     */
+    @Deprecated
+    private CommonType.RuleValueType ruleType;
+
+    /**
+     * 规则数据类型
+     */
+    private CommonType.RuleDataType dataType;
+
+    /**
+     * 规则单位类型
+     */
+    private CommonType.RuleUnitType unitType;
+
+    /**
+     * 规则值,比如:"keyword1,keyword2" 或者 200
+     */
+    private String rule;
+
+    /**
+     * 指标值
+     */
+    private long targetScore;
+
+    /**
+     * 指标状态
+     */
+    private CommonType.RuleTargetStatus targetStatus;
+
+    /**
+     * 判断逻辑类型
+     */
+    private CommonType.LogicType logicType;
+
+    /**
+     * 计算
+     *
+     * @param val 值
+     * @return {@link Pair }<{@link Boolean }, {@link Integer }> 计算结果
+     */
+    public Pair<Boolean, Long> calculate(String val) {
+        return new Pair<>(CommonLogicModel.of(logicType, rule).calculate_internal(val), targetScore);
+    }
+}

+ 22 - 0
easier-common/easier-common-domain/src/main/java/com/yaoyicloud/easier/common/domain/common/CommonSettingItemModel.java

@@ -0,0 +1,22 @@
+package com.yaoyicloud.easier.common.domain.common;
+
+import java.io.Serializable;
+
+import lombok.Data;
+
+/**
+ * 通用设置项模型
+ *
+ * @author jimmy
+ * @version 1.0.0
+ * @date 2024/03/22 10:44
+ */
+@Data
+public class CommonSettingItemModel implements Serializable {
+    private static final long serialVersionUID = -160317249239833486L;
+
+    private Long settingId;
+
+    private Object customizeValue;
+
+}

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff