Просмотр исходного кода

Merge branch 'master' of http://47.107.172.130:3000/storlead/storlead-smarttrade-platform

Fan 1 месяц назад
Родитель
Сommit
9b1f7c3050
100 измененных файлов с 4412 добавлено и 49 удалено
  1. 1 1
      java/storlead-account/pom.xml
  2. 5 0
      java/storlead-account/storlead-account-api/pom.xml
  3. 5 0
      java/storlead-account/storlead-account-biz/pom.xml
  4. 10 1
      java/storlead-api/pom.xml
  5. 5 1
      java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java
  6. 348 0
      java/storlead-dependencies/pom.xml
  7. 41 0
      java/storlead-es/pom.xml
  8. 44 0
      java/storlead-es/src/main/java/com/storlead/es/config/ElasticSearchConfig.java
  9. 45 0
      java/storlead-es/src/main/java/com/storlead/es/pojo/vo/EsGenericVO.java
  10. 67 0
      java/storlead-es/src/main/java/com/storlead/es/pojo/vo/EsQuerySimilarityVO.java
  11. 16 0
      java/storlead-es/src/main/java/com/storlead/es/pojo/vo/EsQueryVO.java
  12. 15 0
      java/storlead-es/src/main/java/com/storlead/es/pojo/vo/FieldConfig.java
  13. 19 0
      java/storlead-es/src/main/java/com/storlead/es/pojo/vo/IndexFieldConfig.java
  14. 51 0
      java/storlead-es/src/main/java/com/storlead/es/server/BaseSearchService.java
  15. 17 0
      java/storlead-es/src/main/java/com/storlead/es/server/EsSearchCustomerService.java
  16. 319 0
      java/storlead-es/src/main/java/com/storlead/es/server/impl/BaseSearchServiceImpl.java
  17. 269 0
      java/storlead-es/src/main/java/com/storlead/es/server/impl/EsSearchCustomerServiceImpl.java
  18. 1 1
      java/storlead-framework/pom.xml
  19. 20 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/page/PageDTO.java
  20. 36 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/query/QueryBaseDTO.java
  21. 5 0
      java/storlead-framework/storlead-core/pom.xml
  22. 10 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/query/page/PageQuery.java
  23. 0 24
      java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/assemble/QueryBaseDTO.java
  24. 2 21
      java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/assemble/QueryBaseEntity.java
  25. 40 0
      java/storlead-message/pom.xml
  26. 20 0
      java/storlead-message/src/main/java/com/storlead/message/controller/UserMessageNoticeConfigController.java
  27. 58 0
      java/storlead-message/src/main/java/com/storlead/message/entity/UserMessageNoticeConfigEntity.java
  28. 21 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/InsideMessageRecordMapper.java
  29. 32 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/InsideMessageSendLogMapper.java
  30. 17 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/MessageTemplateEventDetailMapper.java
  31. 29 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/MessageTemplateEventGroupMapper.java
  32. 16 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/UserMessageNoticeConfigMapper.java
  33. 34 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/xml/InsideMessageRecordMapper.xml
  34. 26 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/xml/InsideMessageSendLogMapper.xml
  35. 35 0
      java/storlead-message/src/main/java/com/storlead/message/mapper/xml/UserMessageNoticeConfigMapper.xml
  36. 27 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageDTO.java
  37. 12 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageTemplateDetailDTO.java
  38. 14 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageTemplateEventDTO.java
  39. 31 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageTestDTO.java
  40. 98 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/entity/InsideMessageRecordEntity.java
  41. 70 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/entity/InsideMessageSendLogEntity.java
  42. 63 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/entity/MessageTemplateEventDetailEntity.java
  43. 64 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/entity/MessageTemplateEventGroupEntity.java
  44. 20 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageArgTemplateVO.java
  45. 46 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageDetailVO.java
  46. 25 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageNoReadTotalVO.java
  47. 29 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageTemplateVO.java
  48. 20 0
      java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageTypeReadStateVO.java
  49. 46 0
      java/storlead-message/src/main/java/com/storlead/message/service/InsideMessageRecordService.java
  50. 31 0
      java/storlead-message/src/main/java/com/storlead/message/service/InsideMessageSendLogService.java
  51. 26 0
      java/storlead-message/src/main/java/com/storlead/message/service/MessageTemplateEventDetailService.java
  52. 21 0
      java/storlead-message/src/main/java/com/storlead/message/service/MessageTemplateEventGroupService.java
  53. 19 0
      java/storlead-message/src/main/java/com/storlead/message/service/UserMessageNoticeConfigService.java
  54. 23 0
      java/storlead-message/src/main/java/com/storlead/message/service/WechatMessageService.java
  55. 130 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/InsideMessageRecordServiceImpl.java
  56. 43 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/InsideMessageSendLogServiceImpl.java
  57. 165 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/MessageService.java
  58. 255 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/MessageTemplateEventDetailServiceImpl.java
  59. 30 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/MessageTemplateEventGroupServiceImpl.java
  60. 56 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/UserMessageNoticeConfigServiceImpl.java
  61. 169 0
      java/storlead-message/src/main/java/com/storlead/message/service/impl/WechatMessageServiceImpl.java
  62. 38 0
      java/storlead-message/src/main/resources/mapper/InsideMessageRecordMapper.xml
  63. 59 0
      java/storlead-message/src/main/resources/mapper/InsideMessageSendLogMapper.xml
  64. 23 0
      java/storlead-message/src/main/resources/mapper/MessageTemplateEventDetailMapper.xml
  65. 26 0
      java/storlead-message/src/main/resources/mapper/MessageTemplateEventGroupMapper.xml
  66. 35 0
      java/storlead-message/src/main/resources/mapper/UserMessageNoticeConfigMapper.xml
  67. 13 0
      java/storlead-message/src/test/java/com/storlead/tems/message/MessageApplicationTests.java
  68. 23 0
      java/storlead-sasa/pom.xml
  69. 31 0
      java/storlead-sasa/storlead-okr/pom.xml
  70. 7 0
      java/storlead-sasa/storlead-okr/src/main/java/org/example/Main.java
  71. 30 0
      java/storlead-sasa/storlead-project/pom.xml
  72. 7 0
      java/storlead-sasa/storlead-project/src/main/java/org/example/Main.java
  73. 30 0
      java/storlead-sasa/storlead-salary/pom.xml
  74. 7 0
      java/storlead-sasa/storlead-salary/src/main/java/org/example/Main.java
  75. 30 0
      java/storlead-sasa/storlead-sales/pom.xml
  76. 7 0
      java/storlead-sasa/storlead-sales/src/main/java/org/example/Main.java
  77. 23 0
      java/storlead-sasa/storlead-trade/pom.xml
  78. 91 0
      java/storlead-sasa/storlead-trade/storlead-acquisition/README.md
  79. 29 0
      java/storlead-sasa/storlead-trade/storlead-acquisition/pom.xml
  80. 4 0
      java/storlead-sasa/storlead-trade/storlead-acquisition/src/main/java/com/storlead/crm/acquisition/package-info.java
  81. 28 0
      java/storlead-sasa/storlead-trade/storlead-customer/README.md
  82. 29 0
      java/storlead-sasa/storlead-trade/storlead-customer/pom.xml
  83. 31 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/controller/CustomerAiAnalysisController.java
  84. 16 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/dto/CustomerSingleAnalysisRequestDTO.java
  85. 62 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerAnalysisResultEntity.java
  86. 54 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerCompanyEntity.java
  87. 100 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerEntity.java
  88. 88 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/LiaisonEntity.java
  89. 9 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/CustomerAnalysisResultEntityMapper.java
  90. 9 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/CustomerCompanyEntityMapper.java
  91. 9 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/CustomerEntityMapper.java
  92. 9 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/LiaisonEntityMapper.java
  93. 4 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/package-info.java
  94. 8 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerAiAnalysisService.java
  95. 7 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerAnalysisResultEntityService.java
  96. 7 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerCompanyEntityService.java
  97. 7 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerEntityService.java
  98. 7 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/LiaisonEntityService.java
  99. 190 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/impl/CustomerAiAnalysisServiceImpl.java
  100. 13 0
      java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/impl/CustomerAnalysisResultEntityServiceImpl.java

+ 1 - 1
java/storlead-account/pom.xml

@@ -5,7 +5,7 @@
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.storlead.boot</groupId>
-        <artifactId>storlead-smarttrade-platform</artifactId>
+        <artifactId>storlead-saas-platform</artifactId>
         <version>1.0</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>

+ 5 - 0
java/storlead-account/storlead-account-api/pom.xml

@@ -15,6 +15,11 @@
     <name>${project.artifactId}</name>
 
     <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-account-biz</artifactId>

+ 5 - 0
java/storlead-account/storlead-account-biz/pom.xml

@@ -15,6 +15,11 @@
     <name>${project.artifactId}</name>
 
     <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-web</artifactId>

+ 10 - 1
java/storlead-api/pom.xml

@@ -5,7 +5,7 @@
     <modelVersion>4.0.0</modelVersion>
     <parent>
         <groupId>com.storlead.boot</groupId>
-        <artifactId>storlead-smarttrade-platform</artifactId>
+        <artifactId>storlead-saas-platform</artifactId>
         <version>1.0</version>
         <relativePath>../../pom.xml</relativePath>
     </parent>
@@ -15,6 +15,11 @@
     <name>storlead-api</name>
 
     <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
         <dependency>
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-web</artifactId>
@@ -23,6 +28,10 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-account-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-customer</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>

+ 5 - 1
java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java

@@ -12,7 +12,11 @@ import org.springframework.core.env.Environment;
 import java.net.InetAddress;
 
 @SpringBootApplication(scanBasePackages = "com.storlead")
-@MapperScan({"com.storlead.account.tenant.mapper", "com.storlead.account.system.mapper"})
+@MapperScan({
+        "com.storlead.account.tenant.mapper",
+        "com.storlead.account.system.mapper",
+        "com.storlead.crm.customer.mapper"
+})
 @Slf4j
 public class StorleadTradeApplication {
 

+ 348 - 0
java/storlead-dependencies/pom.xml

@@ -0,0 +1,348 @@
+<?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>
+    <groupId>com.storlead.boot</groupId>
+    <artifactId>storlead-dependencies</artifactId>
+    <version>${revision}</version>
+
+    <packaging>pom</packaging>
+    <name>${project.artifactId}</name>
+    <description>整个项目的依赖版本</description>
+
+    <properties>
+        <!-- 与根工程保持一致的统一版本号 -->
+        <revision>1.0</revision>
+        <flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <spring.boot.starter.parent>2.7.0</spring.boot.starter.parent>
+        <spring.boot.version>2.7.0</spring.boot.version>
+        <knife4j.version>4.5.0</knife4j.version>
+        <spring-web.version>5.3.32</spring-web.version>
+        <jsoup.version>1.18.3</jsoup.version>
+        <mockito-inline.version>4.11.0</mockito-inline.version>
+        <lombok.version>1.18.36</lombok.version>
+        <spring.framework.version>5.3.20</spring.framework.version>
+        <spring.security.version>5.8.14</spring.security.version>
+        <fastjson.version>2.0.23</fastjson.version>
+        <!--    5.8.35-->
+        <hutool.version>5.4.3</hutool.version>
+        <springdoc.version>1.7.0</springdoc.version>
+        <swagger.ui.version>3.0.0</swagger.ui.version>
+        <swagger.version>3.0.0</swagger.version>
+        <swagger.annotations.version>1.5.22</swagger.annotations.version>
+        <spring.web.socket.version>1.5.3</spring.web.socket.version>
+
+        <!-- DB 相关 -->
+        <druid.version>1.1.24</druid.version>
+        <mybatis.version>3.5.17</mybatis.version>
+        <mybatis-plus.version>3.1.2</mybatis-plus.version>
+        <datasource.spring.boot.starter>2.5.4</datasource.spring.boot.starter>
+        <dynamic-datasource.version>4.3.1</dynamic-datasource.version>
+        <mybatis-plus-join.version>1.4.13</mybatis-plus-join.version>
+        <mysql.connector.version>5.1.47</mysql.connector.version>
+        <guava.version>33.4.0-jre</guava.version>
+        <aviator.version>5.2.7</aviator.version>
+        <transmittable.thread.version>2.14.0</transmittable.thread.version>
+        <google.code.gson.version>2.10.1</google.code.gson.version>
+        <squareup.okhttp.version>4.12.0</squareup.okhttp.version>
+        <okhttp.sse.version>4.10.0</okhttp.sse.version>
+        <spring.validation.version>2.6.3</spring.validation.version>
+        <apache.httpcore5.client5.version>5.2.1</apache.httpcore5.client5.version>
+        <apache.httpcore5.version>5.2.1</apache.httpcore5.version>
+        <apache.httpcore5-h2.version>5.2.1</apache.httpcore5-h2.version>
+        <apache.httpasyncclient.version>4.1.5</apache.httpasyncclient.version>
+        <io.jsonwebtoken.version>0.9.1</io.jsonwebtoken.version>
+        <io.jsonwebtoken.java.jwt.version>3.7.0</io.jsonwebtoken.java.jwt.version>
+    </properties>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework</groupId>
+                <artifactId>spring-web</artifactId>
+                <version>${spring-web.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-generator</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.daydayup.boot</groupId>
+                <artifactId>daydayup-mybatis</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.daydayup.boot</groupId>
+                <artifactId>daydayup-common</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.daydayup.boot</groupId>
+                <artifactId>daydayup-system</artifactId>
+                <version>${revision}</version>
+            </dependency>
+
+<!--            &lt;!&ndash;swagger&ndash;&gt;-->
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger2</artifactId>
+                <version>${swagger.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.springfox</groupId>
+                <artifactId>springfox-swagger-ui</artifactId>
+                <version>${swagger.ui.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>io.swagger</groupId>
+                <artifactId>swagger-annotations</artifactId>
+                <version>${swagger.annotations.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.google.code.gson</groupId>
+                <artifactId>gson</artifactId>
+                <version>${google.code.gson.version}</version> <!-- 建议使用最新版本 -->
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>fastjson</artifactId>
+                <version>${fastjson.version}</version>
+            </dependency>
+
+
+            <dependency>
+                <groupId>org.apache.httpcomponents.client5</groupId>
+                <artifactId>httpclient5</artifactId>
+                <version>${apache.httpcore5.client5.version}</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 -->
+            <dependency>
+                <groupId>org.apache.httpcomponents.core5</groupId>
+                <artifactId>httpcore5</artifactId>
+                <version>${apache.httpcore5.version}</version>
+            </dependency>
+
+            <!--JWT-->
+            <dependency>
+                <groupId>com.auth0</groupId>
+                <artifactId>java-jwt</artifactId>
+                <version>${io.jsonwebtoken.java.jwt.version}</version>
+            </dependency>
+
+            <!--  jwt 需要,移除会报错  -->
+            <dependency>
+                <groupId>io.jsonwebtoken</groupId>
+                <artifactId>jjwt</artifactId>
+                <version>${io.jsonwebtoken.version}</version>
+            </dependency>
+            <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5-h2 -->
+            <dependency>
+                <groupId>org.apache.httpcomponents.core5</groupId>
+                <artifactId>httpcore5-h2</artifactId>
+                <version>${apache.httpcore5-h2.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.httpcomponents</groupId>
+                <artifactId>httpasyncclient</artifactId>
+                <version>${apache.httpasyncclient.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>com.github.xiaoymin</groupId>-->
+<!--                <artifactId>swagger-bootstrap-ui</artifactId>-->
+<!--                <version>1.8.7</version>-->
+<!--            </dependency>-->
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.squareup.okhttp3</groupId>-->
+<!--                <artifactId>okhttp</artifactId>-->
+<!--                <version>${squareup.okhttp.version}</version> &lt;!&ndash; 2023年最新稳定版 &ndash;&gt;-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp-sse</artifactId>
+                <version>${okhttp.sse.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring.boot.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.xiaoymin</groupId>
+                <artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
+                <version>${knife4j.version}</version>
+            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>cn.hutool</groupId>-->
+<!--                <artifactId>hutool-all</artifactId>-->
+<!--                <version>4.5.11</version>-->
+<!--            </dependency>-->
+
+            <dependency>
+                <groupId>com.googlecode.aviator</groupId>
+                <artifactId>aviator</artifactId>
+                <version>${aviator.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>transmittable-thread-local</artifactId>
+                <version>${transmittable.thread.version}</version> <!-- 建议使用最新版本 -->
+            </dependency>
+
+            <!-- mybatis-plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
+            <!--mysql-->
+            <dependency>
+                <groupId>mysql</groupId>
+                <artifactId>mysql-connector-java</artifactId>
+                <version>${mysql.connector.version}</version>
+            </dependency>
+
+            <!-- 动态数据源 -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+                <version>${datasource.spring.boot.starter}</version>
+            </dependency>
+
+            <!-- druid -->
+            <dependency>
+                <groupId>com.alibaba</groupId>
+                <artifactId>druid-spring-boot-starter</artifactId>
+                <version>${druid.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.github.yulichang</groupId>
+                <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->
+                <version>${mybatis-plus-join.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.java-websocket</groupId>
+                <artifactId>Java-WebSocket</artifactId>
+                <version>${spring.web.socket.version}</version>
+            </dependency>
+
+            <!-- Web 相关 -->
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-web</artifactId>
+                <version>2.7.0</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>org.springframework</groupId>
+                        <artifactId>spring-web</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <dependency>
+                <groupId>org.jsoup</groupId>
+                <artifactId>jsoup</artifactId>
+                <version>${jsoup.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>com.google.guava</groupId>
+                <artifactId>guava</artifactId>
+                <version>${guava.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.mockito</groupId>
+                <artifactId>mockito-inline</artifactId>
+                <version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->
+            </dependency>
+
+            <dependency>
+                <groupId>org.springdoc</groupId>
+                <artifactId>springdoc-openapi-ui</artifactId>
+                <version>${springdoc.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-starter-test</artifactId>
+                <version>${spring.boot.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <artifactId>asm</artifactId>
+                        <groupId>org.ow2.asm</groupId>
+                    </exclusion>
+                    <exclusion>
+                        <groupId>org.mockito</groupId>
+                        <artifactId>mockito-core</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+        </dependencies>
+
+    </dependencyManagement>
+
+<!--    <build>-->
+<!--        <plugins>-->
+<!--            &lt;!&ndash; 统一 revision 版本 &ndash;&gt;-->
+<!--            <plugin>-->
+<!--                <groupId>org.codehaus.mojo</groupId>-->
+<!--                <artifactId>flatten-maven-plugin</artifactId>-->
+<!--                <version>${flatten-maven-plugin.version}</version>-->
+<!--                <configuration>-->
+<!--                    <flattenMode>bom</flattenMode>-->
+<!--                    <updatePomFile>true</updatePomFile>-->
+<!--                </configuration>-->
+<!--                <executions>-->
+<!--                    <execution>-->
+<!--                        <goals>-->
+<!--                            <goal>flatten</goal>-->
+<!--                        </goals>-->
+<!--                        <id>flatten</id>-->
+<!--                        <phase>process-resources</phase>-->
+<!--                    </execution>-->
+<!--                    <execution>-->
+<!--                        <goals>-->
+<!--                            <goal>clean</goal>-->
+<!--                        </goals>-->
+<!--                        <id>flatten.clean</id>-->
+<!--                        <phase>clean</phase>-->
+<!--                    </execution>-->
+<!--                </executions>-->
+<!--            </plugin>-->
+<!--        </plugins>-->
+<!--    </build>-->
+</project>

+ 41 - 0
java/storlead-es/pom.xml

@@ -0,0 +1,41 @@
+<?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>
+        <artifactId>storlead-saas-platform</artifactId>
+        <groupId>com.storlead.boot</groupId>
+        <version>1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-es</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-es</name>
+    <version>1.0</version>
+    <description>common project for Spring Boot</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+            <version>1.0</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mybatis</artifactId>
+            <version>1.0</version>
+            <scope>compile</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>
+

+ 44 - 0
java/storlead-es/src/main/java/com/storlead/es/config/ElasticSearchConfig.java

@@ -0,0 +1,44 @@
+package com.storlead.es.config;
+
+import org.apache.http.HttpHost;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @program: sp-tems-gotr
+ * @description:
+ * @author: chenkq
+ * @create: 2023-06-16 17:03
+ */
+@Configuration
+public class ElasticSearchConfig {
+
+    @Bean
+    public RestHighLevelClient restHighLevelClient() {
+
+        String hostname = "39.108.252.62";
+        int port = 9200;
+        // 定义 Elasticsearch 集群的用户名和密码
+        String username = "elastic";
+        String password = "storlead123456";
+        // 配置 RestClient
+        RestHighLevelClient client = new RestHighLevelClient(
+                RestClient.builder(
+                                new HttpHost(hostname, port, "http"))
+                        .setHttpClientConfigCallback(httpClientBuilder ->
+                                httpClientBuilder.setDefaultCredentialsProvider(
+                                        new BasicCredentialsProvider() {{
+                                            setCredentials(AuthScope.ANY,
+                                                    new UsernamePasswordCredentials(username, password));
+                                        }}
+                                )
+                        )
+        );
+        return client;
+    }
+}

+ 45 - 0
java/storlead-es/src/main/java/com/storlead/es/pojo/vo/EsGenericVO.java

@@ -0,0 +1,45 @@
+package com.storlead.es.pojo.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @program: sp-tems-gotr
+ * @description:
+ * @author: chenkq
+ * @create: 2023-07-04 16:29
+ */
+@Data
+public class EsGenericVO implements Serializable {
+
+    private String queryText;
+
+    private String minScore;
+
+    private String indexName;
+
+    private String fieldName;
+
+    private String fieldKey;
+    @ApiModelProperty(value = "客户种类(10客户、11线索)")
+    private Integer customerForm;
+
+    @ApiModelProperty(value = "匹配索引和字段")
+    public List<IndexFieldConfig> indexFields;
+}
+//{
+//        "queryText": "ta@aselsan.com.tr",
+//        "topN": 100,
+//        "fieldKey": "keyword",
+//        "minScore": 60.0,
+//        "indexFields": [
+//        {
+//        "indexName" : "liaison_data_index","resourceType":10,"fields":[{"fieldName":"email.keyword","fieldDescribe":"联系人邮箱"},{"fieldName":"email1.keyword","fieldDescribe":"联系人备用邮箱1"},{"fieldName":"email2.keyword","fieldDescribe":"联系人备用邮箱2"},{"fieldName":"email3.keyword","fieldDescribe":"联系人备用邮箱3"}]
+//        },{
+//        "indexName" : "customer_company_data_index","resourceType":30,"fields":[{"fieldName":"email.keyword","fieldDescribe":"企业邮箱"},{"fieldName":"email1.keyword","fieldDescribe":"企业备用邮箱1"},{"fieldName":"email2.keyword","fieldDescribe":"企业备用邮箱2"},{"fieldName":"email3.keyword","fieldDescribe":"企业备用邮箱3"}]
+//        }
+//        ]
+//}

+ 67 - 0
java/storlead-es/src/main/java/com/storlead/es/pojo/vo/EsQuerySimilarityVO.java

@@ -0,0 +1,67 @@
+package com.storlead.es.pojo.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-04-16 10:16
+ */
+@Data
+public class EsQuerySimilarityVO {
+    @ApiModelProperty(value = "业务Id")
+    private Long id;
+    @ApiModelProperty(value = "业务Id")
+    private Long customerId;
+    @ApiModelProperty(value = "客户名称")
+    private String customerName;
+    @ApiModelProperty(value = "联系人姓名")
+    private String name;
+    @ApiModelProperty(value = "错误提示")
+    private String errorMsg;
+    @ApiModelProperty(value = "原有对象")
+    private Object object;
+
+    @ApiModelProperty(value = "被查重复字段")
+    private String duplicateFieldNow;
+    @ApiModelProperty(value = "被查重复值")
+    private String duplicateValueNow;
+
+    @ApiModelProperty(value = "重复字段")
+    private String duplicateField;
+    @ApiModelProperty(value = "重复值")
+    private String duplicateValue;
+    @ApiModelProperty(value = "相似度")
+    private String similarityScore;
+    @ApiModelProperty(value = "所属索引")
+    private String indexName;
+    @ApiModelProperty(value = "匹配的字段名")
+    private String matchedField;
+    @ApiModelProperty(value = "错误类型")
+    private String resourceType;
+    @ApiModelProperty(value = "最后跟进时间")
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime lastFollowUpTime;
+
+    @ApiModelProperty(value = "未跟进天数")
+    private Integer noFollowUpDate;
+    private Long ownerBy;
+
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+}

+ 16 - 0
java/storlead-es/src/main/java/com/storlead/es/pojo/vo/EsQueryVO.java

@@ -0,0 +1,16 @@
+package com.storlead.es.pojo.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @program: sp-tems
+ * @description:
+ * @author: chenkq
+ * @create: 2023-12-13 17:12
+ */
+@Data
+public class EsQueryVO implements Serializable {
+
+}

+ 15 - 0
java/storlead-es/src/main/java/com/storlead/es/pojo/vo/FieldConfig.java

@@ -0,0 +1,15 @@
+package com.storlead.es.pojo.vo;
+
+import lombok.Data;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-04-16 10:56
+ */
+@Data
+public class FieldConfig {
+    private String fieldName;
+    private String fieldDescribe;
+}

+ 19 - 0
java/storlead-es/src/main/java/com/storlead/es/pojo/vo/IndexFieldConfig.java

@@ -0,0 +1,19 @@
+package com.storlead.es.pojo.vo;
+
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-04-16 10:26
+ */
+@Data
+public class IndexFieldConfig {
+    private String indexName;
+    private Integer resourceType;
+    private List<FieldConfig> fields;
+
+}

+ 51 - 0
java/storlead-es/src/main/java/com/storlead/es/server/BaseSearchService.java

@@ -0,0 +1,51 @@
+package com.storlead.es.server;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.storlead.framework.common.dto.page.PageDTO;
+
+import java.util.List;
+import java.util.Map;
+
+public interface BaseSearchService<T> {
+
+    /**
+     * 搜 索
+     *
+     * @param keyword
+     * @param clazz
+     * @return
+     */
+    List<T> query(Class<T> clazz, String keyword, String indexName);
+
+    /**
+     * 分页搜索
+     *
+     * @param keyword
+     * @param clazz
+     * @return
+     */
+    IPage<T> queryPage(Class<T> clazz, String keyword, PageDTO page, String indexName);
+
+
+    IPage<T> queryHitPage(Class<T> clazz, String keyword, PageDTO page, String... hightFields);
+
+    /**
+     * 搜索高亮显示
+     *
+     * @param keyword    关键字
+     * @param indexName  索引库
+     * @param fieldNames 搜索的字段
+     * @return
+     */
+    List<T> queryHit(Class<T> clazz, String keyword, String indexName, String... fieldNames);
+
+    List<T> queryHitByPage(Class<T> clazz, PageDTO page, String keyword, String indexName, String... fieldNames);
+
+    List<T> queryHit(Class<T> clazz, String keyword, String fieldName, String indexName);
+
+    List<T> queryHitByPage(Class<T> clazz, PageDTO page, String keyword, String fieldName, String indexName);
+
+    List<T> querySimilarityPage(Class<T> clazz, PageDTO page, String keyword, String indexName, String... fieldNames);
+
+    List<Map<String, Object>> searchByNameSimilarity(String inputName);
+}

+ 17 - 0
java/storlead-es/src/main/java/com/storlead/es/server/EsSearchCustomerService.java

@@ -0,0 +1,17 @@
+package com.storlead.es.server;
+
+import com.storlead.es.pojo.vo.EsGenericVO;
+import com.storlead.es.pojo.vo.EsQuerySimilarityVO;
+
+import java.util.List;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-04-16 09:56
+ */
+public interface EsSearchCustomerService {
+
+    List<EsQuerySimilarityVO> listComparisonSimilarity(EsGenericVO dto);
+}

+ 319 - 0
java/storlead-es/src/main/java/com/storlead/es/server/impl/BaseSearchServiceImpl.java

@@ -0,0 +1,319 @@
+package com.storlead.es.server.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.storlead.es.server.BaseSearchService;
+import com.storlead.framework.common.dto.page.PageDTO;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.common.unit.Fuzziness;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.QueryStringQueryBuilder;
+import org.elasticsearch.script.Script;
+import org.elasticsearch.script.ScriptType;
+import org.springframework.data.elasticsearch.core.SearchHit;
+import org.springframework.data.elasticsearch.core.SearchHits;
+import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
+import org.elasticsearch.search.sort.SortBuilders;
+import org.elasticsearch.search.sort.SortOrder;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Logger;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Pageable;
+import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
+import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
+import org.springframework.data.elasticsearch.core.query.*;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-tems-gotr
+ * @description:
+ * @author: chenkq
+ * @create: 2023-06-16 16:42
+ */
+@Service
+public class BaseSearchServiceImpl<T> implements BaseSearchService<T> {
+
+    private Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Resource
+    private ElasticsearchRestTemplate elasticsearchTemplate;
+
+
+    public void save(Class<T> clazz) {
+        try {
+            elasticsearchTemplate.save(clazz);
+        }catch (Exception e) {
+            logger.error("");
+        }
+    }
+
+    public void save(Class<T> clazz,String indexId) {
+        try {
+            T info = elasticsearchTemplate.get(indexId, clazz);
+            if (Objects.nonNull(info)) {
+                elasticsearchTemplate.delete(indexId,clazz);
+                elasticsearchTemplate.save(clazz);
+            }
+        }catch (Exception e) {
+            logger.error("");
+        }
+    }
+
+    @Override
+    public List<T> query(Class<T> clazz,String keyword, String indexName) {
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(new QueryStringQueryBuilder(keyword))
+                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
+                // .withSort(new FieldSortBuilder("createTime").order(SortOrder.DESC))
+                .build();
+        // 使用 IndexCoordinates 指定索引
+        IndexCoordinates indexCoordinates = IndexCoordinates.of(indexName);
+        List<T> infos = elasticsearchTemplate.search(searchQuery,clazz,indexCoordinates)
+                .stream()
+                .map(SearchHit::getContent)
+                .collect(Collectors.toList());
+         return infos;
+    }
+
+    @Override
+    public IPage<T> queryPage(Class<T> clazz, String keyword, PageDTO page, String indexName) {
+        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(new QueryStringQueryBuilder(keyword))
+                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
+                .withPageable(pageable)
+                .build();
+        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery,clazz);
+        long totalHits = searchHits.getTotalHits();
+        List<T> infos = searchHits
+                .stream()
+                .map(org.springframework.data.elasticsearch.core.SearchHit::getContent)
+                .collect(Collectors.toList());
+
+        IPage<T> res = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page.getPageIndex(),page.getPageSize());
+        res.setTotal(totalHits);
+        res.setRecords(infos);
+        return res;
+    }
+
+    @Override
+    public IPage<T> queryHitPage(Class<T> clazz, String keyword, PageDTO page,String ... hightFields) {
+        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
+
+        HighlightBuilder highlightBuilder = builderHighlightField(hightFields);
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(new QueryStringQueryBuilder(keyword))
+                .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
+                .withPageable(pageable)
+                .withHighlightBuilder(highlightBuilder)
+                .build();
+        SearchHits<T> searchHit = elasticsearchTemplate.search(searchQuery,clazz);
+        List<SearchHit<T>> searchHits = searchHit.getSearchHits();
+        List<T> infos = new ArrayList<>();
+        for(SearchHit<T> hit :searchHits) {
+            T info = hit.getContent();
+            Map<String, List<String>> maps =  hit.getHighlightFields();
+            for (String key : maps.keySet()) {
+                 List<String> vals = maps.get(key);
+                 if (!CollectionUtils.isEmpty(vals)) {
+                     String fieldValue = vals.get(0);
+                     try {
+                         Field field = info.getClass().getDeclaredField(key);
+                         field.setAccessible(true);
+                         field.set(info,fieldValue);
+                     }catch (Exception e) {
+                         logger.error("---",e);
+                     }
+                 }
+            }
+            infos.add(hit.getContent());
+        }
+        long totalHits = searchHit.getTotalHits();
+        IPage<T> res = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page.getPageIndex(),page.getPageSize());
+        res.setTotal(totalHits);
+        res.setRecords(infos);
+        return res;
+    }
+
+    private HighlightBuilder builderHighlightField(String ... hightFields) {
+        HighlightBuilder highlightBuilder = new HighlightBuilder();
+        highlightBuilder.preTags("<em>").postTags("</em>");
+        //设置高亮的方法
+        highlightBuilder.highlighterType("plain");
+        //设置分段的数量不做限制
+        highlightBuilder.numOfFragments(0);
+        if (Objects.isNull(hightFields) || hightFields.length == 0) {
+            return highlightBuilder;
+        }
+        for(String hightField : hightFields) {
+            highlightBuilder.field(hightField);
+        }
+        return highlightBuilder;
+    }
+
+    @Override
+    public  List<T> queryHit(Class<T> clazz,String keyword,String fieldName,String indexName) {
+
+        HighlightBuilder highlightBuilder = new HighlightBuilder()
+                .field("title")
+                .preTags("<em>")
+                .postTags("</em>");
+        // 构建查询条件
+        QueryBuilder queryBuilder = QueryBuilders.matchQuery(fieldName,keyword);
+
+        // 构建搜索查询
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(queryBuilder)
+                .withHighlightBuilder(highlightBuilder)
+                .build();
+
+        // 使用 IndexCoordinates 指定索引
+        IndexCoordinates indexCoordinates = IndexCoordinates.of(indexName);
+        // 执行搜索查询
+        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery,clazz,indexCoordinates);
+//      for (HighlightField highlightField : highlightFields) {
+//          String fieldName = highlightField.getName();
+//          String highlightedText = highlightField.getSnippets().get(0);
+//          System.out.println("Highlighted field: " + fieldName + ", Text: " + highlightedText);
+//      }
+        List<T> infos = searchHits
+                .stream()
+                .map(SearchHit::getContent)
+                .collect(Collectors.toList());
+        return infos;
+    }
+
+    @Override
+    public  List<T> queryHitByPage(Class<T> clazz,PageDTO page,String keyword,String fieldName,String indexName) {
+
+        // 构建查询条件
+        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
+        QueryBuilder queryBuilder = QueryBuilders.matchQuery(fieldName,keyword);
+
+        // 构建搜索查询
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(queryBuilder)
+                .withPageable(pageable)
+                .build();
+
+        // 执行搜索查询
+        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery, clazz);
+        List<T> infos = searchHits
+                .stream()
+                .map(org.springframework.data.elasticsearch.core.SearchHit::getContent)
+                .collect(Collectors.toList());
+
+        return infos;
+    }
+
+    /**
+     * 高亮显示
+     * @auther:
+     * @date:
+     */
+    @Override
+    public  List<T> queryHit(Class<T> clazz,String keyword,String indexName,String ... fieldNames) {
+
+        // 构建查询条件
+        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, fieldNames);
+
+        // 构建搜索查询
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(queryBuilder)
+                .build();
+
+        // 执行搜索查询
+        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery,clazz);
+        List<T> infos = searchHits
+                .stream()
+                .map(SearchHit::getContent)
+                .collect(Collectors.toList());
+
+        return infos;
+    }
+
+    @Override
+    public  List<T> queryHitByPage(Class<T> clazz,PageDTO page,String keyword,String indexName,String ... fieldNames) {
+
+        // 构建查询条件
+        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
+        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, fieldNames);
+
+        // 构建搜索查询
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(queryBuilder)
+                .withPageable(pageable)
+                .build();
+
+        // 执行搜索查询
+        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery, clazz);
+        List<T> infos = searchHits
+                .stream()
+                .map(SearchHit::getContent)
+                .collect(Collectors.toList());
+
+        return infos;
+    }
+
+    @Override
+    public  List<T> querySimilarityPage(Class<T> clazz,PageDTO page,String keyword,String indexName,String ... fieldNames) {
+
+        // 构建查询条件
+        Pageable pageable = PageRequest.of(page.getPageIndex(), page.getPageSize());
+        QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword, fieldNames);
+
+        // 构建搜索查询
+        NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
+                .withQuery(queryBuilder)
+                .withPageable(pageable)
+                .build();
+
+        // 执行搜索查询
+        SearchHits<T> searchHits = elasticsearchTemplate.search(searchQuery, clazz);
+        List<T> infos = searchHits
+                .stream()
+                .map(SearchHit::getContent)
+                .collect(Collectors.toList());
+
+        return infos;
+    }
+
+    @Override
+    public List<Map<String, Object>> searchByNameSimilarity(String inputName) {
+
+        SearchRequest searchRequest = new SearchRequest("customer_data");
+        searchRequest.source().query(QueryBuilders.termQuery("customer_name", "sear"));
+
+
+        NativeSearchQuery query = new NativeSearchQueryBuilder()
+                .withQuery(QueryBuilders.fuzzyQuery("customer_name", inputName).fuzziness(Fuzziness.AUTO))
+                .withPageable(PageRequest.of(0, 10)) // 设置分页,最大返回 10 个结果
+                .build();
+
+//        Query query = new NativeSearchQueryBuilder()
+//                .withQuery(QueryBuilders.fuzzyQuery("customer_name", inputName)
+//                        .fuzziness("AUTO"))  // 使用 fuzziness 设置模糊度
+//                .withPageable(PageRequest.of(0, 10)) // 设置分页,最多返回 10 个结果
+//                .build();
+
+        // 执行查询并返回结果
+        SearchHits<Map> searchHits = elasticsearchTemplate.search(query, Map.class, IndexCoordinates.of("customer_data"));
+
+        // 处理查询结果并返回包含 id、customer_name 和 score 的列表
+        return searchHits.getSearchHits().stream().map(hit -> {
+            Map<String, Object> result = new HashMap<>();
+            result.put("id", hit.getId());
+            result.put("customer_name", ((Map<String, Object>) hit.getContent()).get("customer_name"));
+            result.put("similarity_score", hit.getScore());
+            return result;
+        }).collect(Collectors.toList());
+    }
+
+}

+ 269 - 0
java/storlead-es/src/main/java/com/storlead/es/server/impl/EsSearchCustomerServiceImpl.java

@@ -0,0 +1,269 @@
+package com.storlead.es.server.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.es.pojo.vo.EsGenericVO;
+import com.storlead.es.pojo.vo.EsQuerySimilarityVO;
+import com.storlead.es.pojo.vo.FieldConfig;
+import com.storlead.es.pojo.vo.IndexFieldConfig;
+import com.storlead.es.server.EsSearchCustomerService;
+import com.storlead.frame.core.assemble.Result;
+import org.apache.commons.lang3.tuple.Pair;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.functionscore.ScriptScoreQueryBuilder;
+import org.elasticsearch.script.Script;
+import org.elasticsearch.script.ScriptType;
+import org.elasticsearch.search.sort.SortBuilders;
+import org.elasticsearch.search.sort.SortOrder;
+import org.springframework.data.domain.PageRequest;
+import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
+import org.springframework.data.elasticsearch.core.SearchHits;
+import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
+import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-04-16 09:57
+ */
+@Service
+public class EsSearchCustomerServiceImpl implements EsSearchCustomerService {
+
+    @Resource
+    private ElasticsearchRestTemplate elasticsearchTemplate;
+
+    private static String similarityScript = "\n" +
+            "def field = params.field;\n" +  // 使用参数传入字段名
+            "if (!doc.containsKey(field) || doc[field].size() == 0 || doc[field].value == null) {\n" +
+            "    return 0;\n" +
+            "}\n" +
+            "if (params.query == null) return 0;\n" +
+            "def docValue = doc[field].value;\n" +
+            "\n" +
+            "String s1 = docValue.toString().toLowerCase();\n" +
+            "String s2 = params.query?.toString().toLowerCase();\n" +
+            "\n" +
+            "// 空值检查\n" +
+            "if (s1 == null || s2 == null) return 0;\n" +
+            "\n" +
+            "// 完全匹配直接返回100\n" +
+            "if (s1.equals(s2)) return 100;\n" +
+            "\n" +
+            "// 计算编辑距离(内联实现)\n" +
+            "int s1_len = s1.length();\n" +
+            "int s2_len = s2.length();\n" +
+            "\n" +
+            "// 快速检查\n" +
+            "if (s1_len == 0 || s2_len == 0) return 0;\n" +
+            "if (s1_len == 0) return (int)(100 * (1 - (s2_len / Math.max(1, s2_len))));\n" +
+            "if (s2_len == 0) return (int)(100 * (1 - (s1_len / Math.max(1, s1_len))));\n" +
+            "\n" +
+            "// 使用单数组优化空间复杂度\n" +
+            "int[] costs = new int[s2_len + 1];\n" +
+            "for (int j = 0; j <= s2_len; j++) {\n" +
+            "    costs[j] = j;\n" +
+            "}\n" +
+            "\n" +
+            "for (int i = 1; i <= s1_len; i++) {\n" +
+            "    costs[0] = i;\n" +
+            "    int prev = i - 1;\n" +
+            "    for (int j = 1; j <= s2_len; j++) {\n" +
+            "        int cost = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? 0 : 1;\n" +
+            "        int insertion = costs[j] + 1;\n" +
+            "        int deletion = costs[j - 1] + 1;\n" +
+            "        int substitution = prev + cost;\n" +
+            "        int current = insertion;\n" +
+            "        if (deletion < current) {\n" +
+            "           current = deletion;\n" +
+            "        } \n" +
+            "        if (substitution < current) {\n" +
+            "           current = substitution;\n" +
+            "        } \n" +
+            "        prev = costs[j];\n" +
+            "        costs[j] = current;\n" +
+            "    }\n" +
+            "}\n" +
+            "\n" +
+            "int distance = costs[s2_len];\n" +
+            "int maxLen = s1_len > s2_len ? s1_len : s2_len;\n" +
+            "if (maxLen == 0) return 100;\n"+
+            "\n" +
+            "// 计算相似度百分比(0-100)\n" +
+            "double similarity = 100 * (1 - ((double)distance / maxLen));\n" +
+            "return (int) Math.round(similarity);";
+
+    @Override
+    public List<EsQuerySimilarityVO> listComparisonSimilarity(EsGenericVO dto) {
+
+//        Map<String, List<String>> indexFieldsMap = Map.of(
+//                "liaison_data_index", Arrays.asList("email.keyword","email1.keyword","email2.keyword","email3.keyword"),
+//                "customer_company_data_index", Arrays.asList("email.keyword","email1.keyword","email2.keyword","email3.keyword")
+//        );
+
+
+        // 保存索引名称和查询的列表
+        List<IndexFieldConfig>  indexFields=  dto.getIndexFields();
+//        Map<String, List<FieldConfig>> indexFieldsMap = indexFields.stream()
+//                .collect(Collectors.toMap(
+//                        IndexFieldConfig::getIndexName,
+//                        IndexFieldConfig::getFields
+//                ));
+
+        Map<String, IndexFieldConfig> indexFieldsMap = indexFields.stream().collect(Collectors.toMap(IndexFieldConfig::getIndexName, IndexFieldConfig -> IndexFieldConfig));
+
+        List<Map> combinedResults = new ArrayList<>();
+
+        float minScore = Float.parseFloat(dto.getMinScore());
+        List<Pair<String, NativeSearchQuery>> indexQueries = new ArrayList<>();
+        indexFieldsMap.forEach((indexName, indexField) -> {
+            List<FieldConfig> fields = indexField.getFields();
+            for (int i = 0; i < fields.size(); i++) {
+                BoolQueryBuilder indexQuery = QueryBuilders.boolQuery();
+                FieldConfig field = fields.get(i);
+                Map<String, Object> scriptParams = new HashMap<>();
+                scriptParams.put("field", field.getFieldName());
+                scriptParams.put("query", dto.getQueryText());
+                scriptParams.put("targetIndex", indexName);
+                indexQuery.mustNot(QueryBuilders.termQuery("is_delete", 1)); // Exclude deleted records
+                if(!Objects.isNull(dto.getCustomerForm())){
+                    indexQuery.must(QueryBuilders.termQuery("customer_form", dto.getCustomerForm())); // Exclude deleted records
+                }
+
+//                indexQuery.should(
+//                        QueryBuilders.scriptScoreQuery(
+//                                QueryBuilders.matchAllQuery(),
+//                                new Script(
+//                                        ScriptType.INLINE,
+//                                        "painless",
+//                                        similarityScript,
+//                                        scriptParams
+//                                )
+//                        ).setMinScore(minScore)
+//                );
+                ScriptScoreQueryBuilder scriptScoreQuery = QueryBuilders.scriptScoreQuery(
+                        indexQuery,  // 先应用 mustNot 过滤
+                        new Script(
+                                ScriptType.INLINE,
+                                "painless",
+                                similarityScript,
+                                scriptParams
+                        )
+                ).setMinScore(minScore);  // 只返回 _score >= minScore 的数据
+                NativeSearchQuery indexSearchQuery = new NativeSearchQueryBuilder()
+                        .withQuery(scriptScoreQuery)
+                        .withSort(SortBuilders.scoreSort().order(SortOrder.DESC))
+                        .withPageable(PageRequest.of(0, 1000))
+                        .build();
+                indexQueries.add(Pair.of(indexName, indexSearchQuery));
+
+                SearchHits<Map> hits = elasticsearchTemplate.search(
+                        indexSearchQuery,
+                        Map.class,
+                        IndexCoordinates.of(indexName));
+
+                hits.getSearchHits().stream()
+                        .map(hit -> {
+                            Map<String, Object> content = new HashMap<>(hit.getContent());
+                            content.put("similarityScore", hit.getScore());
+                            content.put("matchedIndex", hit.getIndex());
+                            content.put("matchedField", field.getFieldName());
+                            return content;
+                        })
+                        .forEach(combinedResults::add);
+            }
+        });
+
+// 执行查询并合并结果
+
+//        for (Pair<String, NativeSearchQuery> pair : indexQueries) {
+//            String indexName = pair.getLeft();
+//            NativeSearchQuery query = pair.getRight();
+//
+//        }
+
+
+
+        // 按相似度分数排序
+        combinedResults.sort((a, b) -> {
+            float scoreA = (float) a.getOrDefault("similarityScore", 0f);
+            float scoreB = (float) b.getOrDefault("similarityScore", 0f);
+            return Float.compare(scoreB, scoreA); // 降序排序
+        });
+
+        List<EsQuerySimilarityVO> vos = convertToVoList(combinedResults,indexFieldsMap);
+        return vos;
+    }
+
+
+    public  List<EsQuerySimilarityVO> convertToVoList(List<Map> combinedResults,Map<String, IndexFieldConfig> indexFieldsMap) {
+        if (CollectionUtils.isEmpty(combinedResults)) {
+            return new ArrayList<>();
+        }
+        return combinedResults.stream()
+                .map(result -> {
+                    EsQuerySimilarityVO vo = new EsQuerySimilarityVO();
+
+                    // 1. 设置基础字段
+                    vo.setId(Long.valueOf(result.get("id").toString())); // 假设id字段存在
+                    if (result.containsKey("customer_id") && Objects.nonNull(result.get("customer_id"))) {
+                        vo.setCustomerId(Long.valueOf(result.get("customer_id").toString())); // 假设customer_id字段存在
+                    }
+                    if (result.containsKey("owner_by") && Objects.nonNull(result.get("owner_by"))) {
+                        vo.setOwnerBy(Long.valueOf(result.get("owner_by").toString())); // 假设customer_id字段存在
+                    }
+
+                    vo.setObject(result); // 保留原始数据
+
+                    // 2. 设置相似度分数(转换为百分比字符串)
+                    Float score = (Float) result.get("similarityScore");
+                    vo.setSimilarityScore(score.toString());
+                    // 3. 设置索引名称
+                    vo.setIndexName((String) result.get("matchedIndex"));
+                    IndexFieldConfig indexField = indexFieldsMap.get(vo.getIndexName());
+                    vo.setResourceType(indexField.getResourceType().toString());
+//
+//                    vo.setMatchedField(matchedField);
+                    // 4. 找出匹配的字段名
+                    findMatchedField(vo,result, vo.getIndexName(),indexFieldsMap);
+//                    vo.setMatchedField(matchedField);
+                    if (StrUtil.isNotBlank(vo.getMatchedField())) {
+                        vo.setDuplicateValue(result.get(vo.getMatchedField()).toString());
+                    }
+                    return vo;
+                })
+                .collect(Collectors.toList());
+    }
+
+    // 辅助方法:找出实际匹配的字段
+    private void findMatchedField(EsQuerySimilarityVO vo,Map<String, Object> result, String indexName,Map<String, IndexFieldConfig> indexConfigMap) {
+        // 获取该索引对应的字段列表
+        IndexFieldConfig indexConfig = indexConfigMap.get(indexName);
+        List<FieldConfig> fields = indexConfig.getFields();
+        // 遍历字段,找出有值的字段
+        for (FieldConfig field : fields) {
+            // 移除.keyword后缀(如果需要)
+            String cpField =  result.get("matchedField").toString();
+            if (cpField.equals(field.getFieldName())) {
+                String baseField = cpField.replace(".keyword", "");
+                if (result.containsKey(baseField) && result.get(baseField) != null) {
+                    vo.setMatchedField(baseField);
+                    vo.setDuplicateField(baseField);
+//                    vo.setErrorMsg(field.getFieldDescribe()+"重复");
+                    vo.setErrorMsg(field.getFieldDescribe());
+                }
+            }
+
+        }
+    }
+}
+
+
+

+ 1 - 1
java/storlead-framework/pom.xml

@@ -3,7 +3,7 @@
          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">
     <parent>
-        <artifactId>storlead-smarttrade-platform</artifactId>
+        <artifactId>storlead-saas-platform</artifactId>
         <groupId>com.storlead.boot</groupId>
         <version>1.0</version>
         <relativePath>../../pom.xml</relativePath>

+ 20 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/page/PageDTO.java

@@ -0,0 +1,20 @@
+package com.storlead.framework.common.dto.page;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Common pagination request DTO used as API params.
+ */
+@Data
+public class PageDTO implements Serializable {
+    private static final long serialVersionUID = 4556484214576989834L;
+
+    @ApiModelProperty(value = "页码", example = "1")
+    private Integer pageIndex = 1;
+
+    @ApiModelProperty(value = "页面大小", example = "10")
+    private Integer pageSize = 10;
+}

+ 36 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/dto/query/QueryBaseDTO.java

@@ -0,0 +1,36 @@
+package com.storlead.framework.common.dto.query;
+
+import com.storlead.framework.common.dto.page.PageDTO;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-04-22 14:38
+ */
+@Data
+public class QueryBaseDTO extends PageDTO {
+
+    @ApiModelProperty(value = "搜索参数")
+    private String blurry;
+
+    @ApiModelProperty(value = "菜单Id")
+    private Long scopeMenuId;
+
+    @ApiModelProperty(value = "数据权限范围sql")
+    private String dataScopeSql;
+
+    @ApiModelProperty(value = "通用查询条件")
+    private String commonQueryCondition;
+
+    @ApiModelProperty(value = "通用排序")
+    private String commonQueryOrderBy;
+
+    @ApiModelProperty(value = "排序方式")
+    private String sortMethod;
+
+    @ApiModelProperty(value = "默认0: 0 不是 ,1 是公共几口")
+    private Integer commonScope;
+}

+ 5 - 0
java/storlead-framework/storlead-core/pom.xml

@@ -20,6 +20,11 @@
     </properties>
 
     <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
         <!-- Web 相关 -->
         <dependency>
             <groupId>org.springframework.boot</groupId>

+ 10 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/query/page/PageQuery.java

@@ -0,0 +1,10 @@
+package com.storlead.framework.mybatis.query.page;
+
+import com.storlead.framework.mybatis.page.Page;
+
+/**
+ * MyBatis query pagination object.
+ */
+public class PageQuery extends Page {
+    private static final long serialVersionUID = -5677282722081493575L;
+}

+ 0 - 24
java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/assemble/QueryBaseDTO.java

@@ -1,24 +0,0 @@
-package com.storlead.framework.web.assemble;
-
-import io.swagger.annotations.ApiModelProperty;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.extern.log4j.Log4j2;
-
-import java.io.Serializable;
-
-/**
- * @program: storlead-storlead-mail-platform
- * @description:
- * @author: chenkq
- * @create: 2025-07-14 17:10
- */
-@NoArgsConstructor
-@AllArgsConstructor
-@Data
-@Log4j2
-public class QueryBaseDTO implements Serializable {
-    @ApiModelProperty(value = "搜索参数")
-    private String blurry;
-}

+ 2 - 21
java/storlead-framework/storlead-web/src/main/java/com/storlead/framework/web/assemble/QueryBaseEntity.java

@@ -1,7 +1,7 @@
 package com.storlead.framework.web.assemble;
 
 import com.baomidou.mybatisplus.annotation.TableField;
-import com.storlead.framework.mybatis.page.Page;
+import com.storlead.framework.mybatis.query.page.PageQuery;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -18,31 +18,12 @@ import lombok.extern.log4j.Log4j2;
 @AllArgsConstructor
 @Data
 @Log4j2
-public class QueryBaseEntity extends Page {
+public class QueryBaseEntity extends PageQuery {
 
     @TableField(exist = false)
     @ApiModelProperty(value = "搜索参数")
     private String blurry;
 
-//    @TableField(exist = false)
-//    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
-//    @ApiModelProperty(value = "点击左侧树节点的Id")
-//    private Long treeId;
-//
-//    @TableField(exist = false)
-//    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
-//    @ApiModelProperty(value = "点击左侧树节点是否是部门")
-//    private Boolean isDept;
-//
-//    @TableField(exist = false)
-//    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
-//    @ApiModelProperty(value = "部门id列表")
-//    private Set<Long> deptIds;
-//
-//    @TableField(exist = false)
-//    @ApiModelProperty(value = "路由path 用作获取部门tree权限")
-//    private Long menuId;
-
     @TableField(exist = false)
     @ApiModelProperty(value = "菜单Id")
     private Long scopeMenuId;

+ 40 - 0
java/storlead-message/pom.xml

@@ -0,0 +1,40 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-saas-platform</artifactId>
+        <version>1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-message</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-message</name>
+    <version>1.0</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mybatis</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-user</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 20 - 0
java/storlead-message/src/main/java/com/storlead/message/controller/UserMessageNoticeConfigController.java

@@ -0,0 +1,20 @@
+package com.storlead.message.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 消息开关配置 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-04-25
+ */
+@RestController
+@RequestMapping("/user-message-notice-config-entity")
+public class UserMessageNoticeConfigController {
+
+}

+ 58 - 0
java/storlead-message/src/main/java/com/storlead/message/entity/UserMessageNoticeConfigEntity.java

@@ -0,0 +1,58 @@
+package com.storlead.message.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 消息开关配置
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-04-25
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("user_message_notice_config")
+@ApiModel(value="UserMessageNoticeConfigEntity对象", description="消息开关配置")
+public class UserMessageNoticeConfigEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "模板名称")
+    @TableField("template_event_id")
+    private Long templateEventId;
+
+    @ApiModelProperty(value = "模板code")
+    @TableField("template_event_code")
+    private String templateEventCode;
+
+    @ApiModelProperty(value = "site:站内,sms:短信,wecom:企业微信,mail:邮件")
+    @TableField("template_detail_type")
+    private String templateDetailType;
+
+    @ApiModelProperty(value = "数组: site:站内,sms:短信,wecom:企业微信,mail:邮件,需要同时控制的消息类型")
+    @TableField(exist = false)
+    private List<String> templateDetailTypels;
+
+    @ApiModelProperty(value = "备注")
+    @TableField("remark")
+    private Long remark;
+
+
+}

+ 21 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/InsideMessageRecordMapper.java

@@ -0,0 +1,21 @@
+package com.storlead.message.mapper;
+
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.message.pojo.entity.InsideMessageRecordEntity;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * 系统内部消息 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface InsideMessageRecordMapper extends MyBaseMapper<InsideMessageRecordEntity> {
+
+    Map selectOldById(@Param("tableName") String tableName,@Param("fieldColumn") String fieldColumn,@Param("id") Long id);
+}

+ 32 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/InsideMessageSendLogMapper.java

@@ -0,0 +1,32 @@
+package com.storlead.message.mapper;
+
+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.plugins.pagination.Page;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import com.storlead.message.pojo.vo.MessageDetailVO;
+import com.storlead.message.pojo.vo.MessageNoReadTotalVO;
+import com.storlead.message.pojo.vo.MessageTypeReadStateVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 内部消息人员接收记录表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface InsideMessageSendLogMapper extends MyBaseMapper<InsideMessageSendLogEntity> {
+
+    IPage<MessageDetailVO> pageListMessageLog(Page<InsideMessageSendLogEntity> page, @Param(Constants.WRAPPER) Wrapper<InsideMessageSendLogEntity> wrapper);
+
+    MessageNoReadTotalVO countNoReadNumber(@Param(Constants.WRAPPER) Wrapper<InsideMessageSendLogEntity> wrapper);
+
+    List<MessageTypeReadStateVO> countNoReadCount(@Param(Constants.WRAPPER) Wrapper<InsideMessageSendLogEntity> wrapper);
+
+}

+ 17 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/MessageTemplateEventDetailMapper.java

@@ -0,0 +1,17 @@
+package com.storlead.message.mapper;
+
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * <p>
+ * 消息模板 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface MessageTemplateEventDetailMapper extends MyBaseMapper<MessageTemplateEventDetailEntity> {
+
+}

+ 29 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/MessageTemplateEventGroupMapper.java

@@ -0,0 +1,29 @@
+package com.storlead.message.mapper;
+
+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.plugins.pagination.Page;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.storlead.message.pojo.vo.MessageDetailVO;
+import com.storlead.message.pojo.vo.MessageNoReadTotalVO;
+import com.storlead.message.pojo.vo.MessageTemplateVO;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 实践规则模板 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface MessageTemplateEventGroupMapper extends MyBaseMapper<MessageTemplateEventGroupEntity> {
+
+    IPage<MessageTemplateVO> selectMessageTemplateList(Page<MessageTemplateEventGroupEntity> page, @Param(Constants.WRAPPER) Wrapper<MessageTemplateEventGroupEntity> wrapper);
+}

+ 16 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/UserMessageNoticeConfigMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.message.mapper;
+
+import com.storlead.message.entity.UserMessageNoticeConfigEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 消息开关配置 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-04-25
+ */
+public interface UserMessageNoticeConfigMapper extends MyBaseMapper<UserMessageNoticeConfigEntity> {
+
+}

+ 34 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/xml/InsideMessageRecordMapper.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.InsideMessageRecordMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.model.message.entity.InsideMessageRecordEntity">
+                    <id column="id" property="id" />
+                    <result column="title" property="title" />
+                    <result column="content" property="content" />
+                    <result column="message_icon" property="messageIcon" />
+                    <result column="message_tag" property="messageTag" />
+                    <result column="callback_url" property="callbackUrl" />
+                    <result column="wx_callback_url" property="wxCallbackUrl" />
+                    <result column="send_user_id" property="sendUserId" />
+                    <result column="status" property="status" />
+                    <result column="message_type" property="messageType" />
+                    <result column="message_sub_type" property="messageSubType" />
+                    <result column="timer_send_time" property="timerSendTime" />
+                    <result column="actually_send_time" property="actuallySendTime" />
+                    <result column="create_by" property="createBy" />
+                    <result column="create_time" property="createTime" />
+                    <result column="update_by" property="updateBy" />
+                    <result column="update_time" property="updateTime" />
+                    <result column="enabled" property="enabled" />
+                    <result column="is_delete" property="isDelete" />
+                    <result column="sort" property="sort" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, title, content, message_icon, message_tag, callback_url, wx_callback_url, send_user_id, status, message_type, message_sub_type, timer_send_time, actually_send_time, create_by, create_time, update_by, update_time, enabled, is_delete, sort
+        </sql>
+
+</mapper>

+ 26 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/xml/InsideMessageSendLogMapper.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.InsideMessageSendLogMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.model.message.entity.InsideMessageSendLogEntity">
+                    <id column="id" property="id" />
+                    <result column="message_id" property="messageId" />
+                    <result column="receiver_user_id" property="receiverUserId" />
+                    <result column="is_read" property="isRead" />
+                    <result column="status" property="status" />
+                    <result column="create_by" property="createBy" />
+                    <result column="create_time" property="createTime" />
+                    <result column="update_by" property="updateBy" />
+                    <result column="update_time" property="updateTime" />
+                    <result column="enabled" property="enabled" />
+                    <result column="is_delete" property="isDelete" />
+                    <result column="sort" property="sort" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, message_id, receiver_user_id, is_read, status, create_by, create_time, update_by, update_time, enabled, is_delete, sort
+        </sql>
+
+</mapper>

+ 35 - 0
java/storlead-message/src/main/java/com/storlead/message/mapper/xml/UserMessageNoticeConfigMapper.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.UserMessageNoticeConfigMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.message.entity.UserMessageNoticeConfigEntity">
+                    <id column="id" property="id" />
+                <result column="create_by" property="createBy" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_by" property="updateBy" />
+                <result column="update_time" property="updateTime" />
+                <result column="enabled" property="enabled" />
+                <result column="is_delete" property="isDelete" />
+                <result column="sort" property="sort" />
+                    <result column="template_event_id" property="templateEventId" />
+                    <result column="template_event_code" property="templateEventCode" />
+                    <result column="template_detail_type" property="templateDetailType" />
+                    <result column="remark" property="remark" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                create_by,
+                owner_by,
+                create_time,
+                update_by,
+                update_time,
+                enabled,
+                is_delete,
+                sort,
+            id, template_event_id, template_event_code, template_detail_type, remark
+        </sql>
+
+</mapper>

+ 27 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageDTO.java

@@ -0,0 +1,27 @@
+package com.storlead.message.pojo.dto;
+
+import com.storlead.framework.common.dto.page.PageDTO;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-19 16:39
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value="站内消息", description="站内消息")
+public class MessageDTO extends PageDTO {
+
+    @ApiModelProperty(value = "字典")
+    private String messageType;
+
+    @ApiModelProperty(value = "0:未读,1:已读")
+    private Integer isRead;
+}

+ 12 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageTemplateDetailDTO.java

@@ -0,0 +1,12 @@
+package com.storlead.message.pojo.dto;
+
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-09 18:06
+ */
+public class MessageTemplateDetailDTO extends MessageTemplateEventDetailEntity {
+}

+ 14 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageTemplateEventDTO.java

@@ -0,0 +1,14 @@
+package com.storlead.message.pojo.dto;
+
+import lombok.Data;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-26 17:20
+ */
+@Data
+public class MessageTemplateEventDTO extends QueryBaseDTO {
+    private Integer templateServiceType;
+}

+ 31 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/dto/MessageTestDTO.java

@@ -0,0 +1,31 @@
+package com.storlead.message.pojo.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Set;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-15 10:24
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value="站内消息", description="站内消息")
+public class MessageTestDTO {
+
+    @ApiModelProperty(value = "数据Id")
+    private Long dataId;
+
+    @ApiModelProperty(value = "触发事件")
+    private String eventCode;
+
+    @ApiModelProperty(value = "接收人")
+    private Set<Long> toUserIds;
+}

+ 98 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/entity/InsideMessageRecordEntity.java

@@ -0,0 +1,98 @@
+package com.storlead.message.pojo.entity;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+/**
+ * <p>
+ * 系统内部消息
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("inside_message_record")
+@ApiModel(value="InsideMessageRecordEntity对象", description="系统内部消息")
+public class InsideMessageRecordEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "消息标题")
+    private String title;
+
+    @ApiModelProperty(value = "内容")
+    private String content;
+
+    @ApiModelProperty(value = "模板图标")
+    private String messageIcon;
+
+    @ApiModelProperty(value = "模板标签")
+    private String messageTag;
+
+    @ApiModelProperty(value = "回调地址")
+    private String callbackUrl;
+
+    @ApiModelProperty(value = "微信小程序回调地址")
+    private String wxCallbackUrl;
+
+    @ApiModelProperty(value = "发送人")
+    private Long sendUserId;
+
+    @ApiModelProperty(value = "归属人员")
+    private Long ownerBy;
+
+    @ApiModelProperty(value = "是否已读 0:未发送,1:已发送,2:撤回")
+    private Integer status;
+
+    @ApiModelProperty(value = "所属业务组")
+    private Integer messageType;
+
+    @ApiModelProperty(value = "细分消息类型(对应事件)")
+    private Integer messageSubType;
+
+    @ApiModelProperty(value = "定时发送时间")
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime timerSendTime;
+
+    @ApiModelProperty(value = "实际发送时间")
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime actuallySendTime;
+
+
+    public InsideMessageRecordEntity() {}
+
+    public InsideMessageRecordEntity(String title, String content,String messageIcon,String messageTag,String callbackUrl, Long sendUserId, Integer status, Integer messageType) {
+        this.title = title;
+        this.content = content;
+        this.messageTag = messageTag;
+        this.messageIcon = messageIcon;
+        this.callbackUrl = callbackUrl;
+        this.sendUserId = sendUserId;
+        this.status = status;
+        this.messageType = messageType;
+    }
+}

+ 70 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/entity/InsideMessageSendLogEntity.java

@@ -0,0 +1,70 @@
+package com.storlead.message.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 内部消息人员接收记录表
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("inside_message_send_log")
+@ApiModel(value="InsideMessageSendLogEntity对象", description="内部消息人员接收记录表")
+public class InsideMessageSendLogEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "消息id")
+    private Long messageId;
+
+    @ApiModelProperty(value = "接收人")
+    private Long receiverUserId;
+
+    @ApiModelProperty(value = "所属业务组")
+    private Integer messageType;
+
+    @ApiModelProperty(value = "细分消息类型(对应事件)")
+    private Integer messageSubType;
+
+    @ApiModelProperty(value = "未读:0 ,已读:1")
+    private Integer isRead;
+
+    @ApiModelProperty(value = "是否已读 0:未接收,1:已接收")
+    private Integer status;
+
+    @ApiModelProperty(value = "归属人员")
+    private Long ownerBy;
+
+    public InsideMessageSendLogEntity() {
+
+    }
+
+    public InsideMessageSendLogEntity(Long messageId,Long receiverUserId, Integer isRead, Integer status,Integer messageType) {
+        this.messageId = messageId;
+        this.receiverUserId =receiverUserId;
+        this.isRead = isRead;
+        this.status = status;
+        this.messageType = messageType;
+
+    }
+}

+ 63 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/entity/MessageTemplateEventDetailEntity.java

@@ -0,0 +1,63 @@
+package com.storlead.message.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 消息模板
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("message_template_event_detail")
+@ApiModel(value="MessageTemplateEventDetailEntity对象", description="消息模板")
+public class MessageTemplateEventDetailEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "模板id")
+    private Long templateEventId;
+
+    @ApiModelProperty(value = "模板图标")
+    private String templateIcon;
+
+    @ApiModelProperty(value = "模板标签")
+    private String templateTag;
+
+    @ApiModelProperty(value = "模板code")
+    private String templateEventName;
+
+    @ApiModelProperty(value = "site:站内,sms:短信,wecom:企业微信,mail:邮件")
+    private String templateDetailType;
+
+    @ApiModelProperty(value = "msg:消息,card:卡片 :默认:card")
+    private String templateMessageView;
+
+    @ApiModelProperty(value = "消息标题")
+    private String messageTitle;
+
+    @ApiModelProperty(value = "模板标题")
+    private String messageCentent;
+
+    @ApiModelProperty(value = "回调地址")
+    private String callbackUrl;
+}

+ 64 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/entity/MessageTemplateEventGroupEntity.java

@@ -0,0 +1,64 @@
+package com.storlead.message.pojo.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.baomidou.mybatisplus.annotation.TableId;
+import java.time.LocalDateTime;
+
+import com.storlead.frame.annotate.Dict;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.opentracing.tag.IntTag;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * <p>
+ * 实践规则模板
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("message_template_event_group")
+@ApiModel(value="MessageTemplateEventGroupEntity对象", description="实践规则模板")
+public class MessageTemplateEventGroupEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "模块名称")
+    private String templateServiceName;
+
+    @ApiModelProperty(value = "业务模块,对应消息类型")
+    @Dict(dictCode = "work_data_group",valueField = "templateServiceName")
+    private Integer templateServiceType;
+
+    @ApiModelProperty(value = " 对应server 业务模块,匹配表名")
+    private String templateServiceCode;
+
+    @ApiModelProperty(value = "事件")
+    private String eventName;
+
+    @ApiModelProperty(value = "事件 code")
+    private String eventCode;
+
+    @ApiModelProperty(value = "事件规则")
+    private String eventRuleScript;
+
+    @ApiModelProperty(value = "接收人key")
+    private String receiverKey;
+
+    @ApiModelProperty(value = "通知的APPCODE")
+    private String noticeAppCode;
+
+}

+ 20 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageArgTemplateVO.java

@@ -0,0 +1,20 @@
+package com.storlead.message.pojo.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-19 11:58
+ */
+@Data
+public class MessageArgTemplateVO {
+
+    @ApiModelProperty(value = "属性名")
+    private String  propertyName;
+
+    @ApiModelProperty(value = "属性key")
+    private String  propertyKey;
+}

+ 46 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageDetailVO.java

@@ -0,0 +1,46 @@
+package com.storlead.message.pojo.vo;
+
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-19 16:40
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value="站内消息", description="站内消息")
+public class MessageDetailVO extends InsideMessageSendLogEntity {
+
+    @ApiModelProperty(value = "消息标题")
+    private String title;
+
+    @ApiModelProperty(value = "消息内容")
+    private String content;
+
+    @ApiModelProperty(value = "图标")
+    private String messageIcon;
+
+    @ApiModelProperty(value = "标签")
+    private String messageTag;
+
+    @ApiModelProperty(value = "回调地址")
+    private String callbackUrl;
+
+    @ApiModelProperty(value = "发送人id")
+    private Long sendUserId;
+
+    @ApiModelProperty(value = "发送人")
+    private String sendUserName;
+
+    @ApiModelProperty(value = "发送头像图片")
+    private String photo;
+}
+

+ 25 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageNoReadTotalVO.java

@@ -0,0 +1,25 @@
+package com.storlead.message.pojo.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-19 16:43
+ */
+@Data
+public class MessageNoReadTotalVO implements Serializable {
+
+    @ApiModelProperty(value = "所有未读数")
+    private Integer noReadNum = 0;
+
+    @ApiModelProperty(value = "@我未读数")
+    private Integer atNoReadNum = 0;
+
+    @ApiModelProperty(value = "审批未读数")
+    private Integer approveNoReadNum = 0;
+}

+ 29 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageTemplateVO.java

@@ -0,0 +1,29 @@
+package com.storlead.message.pojo.vo;
+
+import com.storlead.message.entity.UserMessageNoticeConfigEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.util.List;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-09 16:32
+ */
+@Data
+public class MessageTemplateVO extends MessageTemplateEventGroupEntity {
+
+    @ApiModelProperty(value = "模块名称")
+    private List<MessageTemplateEventDetailEntity> detailEntities;
+
+    @ApiModelProperty(value = "配置参数")
+    private List<MessageArgTemplateVO> argls;
+
+    @ApiModelProperty(value = "个人设置")
+    private List<UserMessageNoticeConfigEntity> userDetails;
+
+}

+ 20 - 0
java/storlead-message/src/main/java/com/storlead/message/pojo/vo/MessageTypeReadStateVO.java

@@ -0,0 +1,20 @@
+package com.storlead.message.pojo.vo;
+
+import lombok.Data;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-09-06 09:17
+ */
+@Data
+public class MessageTypeReadStateVO {
+
+    private Integer messageType;
+
+    private String messageTypeName;
+
+    private Integer stateNumber;
+
+}

+ 46 - 0
java/storlead-message/src/main/java/com/storlead/message/service/InsideMessageRecordService.java

@@ -0,0 +1,46 @@
+package com.storlead.message.service;
+
+import com.storlead.framework.mybatis.service.MyBaseService;
+import com.storlead.framework.web.assemble.Result;
+import com.storlead.message.pojo.entity.InsideMessageRecordEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * <p>
+ * 系统内部消息 服务类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface InsideMessageRecordService extends MyBaseService<InsideMessageRecordEntity> {
+
+    Map getOldMapDataById(String table,String fieldColumn,String id);
+
+
+    /**
+     * @param templateEventId  站内消息模板
+     * @param map 参数
+     * @param receiverUserId 接收人
+     * @param callbackParam 回调参数
+     * @return
+     */
+    Result sendInsideMessage(MessageTemplateEventGroupEntity eventGroup, MessageTemplateEventDetailEntity templateEventDetail, Map map, Long receiverUserId, String callbackParam, Long sendUserId);
+    /**
+     * @param templateEventId  消息事件模板
+     * @param map 参数
+     * @param receiverUserIds 接收人
+     * @param callbackParam 回调参数
+     * @return
+     */
+    Result sendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam, Long sendUserId);
+
+    void asynSendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Long receiverUserId, String callbackParam,Long sendUserId);
+    void asynSendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam,Long sendUserId);
+
+}

+ 31 - 0
java/storlead-message/src/main/java/com/storlead/message/service/InsideMessageSendLogService.java

@@ -0,0 +1,31 @@
+package com.storlead.message.service;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.mybatis.service.MyBaseService;
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import com.storlead.message.pojo.vo.MessageDetailVO;
+import com.storlead.message.pojo.vo.MessageNoReadTotalVO;
+import com.storlead.message.pojo.vo.MessageTypeReadStateVO;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 内部消息人员接收记录表 服务类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface InsideMessageSendLogService extends MyBaseService<InsideMessageSendLogEntity> {
+
+    IPage<MessageDetailVO> queryListMessageLog(Page<InsideMessageSendLogEntity> page, Wrapper<InsideMessageSendLogEntity> wrapper);
+
+    MessageNoReadTotalVO countNoReadtotal(Wrapper<InsideMessageSendLogEntity> wrapper);
+
+    List<MessageTypeReadStateVO> getCountNoReadCount(Wrapper<InsideMessageSendLogEntity> wrapper);
+
+
+}

+ 26 - 0
java/storlead-message/src/main/java/com/storlead/message/service/MessageTemplateEventDetailService.java

@@ -0,0 +1,26 @@
+package com.storlead.message.service;
+
+import com.storlead.framework.mybatis.service.MyBaseService;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>
+ * 消息模板 服务类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface MessageTemplateEventDetailService extends MyBaseService<MessageTemplateEventDetailEntity> {
+
+    void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap);
+
+    void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap, String... args);
+
+    void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap, Set<Long> toUserIds, String... args);
+}

+ 21 - 0
java/storlead-message/src/main/java/com/storlead/message/service/MessageTemplateEventGroupService.java

@@ -0,0 +1,21 @@
+package com.storlead.message.service;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.mybatis.service.MyBaseService;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import com.storlead.message.pojo.vo.MessageTemplateVO;
+
+/**
+ * <p>
+ * 实践规则模板 服务类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+public interface MessageTemplateEventGroupService extends MyBaseService<MessageTemplateEventGroupEntity> {
+
+    IPage<MessageTemplateVO> listPage(Page<MessageTemplateEventGroupEntity> page, Wrapper<MessageTemplateEventGroupEntity> wrapper);
+}

+ 19 - 0
java/storlead-message/src/main/java/com/storlead/message/service/UserMessageNoticeConfigService.java

@@ -0,0 +1,19 @@
+package com.storlead.message.service;
+
+import com.storlead.message.entity.UserMessageNoticeConfigEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+import java.util.Set;
+
+/**
+ * <p>
+ * 消息开关配置 服务类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-04-25
+ */
+public interface UserMessageNoticeConfigService extends MyBaseService<UserMessageNoticeConfigEntity> {
+
+    Set<Long> checkUserNoticeConfig(Set<Long> ids,Long templateId,String messageType);
+}

+ 23 - 0
java/storlead-message/src/main/java/com/storlead/message/service/WechatMessageService.java

@@ -0,0 +1,23 @@
+package com.storlead.message.service;
+
+import com.storlead.framework.web.assemble.Result;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import org.springframework.stereotype.Component;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+public interface WechatMessageService {
+
+    /**
+     * @param eventDetail  短信模板
+     * @param map 参数
+     * @param receiverUserIds 接收人
+     * @param callbackParam 回调参数
+     * @return
+     */
+    Result sendWechatMessage(MessageTemplateEventDetailEntity eventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam);
+
+    Result sendWechatMessage(MessageTemplateEventDetailEntity eventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam,String appCode);
+}

+ 130 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/InsideMessageRecordServiceImpl.java

@@ -0,0 +1,130 @@
+package com.storlead.message.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.common.util.HtmlUtils;
+import com.storlead.frame.core.assemble.Result;
+import com.storlead.frame.enums.ErrorMsgCode;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.message.pojo.entity.InsideMessageRecordEntity;
+import com.storlead.message.mapper.InsideMessageRecordMapper;
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import com.storlead.message.service.InsideMessageRecordService;
+import com.storlead.message.service.InsideMessageSendLogService;
+import com.storlead.message.service.MessageTemplateEventGroupService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>
+ * 系统内部消息 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Service
+public class InsideMessageRecordServiceImpl extends MyBaseServiceImpl<InsideMessageRecordMapper, InsideMessageRecordEntity> implements InsideMessageRecordService {
+
+    @Override
+    public Map getOldMapDataById(String table,String fieldColumn,String id ) {
+        return this.baseMapper.selectOldById(table,fieldColumn,Long.valueOf(id));
+    }
+
+    @Value("${domainname}")
+    private  String domainname;
+
+    @Autowired
+    private MessageTemplateEventGroupService templateService;
+
+    @Autowired
+    private InsideMessageSendLogService insideMessageSendLogService;
+
+    @Override
+    public Result sendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Long receiverUserId, String callbackParam, Long sendUserId) {
+        return sendInsideMessage(eventGroup,templateEventDetail,map, Arrays.asList(receiverUserId),callbackParam,sendUserId);
+    }
+
+    @Override
+    public Result sendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam, Long sendUserId) {
+
+        String domain = this.domainname;
+
+        MessageTemplateEventDetailEntity templateEntity = templateEventDetail;
+        if (Objects.isNull(templateEntity)) {
+            return Result.error(ErrorMsgCode.WECHAT_SMS_100006);
+        }
+        if (CollectionUtils.isEmpty(receiverUserIds)) {
+            return Result.error(ErrorMsgCode.WECHAT_SMS_100008);
+        }
+        String title = templateEntity.getMessageTitle();
+        title = title.replace("<br>","\n");
+        String content = templateEntity.getMessageCentent();
+        if (Objects.isNull(content)) {
+            content = templateEntity.getMessageTitle();
+        }
+        Iterator<Map.Entry< Integer, String >> iterator = map.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry< Integer, String > entry = iterator.next();
+            if (Objects.nonNull(entry)) {
+                String value =  HtmlUtils.userContent(entry.getValue());
+                content = content.replace("${"+entry.getKey()+"}$",value);
+                title = title.replace("${"+entry.getKey()+"}$",value);
+            }
+        }
+        content = content.replace("<br>","\n");
+        String router = domain +""+ (StrUtil.isNotBlank(templateEntity.getCallbackUrl()) ? templateEntity.getCallbackUrl() : map.get("callbackUrl"));
+        if (Objects.nonNull(router)) {
+            Object jump = Objects.nonNull(map) ? map.get("jump") : null;
+            Object id = Objects.nonNull(map) ? map.get("id") : null;
+            if (Objects.nonNull(id)) {
+                router = router.replace("${id}$",id.toString());
+            } else {
+                router = router.replace("${id}$","");
+            }
+            if (Objects.nonNull(jump)) {
+                router = router.replace("${jump}$",jump.toString());
+            } else {
+                router = router.replace("${jump}$","");
+            }
+        }
+
+        log.error("asynSendWechatMessage - senderUserId3------------"+sendUserId);
+        if (Objects.isNull(sendUserId)) {
+            sendUserId = 0L;
+        }
+
+        InsideMessageRecordEntity entity = new InsideMessageRecordEntity(title,content,templateEntity.getTemplateIcon(),templateEntity.getTemplateTag(),router,sendUserId,1,eventGroup.getTemplateServiceType());
+        entity.setCreateBy(sendUserId);
+        entity.setUpdateBy(sendUserId);
+        saveOrUpdate(entity);
+
+        List<InsideMessageSendLogEntity> messageSendLogs = new ArrayList<>();
+        for (Long receiverUserId : receiverUserIds) {
+            InsideMessageSendLogEntity messageSendLog = new InsideMessageSendLogEntity(entity.getId(),receiverUserId,0,1,entity.getMessageType());
+            messageSendLog.setCreateBy(sendUserId);
+            messageSendLog.setUpdateBy(sendUserId);
+            messageSendLogs.add(messageSendLog);
+        }
+        insideMessageSendLogService.saveOrUpdateBatch(messageSendLogs);
+        return Result.ok();
+    }
+
+    @Override
+    public void asynSendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Long receiverUserId, String callbackParam,Long sendUserId) {
+        sendInsideMessage(eventGroup,templateEventDetail,map,Arrays.asList(receiverUserId),callbackParam,sendUserId);
+    }
+
+    @Override
+    public void asynSendInsideMessage(MessageTemplateEventGroupEntity eventGroup,MessageTemplateEventDetailEntity templateEventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam,Long sendUserId) {
+        sendInsideMessage(eventGroup,templateEventDetail,map,receiverUserIds,callbackParam,sendUserId);
+    }
+}

+ 43 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/InsideMessageSendLogServiceImpl.java

@@ -0,0 +1,43 @@
+package com.storlead.message.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import com.storlead.message.pojo.vo.MessageDetailVO;
+import com.storlead.message.pojo.vo.MessageNoReadTotalVO;
+import com.storlead.message.mapper.InsideMessageSendLogMapper;
+import com.storlead.message.pojo.vo.MessageTypeReadStateVO;
+import com.storlead.message.service.InsideMessageSendLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 内部消息人员接收记录表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Service
+public class InsideMessageSendLogServiceImpl extends MyBaseServiceImpl<InsideMessageSendLogMapper, InsideMessageSendLogEntity> implements InsideMessageSendLogService {
+
+    @Override
+    public IPage<MessageDetailVO> queryListMessageLog(Page<InsideMessageSendLogEntity> page, Wrapper<InsideMessageSendLogEntity> wrapper) {
+        return this.baseMapper.pageListMessageLog(page,wrapper);
+    }
+
+    @Override
+    public MessageNoReadTotalVO countNoReadtotal(Wrapper<InsideMessageSendLogEntity> wrapper) {
+        return this.baseMapper.countNoReadNumber(wrapper);
+    }
+
+    @Override
+    public List<MessageTypeReadStateVO> getCountNoReadCount(Wrapper<InsideMessageSendLogEntity> wrapper) {
+        return this.baseMapper.countNoReadCount(wrapper);
+    }
+
+}

+ 165 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/MessageService.java

@@ -0,0 +1,165 @@
+package com.storlead.message.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.Expression;
+import com.googlecode.aviator.runtime.function.AbstractFunction;
+import com.googlecode.aviator.runtime.type.AviatorBoolean;
+import com.googlecode.aviator.runtime.type.AviatorObject;
+import com.storlead.common.aviator.MapKeyExistsFunction;
+import com.storlead.frame.auth.util.LoginUserUtil;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import com.storlead.message.service.InsideMessageRecordService;
+import com.storlead.message.service.MessageTemplateEventDetailService;
+import com.storlead.message.service.MessageTemplateEventGroupService;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import com.googlecode.aviator.utils.Reflector;
+import javax.annotation.Resource;
+import java.util.*;
+
+import com.storlead.common.aviator.EqualsFunction;
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-23 11:34
+ */
+
+@Log4j2
+@Service
+public class MessageService {
+
+    @Resource
+    private InsideMessageRecordService messageRecordService;
+
+    @Resource
+    private MessageTemplateEventGroupService eventGroupService;
+
+    @Resource
+    private MessageTemplateEventDetailService eventDetailService;
+
+
+    // 客户
+    // 商机
+//    public void autoMatchEventSendMessage(Map beforeMap,Map afterMap,String templateService,String... sendType) {
+//
+//    }
+
+
+
+    public Map getOldMapData(Map sqlmap,String fieldColumn,String table){
+        try {
+            Map<String,Object> var1 = messageRecordService.getOldMapDataById(table,fieldColumn,sqlmap.get("id").toString());
+            Map<String,Object> vargs = new HashMap<>();
+            if (!CollectionUtils.isEmpty(var1)) {
+                for (Map.Entry<String, Object> entry : var1.entrySet()) {
+                    String camelCaseKey= StrUtil.toCamelCase("before"+entry.getKey());
+                    vargs.put(camelCaseKey,entry.getValue());
+                }
+            }
+            return vargs;
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+//    public void autoMatchEventSendMessage(Map beforeMap,Map afterMap,String templateService) {
+//        autoMatchEventSendMessage(beforeMap,afterMap,templateService,"site","wecom","sms","mail");
+//    }
+    public void autoMatchEventSendMessage(Map beforeMap,Map afterMap,String templateService) {
+
+        autoMatchEventSendMessage(beforeMap ,afterMap,templateService,"site","wecom","sms","mail");
+    }
+
+    public void autoMatchEventSendMessage(Map<String,Object> messageMap, String templateServiceType, String eventCode, Set<Long> toUserIds, String... args) {
+        try {
+            LambdaQueryWrapper<MessageTemplateEventGroupEntity> templateEventWrapper = new LambdaQueryWrapper<>();
+            templateEventWrapper.eq(MessageTemplateEventGroupEntity::getTemplateServiceType,templateServiceType);
+            templateEventWrapper.eq(MessageTemplateEventGroupEntity::getEventCode,eventCode);
+            templateEventWrapper.last(" limit 1");
+            MessageTemplateEventGroupEntity  eventGroup = eventGroupService.getOne(templateEventWrapper);
+            if (Objects.isNull(eventGroup)) {
+                return;
+            }
+            asynSendMessage(eventGroup,messageMap,toUserIds,args);
+        }catch (Exception e) {
+            log.error("autoMatchEventSendMessage --- error",e);
+        }
+    }
+
+    /**
+     * 自动匹配消息事件规则
+     * @param updateDataMap
+     * @param oldDataMap
+     * @param templateService
+     */
+    public void autoMatchEventSendMessage(Map beforeMap,Map afterMap,String templateService,String... args) {
+        HashMap<String,Object> vargs = new HashMap();
+        if (!CollectionUtils.isEmpty(beforeMap)) {
+            vargs.putAll(beforeMap);
+        }
+        vargs.put("beforeMap",beforeMap);
+        if (!CollectionUtils.isEmpty(afterMap)) {
+            vargs.putAll(afterMap);
+        }
+        vargs.put("afterMap",afterMap);
+        Map<String, Object> env = new HashMap<>();
+        env.put("map", vargs);
+        LambdaQueryWrapper<MessageTemplateEventGroupEntity> templateEventWrapper = new LambdaQueryWrapper<>();
+        templateEventWrapper.eq(MessageTemplateEventGroupEntity::getTemplateServiceCode,templateService);
+        List<MessageTemplateEventGroupEntity>  eventGroups = eventGroupService.list(templateEventWrapper);
+        if (CollectionUtils.isEmpty(eventGroups)) {
+            return;
+        }
+        for(MessageTemplateEventGroupEntity eventGroup : eventGroups) {
+            if (StrUtil.isNotBlank(eventGroup.getEventRuleScript())) {
+                AviatorEvaluator.addFunction(new MapKeyExistsFunction());
+                String expression = eventGroup.getEventRuleScript();
+                Expression exp = AviatorEvaluator.compile(expression);
+                Boolean rst = (Boolean)exp.execute(env);
+                if (rst) {
+                    log.info("-------------匹配到消息事件规则:"+eventGroup.getEventRuleScript());
+                    asynSendMessage(eventGroup,vargs,null,args);
+                }
+            }
+        }
+    }
+
+    public void sendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap){
+        eventDetailService.asynSendMessage(eventGroup,messageMap);
+    }
+
+    public void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap, Set<Long> toUserIds,String... args){
+        eventDetailService.asynSendMessage(eventGroup,messageMap,toUserIds,args);
+    }
+
+
+
+    public static void main(String[] args) {
+        // 注册自定义函数
+        AviatorEvaluator.addFunction(new MapKeyExistsFunction());
+        // 创建一个Map对象并添加一些键值对
+        Map<String, Object> map = new HashMap<>();
+        map.put("age", 25);
+        map.put("city", "New York");
+
+        // 编写表达式判断Map中是否不包含"userName"键
+        String expression = "!containsKey(map,'id')";
+
+        // 编译表达式
+        Expression compiledExp = AviatorEvaluator.compile(expression);
+
+        // 创建环境变量,将map传入
+        Map<String, Object> env = new HashMap<>();
+        env.put("map", map);
+
+        // 计算表达式
+        Boolean result = (Boolean) compiledExp.execute(env);
+
+        // 输出结果
+        System.out.println("Map中是否不包含'userName'键: " + result);
+    }
+}

+ 255 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/MessageTemplateEventDetailServiceImpl.java

@@ -0,0 +1,255 @@
+package com.storlead.message.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.ttl.TtlRunnable;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.common.util.HtmlUtils;
+import com.storlead.common.util.encryptor.AccessKeyEncryptor;
+import com.storlead.frame.auth.util.LoginUserUtil;
+import com.storlead.frame.core.assemble.Result;
+import com.storlead.frame.enums.ErrorMsgCode;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.frame.thread.ThreadPoolUtil;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.storlead.message.mapper.MessageTemplateEventDetailMapper;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import com.storlead.message.service.InsideMessageRecordService;
+import com.storlead.message.service.MessageTemplateEventDetailService;
+import com.storlead.message.service.UserMessageNoticeConfigService;
+import com.storlead.message.service.WechatMessageService;
+import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
+import com.storlead.sales.mail.pojo.SendMailDTO;
+import com.storlead.sales.mail.service.EmailsService;
+import com.storlead.sales.mail.service.SmtpPopSettingsService;
+import com.storlead.user.pojo.entity.DeptEntity;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.IDepartService;
+import com.storlead.user.service.IUserService;
+import org.apache.catalina.User;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 消息模板 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Service
+public class MessageTemplateEventDetailServiceImpl extends MyBaseServiceImpl<MessageTemplateEventDetailMapper, MessageTemplateEventDetailEntity> implements MessageTemplateEventDetailService {
+
+    @Value("${domainname}")
+    private  String domainname;
+
+    @Resource
+    private InsideMessageRecordService insideMessageRecordService;
+
+    @Resource
+    private SmtpPopSettingsService smtpPopSettingsService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @Resource
+    private WechatMessageService  wechatMessageService;
+
+    @Resource
+    private IUserService userService;
+
+    @Resource
+    private IDepartService departService;
+
+    @Resource
+    private UserMessageNoticeConfigService userMessageNoticeConfigService;
+
+
+    @Override
+    public void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap) {
+        asynSendMessage(eventGroup,messageMap,"site","wecom","sms","mail");
+    }
+    @Override
+    public void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap, String... args) {
+        asynSendMessage(eventGroup,messageMap,null,args);
+    }
+
+    @Override
+    public void asynSendMessage(MessageTemplateEventGroupEntity eventGroup, Map messageMap,Set<Long> toUserIds,String... rmindArgs) {
+
+        String domain = this.domainname;
+        Long senderUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(senderUserId)) {
+            senderUserId = 1L;
+        }
+        Set<Long> receiverUserIds = new HashSet<>();
+        if (!CollectionUtils.isEmpty(toUserIds)) {
+            receiverUserIds.addAll(toUserIds);
+        }
+       log.error("asynSendWechatMessage - senderUserId1 ----------- " +LoginUserUtil.getCurrentUserId());
+
+
+
+        // 当前用户
+        // 创建人 createBy
+        // 归属人 ownerBy
+        // 部门负责人 deptManagerId
+        // 部门负责人 deptManagerId
+        String noticeAppCode = eventGroup.getNoticeAppCode();
+        log.error("asynSendWechatMessage - getNoticeAppCode ----------- " +noticeAppCode);
+
+        String receiverKey =  eventGroup.getReceiverKey();
+        if (StrUtil.isNotBlank(receiverKey)) {
+            List<String> receiverls = Arrays.asList(receiverKey.split(","));
+            //  deptManagerId,ownerBy,createBy
+            // 创建人、归属人、部门负责人
+            if (receiverls.contains("deptManagerId")) {
+                if (messageMap.containsKey("ownerBy")) {
+                    Long ownerBy = (Long) messageMap.get("ownerBy");
+                    Long deptManagerId =  getDeptManagerIdByUser(ownerBy);
+                    if (Objects.nonNull(deptManagerId) && !deptManagerId.equals(ownerBy)) {
+                        receiverUserIds.add(deptManagerId);
+                    }
+                }
+            }
+            else if (!CollectionUtils.isEmpty(receiverls)) {
+                for (String receiverkey : receiverls) {
+                    if (messageMap.containsKey(receiverkey)) {
+                        Object receiver = messageMap.get(receiverkey);
+                        if (Objects.nonNull(receiver)) {
+                            if (receiver instanceof String) {
+                                receiverUserIds.addAll(Arrays.asList(receiver.toString().split(",")).stream().map(Long::parseLong).collect(Collectors.toSet()));
+                            } else if (receiver instanceof Long) {
+                                receiverUserIds.add((Long) receiver);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (Objects.nonNull(senderUserId) && !CollectionUtils.isEmpty(receiverUserIds) && !receiverUserIds.equals(Set.of(1L))) {
+            receiverUserIds.remove(senderUserId);
+        }
+        if (CollectionUtils.isEmpty(receiverUserIds)) {
+            return;
+        }
+        Long finalSenderUserId = senderUserId;
+        MessageTemplateEventGroupEntity newEventGroup = new MessageTemplateEventGroupEntity();
+        BeanUtils.copyProperties(eventGroup,newEventGroup);
+        Runnable ttlRunnable = TtlRunnable.get(new Runnable() {
+            @Override
+            public void run() {
+
+                List<String> rmindTypes =  Arrays.asList(rmindArgs);
+                List<MessageTemplateEventDetailEntity> eventDetaills = list(new LambdaQueryWrapper<MessageTemplateEventDetailEntity>().eq(MessageTemplateEventDetailEntity::getTemplateEventId,eventGroup.getId()));
+
+                if (CollectionUtils.isEmpty(receiverUserIds)) {
+                    return;
+                }
+                for (MessageTemplateEventDetailEntity eventDetail : eventDetaills) {
+                    /**
+                     * 站内
+                     */
+                    if (eventDetail.getTemplateDetailType().equals("site")  && eventDetail.getEnabled() && rmindTypes.contains("site")) {
+                        Set<Long> toUserIds =  userMessageNoticeConfigService.checkUserNoticeConfig(receiverUserIds,newEventGroup.getId(),"site");
+                        insideMessageRecordService.sendInsideMessage(newEventGroup,eventDetail,messageMap,toUserIds, eventDetail.getCallbackUrl(), finalSenderUserId);
+                    }
+                    /**
+                     * 企业微信
+                     */
+                    if (eventDetail.getTemplateDetailType().equals("wecom") && eventDetail.getEnabled() && rmindTypes.contains("wecom")) {
+                        Set<Long> toUserIds =  userMessageNoticeConfigService.checkUserNoticeConfig(receiverUserIds,newEventGroup.getId(),"wecom");
+                        if (StrUtil.isNotBlank(noticeAppCode)) {
+                            wechatMessageService.sendWechatMessage(eventDetail,messageMap,toUserIds,eventDetail.getCallbackUrl(),noticeAppCode);
+                        } else {
+                            wechatMessageService.sendWechatMessage(eventDetail,messageMap,toUserIds,eventDetail.getCallbackUrl());
+                        }
+                    }
+                    /**
+                     * 短信
+                     */
+                    if (eventDetail.getTemplateDetailType().equals("sms")  && eventDetail.getEnabled() && rmindTypes.contains("sms")) {
+
+                    }
+                    /**
+                     * 邮件
+                     */
+                    if (eventDetail.getTemplateDetailType().equals("mail")  && eventDetail.getEnabled() && rmindTypes.contains("mail")) {
+                        Set<Long> toUserIds =  userMessageNoticeConfigService.checkUserNoticeConfig(receiverUserIds,newEventGroup.getId(),"mail");
+                        if (CollectionUtils.isEmpty(toUserIds)) {
+                            continue;
+                        }
+                        SmtpPopSettingsEntity sendSmtpPops = smtpPopSettingsService.getSystemSmtpPop(1L);
+                        if (Objects.isNull(sendSmtpPops)) {
+                            continue;
+                        }
+                        List<SmtpPopSettingsEntity> receiverSmtpPops = smtpPopSettingsService.getDefaultSmtpPopls(receiverUserIds);
+                        if (CollectionUtils.isEmpty(receiverSmtpPops)) {
+                            continue;
+                        }
+                        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+                        String pass = accessKeyEncryptor.decrypt(sendSmtpPops.getEmailPassword());
+                        sendSmtpPops.setEmailPassword(pass);
+
+                        MessageTemplateEventDetailEntity templateEntity = eventDetail;
+                        String title = templateEntity.getMessageTitle();
+                        title = title.replace("<br>","\n");
+                        String content = templateEntity.getMessageCentent();
+
+                        Iterator <Map.Entry< Integer, String >> iterator = messageMap.entrySet().iterator();
+                        while (iterator.hasNext()) {
+                            Map.Entry< Integer, String > entry = iterator.next();
+                            String value =  HtmlUtils.userContent(entry.getValue());
+                            content = content.replace("${"+entry.getKey()+"}$",value);
+                        }
+                        content = content.replace("<br>","\n");
+                        String router = domain +""+ (StrUtil.isNotBlank(templateEntity.getCallbackUrl()) ? templateEntity.getCallbackUrl() : messageMap.get("callbackUrl"));
+                        Object id = Objects.nonNull(messageMap) ? messageMap.get("id") : null;
+                        Object jump = Objects.nonNull(messageMap) ? messageMap.get("jump") : null;
+                        if (StrUtil.isNotBlank(router)) {
+                            if (Objects.nonNull(id)) {
+                                router = router.replace("${id}$",id.toString());
+                            } else {
+                                router = router.replace("${id}$","");
+                            }
+                            if (Objects.nonNull(jump)) {
+                                router = router.replace("${jump}$",jump.toString());
+                            } else {
+                                router = router.replace("${jump}$","");
+                            }
+                        }
+                        List<String> recipientls = receiverSmtpPops.stream().map(SmtpPopSettingsEntity::getEmailAddress).collect(Collectors.toList());
+                        SendMailDTO dto = new SendMailDTO();
+                        dto.setSubject(title);
+                        String routers = "</p><p><a href='"+router+"'>查看</a></p>";
+                        dto.setContent(content+""+routers);
+                        dto.setRecipientls(recipientls);
+                        emailsService.sendEmail(dto,sendSmtpPops);
+                    }
+                }
+            }
+        });
+        ThreadPoolUtil.execute(ttlRunnable);
+    }
+
+    /**
+     * 根据用户id 获取用户部门负责人
+     */
+    public Long getDeptManagerIdByUser(Long userId) {
+        UserEntity user =  userService.getById(userId);
+        if (Objects.nonNull(user) && user.getDeptId() != null) {
+           DeptEntity dept = departService.getById(user.getDeptId());
+           if (Objects.nonNull(dept)) {
+               return dept.getManagerId();
+           }
+        }
+        return null;
+    }
+}

+ 30 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/MessageTemplateEventGroupServiceImpl.java

@@ -0,0 +1,30 @@
+package com.storlead.message.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
+
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.message.pojo.entity.InsideMessageSendLogEntity;
+import com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity;
+import com.storlead.message.mapper.MessageTemplateEventGroupMapper;
+import com.storlead.message.pojo.vo.MessageDetailVO;
+import com.storlead.message.pojo.vo.MessageTemplateVO;
+import com.storlead.message.service.MessageTemplateEventGroupService;
+import org.springframework.stereotype.Service;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+/**
+ * <p>
+ * 实践规则模板 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-04-19
+ */
+@Service
+public class MessageTemplateEventGroupServiceImpl extends MyBaseServiceImpl<MessageTemplateEventGroupMapper, MessageTemplateEventGroupEntity> implements MessageTemplateEventGroupService {
+
+    @Override
+    public IPage<MessageTemplateVO> listPage(Page<MessageTemplateEventGroupEntity> page, Wrapper<MessageTemplateEventGroupEntity> wrapper) {
+        return this.baseMapper.selectMessageTemplateList(page,wrapper);
+    }
+}

+ 56 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/UserMessageNoticeConfigServiceImpl.java

@@ -0,0 +1,56 @@
+package com.storlead.message.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.common.constant.CommonConstant;
+import com.storlead.message.entity.UserMessageNoticeConfigEntity;
+import com.storlead.message.mapper.UserMessageNoticeConfigMapper;
+import com.storlead.message.service.UserMessageNoticeConfigService;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 消息开关配置 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-04-25
+ */
+@Service
+public class UserMessageNoticeConfigServiceImpl extends MyBaseServiceImpl<UserMessageNoticeConfigMapper, UserMessageNoticeConfigEntity> implements UserMessageNoticeConfigService {
+
+    @Override
+    public Set<Long> checkUserNoticeConfig(Set<Long> userIds,Long templateId,String messageType) {
+        Set<Long> newToUser = new HashSet<>(userIds);
+        try {
+            if (CollectionUtils.isEmpty(newToUser)) {
+                return newToUser;
+            }
+            LambdaQueryWrapper<UserMessageNoticeConfigEntity> userMessageConfigW = new LambdaQueryWrapper<>();
+            userMessageConfigW.eq(UserMessageNoticeConfigEntity::getTemplateEventId,templateId);
+            userMessageConfigW.eq(UserMessageNoticeConfigEntity::getTemplateDetailType,messageType);
+            userMessageConfigW.in(UserMessageNoticeConfigEntity::getOwnerBy,userIds);
+            userMessageConfigW.in(UserMessageNoticeConfigEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+            userMessageConfigW.in(UserMessageNoticeConfigEntity::getEnabled, Integer.valueOf(0));
+            List<UserMessageNoticeConfigEntity> ls = this.list(userMessageConfigW);
+
+            if (!CollectionUtils.isEmpty(ls)) {
+                Set<Long> rmUserIds = ls.stream().map(UserMessageNoticeConfigEntity::getOwnerBy).collect(Collectors.toSet());
+                if (!CollectionUtils.isEmpty(rmUserIds)) {
+                    newToUser.removeAll(rmUserIds);
+                }
+            }
+            return newToUser;
+        } catch (Exception e) {
+            log.error("checkUserNoticeConfig --- error -- ",e);
+            return newToUser;
+        }
+
+    }
+}

+ 169 - 0
java/storlead-message/src/main/java/com/storlead/message/service/impl/WechatMessageServiceImpl.java

@@ -0,0 +1,169 @@
+package com.storlead.message.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.common.util.HtmlUtils;
+import com.storlead.frame.core.assemble.Result;
+import com.storlead.frame.enums.ErrorMsgCode;
+import com.storlead.framework.web.assemble.Result;
+import com.storlead.framework.web.enums.ErrorMsgCode;
+import com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity;
+import com.storlead.message.service.WechatMessageService;
+import com.storlead.user.pojo.entity.UserEntity;
+import com.storlead.user.service.IUserService;
+import com.storlead.user.service.impl.UserServiceImpl;
+import com.storlead.wx.service.CorpWeChatService;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-04-26 10:14
+ */
+@Log4j2
+@Service
+public class WechatMessageServiceImpl implements WechatMessageService {
+
+
+    @Resource
+    private IUserService userService;
+
+    @Autowired
+    private CorpWeChatService corpWeChatService;
+
+    /**
+     * @param eventDetail  短信模板
+     * @param map 参数
+     * @param receiverUserIds 接收人
+     * @param callbackParam 回调参数
+     * @return
+     */
+    @Override
+    public Result sendWechatMessage(MessageTemplateEventDetailEntity eventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam){
+        //发送企业微信
+        try {
+            MessageTemplateEventDetailEntity templateEntity = eventDetail;
+            if (Objects.isNull(templateEntity)) {
+                return Result.error(ErrorMsgCode.WECHAT_SMS_100006);
+            }
+            if (CollectionUtils.isEmpty(receiverUserIds)) {
+                return Result.error(ErrorMsgCode.WECHAT_SMS_100008);
+            }
+            String title = templateEntity.getMessageTitle();
+            title = title.replace("<br>","\n");
+            Object titleRemark = Objects.nonNull(map) ? map.get("titleRemark") : null;
+            if (Objects.nonNull(titleRemark)) {
+                title = title.replace("${titleRemark}$",titleRemark.toString());
+            }
+
+            String content = templateEntity.getMessageCentent();
+
+            Iterator <Map.Entry< Integer, String >> iterator = map.entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry< Integer, String > entry = iterator.next();
+                String value =  HtmlUtils.userContent(entry.getValue());
+                content = content.replace("${"+entry.getKey()+"}$",value);
+            }
+            content = content.replace("<br>","\n");
+            String router =""+ (StrUtil.isNotBlank(templateEntity.getCallbackUrl()) ? templateEntity.getCallbackUrl() : map.get("callbackUrl"));
+            if (StrUtil.isNotBlank(router)) {
+                Object id = Objects.nonNull(map) ? map.get("id") : null;
+                Object jump = Objects.nonNull(map) ? map.get("jump") : null;
+                if (StrUtil.isNotBlank(router)) {
+                    if (Objects.nonNull(id)) {
+                        router = router.replace("${id}$",id.toString());
+                    } else {
+                        router = router.replace("${id}$","");
+                    }
+                    if (Objects.nonNull(jump)) {
+                        router = router.replace("${jump}$",jump.toString());
+                    } else {
+                        router = router.replace("${jump}$","");
+                    }
+                }
+            }
+            List<UserEntity> userEntityList = userService.getUserListByStaffIds(receiverUserIds);
+            if (CollectionUtils.isEmpty(userEntityList)) {
+                return Result.error(ErrorMsgCode.WECHAT_SMS_100009);
+            }
+            List<String> wxUseridList = userEntityList.stream().map(e->e.getXworkUserId()).collect(Collectors.toList());
+            Set<String> userids = new HashSet<>(wxUseridList);
+
+            if ("card".equals(eventDetail.getTemplateMessageView())) {
+                corpWeChatService.sendTextCardMsgByUserId(title,content,userids,router);
+            } else {
+                corpWeChatService.sendTextMsg(content,userids,null,null);
+            }
+        } catch (Exception e) {
+            log.error("目标提醒发送企业微信失败 错误为 " + e);
+            return Result.error(ErrorMsgCode.WECHAT_SMS_100007.getCode(),e.getMessage());
+        }
+        return Result.ok();
+    }
+
+    @Override
+    public Result sendWechatMessage(MessageTemplateEventDetailEntity eventDetail, Map map, Collection<Long> receiverUserIds, String callbackParam, String appCode) {
+        //发送企业微信
+        try {
+            MessageTemplateEventDetailEntity templateEntity = eventDetail;
+            if (Objects.isNull(templateEntity)) {
+                return Result.error(ErrorMsgCode.WECHAT_SMS_100006);
+            }
+            if (CollectionUtils.isEmpty(receiverUserIds)) {
+                return Result.error(ErrorMsgCode.WECHAT_SMS_100008);
+            }
+            String title = templateEntity.getMessageTitle();
+            title = title.replace("<br>","\n");
+            Object titleRemark = Objects.nonNull(map) ? map.get("titleRemark") : null;
+            if (Objects.nonNull(titleRemark)) {
+                title = title.replace("${titleRemark}$",titleRemark.toString());
+            }
+            String content = templateEntity.getMessageCentent();
+            Iterator <Map.Entry< Integer, String >> iterator = map.entrySet().iterator();
+            while (iterator.hasNext()) {
+                Map.Entry< Integer, String > entry = iterator.next();
+                String value =  HtmlUtils.userContent(entry.getValue());
+                content = content.replace("${"+entry.getKey()+"}$",value);
+            }
+            content = content.replace("<br>","\n");
+            String router =""+ (StrUtil.isNotBlank(templateEntity.getCallbackUrl()) ? templateEntity.getCallbackUrl() : map.get("callbackUrl"));
+            if (StrUtil.isNotBlank(router)) {
+                Object id = Objects.nonNull(map) ? map.get("id") : null;
+                Object jump = Objects.nonNull(map) ? map.get("jump") : null;
+                if (StrUtil.isNotBlank(router)) {
+                    if (Objects.nonNull(id)) {
+                        router = router.replace("${id}$",id.toString());
+                    } else {
+                        router = router.replace("${id}$","");
+                    }
+                    if (Objects.nonNull(jump)) {
+                        router = router.replace("${jump}$",jump.toString());
+                    } else {
+                        router = router.replace("${jump}$","");
+                    }
+                }
+            }
+            List<UserEntity> userEntityList = userService.getUserListByStaffIds(receiverUserIds);
+            if (CollectionUtils.isEmpty(userEntityList)) {
+                return Result.error(ErrorMsgCode.WECHAT_SMS_100009);
+            }
+            List<String> wxUseridList = userEntityList.stream().map(e->e.getXworkUserId()).collect(Collectors.toList());
+            Set<String> userids = new HashSet<>(wxUseridList);
+
+                 corpWeChatService.sendTextCardMsgByUserIdByAppCode(title,content,router,userids,null,appCode);
+
+        } catch (Exception e) {
+            log.error("目标提醒发送企业微信失败 错误为 " + e);
+            return Result.error(ErrorMsgCode.WECHAT_SMS_100007.getCode(),e.getMessage());
+        }
+        return Result.ok();
+    }
+}

+ 38 - 0
java/storlead-message/src/main/resources/mapper/InsideMessageRecordMapper.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.InsideMessageRecordMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.message.pojo.entity.InsideMessageRecordEntity">
+                    <id column="id" property="id" />
+                    <result column="title" property="title" />
+                    <result column="content" property="content" />
+                    <result column="message_icon" property="messageIcon" />
+                    <result column="message_tag" property="messageTag" />
+                    <result column="callback_url" property="callbackUrl" />
+                    <result column="wx_callback_url" property="wxCallbackUrl" />
+                    <result column="send_user_id" property="sendUserId" />
+                    <result column="status" property="status" />
+                    <result column="message_type" property="messageType" />
+                    <result column="message_sub_type" property="messageSubType" />
+                    <result column="timer_send_time" property="timerSendTime" />
+                    <result column="actually_send_time" property="actuallySendTime" />
+                    <result column="create_by" property="createBy" />
+                    <result column="create_time" property="createTime" />
+                    <result column="update_by" property="updateBy" />
+                    <result column="update_time" property="updateTime" />
+                    <result column="enabled" property="enabled" />
+                    <result column="is_delete" property="isDelete" />
+                    <result column="sort" property="sort" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, title, content, message_icon, message_tag, callback_url, wx_callback_url, send_user_id, status, message_type, message_sub_type, timer_send_time, actually_send_time, create_by, create_time, update_by, update_time, enabled, is_delete, sort
+        </sql>
+
+    <select id="selectOldById" resultType="map">
+        select ${fieldColumn} from ${tableName} where id = #{id}
+    </select>
+
+</mapper>

+ 59 - 0
java/storlead-message/src/main/resources/mapper/InsideMessageSendLogMapper.xml

@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.InsideMessageSendLogMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.message.pojo.entity.InsideMessageSendLogEntity">
+                    <id column="id" property="id" />
+                    <result column="message_id" property="messageId" />
+                    <result column="receiver_user_id" property="receiverUserId" />
+                    <result column="is_read" property="isRead" />
+                    <result column="status" property="status" />
+                    <result column="create_by" property="createBy" />
+                    <result column="create_time" property="createTime" />
+                    <result column="update_by" property="updateBy" />
+                    <result column="update_time" property="updateTime" />
+                    <result column="enabled" property="enabled" />
+                    <result column="is_delete" property="isDelete" />
+                    <result column="sort" property="sort" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, message_id, receiver_user_id, is_read, status, create_by, create_time, update_by, update_time, enabled, is_delete, sort
+        </sql>
+
+
+    <select id="pageListMessageLog" resultType="com.storlead.message.pojo.vo.MessageDetailVO">
+        SELECT
+            mlog.* ,mr.title,mr.content,mr.message_icon,mr.message_tag,mr.callback_url,send_user_id,us.avatar as photo,us.real_name as send_user_name
+        FROM
+            inside_message_send_log AS mlog
+                LEFT JOIN inside_message_record AS mr ON mlog.message_id = mr.id
+                LEFT JOIN `user` as us on us.id = mr.send_user_id
+            ${ew.customSqlSegment}
+    </select>
+
+    <select id="countNoReadNumber" resultType="com.storlead.message.pojo.vo.MessageNoReadTotalVO">
+        SELECT
+            ifnull(sum(case when mlog.is_read = 0 then 1 else 0 end),0) as no_read_num,
+            ifnull(sum(case when mlog.is_read = 0 and  mlog.message_type = 1 then 1 else 0 end),0) as at_no_read_num,
+            ifnull(sum(case when mlog.is_read = 0 and  mlog.message_type = 2 then 1 else 0 end),0) as approve_no_read_num
+        FROM
+            inside_message_send_log AS mlog
+                LEFT JOIN inside_message_record AS mr ON mlog.message_id = mr.id
+                LEFT JOIN `user` as us on us.id = mr.send_user_id
+            ${ew.customSqlSegment}
+    </select>
+
+    <select id="countNoReadCount" resultType="com.storlead.message.pojo.vo.MessageTypeReadStateVO">
+        SELECT
+            mr.message_type as messageType,ifnull(sum(case when mlog.is_read = 0 then 1 else 0 end),0) as stateNumber
+        FROM
+            inside_message_send_log AS mlog
+                LEFT JOIN inside_message_record AS mr ON mlog.message_id = mr.id
+                LEFT JOIN `user` as us on us.id = mr.send_user_id
+            ${ew.customSqlSegment}
+        group by mr.message_type
+    </select>
+</mapper>

+ 23 - 0
java/storlead-message/src/main/resources/mapper/MessageTemplateEventDetailMapper.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.MessageTemplateEventDetailMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" extends="com.storlead.frame.mapper.SysBaseFieldMapper.BaseResultMap" type="com.storlead.message.pojo.entity.MessageTemplateEventDetailEntity">
+            <id column="id" property="id" />
+            <result column="template_event_id" property="templateEventId" />
+            <result column="template_event_name" property="templateEventName" />
+            <result column="template_detail_type" property="templateDetailType" />
+            <result column="message_title" property="messageTitle" />
+            <result column="message_centent" property="messageCentent" />
+            <result column="callback_url" property="callbackUrl" />
+            <result column="template_icon" property="templateIcon" />
+            <result column="template_tag" property="templateTag" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, template_event_id, template_event_name, template_icon,template_tag,template_detail_type, message_title, message_centent, callback_url, remark,<include refid="com.storlead.frame.mapper.SysBaseFieldMapper.Base_Column_List"></include>
+        </sql>
+
+</mapper>

+ 26 - 0
java/storlead-message/src/main/resources/mapper/MessageTemplateEventGroupMapper.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.MessageTemplateEventGroupMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" extends="com.storlead.frame.mapper.SysBaseFieldMapper.BaseResultMap" type="com.storlead.message.pojo.entity.MessageTemplateEventGroupEntity">
+             <id column="id" property="id" />
+             <result column="template_service_name" property="templateServiceName" />
+            <result column="template_service_type" property="templateServiceType" />
+             <result column="template_service_code" property="templateServiceCode" />
+             <result column="event_name" property="eventName" />
+             <result column="event_code" property="eventCode" />
+             <result column="event_rule_script" property="eventRuleScript" />
+            <result column="receiver_key" property="receiverKey" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, template_service_name, template_service_code, event_name, event_code, event_rule_script,receiver_key,
+            <include refid="com.storlead.frame.mapper.SysBaseFieldMapper.Base_Column_List"></include>
+        </sql>
+
+        <select id="selectMessageTemplateList" resultType="com.storlead.message.pojo.vo.MessageTemplateVO">
+            select * from message_template_event_group ${ew.customSqlSegment}
+        </select>
+</mapper>

+ 35 - 0
java/storlead-message/src/main/resources/mapper/UserMessageNoticeConfigMapper.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.storlead.message.mapper.UserMessageNoticeConfigMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.message.entity.UserMessageNoticeConfigEntity">
+                    <id column="id" property="id" />
+                <result column="create_by" property="createBy" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_by" property="updateBy" />
+                <result column="update_time" property="updateTime" />
+                <result column="enabled" property="enabled" />
+                <result column="is_delete" property="isDelete" />
+                <result column="sort" property="sort" />
+                    <result column="template_event_id" property="templateEventId" />
+                    <result column="template_event_code" property="templateEventCode" />
+                    <result column="template_detail_type" property="templateDetailType" />
+                    <result column="remark" property="remark" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                create_by,
+                owner_by,
+                create_time,
+                update_by,
+                update_time,
+                enabled,
+                is_delete,
+                sort,
+            id, template_event_id, template_event_code, template_detail_type, remark
+        </sql>
+
+</mapper>

+ 13 - 0
java/storlead-message/src/test/java/com/storlead/tems/message/MessageApplicationTests.java

@@ -0,0 +1,13 @@
+package com.storlead.sales.message;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+@SpringBootTest
+class MessageApplicationTests {
+
+    @Test
+    void contextLoads() {
+    }
+
+}

+ 23 - 0
java/storlead-sasa/pom.xml

@@ -0,0 +1,23 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-saas-platform</artifactId>
+        <version>1.0</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-sasa</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>storlead-trade</module>
+        <module>storlead-salary</module>
+        <module>storlead-sales</module>
+        <module>storlead-okr</module>
+        <module>storlead-project</module>
+    </modules>
+</project>

+ 31 - 0
java/storlead-sasa/storlead-okr/pom.xml

@@ -0,0 +1,31 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-sasa</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-okr</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <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>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 7 - 0
java/storlead-sasa/storlead-okr/src/main/java/org/example/Main.java

@@ -0,0 +1,7 @@
+package org.example;
+
+public class Main {
+    public static void main(String[] args) {
+        System.out.println("Hello world!");
+    }
+}

+ 30 - 0
java/storlead-sasa/storlead-project/pom.xml

@@ -0,0 +1,30 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-sasa</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-project</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <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>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 7 - 0
java/storlead-sasa/storlead-project/src/main/java/org/example/Main.java

@@ -0,0 +1,7 @@
+package org.example;
+
+public class Main {
+    public static void main(String[] args) {
+        System.out.println("Hello world!");
+    }
+}

+ 30 - 0
java/storlead-sasa/storlead-salary/pom.xml

@@ -0,0 +1,30 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-sasa</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-salary</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <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>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 7 - 0
java/storlead-sasa/storlead-salary/src/main/java/org/example/Main.java

@@ -0,0 +1,7 @@
+package org.example;
+
+public class Main {
+    public static void main(String[] args) {
+        System.out.println("Hello world!");
+    }
+}

+ 30 - 0
java/storlead-sasa/storlead-sales/pom.xml

@@ -0,0 +1,30 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-sasa</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-sales</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+
+    <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>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 7 - 0
java/storlead-sasa/storlead-sales/src/main/java/org/example/Main.java

@@ -0,0 +1,7 @@
+package org.example;
+
+public class Main {
+    public static void main(String[] args) {
+        System.out.println("Hello world!");
+    }
+}

+ 23 - 0
java/storlead-sasa/storlead-trade/pom.xml

@@ -0,0 +1,23 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-saas-platform</artifactId>
+        <version>1.0</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-trade</artifactId>
+    <packaging>pom</packaging>
+    <name>storlead-trade</name>
+    <description>客户经营域聚合模块;具体业务在下方子模块中实现。</description>
+
+    <modules>
+        <module>storlead-customer</module>
+        <module>storlead-marketing</module>
+        <module>storlead-acquisition</module>
+    </modules>
+</project>

+ 91 - 0
java/storlead-sasa/storlead-trade/storlead-acquisition/README.md

@@ -0,0 +1,91 @@
+# 获客(storlead-acquisition)功能需求文档
+
+对应目录:`storlead-acquisition` · Maven `artifactId`:`storlead-acquisition` · Java 包根:`com.storlead.crm.acquisition`
+
+## 1. 模块目标
+
+本模块核心能力不是渠道投放,而是将业务侧定义的策略和基础画像传给 Python 服务,由 Python 服务执行客户匹配与检索,再将候选客户返回 CRM,供后续入池和跟进。
+
+## 2. 业务场景
+
+1. 运营或销售负责人在 CRM 中配置获客策略(行业、规模、地域、行为标签、预算偏好等)。
+2. 系统将策略与基础画像组装为标准请求,调用 Python 智能匹配服务。
+3. Python 服务返回候选客户列表及匹配分、标签、推荐理由。
+4. CRM 侧落库匹配结果,支持人工筛选、批量入客户池、建立跟进任务。
+
+## 3. 功能范围
+
+### 3.1 策略管理
+
+- 新建/编辑/启停策略。
+- 支持策略版本管理(每次变更形成版本快照)。
+- 支持策略试跑(不入正式结果,仅返回预览)。
+
+### 3.2 业务画像管理
+
+- 维护基础画像模板(行业、企业规模、岗位、地区、关键词、负向关键词等)。
+- 支持画像参数与策略参数合并规则(策略优先、模板兜底)。
+- 支持画像校验(必填项、取值范围、枚举合法性)。
+
+### 3.3 Python 服务对接
+
+- 提供统一调用网关(HTTP/HTTPS)。
+- 支持同步调用(小批量)与异步任务调用(大批量)。
+- 记录请求体、响应体摘要、耗时、状态码、错误码、重试次数。
+- 支持超时控制、重试策略、熔断与降级(避免阻塞主业务)。
+
+### 3.4 匹配结果管理
+
+- 存储候选客户基础信息、匹配得分、命中标签、推荐理由、来源策略版本。
+- 去重与合并(同一客户跨任务命中时保留最新和最高分)。
+- 支持结果筛选(分值区间、标签、区域、时间)与导出。
+- 支持批量转入 `storlead-customer` 模块。
+
+### 3.5 任务与流程
+
+- 支持手动触发与定时触发(如每天 09:00)。
+- 任务状态:待执行、执行中、成功、部分成功、失败、已取消。
+- 失败任务支持重跑,并保留历史执行记录。
+
+### 3.6 权限与多租户
+
+- 按租户隔离策略、画像、任务、匹配结果。
+- 仅授权角色可创建/修改策略与触发任务。
+- 跨租户数据访问严格禁止。
+
+## 4. 外部接口需求(CRM -> Python)
+
+建议统一定义请求对象:
+
+- `tenantId`:租户标识
+- `strategyId` / `strategyVersion`
+- `portrait`:业务画像参数
+- `limit`:期望返回条数
+- `traceId`:链路追踪 ID
+
+Python 返回建议包含:
+
+- `taskId`(同步可选)
+- `candidates`:候选客户集合
+- `score`:匹配分
+- `tags`:命中标签
+- `reason`:推荐理由
+- `status` / `errorCode` / `errorMessage`
+
+## 5. 数据与审计需求
+
+- 所有策略变更要留痕(谁在何时改了什么)。
+- 任务执行要可追踪(入参快照、响应摘要、执行日志)。
+- 对外返回敏感信息需脱敏或按权限控制展示。
+
+## 6. 非功能需求
+
+- 性能:单次同步请求建议 3-5 秒内返回;异步任务支持分页回传。
+- 可用性:Python 服务不可用时,不影响 CRM 其他模块运行。
+- 可观测性:提供任务成功率、平均耗时、失败原因分布监控。
+
+## 7. 与其他模块边界
+
+- 本模块负责“策略/画像编排 + Python 匹配调用 + 结果回收”。
+- 客户入池后的分配与跟进由 `storlead-customer` 负责。
+- 订单、邮件、营销执行分别由 `storlead-order`、`storlead-email`、`storlead-marketing` 负责。

+ 29 - 0
java/storlead-sasa/storlead-trade/storlead-acquisition/pom.xml

@@ -0,0 +1,29 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-trade</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-acquisition</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>获客</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 4 - 0
java/storlead-sasa/storlead-trade/storlead-acquisition/src/main/java/com/storlead/crm/acquisition/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 获客模块。
+ */
+package com.storlead.crm.acquisition;

+ 28 - 0
java/storlead-sasa/storlead-trade/storlead-customer/README.md

@@ -0,0 +1,28 @@
+# 客户(storlead-customer)
+
+对应目录:`storlead-customer` · Maven `artifactId`:`storlead-customer` · Java 包根:`com.storlead.crm.customerpool`
+
+## 模块定位
+
+集中管理「可跟进的客户线索与联系人」资源,为销售、运营与营销提供统一的客户资产视图与分配能力。
+
+## 建议实现的业务功能
+
+1. **资源入库与归集**  
+   支持从获客、导入、外部系统同步等渠道写入客户/线索;去重、合并、标签与来源标记。
+
+2. **资源池分层与视图**  
+   公海池、部门池、个人库等分层;列表筛选、排序、批量操作;与租户/组织权限一致。
+
+3. **分配与流转**  
+   领取、分配、退回、转移;记录操作人与时间,满足审计与协作。
+
+4. **客户侧摘要入口(可选)**  
+   展示与该客户关联的订单、邮件、营销触达等摘要链接(明细由 `storlead-order`、`storlead-email`、`storlead-marketing` 等子模块提供)。
+
+5. **多租户**  
+   数据按租户隔离;接口与查询落在当前租户上下文。
+
+## 非本模块职责
+
+订单履约、邮件投递与记录详情、营销活动执行明细、获客渠道与表单配置等,分别由 `storlead-order`、`storlead-email`、`storlead-marketing`、`storlead-acquisition` 等子模块负责。

+ 29 - 0
java/storlead-sasa/storlead-trade/storlead-customer/pom.xml

@@ -0,0 +1,29 @@
+<?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.storlead.boot</groupId>
+        <artifactId>storlead-trade</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-customer</artifactId>
+    <packaging>jar</packaging>
+    <name>${project.artifactId}</name>
+    <description>客户资源池</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 31 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/controller/CustomerAiAnalysisController.java

@@ -0,0 +1,31 @@
+package com.storlead.crm.customer.controller;
+
+import com.storlead.crm.customer.dto.CustomerSingleAnalysisRequestDTO;
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
+import com.storlead.crm.customer.service.CustomerAiAnalysisService;
+import com.storlead.framework.web.assemble.Result;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+
+@RestController
+@RequestMapping("/customer/analysis")
+@Api(tags = "客户AI分析")
+public class CustomerAiAnalysisController {
+
+    @Resource
+    private CustomerAiAnalysisService customerAiAnalysisService;
+
+    @PostMapping("/single")
+    @ApiOperation("单客户AI分析并保存结果")
+    public Result<Object> analyzeSingle(@RequestBody CustomerSingleAnalysisRequestDTO request) {
+        if (request == null || request.getCustomerId() == null) {
+            return Result.error("customerId不能为空");
+        }
+        CustomerAnalysisResultEntity result = customerAiAnalysisService
+                .analyzeSingleCustomer(request.getCustomerId(), request.getAnalysisScene());
+        return Result.ok(result);
+    }
+}

+ 16 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/dto/CustomerSingleAnalysisRequestDTO.java

@@ -0,0 +1,16 @@
+package com.storlead.crm.customer.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@Data
+@ApiModel(value = "CustomerSingleAnalysisRequestDTO", description = "单客户AI分析请求")
+public class CustomerSingleAnalysisRequestDTO {
+
+    @ApiModelProperty(value = "客户ID", required = true)
+    private Long customerId;
+
+    @ApiModelProperty(value = "分析场景,默认 potential")
+    private String analysisScene;
+}

+ 62 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerAnalysisResultEntity.java

@@ -0,0 +1,62 @@
+package com.storlead.crm.customer.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("customer_analysis_result")
+@ApiModel(value = "CustomerAnalysisResultEntity", description = "客户分析结果")
+public class CustomerAnalysisResultEntity extends SysBaseField {
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+    @ApiModelProperty(value = "客户ID")
+    private Long customerId;
+    @ApiModelProperty(value = "分析批次号")
+    private String analysisBatchNo;
+    @ApiModelProperty(value = "分析场景")
+    private String analysisScene;
+    @ApiModelProperty(value = "分析状态")
+    private Integer analysisStatus;
+    @ApiModelProperty(value = "潜力分")
+    private Double potentialScore;
+    @ApiModelProperty(value = "客户等级")
+    private String customerLevel;
+    @ApiModelProperty(value = "置信度")
+    private Double confidence;
+    @ApiModelProperty(value = "优先级")
+    private Integer priority;
+    @ApiModelProperty(value = "核心标签")
+    private String coreTags;
+    @ApiModelProperty(value = "分析摘要")
+    private String reasonSummary;
+    @ApiModelProperty(value = "建议动作")
+    private String suggestedActions;
+    @ApiModelProperty(value = "风险信号")
+    private String riskSignals;
+    @ApiModelProperty(value = "特征快照")
+    private String featureSnapshot;
+    @ApiModelProperty(value = "模型名称")
+    private String modelName;
+    @ApiModelProperty(value = "模型版本")
+    private String modelVersion;
+    @ApiModelProperty(value = "提示词版本")
+    private String promptVersion;
+    @ApiModelProperty(value = "错误码")
+    private String errorCode;
+    @ApiModelProperty(value = "错误信息")
+    private String errorMessage;
+    @ApiModelProperty(value = "建议下次跟进时间")
+    private Date nextFollowUpAt;
+    @ApiModelProperty(value = "分析时间")
+    private Date analysisTime;
+}

+ 54 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerCompanyEntity.java

@@ -0,0 +1,54 @@
+package com.storlead.crm.customer.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("customer_company")
+@ApiModel(value = "CustomerCompanyEntity", description = "客户企业信息")
+public class CustomerCompanyEntity extends SysBaseField {
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+    @ApiModelProperty(value = "关联客户ID")
+    private Long customerId;
+    @ApiModelProperty(value = "企业名称")
+    private String name;
+    @ApiModelProperty(value = "详细地址")
+    private String address;
+    @ApiModelProperty(value = "企业规模value")
+    private String scaleDictValue;
+    @ApiModelProperty(value = "企业网址")
+    private String website;
+    @ApiModelProperty(value = "其他网址1")
+    private String website1;
+    @ApiModelProperty(value = "其他网址2")
+    private String website2;
+    @ApiModelProperty(value = "其他网址3")
+    private String website3;
+    @ApiModelProperty(value = "所属行业")
+    private String industry;
+    @ApiModelProperty(value = "通信地址")
+    private String communicationAddress;
+    @ApiModelProperty(value = "邮政编码")
+    private String postcode;
+    @ApiModelProperty(value = "传真号码")
+    private String faxNumber;
+    @ApiModelProperty(value = "电子邮箱")
+    private String email;
+    @ApiModelProperty(value = "公司备用邮箱1")
+    private String email1;
+    @ApiModelProperty(value = "公司备用邮箱2")
+    private String email2;
+    @ApiModelProperty(value = "公司备用邮箱3")
+    private String email3;
+    @ApiModelProperty(value = "联系电话")
+    private String telephone;
+}

+ 100 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/CustomerEntity.java

@@ -0,0 +1,100 @@
+package com.storlead.crm.customer.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("customer")
+@ApiModel(value = "CustomerEntity", description = "客户基本信息")
+public class CustomerEntity extends SysBaseField {
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+    @ApiModelProperty(value = "编码")
+    private String dataCode;
+    @ApiModelProperty(value = "客户种类(10客户、11线索)")
+    private Integer customerForm;
+    @ApiModelProperty(value = "是否从线索过来(0否,1是)")
+    private Integer hasFromClue;
+    @ApiModelProperty(value = "是否公海客户(0否,1是)")
+    private Integer hasPublicCustomer;
+    @ApiModelProperty(value = "是否垃圾客户(0否,1是)")
+    private Integer hasGarbageCustomer;
+    @ApiModelProperty(value = "是否重点关注(0否,1是)")
+    private Integer hasFollowCustomer;
+    @ApiModelProperty(value = "客户代码")
+    private String customerCode;
+    @ApiModelProperty(value = "所属大洲")
+    private String continent;
+    @ApiModelProperty(value = "所属国家")
+    private String country;
+    @ApiModelProperty(value = "客户名称")
+    private String customerName;
+    @ApiModelProperty(value = "协作人员ID,逗号分割")
+    private String collaborator;
+    @ApiModelProperty(value = "意向产品ID,逗号分隔")
+    private String intendedProductsIds;
+    @ApiModelProperty(value = "意向产品名称,逗号分隔")
+    private String intendedProductsNames;
+    @ApiModelProperty(value = "线索状态value")
+    private String clueStatusDictValue;
+    @ApiModelProperty(value = "客户状态value")
+    private String customerStatusDictValue;
+    @ApiModelProperty(value = "客户来源value")
+    private String customerSourceDictValue;
+    @ApiModelProperty(value = "客户类型value")
+    private String customerTypeDictValue;
+    @ApiModelProperty(value = "代理合作客户ID")
+    private Long cooperativeCustomerId;
+    @ApiModelProperty(value = "客户等级value")
+    private String customerLevelDictValue;
+    @ApiModelProperty(value = "客户信用等级value")
+    private String customerCreditLevelDictValue;
+    @ApiModelProperty(value = "客户经济性质value")
+    private String customerEconomicNatureDictValue;
+    @ApiModelProperty(value = "助记名称")
+    private String mnemonicName;
+    @ApiModelProperty(value = "备注信息")
+    private String remark;
+    @ApiModelProperty(value = "开户行")
+    private String openingBank;
+    @ApiModelProperty(value = "税号")
+    private String taxNumber;
+    @ApiModelProperty(value = "银行账号")
+    private String bankAccount;
+    @ApiModelProperty(value = "置公原因value")
+    private String reasonPublicCustomerDictValue;
+    @ApiModelProperty(value = "是否有订单(0否,1是)")
+    private Integer hasOrderForm;
+    @ApiModelProperty(value = "最后跟进时间")
+    private Date lastFollowUpTime;
+    @ApiModelProperty(value = "未跟进天数")
+    private Integer noFollowUpDate;
+    @ApiModelProperty(value = "置公日期")
+    private Date publicDate;
+    @ApiModelProperty(value = "置公提醒日期")
+    private Date publicRemindDate;
+    @ApiModelProperty(value = "转垃圾客户日期")
+    private Date garbageDate;
+    @ApiModelProperty(value = "转垃圾客户提醒日期")
+    private Date garbageRemindDate;
+    @ApiModelProperty(value = "清除日期")
+    private Date deleteDate;
+    @ApiModelProperty(value = "清除预警日期")
+    private Date deleteRemindDate;
+    @ApiModelProperty(value = "客户归属时间")
+    private Date ownerTime;
+    @ApiModelProperty(value = "放弃时间")
+    private Date giveUpTime;
+    @ApiModelProperty(value = "前拥有者")
+    private Long formerOwnerBy;
+}

+ 88 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/entity/LiaisonEntity.java

@@ -0,0 +1,88 @@
+package com.storlead.crm.customer.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("liaison")
+@ApiModel(value = "LiaisonEntity", description = "联系人")
+public class LiaisonEntity extends SysBaseField {
+    @TableId(type = IdType.AUTO)
+    @ApiModelProperty(value = "主键ID")
+    private Long id;
+    @ApiModelProperty(value = "关联客户ID")
+    private Long customerId;
+    @ApiModelProperty(value = "是否首要联系人(0否,1是)")
+    private Integer hasPrimaryLialison;
+    @ApiModelProperty(value = "联系人姓名")
+    private String name;
+    @TableField("`call`")
+    @ApiModelProperty(value = "尊称(0未知,1先生,2女士)")
+    private Integer call;
+    @ApiModelProperty(value = "资源类型")
+    private String resourceType;
+    @ApiModelProperty(value = "资源类型关联ID")
+    private Long resourceTypeId;
+    @ApiModelProperty(value = "联系人角色value")
+    private String liaisonRoleDictValue;
+    @ApiModelProperty(value = "生日")
+    private Date birthday;
+    @ApiModelProperty(value = "部门职务")
+    private String departmentPosition;
+    @ApiModelProperty(value = "手机号码")
+    private String telephone;
+    @ApiModelProperty(value = "电子邮箱")
+    private String email;
+    @ApiModelProperty(value = "备用邮箱1")
+    private String email1;
+    @ApiModelProperty(value = "备用邮箱2")
+    private String email2;
+    @ApiModelProperty(value = "备用邮箱3")
+    private String email3;
+    @ApiModelProperty(value = "传真号码")
+    private String faxNumber;
+    @ApiModelProperty(value = "邮政编码")
+    private String postcode;
+    @ApiModelProperty(value = "联系方式")
+    private String contactInformation;
+    @ApiModelProperty(value = "备注")
+    private String remark;
+    @ApiModelProperty(value = "QQ")
+    private String qq;
+    @ApiModelProperty(value = "Skype")
+    private String skype;
+    @ApiModelProperty(value = "旺旺")
+    private String wangwang;
+    @ApiModelProperty(value = "微信")
+    private String wechat;
+    @ApiModelProperty(value = "Twitter")
+    private String twitter;
+    @ApiModelProperty(value = "LinkedIn")
+    private String linkedin;
+    @ApiModelProperty(value = "Facebook")
+    private String facebook;
+    @ApiModelProperty(value = "Whatsapp")
+    private String whatsapp;
+    @ApiModelProperty(value = "固定电话")
+    private String fixedLineTelephone;
+    @ApiModelProperty(value = "宅电")
+    private String homeElectricity;
+    @ApiModelProperty(value = "详细地址")
+    private String address;
+    @ApiModelProperty(value = "国际区号")
+    private String internationalArea;
+    @ApiModelProperty(value = "最后跟进时间")
+    private Date lastFollowUpTime;
+    @ApiModelProperty(value = "前拥有者")
+    private Long formerOwnerBy;
+}

+ 9 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/CustomerAnalysisResultEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.crm.customer.mapper;
+
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CustomerAnalysisResultEntityMapper extends MyBaseMapper<CustomerAnalysisResultEntity> {
+}

+ 9 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/CustomerCompanyEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.crm.customer.mapper;
+
+import com.storlead.crm.customer.entity.CustomerCompanyEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CustomerCompanyEntityMapper extends MyBaseMapper<CustomerCompanyEntity> {
+}

+ 9 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/CustomerEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.crm.customer.mapper;
+
+import com.storlead.crm.customer.entity.CustomerEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface CustomerEntityMapper extends MyBaseMapper<CustomerEntity> {
+}

+ 9 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/mapper/LiaisonEntityMapper.java

@@ -0,0 +1,9 @@
+package com.storlead.crm.customer.mapper;
+
+import com.storlead.crm.customer.entity.LiaisonEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface LiaisonEntityMapper extends MyBaseMapper<LiaisonEntity> {
+}

+ 4 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 客户资源池。
+ */
+package com.storlead.crm.customerpool;

+ 8 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerAiAnalysisService.java

@@ -0,0 +1,8 @@
+package com.storlead.crm.customer.service;
+
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
+
+public interface CustomerAiAnalysisService {
+
+    CustomerAnalysisResultEntity analyzeSingleCustomer(Long customerId, String analysisScene);
+}

+ 7 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerAnalysisResultEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.crm.customer.service;
+
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface CustomerAnalysisResultEntityService extends MyBaseService<CustomerAnalysisResultEntity> {
+}

+ 7 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerCompanyEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.crm.customer.service;
+
+import com.storlead.crm.customer.entity.CustomerCompanyEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface CustomerCompanyEntityService extends MyBaseService<CustomerCompanyEntity> {
+}

+ 7 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/CustomerEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.crm.customer.service;
+
+import com.storlead.crm.customer.entity.CustomerEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface CustomerEntityService extends MyBaseService<CustomerEntity> {
+}

+ 7 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/LiaisonEntityService.java

@@ -0,0 +1,7 @@
+package com.storlead.crm.customer.service;
+
+import com.storlead.crm.customer.entity.LiaisonEntity;
+import com.storlead.framework.mybatis.service.MyBaseService;
+
+public interface LiaisonEntityService extends MyBaseService<LiaisonEntity> {
+}

+ 190 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/impl/CustomerAiAnalysisServiceImpl.java

@@ -0,0 +1,190 @@
+package com.storlead.crm.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
+import com.storlead.crm.customer.entity.CustomerCompanyEntity;
+import com.storlead.crm.customer.entity.CustomerEntity;
+import com.storlead.crm.customer.entity.LiaisonEntity;
+import com.storlead.crm.customer.service.*;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.client.RestTemplate;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Service
+public class CustomerAiAnalysisServiceImpl implements CustomerAiAnalysisService {
+
+    private static final String AI_ANALYSIS_URL = "http://127.0.0.1:18080/mock/ai/customer/analyze";
+
+    @Resource
+    private CustomerEntityService customerEntityService;
+    @Resource
+    private CustomerCompanyEntityService customerCompanyEntityService;
+    @Resource
+    private LiaisonEntityService liaisonEntityService;
+    @Resource
+    private CustomerAnalysisResultEntityService customerAnalysisResultEntityService;
+
+    @Override
+    public CustomerAnalysisResultEntity analyzeSingleCustomer(Long customerId, String analysisScene) {
+        if (customerId == null) {
+            throw new IllegalArgumentException("customerId不能为空");
+        }
+
+        CustomerEntity customer = customerEntityService.getOne(new LambdaQueryWrapper<CustomerEntity>()
+                .eq(CustomerEntity::getId, customerId)
+                .eq(CustomerEntity::getIsDelete, 0)
+                .last("limit 1"));
+        if (customer == null) {
+            throw new IllegalArgumentException("客户不存在");
+        }
+
+        CustomerCompanyEntity company = customerCompanyEntityService.getOne(new LambdaQueryWrapper<CustomerCompanyEntity>()
+                .eq(CustomerCompanyEntity::getCustomerId, customerId)
+                .eq(CustomerCompanyEntity::getIsDelete, 0)
+                .orderByDesc(CustomerCompanyEntity::getId)
+                .last("limit 1"));
+
+        List<LiaisonEntity> liaisons = liaisonEntityService.list(new LambdaQueryWrapper<LiaisonEntity>()
+                .eq(LiaisonEntity::getCustomerId, customerId)
+                .eq(LiaisonEntity::getIsDelete, 0)
+                .orderByDesc(LiaisonEntity::getHasPrimaryLialison)
+                .orderByDesc(LiaisonEntity::getLastFollowUpTime)
+                .last("limit 5"));
+
+        String scene = StringUtils.defaultIfBlank(analysisScene, "potential");
+        Map<String, Object> requestPayload = buildAiRequest(customer, company, liaisons, scene);
+
+        CustomerAnalysisResultEntity entity = new CustomerAnalysisResultEntity();
+        entity.setCustomerId(customer.getId());
+        entity.setTenantId(customer.getTenantId());
+        entity.setOwnerBy(customer.getOwnerBy());
+        entity.setAnalysisBatchNo(UUID.randomUUID().toString().replace("-", ""));
+        entity.setAnalysisScene(scene);
+        entity.setAnalysisTime(new Date());
+
+        try {
+            @SuppressWarnings("unchecked")
+            Map<String, Object> aiResult = new RestTemplate().postForObject(AI_ANALYSIS_URL, requestPayload, Map.class);
+            if (aiResult == null || aiResult.isEmpty()) {
+                aiResult = buildFallbackResult(customer, company, liaisons);
+            }
+            applyAiResult(entity, aiResult);
+            entity.setAnalysisStatus(1);
+        } catch (Exception e) {
+            Map<String, Object> fallback = buildFallbackResult(customer, company, liaisons);
+            applyAiResult(entity, fallback);
+            entity.setAnalysisStatus(0);
+            entity.setErrorCode("AI_CALL_FAILED");
+            entity.setErrorMessage(e.getMessage());
+        }
+
+        customerAnalysisResultEntityService.save(entity);
+        return entity;
+    }
+
+    private Map<String, Object> buildAiRequest(CustomerEntity customer,
+                                               CustomerCompanyEntity company,
+                                               List<LiaisonEntity> liaisons,
+                                               String scene) {
+        Map<String, Object> payload = new HashMap<>();
+        payload.put("analysisScene", scene);
+        payload.put("customer", customer);
+        payload.put("company", company);
+        payload.put("liaisons", liaisons);
+        return payload;
+    }
+
+    private void applyAiResult(CustomerAnalysisResultEntity entity, Map<String, Object> aiResult) {
+        entity.setPotentialScore(toDouble(aiResult.get("potentialScore"), 50D));
+        entity.setCustomerLevel(String.valueOf(aiResult.getOrDefault("customerLevel", "C")));
+        entity.setConfidence(toDouble(aiResult.get("confidence"), 70D));
+        entity.setPriority(toInteger(aiResult.get("priority"), 3));
+        entity.setCoreTags(String.valueOf(aiResult.getOrDefault("coreTags", "[]")));
+        entity.setReasonSummary(String.valueOf(aiResult.getOrDefault("reasonSummary", "基于客户基础信息自动分析")));
+        entity.setSuggestedActions(String.valueOf(aiResult.getOrDefault("suggestedActions", "[]")));
+        entity.setRiskSignals(String.valueOf(aiResult.getOrDefault("riskSignals", "[]")));
+        entity.setFeatureSnapshot(String.valueOf(aiResult.getOrDefault("featureSnapshot", "{}")));
+        entity.setModelName(String.valueOf(aiResult.getOrDefault("modelName", "mock-ai")));
+        entity.setModelVersion(String.valueOf(aiResult.getOrDefault("modelVersion", "v0")));
+        entity.setPromptVersion(String.valueOf(aiResult.getOrDefault("promptVersion", "p0")));
+        entity.setErrorCode(null);
+        entity.setErrorMessage(null);
+        entity.setNextFollowUpAt(calcNextFollowUpDate());
+    }
+
+    private Map<String, Object> buildFallbackResult(CustomerEntity customer,
+                                                    CustomerCompanyEntity company,
+                                                    List<LiaisonEntity> liaisons) {
+        Map<String, Object> result = new HashMap<>();
+        int score = 40;
+        if (customer.getHasOrderForm() != null && customer.getHasOrderForm() == 1) {
+            score += 20;
+        }
+        if (StringUtils.isNotBlank(customer.getIntendedProductsIds())) {
+            score += 15;
+        }
+        if (company != null && StringUtils.isNotBlank(company.getIndustry())) {
+            score += 10;
+        }
+        if (liaisons != null && !liaisons.isEmpty()) {
+            score += 10;
+        }
+        score = Math.min(score, 95);
+
+        String level;
+        if (score >= 80) {
+            level = "A";
+        } else if (score >= 65) {
+            level = "B";
+        } else if (score >= 50) {
+            level = "C";
+        } else {
+            level = "D";
+        }
+
+        result.put("potentialScore", score);
+        result.put("customerLevel", level);
+        result.put("confidence", 65);
+        result.put("priority", "A".equals(level) ? 1 : ("B".equals(level) ? 2 : 3));
+        result.put("coreTags", "[\"fallback-rule\",\"single-customer\"]");
+        result.put("reasonSummary", "AI接口不可用,已使用本地规则完成潜力分析");
+        result.put("suggestedActions", "[\"建议3日内首次触达\",\"建议补全关键联系人信息\"]");
+        result.put("riskSignals", score < 60 ? "[\"客户潜力偏低\"]" : "[]");
+        result.put("featureSnapshot", "{\"customerId\":" + customer.getId() + "}");
+        result.put("modelName", "fallback-rule-engine");
+        result.put("modelVersion", "rule-v1");
+        result.put("promptVersion", "local");
+        return result;
+    }
+
+    private Date calcNextFollowUpDate() {
+        Calendar calendar = Calendar.getInstance();
+        calendar.add(Calendar.DAY_OF_MONTH, 3);
+        return calendar.getTime();
+    }
+
+    private Double toDouble(Object val, Double defaultValue) {
+        if (val == null) {
+            return defaultValue;
+        }
+        try {
+            return Double.parseDouble(String.valueOf(val));
+        } catch (Exception ignored) {
+            return defaultValue;
+        }
+    }
+
+    private Integer toInteger(Object val, Integer defaultValue) {
+        if (val == null) {
+            return defaultValue;
+        }
+        try {
+            return Integer.parseInt(String.valueOf(val));
+        } catch (Exception ignored) {
+            return defaultValue;
+        }
+    }
+}

+ 13 - 0
java/storlead-sasa/storlead-trade/storlead-customer/src/main/java/com/storlead/crm/customer/service/impl/CustomerAnalysisResultEntityServiceImpl.java

@@ -0,0 +1,13 @@
+package com.storlead.crm.customer.service.impl;
+
+import com.storlead.crm.customer.entity.CustomerAnalysisResultEntity;
+import com.storlead.crm.customer.mapper.CustomerAnalysisResultEntityMapper;
+import com.storlead.crm.customer.service.CustomerAnalysisResultEntityService;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+@Service
+public class CustomerAnalysisResultEntityServiceImpl
+        extends MyBaseServiceImpl<CustomerAnalysisResultEntityMapper, CustomerAnalysisResultEntity>
+        implements CustomerAnalysisResultEntityService {
+}

Некоторые файлы не были показаны из-за большого количества измененных файлов