9
0

2 کامیت‌ها 5883ec6691 ... 7449104241

نویسنده SHA1 پیام تاریخ
  1811872455@163.com 7449104241 迁移销售模块代码和邮件模块 1 هفته پیش
  1811872455@163.com 7b6aae06fc 迁移小时销售代码 2 هفته پیش
100فایلهای تغییر یافته به همراه8383 افزوده شده و 67 حذف شده
  1. 4 0
      java/storlead-api/pom.xml
  2. 3 1
      java/storlead-api/src/main/java/com/storlead/api/StorleadTradeApplication.java
  3. 0 2
      java/storlead-api/src/main/java/com/storlead/system/controller/FileResourceController.java
  4. 1 1
      java/storlead-api/src/main/resources/application-dev.yml
  5. 1 1
      java/storlead-api/src/main/resources/application-prod.yml
  6. 11 1
      java/storlead-api/src/main/resources/application-test.yml
  7. 10 0
      java/storlead-api/src/main/resources/application.yml
  8. 68 56
      java/storlead-dependencies/pom.xml
  9. 24 0
      java/storlead-framework/storlead-common/pom.xml
  10. 5 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java
  11. 1 1
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java
  12. 12 2
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java
  13. 1 2
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java
  14. 151 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HttpUtil.java
  15. 16 0
      java/storlead-framework/storlead-mybatis/pom.xml
  16. 113 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceAspect.java
  17. 9 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceConfiguration.java
  18. 40 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceProperties.java
  19. 42 0
      java/storlead-mail/pom.xml
  20. 41 0
      java/storlead-mail/storlead-mail-api/pom.xml
  21. 19 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/ClientSentEmailsController.java
  22. 170 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailFolderRuleApiController.java
  23. 94 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailServiceChecker.java
  24. 155 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MaiAttachmentApiController.java
  25. 133 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailBlacklistRecordApiController.java
  26. 123 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailTemplatesApiController.java
  27. 136 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailboxAutoReplySetController.java
  28. 442 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopMailApiController.java
  29. 457 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopSettingsApiController.java
  30. 135 0
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/UserEmailFolderApiController.java
  31. 46 0
      java/storlead-mail/storlead-mail-biz/pom.xml
  32. 157 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailReceiver.java
  33. 53 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailSender.java
  34. 129 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnection.java
  35. 27 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnectionFactory.java
  36. 21 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnection.java
  37. 26 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnectionFactory.java
  38. 126 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3Connection.java
  39. 26 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3ConnectionFactory.java
  40. 53 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnection.java
  41. 25 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnectionFactory.java
  42. 93 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailConnectionUtil.java
  43. 48 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailReceiverUtil.java
  44. 38 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailSenderUtil.java
  45. 43 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/config/MailProperties.java
  46. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/AttachmentMapper.java
  47. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/ClientSentEmailsMapper.java
  48. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailBlacklistRecordMapper.java
  49. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFileFolderMapper.java
  50. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFolderRuleMapper.java
  51. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFoldersMapper.java
  52. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailTemplatesMapper.java
  53. 107 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailsMapper.java
  54. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/FoldersMapper.java
  55. 23 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailAttachmentMapper.java
  56. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailDelayPullRecordMapper.java
  57. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailServerPortConfigMapper.java
  58. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailTempAttachmentMapper.java
  59. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailboxAutoReplySetMapper.java
  60. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/SmtpPopSettingsMapper.java
  61. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserEmailFolderMapper.java
  62. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserMailTrackRecordMapper.java
  63. 57 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/properties/MailFileProperties.java
  64. 14 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AbstractMailFunctionApiFactory.java
  65. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AttachmentServiceImpl.java
  66. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/ClientSentEmailsServiceImpl.java
  67. 38 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailBlacklistRecordServiceImpl.java
  68. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFileFolderServiceImpl.java
  69. 77 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFolderRuleServiceImpl.java
  70. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFoldersServiceImpl.java
  71. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailTemplatesServiceImpl.java
  72. 1959 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailsServiceImpl.java
  73. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/FoldersServiceImpl.java
  74. 29 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailAttachmentServiceImpl.java
  75. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailDelayPullRecordServiceImpl.java
  76. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailServerPortConfigServiceImpl.java
  77. 101 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailTempAttachmentServiceImpl.java
  78. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailboxAutoReplySetServiceImpl.java
  79. 67 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/SmtpPopSettingsServiceImpl.java
  80. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/UserEmailFolderServiceImpl.java
  81. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/UserMailTrackRecordServiceImpl.java
  82. 285 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/EmailHelper.java
  83. 203 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/EmailSenderWithThreadLocal.java
  84. 174 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/FileExtractor.java
  85. 42 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/FileHelper.java
  86. 50 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/MailFormatPartUtil.java
  87. 97 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/ReceiveMailQueueThreadPool.java
  88. 90 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/ZipUtility.java
  89. 2 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/META-INF/spring.factories
  90. 35 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/AttachmentMapper.xml
  91. 66 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/ClientSentEmailsMapper.xml
  92. 32 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailBlacklistRecordMapper.xml
  93. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFileFolderMapper.xml
  94. 34 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFolderRuleMapper.xml
  95. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFoldersMapper.xml
  96. 35 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailTemplatesMapper.xml
  97. 807 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailsMapper.xml
  98. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/FoldersMapper.xml
  99. 37 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailAttachmentMapper.xml
  100. 35 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailDelayPullRecordMapper.xml

+ 4 - 0
java/storlead-api/pom.xml

@@ -36,6 +36,10 @@
             <groupId>com.storlead.boot</groupId>
             <artifactId>storlead-customer</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-sales</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-web</artifactId>

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

@@ -18,7 +18,9 @@ import java.net.InetAddress;
         "com.storlead.crm.customer.mapper",
         "com.storlead.user.mapper",
         "com.storlead.sms.mapper",
-        "com.storlead.system.mapper"
+        "com.storlead.system.mapper",
+        "com.storlead.sales.mapper",
+        "com.storlead.mail.mapper"
 })
 @Slf4j
 public class StorleadTradeApplication {

+ 0 - 2
java/storlead-api/src/main/java/com/storlead/system/controller/FileResourceController.java

@@ -9,7 +9,6 @@ import com.storlead.system.pojo.entity.FileResourceEntity;
 import com.storlead.system.properties.FileProperties;
 import com.storlead.system.service.FileResourceService;
 import com.storlead.system.service.IOSSFileService;
-import com.storlead.system.util.FileUtil;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -19,7 +18,6 @@ import org.springframework.core.env.Environment;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 
-import javax.annotation.Resource;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import java.io.File;

+ 1 - 1
java/storlead-api/src/main/resources/application-dev.yml

@@ -130,7 +130,7 @@ spring:
 #          password: rCgRgLjH99Xvg5BN
 #mybatis plus 设置
 mybatis-plus:
-  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
   # 实体扫描,多个 package 用逗号或者分号分隔
   type-aliases-package:
   typeEnumsPackage:

+ 1 - 1
java/storlead-api/src/main/resources/application-prod.yml

@@ -108,7 +108,7 @@ spring:
           password: 3raNoDvbo7jqbwtedQGQ
 #mybatis plus 设置
 mybatis-plus:
-  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
   # 实体扫描,多个 package 用逗号或者分号分隔
   type-aliases-package: com.storlead.tems.modules.*.entity
   type-enums-package: com.storlead.tems.modules.console.enums;com.storlead.tems.modules.perform.enums;com.storlead.tems.modules.project.enums

+ 11 - 1
java/storlead-api/src/main/resources/application-test.yml

@@ -93,10 +93,20 @@ spring:
           username: root
           password: rCgRgLjH99Xvg5BN
           driver-class-name: com.mysql.jdbc.Driver
+        sales:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/sp_sales_test?useSSL=false&useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
+          username: root
+          password: rCgRgLjH99Xvg5BN
+        management:
+          driver-class-name: com.mysql.jdbc.Driver
+          url: jdbc:mysql://mysql.test.storlead.com:39091/storlead_test?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&noDatetimeStringSync=true&serverTimezone=Asia/Shanghai
+          username: root
+          password: rCgRgLjH99Xvg5BN
 
 #mybatis plus 设置
 mybatis-plus:
-  mapper-locations: classpath:/mapper/*/*Mapper.xml
+  mapper-locations: classpath*:/mapper/*Mapper.xml,classpath*:/mapper/*/*Mapper.xml
   # 实体扫描,多个 package 用逗号或者分号分隔
   type-aliases-package: com.storlead.tems.modules.*.entity
   type-enums-package:

+ 10 - 0
java/storlead-api/src/main/resources/application.yml

@@ -54,3 +54,13 @@ storlead:
   tenant:
     ignore-tables:
       - system_wechat_config
+  datasource:
+    # 子模块默认数据源(未标注 @DS 时生效;标注 @DS 则以注解为准)
+    module-default-enabled: true
+    module-defaults:
+      - packages:
+          - com.storlead.sales.service
+          - com.storlead.sales.mapper
+          - com.storlead.mail.service
+          - com.storlead.mail.mapper
+        datasource: sales

+ 68 - 56
java/storlead-dependencies/pom.xml

@@ -66,11 +66,7 @@
                 <version>${spring-web.version}</version>
             </dependency>
 
-            <dependency>
-                <groupId>com.baomidou</groupId>
-                <artifactId>mybatis-plus-generator</artifactId>
-                <version>${mybatis-plus.version}</version>
-            </dependency>
+
 
 
 <!--            &lt;!&ndash;swagger&ndash;&gt;-->
@@ -90,11 +86,11 @@
                 <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.google.code.gson</groupId>-->
+<!--                <artifactId>gson</artifactId>-->
+<!--                <version>${google.code.gson.version}</version> &lt;!&ndash; 建议使用最新版本 &ndash;&gt;-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>com.alibaba</groupId>
@@ -108,20 +104,18 @@
                 <version>4.5.3</version>
             </dependency>
 
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.client5</groupId>-->
+<!--                <artifactId>httpclient5</artifactId>-->
+<!--                <version>${apache.httpcore5.client5.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>
+<!--            &lt;!&ndash; https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5 &ndash;&gt;-->
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--                <artifactId>httpcore5</artifactId>-->
+<!--                <version>${apache.httpcore5.version}</version>-->
+<!--            </dependency>-->
 
             <!--JWT-->
             <dependency>
@@ -136,12 +130,12 @@
                 <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>
+<!--            &lt;!&ndash; https://mvnrepository.com/artifact/org.apache.httpcomponents.core5/httpcore5-h2 &ndash;&gt;-->
+<!--            <dependency>-->
+<!--                <groupId>org.apache.httpcomponents.core5</groupId>-->
+<!--                <artifactId>httpcore5-h2</artifactId>-->
+<!--                <version>${apache.httpcore5-h2.version}</version>-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>org.apache.httpcomponents</groupId>
@@ -161,11 +155,11 @@
 <!--                <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>com.squareup.okhttp3</groupId>-->
+<!--                <artifactId>okhttp-sse</artifactId>-->
+<!--                <version>${okhttp.sse.version}</version>-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>org.projectlombok</groupId>
@@ -222,11 +216,26 @@
                 <version>${mysql.connector.version}</version>
             </dependency>
 
+            <!-- 动态数据源 -->
+            <!-- mybatis-plus -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-boot-starter</artifactId>
+                <version>${mybatis-plus.version}</version>
+            </dependency>
+
             <!-- 动态数据源 -->
             <dependency>
                 <groupId>com.baomidou</groupId>
                 <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
-                <version>${datasource.spring.boot.starter}</version>
+                <version>2.5.4</version>
+            </dependency>
+
+            <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
+            <dependency>
+                <groupId>com.baomidou</groupId>
+                <artifactId>mybatis-plus-generator</artifactId>
+                <version>${mybatis-plus.version}</version>
             </dependency>
 
             <!-- druid -->
@@ -236,17 +245,26 @@
                 <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>
+<!--            <dependency>-->
+<!--                <groupId>com.aliyun.kms</groupId>-->
+<!--                <artifactId>kms-transfer-client</artifactId>-->
+<!--                <version>0.1.0</version>-->
+<!--            </dependency>-->
+
+
+
+<!--            <dependency>-->
+<!--                <groupId>com.github.yulichang</groupId>-->
+<!--                <artifactId>mybatis-plus-join-boot-starter</artifactId> &lt;!&ndash; MyBatis 联表查询 &ndash;&gt;-->
+<!--                <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>
@@ -261,11 +279,11 @@
                 </exclusions>
             </dependency>
 
-            <dependency>
-                <groupId>org.jsoup</groupId>
-                <artifactId>jsoup</artifactId>
-                <version>${jsoup.version}</version>
-            </dependency>
+<!--            <dependency>-->
+<!--                <groupId>org.jsoup</groupId>-->
+<!--                <artifactId>jsoup</artifactId>-->
+<!--                <version>${jsoup.version}</version>-->
+<!--            </dependency>-->
 
             <dependency>
                 <groupId>com.google.guava</groupId>
@@ -273,12 +291,6 @@
                 <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>

+ 24 - 0
java/storlead-framework/storlead-common/pom.xml

@@ -66,6 +66,12 @@
             <version>3.17.4</version>
         </dependency>
 
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>sts20150401</artifactId>
+            <version>1.1.6</version>
+        </dependency>
+
         <dependency>
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-pool2</artifactId>
@@ -156,6 +162,24 @@
             <artifactId>lombok</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.jsoup</groupId>
+            <artifactId>jsoup</artifactId>
+            <version>1.15.3</version> <!-- 使用最新的版本 -->
+        </dependency>
+
+        <!-- commons -->
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>${commons.version}</version>
+        </dependency>
+
 <!--        <dependency>-->
 <!--            <groupId>com.squareup.okhttp3</groupId>-->
 <!--            <artifactId>okhttp</artifactId>-->

+ 5 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java

@@ -16,6 +16,11 @@ public class DSConstants {
      */
     public static final String DATASOURCE_MASTER = "master";
 
+    /**
+     * SASA / CRM 等业务库(与 spring.datasource.dynamic.datasource.sales 的 key 一致)
+     */
+    public static final String DATASOURCE_SALES = "sales";
+
     /**
      * 数据源分组 oa库
      */

+ 1 - 1
java/storlead-system/storlead-system-biz/src/main/java/com/storlead/system/util/CloseUtil.java → java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java

@@ -1,4 +1,4 @@
-package com.storlead.system.util;
+package com.storlead.framework.common.util;
 
 import java.io.Closeable;
 

+ 12 - 2
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/ConvertUtils.java

@@ -180,7 +180,8 @@ public class ConvertUtils {
 		Integer[] result = new Integer[len];
 		try {
 			for (int i = 0; i < len; i++) {
-				result[i] = new Integer(object[i].trim());
+				result[i] = Integer.valueOf(object[i].trim());
+				//new Integer(object[i].trim());
 			}
 			return result;
 		} catch (NumberFormatException e) {
@@ -231,7 +232,7 @@ public class ConvertUtils {
 	}
 
 	public static long stringToLong(String str) {
-		Long test = new Long(0);
+		Long test = Long.valueOf(0);
 		try {
 			test = Long.valueOf(str);
 		} catch (Exception e) {
@@ -334,6 +335,15 @@ public class ConvertUtils {
 
 	}
 
+	public static String replaceTrimBlank(String str) {
+		String dest = "";
+		if (str != null) {
+			dest = str.replaceAll("^[\\s\\n\\r\\t]+|[\\s\\n\\r\\t]+$", "");
+			return dest;
+		}
+		return dest;
+	}
+
 	/**
 	 * 判断元素是否在数组内
 	 *

+ 1 - 2
java/storlead-system/storlead-system-biz/src/main/java/com/storlead/system/util/FileUtil.java → java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java

@@ -1,4 +1,4 @@
-package com.storlead.system.util;
+package com.storlead.framework.common.util;
 
 import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.util.IdUtil;
@@ -18,7 +18,6 @@ import java.security.MessageDigest;
 import java.text.DecimalFormat;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 /**
  * @program: sp-sales

+ 151 - 0
java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/HttpUtil.java

@@ -0,0 +1,151 @@
+package com.storlead.framework.common.util;
+
+import cn.hutool.http.HttpRequest;
+import com.alibaba.fastjson.JSONObject;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-14 15:17
+ */
+public class HttpUtil {
+
+    /**
+     * 发送GET请求并返回响应内容
+     * @param url
+     * @param body 请求体
+     * @param headers 请求头
+     * @param timeout 超时时间
+     * @return
+     * @throws IOException
+     */
+    public static String get(String url, Map<String, String> body, Map<String, String> headers, int timeout) throws IOException {
+        Map<String, String> header = new HashMap<>();
+        header.put("Content-Type", "application/json;charset=UTF-8");
+        header.put("Accept", "application/json;charset=UTF-8");
+        if (Objects.nonNull(headers)) {
+            header.putAll(headers);
+        }
+        HttpRequest httpRequest = HttpRequest.get(url).addHeaders(header).body(JSONObject.toJSONString(body));
+        if (timeout > 0) {
+            httpRequest.setConnectionTimeout(timeout);
+            httpRequest.setReadTimeout(timeout);
+        }
+        String response = httpRequest.execute().body();
+        return decodeUnicode(response);
+    }
+
+    /**
+     * 发送POST请求并返回响应内容
+     * @param url
+     * @param body 请求体
+     * @param headers 请求头
+     * @param timeout 超时时间
+     * @return
+     * @throws IOException
+     */
+    public static String post(String url, Map<String, Object> body,Map<String, String> headers, int timeout) throws IOException {
+        Map<String, String> header = new HashMap<>();
+        header.put("Content-Type","application/json;charset=UTF-8");
+        header.put("Accept","application/json;charset=UTF-8");
+        if (Objects.nonNull(headers)) {
+            header.putAll(headers);
+        }
+        HttpRequest httpRequest = HttpRequest.post(url).addHeaders(header).body(JSONObject.toJSONString(body));
+        if (timeout > 0) {
+            httpRequest.setConnectionTimeout(timeout);
+            httpRequest.setReadTimeout(timeout);
+        }
+        String response = httpRequest.execute().body();
+
+        return decodeUnicode(response);
+    }
+
+    /**
+     * http 请求数据返回 json 中中文字符为 unicode 编码转汉字转码
+     * @param theString
+     * @return
+     */
+    public static String decodeUnicode(String theString) {
+        char aChar;
+        int len = theString.length();
+        StringBuffer outBuffer = new StringBuffer(len);
+        for (int x = 0; x < len;) {
+            aChar = theString.charAt(x++);
+            if (aChar == '\\') {
+                aChar = theString.charAt(x++);
+                if (aChar == 'u') {
+                    int value = 0;
+                    for (int i = 0; i < 4; i++) {
+                        aChar = theString.charAt(x++);
+                        switch (aChar) {
+                            case '0':
+                            case '1':
+                            case '2':
+                            case '3':
+                            case '4':
+                            case '5':
+                            case '6':
+                            case '7':
+                            case '8':
+                            case '9':
+                                value = (value << 4) + aChar - '0';
+                                break;
+                            case 'a':
+                            case 'b':
+                            case 'c':
+                            case 'd':
+                            case 'e':
+                            case 'f':
+                                value = (value << 4) + 10 + aChar - 'a';
+                                break;
+                            case 'A':
+                            case 'B':
+                            case 'C':
+                            case 'D':
+                            case 'E':
+                            case 'F':
+                                value = (value << 4) + 10 + aChar - 'A';
+                                break;
+                            default:
+                                throw new IllegalArgumentException(
+                                        "Malformed   \\uxxxx   encoding.");
+                        }
+
+                    }
+                    outBuffer.append((char) value);
+                } else {
+                    if (aChar == 't')
+                        aChar = '\t';
+                    else if (aChar == 'r')
+                        aChar = '\r';
+                    else if (aChar == 'n')
+                        aChar = '\n';
+                    else if (aChar == 'f')
+                        aChar = '\f';
+                    outBuffer.append(aChar);
+                }
+            } else {
+                outBuffer.append(aChar);
+            }
+        }
+        return outBuffer.toString();
+    }
+
+    public static void main(String[] args) {
+//        try {
+//            Map<String, Object> parameters = new HashMap<>();
+//            parameters.put("activationCode", "activationCode");
+//            String response = HttpUtil.sendPost("http://localhost:10010/api/tenant/license/getLicenseCode", parameters,5000);
+//            log.error("response = "+response);
+//        }catch (Exception e) {
+//            log.error("error = ",e);
+//        }
+    }
+}

+ 16 - 0
java/storlead-framework/storlead-mybatis/pom.xml

@@ -38,6 +38,17 @@
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-boot-starter</artifactId>
         </dependency>
+        <!-- 动态数据源 -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
+        </dependency>
+
+        <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+        </dependency>
 
         <!--mysql-->
         <dependency>
@@ -60,6 +71,11 @@
             <version>2.5.4</version>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-aop</artifactId>
+        </dependency>
+
     </dependencies>
 
 </project>

+ 113 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceAspect.java

@@ -0,0 +1,113 @@
+package com.storlead.framework.mybatis.datasource;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.core.annotation.AnnotatedElementUtils;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.lang.reflect.Method;
+
+/**
+ * 子模块默认数据源:按配置包前缀 push 数据源;若目标已标注 {@link DS} 则跳过,由官方 {@code @DS} 切面处理。
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(0)
+@RequiredArgsConstructor
+public class ModuleDefaultDataSourceAspect {
+
+    private final ModuleDefaultDataSourceProperties properties;
+
+    @Pointcut("execution(* *(..)) && (within(com.storlead..service..*) || within(com.storlead..mapper..*))")
+    public void moduleServiceOrMapperLayer() {
+    }
+
+    @Around("moduleServiceOrMapperLayer()")
+    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
+        if (!properties.isModuleDefaultEnabled()) {
+            return joinPoint.proceed();
+        }
+        if (hasDsAnnotation(joinPoint)) {
+            return joinPoint.proceed();
+        }
+        String datasource = resolveDatasource(joinPoint);
+        if (!StringUtils.hasText(datasource)) {
+            return joinPoint.proceed();
+        }
+        DynamicDataSourceContextHolder.push(datasource);
+        try {
+            if (log.isDebugEnabled()) {
+                log.debug("module default datasource [{}] -> {}", datasource, joinPoint.getSignature().toShortString());
+            }
+            return joinPoint.proceed();
+        } finally {
+            DynamicDataSourceContextHolder.poll();
+        }
+    }
+
+    private boolean hasDsAnnotation(ProceedingJoinPoint joinPoint) {
+        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+        Method method = signature.getMethod();
+        Class<?> declaringType = signature.getDeclaringType();
+        if (AnnotatedElementUtils.hasAnnotation(method, DS.class)) {
+            return true;
+        }
+        if (declaringType != null && AnnotatedElementUtils.hasAnnotation(declaringType, DS.class)) {
+            return true;
+        }
+        return AnnotatedElementUtils.hasAnnotation(joinPoint.getTarget().getClass(), DS.class);
+    }
+
+    /**
+     * 最长包前缀匹配
+     */
+    private String resolveDatasource(ProceedingJoinPoint joinPoint) {
+        String className = resolveBusinessClassName(joinPoint);
+        String matchedDs = null;
+        int matchedLen = -1;
+        for (ModuleDefaultDataSourceProperties.ModuleDefaultRule rule : properties.getModuleDefaults()) {
+            if (rule.getPackages() == null || !StringUtils.hasText(rule.getDatasource())) {
+                continue;
+            }
+            for (String pkg : rule.getPackages()) {
+                if (!StringUtils.hasText(pkg)) {
+                    continue;
+                }
+                if (matchesPackage(className, pkg.trim()) && pkg.length() > matchedLen) {
+                    matchedLen = pkg.length();
+                    matchedDs = rule.getDatasource();
+                }
+            }
+        }
+        return matchedDs;
+    }
+
+    private boolean matchesPackage(String className, String packagePrefix) {
+        return className.equals(packagePrefix)
+                || className.startsWith(packagePrefix + ".");
+    }
+
+    /**
+     * Mapper 为 JDK 代理时,用接口全限定名匹配包前缀。
+     */
+    private String resolveBusinessClassName(ProceedingJoinPoint joinPoint) {
+        if (joinPoint.getSignature() instanceof MethodSignature) {
+            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
+            Class<?> declaringType = signature.getDeclaringType();
+            if (declaringType != null && declaringType.getName().startsWith("com.storlead.")) {
+                return declaringType.getName();
+            }
+        }
+        return joinPoint.getTarget().getClass().getName();
+    }
+}

+ 9 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceConfiguration.java

@@ -0,0 +1,9 @@
+package com.storlead.framework.mybatis.datasource;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@EnableConfigurationProperties(ModuleDefaultDataSourceProperties.class)
+public class ModuleDefaultDataSourceConfiguration {
+}

+ 40 - 0
java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceProperties.java

@@ -0,0 +1,40 @@
+package com.storlead.framework.mybatis.datasource;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 按业务包前缀绑定默认数据源;未命中或未配置时走 dynamic-datasource 的 primary。
+ * 与 {@link ModuleDefaultDataSourceAspect} 配合使用,方法/类上的 {@code @DS} 优先生效。
+ */
+@Data
+@ConfigurationProperties(prefix = "storlead.datasource")
+public class ModuleDefaultDataSourceProperties {
+
+    /**
+     * 是否启用模块默认数据源切面
+     */
+    private boolean moduleDefaultEnabled = true;
+
+    /**
+     * 模块默认数据源规则(最长包前缀匹配)
+     */
+    private List<ModuleDefaultRule> moduleDefaults = new ArrayList<>();
+
+    @Data
+    public static class ModuleDefaultRule {
+
+        /**
+         * 包前缀,如 com.storlead.sales.service
+         */
+        private List<String> packages = new ArrayList<>();
+
+        /**
+         * 数据源 key,与 spring.datasource.dynamic.datasource 下的名称一致
+         */
+        private String datasource;
+    }
+}

+ 42 - 0
java/storlead-mail/pom.xml

@@ -0,0 +1,42 @@
+<?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-mail</artifactId>
+    <packaging>pom</packaging>
+    <name>storlead-mail</name>
+    <version>1.0</version>
+
+    <modules>
+        <module>storlead-mail-core</module>
+        <module>storlead-mail-spi</module>
+        <module>storlead-mail-biz</module>
+        <module>storlead-mail-api</module>
+    </modules>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>com.sun.mail</groupId>
+                <artifactId>javax.mail</artifactId>
+                <version>1.6.2</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 41 - 0
java/storlead-mail/storlead-mail-api/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>
+        <groupId>com.storlead.boot</groupId>
+        <artifactId>storlead-mail</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-mail-api</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-mail-api</name>
+    <description>邮件域 Web:Controller 等。</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-biz</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 19 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/ClientSentEmailsController.java

@@ -0,0 +1,19 @@
+package com.storlead.mail.controller;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 邮件表 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-12-04
+ */
+@RestController
+@RequestMapping("/client-sent-emails-entity")
+public class ClientSentEmailsController {
+
+}

+ 170 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailFolderRuleApiController.java

@@ -0,0 +1,170 @@
+package com.storlead.mail.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.EmailFolderRuleEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.enums.EmailFolderlRuleEnum;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.pojo.dto.FolderRuleDTO;
+import com.storlead.mail.service.EmailFolderRuleService;
+import com.storlead.mail.service.EmailsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * <p>
+ * 用户邮件文件夹出入规则 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@RestController
+@Log4j2
+@RequestMapping("/email/folder/rule")
+@Api(tags = "邮件:邮件自定义入站规则")
+public class EmailFolderRuleApiController {
+
+    @Resource
+    private EmailFolderRuleService folderRuleService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @PostMapping(value = "/get_folder_rule")
+    @ApiOperation(value = "获取规则" )
+    public Result<?> getFolderRule() {
+        List<EmailFolderlRuleEnum>  ls = Arrays.asList(EmailFolderlRuleEnum.values());
+        List<Map>  enumls = new ArrayList<>();
+        for (EmailFolderlRuleEnum e : ls) {
+            Map<String,String> stringMap = new HashMap<>();
+            stringMap.put("code",e.getCode());
+            stringMap.put("desc",e.getDesc());
+            enumls.add(stringMap);
+        }
+        return Result.ok(enumls);
+    }
+
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取文件夹入站规则配置(分页)" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailFolderRuleEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailFolderRuleEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailFolderRuleEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailFolderRuleEntity::getIsDelete,Integer.valueOf(0));
+        IPage<EmailFolderRuleEntity> pageList = folderRuleService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取文件夹入站规则" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list() {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        List<EmailFolderRuleEntity> list = folderRuleService.getUserFolderRules(currentUserId);
+        return Result.result(list);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存用户规则配置" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(EmailFolderRuleEntity entity) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        LambdaQueryWrapper<EmailFolderRuleEntity> w = new LambdaQueryWrapper<>();
+        w.eq(EmailFolderRuleEntity::getFolderRuleCode,entity.getFolderRuleCode());
+        w.eq(EmailFolderRuleEntity::getCompareContent,entity.getCompareContent());
+        w.eq(EmailFolderRuleEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        w.eq(EmailFolderRuleEntity::getOwnerBy,currentUserId);
+        if (Objects.isNull(entity.getId())) {
+            Integer exit = folderRuleService.count(w);
+            if (exit > 0) {
+                return Result.error("已存在同样的规则,不能新增同样的入站规则!");
+            }
+        } else {
+
+            EmailFolderRuleEntity oldFolder = folderRuleService.getOne(w);
+            if (Objects.nonNull(oldFolder)) {
+                if (!oldFolder.getId().equals(entity.getId())) {
+                    return Result.error("已存在同样的入站规则,无法修改!");
+                }
+            }
+        }
+
+        folderRuleService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/saveBatch")
+    @ApiOperation(value = "保存用户规则配置" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(@RequestBody FolderRuleDTO dto) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (CollectionUtils.isEmpty(dto.getFolderRules())) {
+            return Result.error("参数错误");
+        }
+        LambdaQueryWrapper<EmailFolderRuleEntity> deleteW = new LambdaQueryWrapper<>();
+        deleteW.eq(EmailFolderRuleEntity::getOwnerBy,currentUserId);
+        folderRuleService.remove(deleteW);
+        folderRuleService.saveOrUpdateBatch(dto.getFolderRules());
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        folderRuleService.lgDelete(Arrays.asList(ids));
+
+        LambdaUpdateWrapper<EmailsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.in(EmailsEntity::getCustomFolderId,Arrays.asList(ids));
+        updateWrapper.set(EmailsEntity::getCustomFolderId,null);
+        emailsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<EmailFolderRuleEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(EmailFolderRuleEntity::getEnabled,enable);
+        updateWrapper.eq(EmailFolderRuleEntity::getId,id);
+        folderRuleService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 94 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailServiceChecker.java

@@ -0,0 +1,94 @@
+package com.storlead.mail.controller;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-08 17:46
+ */
+
+import javax.naming.Context;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+import java.util.Hashtable;
+
+public class EmailServiceChecker {
+    public static void main(String[] args) {
+//        String email = "chenkaiqiang@lcjs43.wecom.work";
+        String email = "fan@storlead.com";
+        String emailService = getEmailService(email);
+        System.out.println("邮箱服务提供商: " + emailService);
+    }
+
+    public static String getEmailService(String email) {
+        if (email == null || !email.contains("@")) {
+            return "";
+        }
+
+        String domain = email.substring(email.indexOf("@") + 1);
+
+        // 使用域名匹配常见邮箱服务提供商
+        switch (domain) {
+            case "gmail.com":
+                return "Google Gmail";
+            case "yahoo.com":
+            case "yahoo.co.jp":
+                return "yahoo.com";
+            case "outlook.com":
+                return "outlook.com";
+            case "hotmail.com":
+                return "hotmail.com";
+            case "qq.com":
+                return "qq.com";
+            case "exmail.qq.com":
+                return "exmail.qq.com";
+            case "163.com":
+                return "163.com";
+            case "storlead.com":
+                return "exmail.qq.com";
+            case "renice-tech.com":
+                return "exmail.qq.com";
+            // 添加其他常见的邮箱服务提供商...
+            default:
+                return checkMXRecords(domain);
+        }
+    }
+
+    public static String checkMXRecords(String domain) {
+        try {
+            Hashtable<String, String> env = new Hashtable<>();
+            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
+            DirContext ctx = new InitialDirContext(env);
+            Attributes attrs = ctx.getAttributes(domain, new String[]{"MX"});
+            Attribute attr = attrs.get("MX");
+
+            if (attr == null) {
+                return "";
+            }
+
+            for (int i = 0; i < attr.size(); i++) {
+                String mxRecord = (String) attr.get(i);
+                if (mxRecord.contains("google.com")) {
+                    return "gmail.com";
+                } else if (mxRecord.contains("outlook.com")) {
+                    return "outlook.com";
+                } else if (mxRecord.contains("hotmail.com")) {
+                    return "hotmail.com";
+                }  else if (mxRecord.contains("qq.com") && (mxRecord.contains("mx1") || mxRecord.contains("mx2"))) {
+                    return "qq.com";
+                } else if (mxRecord.contains("qq.com") && mxRecord.contains("mxbiz")) {
+                    return "exmail.qq.com";
+                } else if (mxRecord.contains("yahoo.com")) {
+                    return "yahoo.com";
+                }
+            }
+        } catch (NamingException e) {
+            e.printStackTrace();
+            return "";
+        }
+        return "";
+    }
+}

+ 155 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MaiAttachmentApiController.java

@@ -0,0 +1,155 @@
+package com.storlead.mail.controller;
+
+import com.alibaba.fastjson.JSONObject;
+import com.storlead.framework.common.util.DateUtils;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.MailTempAttachmentEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.properties.MailFileProperties;
+import com.storlead.mail.service.MailTempAttachmentService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import com.storlead.mail.util.FileHelper;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletResponse;
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.*;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-10 09:14
+ */
+@RestController
+@RequestMapping("/mail/file")
+@Api(tags = "020.邮件管理相关接口")
+@Log4j2
+public class MaiAttachmentApiController {
+
+    @Resource
+    private MailFileProperties mailFileProperties;
+    @Resource
+    private SmtpPopSettingsService smtpPopSettingsService;
+    @Resource
+    private MailTempAttachmentService tempAttachmentService;
+
+    @PostMapping(value = "/cache_mail_file")
+    @ApiOperation(value = "缓存需要上传的附件" )
+    public Result<?> cacheMailFile(@RequestParam("files") List<MultipartFile> files) {
+        Result result = new Result();
+
+        try {
+            List<JSONObject> res = new ArrayList<>();
+
+            for (MultipartFile file : files) {
+                Long currentUserId = LoginUserUtil.getCurrentUserId();
+                SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+                String recipient = DateUtils.date2Str(new Date(), DateUtils.yyyyMMdd);
+                String downloadDir = mailFileProperties.getPath().getPath() + File.separator + smtpPop.getEmailAddress() + File.separator + recipient + File.separator;
+                JSONObject jsonObject = new JSONObject();
+                Long fileId = tempAttachmentService.saveTempAttachment(file, downloadDir, null);
+                if (Objects.isNull(fileId)) {
+                    return Result.error("上传错误");
+                }
+                jsonObject.put("id", fileId);
+                jsonObject.put("fileShowName", file.getOriginalFilename());
+                jsonObject.put("fileName", FilenameUtils.getBaseName(file.getOriginalFilename()));
+                jsonObject.put("fileSize", file.getSize());
+                jsonObject.put("fileSizeName", FileHelper.formatFileSize(file.getSize()));
+                jsonObject.put("fileExt", FilenameUtils.getExtension(file.getOriginalFilename()));
+                res.add(jsonObject);
+            }
+            return Result.result(res);
+
+        } catch (Exception e) {
+            log.error("--upload -- error =", e);
+            return Result.error("上传错误");
+        }
+    }
+
+    @PostMapping(value = "/cache_image_file")
+    @ApiOperation(value = "缓存图片文件" )
+    public Map cacheImageFile(@RequestParam("upload") MultipartFile file) {
+        Map jsonObject = new HashMap();
+        try {
+            Long currentUserId = LoginUserUtil.getCurrentUserId();
+            String downloadDir = mailFileProperties.getPath().getJpeg();
+            MailTempAttachmentEntity tempAttachment = tempAttachmentService.saveTempImageAttachment(file, downloadDir);
+            if (Objects.isNull(tempAttachment) || Objects.isNull(tempAttachment.getId())) {
+                return jsonObject;
+            }
+            String fileName =  tempAttachment.getFileName()+"."+tempAttachment.getFileExt();
+            jsonObject.put("id", tempAttachment.getId());
+            jsonObject.put("fileName", fileName);
+            jsonObject.put("uploaded", 1);
+            jsonObject.put("url", "/sales/mail/file/images/"+fileName);
+            return jsonObject;
+        } catch (Exception e) {
+            log.error("--upload -- error =", e);
+            return jsonObject;
+        }
+    }
+
+    @PostMapping(value = "/download_mail_file")
+    @ApiOperation(value = "下载临时附件" )
+    public void downLoadMailFile(Long fileId, HttpServletResponse response) {
+        try {
+            MailTempAttachmentEntity resource = tempAttachmentService.getById(fileId);
+            String downloadDir = mailFileProperties.getPath().getPath() +resource.getFilePath();
+            InputStream inputStream = new BufferedInputStream(new FileInputStream(downloadDir));
+            response.reset();
+            response.setContentType("application/octet-stream;charset=utf-8");
+            /*
+             * 跨域配置
+             * */
+            response.addHeader("Access-Control-Allow-Origin", "*");
+            response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
+            response.addHeader("Access-Control-Allow-Headers", "Content-Type");
+
+            String filename = resource.getFileName()+"."+resource.getFileExt();
+            filename = filename.replace(" ","");
+            response.addHeader("content-disposition",
+                    "attachment;filename=" + URLEncoder.encode(filename, "UTF-8")
+                            + ";filename*=utf-8''" + URLEncoder.encode(filename, "UTF-8"));
+
+            ServletOutputStream outputStream = response.getOutputStream();
+            byte[] b = new byte[1024];
+            int len;
+//从输入流中读取一定数量的字节,并将其存储在缓冲区字节数组中,读到末尾返回-1
+            while ((len = inputStream.read(b)) > 0) {
+                outputStream.write(b, 0, len);
+            }
+            inputStream.close();
+            outputStream.close();
+
+        } catch (Exception e) {
+            log.error("--upload -- error =", e);
+        }
+    }
+
+    @GetMapping("/images/{imageName}")
+    public ResponseEntity<FileSystemResource> getImage(@PathVariable String imageName) {
+
+        String downloadDir = mailFileProperties.getPath().getJpeg();
+        FileSystemResource resource = new FileSystemResource(downloadDir + imageName);
+        return ResponseEntity.ok()
+                .contentType(MediaType.IMAGE_JPEG)  // 根据图片类型调整
+                .body(resource);
+    }
+}

+ 133 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailBlacklistRecordApiController.java

@@ -0,0 +1,133 @@
+package com.storlead.mail.controller;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.enums.EmailBoxEnum;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.service.EmailBlacklistRecordService;
+import com.storlead.mail.service.EmailsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-10-31 10:51
+ */
+@RestController
+@RequestMapping("/email/blacklist")
+@Api(tags = "邮件系统:黑名单")
+@Log4j2
+public class MailBlacklistRecordApiController {
+
+    @Resource
+    private EmailBlacklistRecordService blacklistRecordService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailBlacklistRecordEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailBlacklistRecordEntity::getIsDelete,Integer.valueOf(0));
+        IPage<EmailBlacklistRecordEntity> pageList = blacklistRecordService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailBlacklistRecordEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailBlacklistRecordEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(EmailBlacklistRecordEntity::getEnabled,dto.getEnabled());
+        IPage<EmailBlacklistRecordEntity> pageList = blacklistRecordService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(EmailBlacklistRecordEntity entity) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (StrUtil.isBlank(entity.getEmailAddress())) {
+            return Result.error("邮箱不能为空");
+        }
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> wrapper =new LambdaQueryWrapper<>();
+        wrapper.eq(EmailBlacklistRecordEntity::getEmailAddress,entity.getEmailAddress());
+        wrapper.eq(EmailBlacklistRecordEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,currentUserId);
+        wrapper.last(" limit 1");
+        EmailBlacklistRecordEntity record = blacklistRecordService.getOne(wrapper);
+        if (record != null){
+            return Result.error("该邮箱已在黑名单中已存在");
+        }
+        Boolean b = blacklistRecordService.saveOrUpdate(entity);
+        if (b) {
+            /** 将黑名单邮箱的邮件移到垃圾箱中 **/
+            LambdaUpdateWrapper<EmailsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+            updateWrapper.eq(EmailsEntity::getOwnerBy,currentUserId);
+            updateWrapper.eq(EmailsEntity::getFolder, EmailBoxEnum.INBOX);
+            updateWrapper.eq(EmailsEntity::getFrom,entity.getEmailAddress());
+            updateWrapper.set(EmailsEntity::getIsTrash,1);
+            emailsService.update(updateWrapper);
+        }
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        blacklistRecordService.lgDelete(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<EmailBlacklistRecordEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(EmailBlacklistRecordEntity::getEnabled,enable);
+        updateWrapper.eq(EmailBlacklistRecordEntity::getId,id);
+        blacklistRecordService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 123 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailTemplatesApiController.java

@@ -0,0 +1,123 @@
+package com.storlead.mail.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.EmailTemplatesEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.service.EmailTemplatesService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-29 16:02
+ */
+@RestController
+@RequestMapping("/email/templates")
+@Api(tags = "邮件系统:邮件模板")
+@Log4j2
+public class MailTemplatesApiController {
+
+    @Resource
+    private EmailTemplatesService templatesService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailTemplatesEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailTemplatesEntity> queryWrapper = new LambdaQueryWrapper();
+        // queryWrapper.select(EmailTemplatesEntity::getId,EmailTemplatesEntity::getIsDelete,EmailTemplatesEntity::getTemplateTitle,EmailTemplatesEntity::getTemplateType,EmailTemplatesEntity::getCreateTime,EmailTemplatesEntity::getUpdateTime);
+        queryWrapper.eq(EmailTemplatesEntity::getOwnerBy,currentUserId);
+        if (Objects.nonNull(dto.getTemplateType())) {
+            queryWrapper.eq(EmailTemplatesEntity::getTemplateType,dto.getTemplateType());
+        }
+        queryWrapper.eq(EmailTemplatesEntity::getIsDelete,Integer.valueOf(0));
+        IPage<EmailTemplatesEntity> pageList = templatesService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/getById")
+    @ApiOperation(value = "获取邮件模板详情" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> getById(Long id) {
+        EmailTemplatesEntity templatesEntity = templatesService.getById(id);
+        return Result.result(templatesEntity);
+    }
+
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<EmailTemplatesEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<EmailTemplatesEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.select(EmailTemplatesEntity::getId,EmailTemplatesEntity::getIsDelete,EmailTemplatesEntity::getTemplateTitle,EmailTemplatesEntity::getTemplateType,EmailTemplatesEntity::getCreateTime,EmailTemplatesEntity::getUpdateTime);
+        queryWrapper.eq(EmailTemplatesEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(EmailTemplatesEntity::getIsDelete,Integer.valueOf(0));
+        if (Objects.nonNull(dto.getTemplateType())) {
+            queryWrapper.eq(EmailTemplatesEntity::getTemplateType,dto.getTemplateType());
+        }
+        queryWrapper.eq(EmailTemplatesEntity::getEnabled,dto.getEnabled());
+        IPage<EmailTemplatesEntity> pageList = templatesService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(EmailTemplatesEntity entity) {
+        templatesService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        templatesService.lgDelete(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<EmailTemplatesEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(EmailTemplatesEntity::getEnabled,enable);
+        updateWrapper.eq(EmailTemplatesEntity::getId,id);
+        templatesService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 136 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailboxAutoReplySetController.java

@@ -0,0 +1,136 @@
+package com.storlead.mail.controller;
+
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.service.MailboxAutoReplySetService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 邮箱自动回复内容 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-02-24
+ */
+@RestController
+@RequestMapping("/mail/mailbox/reply")
+@Api(tags = "020.邮件管理相关接口")
+@Log4j2
+public class MailboxAutoReplySetController {
+
+    @Resource
+    private MailboxAutoReplySetService replySetService;
+
+    @Resource
+    private SmtpPopSettingsService popSettingsService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取邮件自动回复配置" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<MailboxAutoReplySetEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<MailboxAutoReplySetEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(MailboxAutoReplySetEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(MailboxAutoReplySetEntity::getIsDelete,Integer.valueOf(0));
+        IPage<MailboxAutoReplySetEntity> pageList = replySetService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+            List<Long> popIds = pageList.getRecords().stream().map(MailboxAutoReplySetEntity::getSmtpPopId).collect(Collectors.toList());
+            List<SmtpPopSettingsEntity> smtpPopSettings = popSettingsService.list(new LambdaQueryWrapper<SmtpPopSettingsEntity>().in(SmtpPopSettingsEntity::getId, popIds));
+            if (!CollectionUtils.isEmpty(smtpPopSettings)) {
+                Map<Long,String> smtpPopMap =  smtpPopSettings.stream().collect(Collectors.toMap(SmtpPopSettingsEntity::getId,SmtpPopSettingsEntity::getEmailAddress, (existing, replacement) -> existing));
+                pageList.getRecords().forEach(e -> {
+                    e.setMailAddress(smtpPopMap.get(e.getSmtpPopId()));
+                });
+            }
+        }
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取邮件模板" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> list(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<MailboxAutoReplySetEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<MailboxAutoReplySetEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(MailboxAutoReplySetEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(MailboxAutoReplySetEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(MailboxAutoReplySetEntity::getEnabled,dto.getEnabled());
+        IPage<MailboxAutoReplySetEntity> pageList = replySetService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+           List<Long> popIds = pageList.getRecords().stream().map(MailboxAutoReplySetEntity::getSmtpPopId).collect(Collectors.toList());
+           List<SmtpPopSettingsEntity> smtpPopSettings = popSettingsService.list(new LambdaQueryWrapper<SmtpPopSettingsEntity>().in(SmtpPopSettingsEntity::getId, popIds));
+           if (!CollectionUtils.isEmpty(smtpPopSettings)) {
+              Map<Long,String> smtpPopMap =  smtpPopSettings.stream().collect(Collectors.toMap(SmtpPopSettingsEntity::getId,SmtpPopSettingsEntity::getEmailAddress, (existing, replacement) -> existing));
+               pageList.getRecords().forEach(e -> {
+                   e.setMailAddress(smtpPopMap.get(e.getSmtpPopId()));
+               });
+           }
+        }
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> save(MailboxAutoReplySetEntity entity) {
+        replySetService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        replySetService.lgDelete(Arrays.asList(ids));
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<MailboxAutoReplySetEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(MailboxAutoReplySetEntity::getEnabled,enable);
+        updateWrapper.eq(MailboxAutoReplySetEntity::getId,id);
+        replySetService.update(updateWrapper);
+        return Result.ok();
+    }
+
+}

+ 442 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopMailApiController.java

@@ -0,0 +1,442 @@
+package com.storlead.mail.controller;//package com.storlead.sales.mail;
+//
+//import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+//import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+//import com.baomidou.mybatisplus.core.metadata.IPage;
+//import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+//import com.storlead.framework.common.util.DateUtils;
+//import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+//import com.storlead.framework.util.LoginUserUtil;
+//import com.storlead.framework.common.result.Result;
+//import com.storlead.mail.connection.MailConnection;
+//import com.storlead.mail.connection.client.MailConnectionUtil;
+//import com.storlead.mail.connection.config.MailProperties;
+//import com.storlead.mail.entity.EmailsEntity;
+//import com.storlead.mail.entity.MailAttachmentEntity;
+//import com.storlead.mail.entity.SmtpPopSettingsEntity;
+//import com.storlead.mail.pojo.MailDTO;
+//import com.storlead.mail.pojo.SendMailDTO;
+//import com.storlead.mail.properties.MailFileProperties;
+//import com.storlead.mail.service.EmailsService;
+//import com.storlead.mail.service.MailAttachmentService;
+//import com.storlead.mail.service.SmtpPopSettingsService;
+//import com.sun.mail.imap.IMAPFolder;
+//import io.swagger.annotations.Api;
+//import io.swagger.annotations.ApiOperation;
+//import io.swagger.annotations.ApiResponse;
+//import io.swagger.annotations.ApiResponses;
+//import lombok.extern.log4j.Log4j2;
+////import org.springframework.mail.javamail.JavaMailSender;
+//import org.springframework.mail.javamail.MimeMessageHelper;
+//import org.springframework.util.CollectionUtils;
+//import org.springframework.web.bind.annotation.*;
+//import org.springframework.web.multipart.MultipartFile;
+//
+//import javax.annotation.Resource;
+//import javax.mail.*;
+//import javax.mail.internet.InternetAddress;
+//import javax.mail.internet.MimeMessage;
+//import javax.mail.internet.MimeUtility;
+//import javax.mail.search.ComparisonTerm;
+//import javax.mail.search.ReceivedDateTerm;
+//import javax.mail.search.SearchTerm;
+//import java.io.File;
+//import java.io.FileOutputStream;
+//import java.io.IOException;
+//import java.text.SimpleDateFormat;
+//import java.util.*;
+//import java.util.stream.Collectors;
+//
+///**
+// * @program: sp-sales-platform
+// * @description:
+// * @author: chenkq
+// * @create: 2024-05-29 16:02
+// */
+//@RestController
+//@RequestMapping("/mail")
+//@Api(tags = "020.邮件管理相关接口")
+//public class SmtpPopMailApiController {
+//
+////    @Resource
+////    private JavaMailSender mailSender;
+//
+//    @Resource
+//    private EmailsService emailsService;
+//
+//    @Resource
+//    private MailFileProperties mailFileProperties;
+//
+//    @Resource
+//    private MailAttachmentService mailAttachmentService;
+//
+//    @Resource
+//    private SmtpPopSettingsService smtpPopSettingsService;
+//
+//
+//    @PostMapping(value = "/pageList")
+//    @ApiOperation(value = "获取邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> pageList(com.storlead.common.object.Page dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.result(null);
+//        }
+//        Page<EmailsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+//        LambdaQueryWrapper<EmailsEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(EmailsEntity::getOwnerBy,currentUserId);
+//        queryWrapper.eq(EmailsEntity::getFrom,smtpPop.getEmailAddress());
+//        IPage<EmailsEntity> pageList = emailsService.page(page,queryWrapper);
+//        return Result.result(pageList);
+//    }
+//
+//    @PostMapping(value = "/sendMail")
+//    @ApiOperation(value = "发送邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> sendMail(SendMailDTO dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+//        String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+//        smtpPop.setEmailPassword(pass);
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.result(null);
+//        }
+//        try {
+//           Session session = getSendEmailsSession(smtpPop);
+//            try {
+//
+//                InternetAddress [] froms = new InternetAddress[dto.getRecipientls().size()];
+//                for (int i = 0; i < dto.getRecipientls().size(); i++) {
+//                    froms[i] = new InternetAddress(dto.getRecipientls().get(i));
+//                }
+//                if (CollectionUtils.isEmpty(dto.getFiles())) {
+//                    Message message = new MimeMessage(session);
+//                    message.setFrom(new InternetAddress(smtpPop.getEmailAddress()));
+//                    message.setRecipients(Message.RecipientType.TO, froms);
+//                    message.setSubject(dto.getSubject());
+//                    message.setText(dto.getContent());
+//                    Transport.send(message);
+//                } else {
+////                    MimeMessage message = mailSender.createMimeMessage();
+////                    MimeMessageHelper helper = new MimeMessageHelper(message, true);
+////                    helper.setFrom(new InternetAddress(smtpPop.getEmailAddress()));
+////                    helper.setTo(froms);
+////                    helper.setSubject(dto.getSubject());
+////                    helper.setText(dto.getContent());
+////                    List<MultipartFile> files = dto.getFiles();
+////                    for (MultipartFile multipartFile : files) {
+////                        File file = multipartFile.getResource().getFile();
+////                        helper.addAttachment(file.getName(), file);
+////                    }
+//                }
+//            }catch (Exception e) {
+//                log.error("getSendEmailsSession -error",e);
+//            }
+//            return Result.ok();
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//            log.error("send sendMail -error",e);
+//        }
+//        return Result.ok();
+//    }
+//
+//
+//    @PostMapping(value = "/receiveMail")
+//    @ApiOperation(value = "接收邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> receiveMail(MailDTO dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.error("请先设置邮箱账号!");
+//        }
+//
+//        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+//        String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+//        smtpPop.setEmailPassword(pass);
+//        receiveEmails(smtpPop, "INBOX");
+////        receiveEmails(smtpPop, "Sent Items");
+////        receiveEmails(smtpPop, "Drafts");
+////        receiveEmails(smtpPop, "Trash");
+//        return Result.ok();
+//    }
+//
+//
+//    @PostMapping(value = "/receiveMail1")
+//    @ApiOperation(value = "接收邮件" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> receiveMail1(MailDTO dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getDefaultSmtpPop(currentUserId);
+//        if (Objects.isNull(currentUserId) && Objects.nonNull(smtpPop)) {
+//            return Result.error("请先设置邮箱账号!");
+//        }
+//        return Result.ok();
+//    }
+//
+//    private void receiveEmails(SmtpPopSettingsEntity smtpPop, String folderName) {
+//        MailProperties properties = new MailProperties(smtpPop);
+//        MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+//        if (Objects.isNull(mailConnection)) {
+//            return;
+//        }
+//        List<EmailsEntity> entities = new ArrayList<>();
+//
+//        QueryWrapper queryWrapper = new QueryWrapper<>();
+//        queryWrapper.eq("smtp_pop_id",smtpPop.getId());
+//        queryWrapper.eq("folder",folderName);
+//        queryWrapper.select("max(recipient_date) as recipientDate");
+//        Map<String, Date> qv = emailsService.getMap(queryWrapper);
+//
+//        QueryWrapper queryMsgIdWp = new QueryWrapper<>();
+//        queryMsgIdWp.select("msg_uid");
+//        queryMsgIdWp.eq("folder",folderName);
+//        queryMsgIdWp.eq("smtp_pop_id",smtpPop.getId());
+//        queryMsgIdWp.last("order by msg_uid desc limit 100");
+//
+//        List<String> dis = emailsService.listObjs(queryMsgIdWp);
+//        List<Long> msgUids = new ArrayList<>();
+//        if (!CollectionUtils.isEmpty(dis)) {
+//            msgUids = dis.stream().map(Long::valueOf).collect(Collectors.toList());
+//        }
+//        try {
+//            Store store = mailConnection.getStore();
+//            Folder inbox = store.getFolder(folderName);
+//            inbox.open(Folder.READ_WRITE);
+//            Date beginDate = null;
+//            if (!CollectionUtils.isEmpty(qv) && Objects.nonNull(qv.get("recipientDate"))) {
+//                beginDate = qv.get("recipientDate");
+//            }
+//            if (beginDate == null) {
+//                SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+//                String dateStr = "2024-03-28"; // 替换为你需要的日期
+//                beginDate = dateFormat.parse(dateStr);
+//            }
+//
+//            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+//            Date date = dateFormat.parse("2024-05-28");
+//
+//            SearchTerm searchTerm = new ReceivedDateTerm(ComparisonTerm.GT, date);
+//            Message[] messages = inbox.search(searchTerm);
+//            for (Message message : messages) {
+//                Long mesId = getMessageId(message,inbox);
+//                if (!CollectionUtils.isEmpty(msgUids) && msgUids.contains(mesId)) {
+//                    continue;
+//                }
+//                msgUids.add(mesId);
+//                EmailsEntity entity = convertMessageToEmailVo(message,smtpPop);
+//                entity.setSmtpPopId(smtpPop.getId());
+//                entity.setMsgUid(mesId);
+//                entity.setFolder(folderName);
+//                entities.add(entity);
+//                emailsService.save(entity);
+//
+//                String downloadDir = mailFileProperties.getPath().getPath()+File.separator+smtpPop.getEmailAddress()+File.separator;
+//                downloadAttachmentFromMessage(message,downloadDir,entity.getId());
+//            }
+//            inbox.close(false);
+//            mailConnection.close();
+//        }catch (Exception e) {
+//            log.error("receiveEmails - error =",e);
+//            mailConnection.close();
+//        }
+//
+//    }
+//
+//    private Session getSendEmailsSession(SmtpPopSettingsEntity smtpPop) {
+//
+//        Properties properties = new Properties();
+//        properties.put("mail.smtp.host", smtpPop.getSmtpServer());
+//        properties.put("mail.smtp.port", smtpPop.getSmtpPort());
+//        properties.put("mail.smtp.auth", "true");
+//        properties.put("mail.user", smtpPop.getEmailAddress());
+//        properties.put("mail.smtp.starttls.enable",smtpPop.getUseTls());
+//        properties.put("mail.smtp.ssl.enable", smtpPop.getUseSsl());
+//
+//        Session session = Session.getInstance(properties, new Authenticator() {
+//            protected PasswordAuthentication getPasswordAuthentication() {
+//                return new PasswordAuthentication(smtpPop.getEmailAddress(), smtpPop.getEmailPassword());
+//            }
+//        });
+//        return session;
+//    }
+//
+//        private static Long getMessageId(Message message, Folder folder) throws MessagingException {
+//        if (folder instanceof IMAPFolder) {
+//            IMAPFolder imapFolder = (IMAPFolder) folder;
+//            return  imapFolder.getUID(message);
+//        } else {
+//            return null;
+//        }
+//    }
+//
+//
+//    private EmailsEntity convertMessageToEmailVo(Message message,SmtpPopSettingsEntity smtpPop) throws MessagingException, IOException {
+//
+//        EmailsEntity entity = new EmailsEntity();
+//        String subject =  MimeUtility.decodeText(message.getSubject());
+//        entity.setSubject(subject);
+//
+//        Map<String,String>  fromMap = decodeAndPrintAddresses(message.getFrom());
+//        entity.setFrom(mapKeyToString(fromMap));
+//        entity.setFromName(mapValueToString(fromMap));
+//
+//        Map<String,String>  toMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.TO));
+//        entity.setRecipient(mapKeyToString(toMap));
+//        entity.setRecipientName(mapValueToString(toMap));
+//
+//        if (Objects.nonNull(message.getSentDate())) {
+//            entity.setSentDate(message.getSentDate());
+//        }
+//
+//        if (Objects.nonNull(message.getReceivedDate())) {
+//            entity.setRecipientDate(message.getReceivedDate());
+//        }
+//        boolean isSeen = message.isSet(Flags.Flag.SEEN);
+//        entity.setIsRead(isSeen);
+//
+////      Map<String,String>  ccMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.CC));
+////      Map<String,String>  bccMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.BCC));
+//        Object content = message.getContent();
+//        if (content instanceof String) {
+//            entity.setContent(message.getContent().toString());
+//        } else if (content instanceof Multipart) {
+//            Multipart multipart = (Multipart) content;
+//            String str = parseMultipart(multipart);
+//            entity.setContent(str);
+//        }
+//        return entity;
+//    }
+//
+//    private String parseMultipart(Multipart multipart) {
+//        try {
+//            StringBuilder stringBuilder = new StringBuilder();
+//            for (int i = 0; i < multipart.getCount(); i++) {
+//                BodyPart bodyPart = multipart.getBodyPart(i);
+//                if (bodyPart.isMimeType("text/plain")) {
+//                    stringBuilder.append(bodyPart.getContent());
+//                } else if (bodyPart.isMimeType("text/html")) {
+//                    stringBuilder.append(bodyPart.getContent());
+//                } else if (bodyPart.isMimeType("multipart/alternative")) {
+//                    parseMultipart((Multipart) bodyPart.getContent());
+//                } else if (bodyPart.isMimeType("multipart/*")) {
+//                    parseMultipart((Multipart) bodyPart.getContent());
+//                }
+//            }
+//            return stringBuilder.toString();
+//        }catch (Exception e) {
+//            return "";
+//        }
+//    }
+//
+//    private void downloadAttachmentFromMessage(Message message, String downloadDir, long emailId) {
+//        try {
+//            Object content = message.getContent();
+//            if (content instanceof Multipart) {
+//                Multipart multipart = (Multipart) content;
+//                for (int i = 0; i < multipart.getCount(); i++) {
+//                    BodyPart bodyPart = multipart.getBodyPart(i);
+//                    if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
+//                        saveAttachment(bodyPart, downloadDir, emailId);
+//                    }
+//                }
+//            }
+//        }catch (Exception e) {
+//            log.error("downloadAttachmentFromMessage -- error = ",e);
+//        }
+//    }
+//
+//    private void saveAttachment(BodyPart bodyPart,  String downloadDir, long emailId) throws MessagingException, IOException {
+//        String fileName = bodyPart.getFileName();
+//        fileName = MimeUtility.decodeText(fileName);
+//        String path = DateUtils.date2Str(new Date(), DateUtils.date_yyyy_mm);
+//        // 如果不存在这创建路径
+//        downloadDir = downloadDir + File.separator + path;
+//        if (!new File(downloadDir).exists()) {
+//            new File(downloadDir).mkdirs();
+//        }
+//        File file = new File(downloadDir +  File.separator +fileName);
+//        try (FileOutputStream output = new FileOutputStream(file)) {
+//            bodyPart.getInputStream().transferTo(output);
+//        }
+//        System.out.println("Saved attachment: " + file.getAbsolutePath());
+//        saveAttachmentToDatabase(file, emailId);
+//    }
+//
+//    private void saveAttachmentToDatabase(File file, long emailId) {
+//
+//        String fileName = file.getName();
+//        MailAttachmentEntity attachment = new MailAttachmentEntity();
+//        attachment.setEmailId(emailId);
+//        attachment.setFileName(fileName);
+//        attachment.setFilePath(file.getAbsolutePath());
+//        attachment.setFilePath(file.getAbsolutePath());
+//
+//        String fileExtension = fileName.substring(fileName.lastIndexOf(".") + 1);
+//        attachment.setFileExt(fileExtension);
+//        attachment.setFileSize(file.length());
+//        mailAttachmentService.save(attachment);
+//    }
+//
+//    private static String formatDate(Date date) {
+//        if (date == null) return "N/A";
+//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+//        return sdf.format(date);
+//    }
+//
+//    private static String decodeAddresses(Address [] addresses) throws MessagingException {
+//        try {
+//            if (addresses == null) return "[]";
+//            String[] decodedAddresses = new String[addresses.length];
+//            for (int i = 0; i < addresses.length; i++) {
+//                decodedAddresses[i] = MimeUtility.decodeText(addresses[i].toString());
+//            }
+//            return Arrays.toString(decodedAddresses);
+//        }catch (Exception e) {
+//            return "";
+//        }
+//    }
+//
+//    private static Map<String,String> decodeAndPrintAddresses(Address[] addresses) throws MessagingException, IOException {
+//
+//        Map<String,String> map = new HashMap<>();
+//        if (addresses == null) return new HashMap<>();
+//        StringBuilder decodedAddresses = new StringBuilder();
+//        for (Address address : addresses) {
+//            InternetAddress internetAddress = (InternetAddress) address;
+//            String personal = internetAddress.getPersonal();
+//            String email = internetAddress.getAddress();
+//            if (personal != null) {
+//                personal = MimeUtility.decodeText(personal);
+//            }
+//            map.put(email,(personal != null ? personal : "N/A"));
+////            decodedAddresses.append("[Name: ").append(personal != null ? personal : "N/A").append(", Email: ").append(email).append("] ");
+//        }
+//        return map;
+//    }
+//
+//    private static String mapKeyToString(Map<String,String> map) {
+//        if (map == null) return "";
+//        return map.entrySet().stream()
+//                .map(entry -> entry.getKey())
+//                .collect(Collectors.joining(","));
+//    }
+//
+//    private static String mapValueToString(Map<String,String> map) {
+//        if (map == null) return "";
+//        return map.entrySet().stream()
+//                .map(entry -> entry.getValue())
+//                .collect(Collectors.joining(","));
+//    }
+//}

+ 457 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopSettingsApiController.java

@@ -0,0 +1,457 @@
+package com.storlead.mail.controller;
+
+
+import cn.hutool.core.collection.CollectionUtil;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.client.MailConnectionUtil;
+import com.storlead.mail.connection.config.MailProperties;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.enums.EmailBoxEnum;
+import com.storlead.mail.pojo.vo.MailAcountVO;
+import com.storlead.mail.service.EmailsService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.util.CollectionUtils;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.mail.*;
+import java.util.*;
+
+/**
+ * <p>
+ * 邮箱smtp_setting 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Log4j2
+@RestController
+@RequestMapping("/smtp/pop/setting")
+@Api(tags = "020.邮件管理相关接口")
+public class SmtpPopSettingsApiController {
+
+    @Resource
+    private SmtpPopSettingsService smtpPopSettingsService;
+
+    @Resource
+    private EmailsService emailsService;
+
+
+    @PostMapping(value = "/pageList")
+    @ApiOperation(value = "获取我的邮箱配置信息" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(com.storlead.framework.mybatis.page.Page dto) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId)) {
+            return Result.result(null);
+        }
+        AccessKeyEncryptor encryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+        Page<SmtpPopSettingsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(SmtpPopSettingsEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+        IPage<SmtpPopSettingsEntity> pageList =  smtpPopSettingsService.page(page,queryWrapper);
+        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+            pageList.getRecords().forEach(e -> {
+                e.setEmailPassword(encryptor.decrypt(e.getEmailPassword()));
+            });
+        }
+        return Result.result(pageList);
+    }
+
+//    @PostMapping(value = "/listAutoReplySettings")
+//    @ApiOperation(value = "获取我的邮箱配置信息" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> listAutoReplySettings(com.storlead.common.object.Page dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        if (Objects.isNull(currentUserId)) {
+//            return Result.result(null);
+//        }
+//        AccessKeyEncryptor encryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+//        Page<SmtpPopSettingsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+//        LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(SmtpPopSettingsEntity::getIsDelete,Integer.valueOf(0));
+//        queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+//        IPage<SmtpPopSettingsEntity> pageList =  smtpPopSettingsService.page(page,queryWrapper);
+//        if (!CollectionUtils.isEmpty(pageList.getRecords())) {
+//            pageList.getRecords().forEach(e -> {
+//                e.setEmailPassword(encryptor.decrypt(e.getEmailPassword()));
+//            });
+//        }
+//        return Result.result(pageList);
+//    }
+    @PostMapping(value = "/setReplyContent")
+    @ApiOperation(value = "设置自动回复内容,并且开始" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> setReplyContent(Long id,String replyContent,String replyTitle,Integer autoReplyStatus) {
+        if (Objects.isNull(id) || Objects.isNull(autoReplyStatus)) {
+            return Result.error("参数错误");
+        }
+        if (Integer.valueOf(1).equals(autoReplyStatus)) {
+            if(StrUtil.isBlank(replyContent) || StrUtil.isBlank(replyTitle)) {
+                return Result.error("参数错误");
+            }
+        }
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyContent,replyContent);
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyTitle,replyTitle);
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyOnTime,new Date());
+        updateWrapper.set(SmtpPopSettingsEntity::getIsAutoReply,autoReplyStatus);
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/setAutoReplyStatus")
+    @ApiOperation(value = "设置自动回复是否开启:1:开启,0:关闭" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = MailboxAutoReplySetEntity.class)
+    })
+    public Result<?> setAutoReplyStatus(Long id,Integer autoReplyStatus) {
+        if (Objects.isNull(id) || Objects.isNull(autoReplyStatus)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getIsAutoReply,autoReplyStatus);
+        updateWrapper.set(SmtpPopSettingsEntity::getReplyOnTime,new Date());
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+//    @PostMapping(value = "/list")
+//    @ApiOperation(value = "获取我的邮箱配置信息" )
+//    @ApiResponses({
+//            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+//    })
+//    public Result<?> list(com.storlead.common.object.Page dto) {
+//        Long currentUserId = LoginUserUtil.getCurrentUserId();
+//        if (Objects.isNull(currentUserId)) {
+//            return Result.result(null);
+//        }
+//        Page<SmtpPopSettingsEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+//        LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+//        queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+//        IPage<SmtpPopSettingsEntity> pageList =  smtpPopSettingsService.page(page,queryWrapper);
+//        return Result.result(pageList);
+//    }
+
+    @PostMapping(value = "/delete")
+    @ApiOperation(value = "删除" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> delete(Long id) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId)) {
+            return Result.result(null);
+        }
+        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getById(id);
+        if (Objects.isNull(smtpPop)) {
+            return Result.error("参数错误");
+        }
+
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(SmtpPopSettingsEntity::getIsDelete,Integer.valueOf(1));
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "保存或修改" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = Result.class)
+    })
+    public Result<?> save(@RequestBody SmtpPopSettingsEntity entity) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId)) {
+            return Result.result(null);
+        }
+        /**
+         * 保存邮箱先校验
+         */
+        if ("POP3".equals(entity.getProtocolType())) {
+            if (!testPop3(entity)) {
+                MailProperties properties = new MailProperties(entity);
+                MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+                if (Objects.isNull(mailConnection)) {
+                    return Result.error("邮箱校验失败,请输入正确的账号密码");
+                }
+                mailConnection.close();
+            }
+        }else if("IMAP".equals(entity.getProtocolType())) {
+            MailProperties properties = new MailProperties(entity);
+            MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+            if (!testImap(entity)) {
+                return Result.error("邮箱校验失败,请输入正确的账号密码");
+            }
+            mailConnection.close();
+        } else {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        if (!testSmtp(entity)) {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        /**
+         * 修改和增加根据邮箱进行查重
+         */
+        AccessKeyEncryptor encryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+        entity.setEmailPassword(encryptor.encrypt(entity.getEmailPassword()));
+        if (!Integer.valueOf(1).equals(entity.getUseDefault())) {
+            LambdaQueryWrapper<SmtpPopSettingsEntity> queryWrapper = new LambdaQueryWrapper();
+            queryWrapper.eq(SmtpPopSettingsEntity::getUseDefault, Integer.valueOf(1));
+            queryWrapper.eq(SmtpPopSettingsEntity::getOwnerBy, currentUserId);
+            queryWrapper.eq(SmtpPopSettingsEntity::getIsDelete, Integer.valueOf(0));
+            queryWrapper.last("limit 1");
+            SmtpPopSettingsEntity c = smtpPopSettingsService.getOne(queryWrapper);
+            if (Objects.isNull(c)) {
+                return Result.error("请设置默认邮箱");
+            }
+            if (Objects.nonNull(c) && c.getId().equals(entity.getId())) {
+                return Result.error("请设置默认邮箱");
+            }
+        }
+
+        if (Objects.nonNull(entity.getId())) {
+            // 修改
+            SmtpPopSettingsEntity old = smtpPopSettingsService.getById(entity.getId());
+            if (Objects.nonNull(old)) {
+                if(!old.getEmailAddress().equals(entity.getEmailAddress())){
+                    return Result.error("已绑定的邮箱无法修改,可新增邮箱配置!");
+                }
+            }
+        }
+        Boolean b = smtpPopSettingsService.saveOrUpdate(entity);
+        if (Integer.valueOf(1).equals(entity.getUseDefault()) && b) {
+            // 如果设置了默认邮箱,则将所有默认邮箱改为非默认
+            LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+            updateWrapper.set(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(0));
+            updateWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+            updateWrapper.ne(SmtpPopSettingsEntity::getId,entity.getId());
+            smtpPopSettingsService.update(updateWrapper);
+        }
+
+        return Result.ok();
+    }
+
+    @PostMapping(value = "/defaultUse")
+    @ApiOperation(value = "设置默认邮箱" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = Result.class)
+    })
+    public Result<?> defaultUse(Long id) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        if (Objects.isNull(currentUserId) || Objects.isNull(id)) {
+            return Result.result(null);
+        }
+
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(0));
+        updateWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+        smtpPopSettingsService.update(updateWrapper);
+
+        updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        updateWrapper.eq(SmtpPopSettingsEntity::getOwnerBy,currentUserId);
+        updateWrapper.set(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(1));
+        smtpPopSettingsService.update(updateWrapper);
+        /**
+         * 需要当前默认邮箱的邮件和未读数
+         */
+        return Result.ok();
+    }
+
+
+    @PostMapping(value = "/isEnabledDelete")
+    @ApiOperation(value = "是否开启删除服务器邮件;0 不开启,1:开启" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> setReplyContent(Long id,Integer isEnabled) {
+        if (Objects.isNull(id) || Objects.isNull(isEnabled)) {
+            return Result.error("参数错误");
+        }
+        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper();
+        updateWrapper.set(SmtpPopSettingsEntity::getIsDeleteMail,isEnabled);
+        updateWrapper.eq(SmtpPopSettingsEntity::getId,id);
+        smtpPopSettingsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+
+    @PostMapping(value = "/personal_mail_account")
+    @ApiOperation(value = "获取个人邮箱账户" )
+    public Result personalMailAccount() {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        List<SmtpPopSettingsEntity> emails =  smtpPopSettingsService.getEmailAccount(currentUserId);
+        QueryWrapper<EmailsEntity> queryWrapper = new QueryWrapper<EmailsEntity>();
+        queryWrapper.select("smtp_pop_id", "COUNT(*) AS email_count");
+        queryWrapper.eq("owner_by", currentUserId);
+        queryWrapper.eq("folder", EmailBoxEnum.INBOX.code);
+        queryWrapper.eq("is_trash", Integer.valueOf(0));
+        queryWrapper.eq("is_read", Integer.valueOf(0));
+        queryWrapper.eq("is_delete", Integer.valueOf(0));
+        queryWrapper.isNotNull("smtp_pop_id");
+        queryWrapper.ne("`from`", "system@storlead.com");
+        queryWrapper.groupBy("smtp_pop_id");
+        List<Map<String,Object>> readMapls = emailsService.listMaps(queryWrapper);
+        Map<Long, Integer> readMap = new HashMap<>();
+        if (!CollectionUtils.isEmpty(readMapls)) {
+            for (Map<String, Object> map : readMapls) {
+                Long key = Long.valueOf(map.get("smtp_pop_id").toString());
+                Integer val = Integer.valueOf(map.get("email_count").toString());
+                readMap.put(key, val);
+            }
+        }
+
+        List<MailAcountVO> mails = new ArrayList<>();
+        if (CollectionUtil.isNotEmpty(emails)) {
+            emails.forEach(e -> {
+                MailAcountVO mailvo = new MailAcountVO();
+                mailvo.setMailAccount(e.getEmailAddress());
+                mailvo.setId(e.getId());
+                mailvo.setUseDefault(e.getUseDefault());
+                Integer readNum = readMap.get(e.getId());
+                if (Objects.nonNull(readNum)) {
+                    mailvo.setUnReadCount(readNum);
+                } else {
+                    mailvo.setUnReadCount(0);
+                }
+                mails.add(mailvo);
+            });
+        }
+        return Result.result(mails);
+    }
+
+    @PostMapping(value = "/test_connect")
+    @ApiOperation(value = "测试" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> receiveMail(@RequestBody SmtpPopSettingsEntity mailSet) {
+
+        if (!testSmtp(mailSet)) {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        if ("POP3".equals(mailSet.getProtocolType())) {
+            if (!testPop3(mailSet)) {
+                return Result.error("邮箱校验失败,请输入正确的账号密码");
+            }
+        }else if("IMAP".equals(mailSet.getProtocolType())) {
+            if (!testImap(mailSet)) {
+                return Result.error("邮箱校验失败,请输入正确的账号密码");
+            }
+        } else {
+            return Result.error("邮箱校验失败,请输入正确的账号密码");
+        }
+        return Result.ok();
+    }
+
+
+
+    public Boolean testImap(SmtpPopSettingsEntity mailSet){
+        try {
+            MailProperties properties = new MailProperties(mailSet);
+            MailConnection mailConnection = MailConnectionUtil.testReceiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return false;
+            }
+            log.error("mailConnection -- testImap == "+mailConnection);
+//            String host = mailSet.getReceiveServer();
+//            String port = mailSet.getReceivePort();
+//            String username = mailSet.getEmailAddress();
+//            String password = mailSet.getEmailPassword();;
+//
+//            Properties properties = new Properties();
+//            properties.put("mail.imap.host", host);
+//            properties.put("mail.imap.port", port);
+//            properties.put("mail.imap.ssl.enable",Integer.valueOf(1).equals(mailSet.getReceiveSsl()) ? "true" : "false");
+//            Session session = Session.getDefaultInstance(properties);
+//            Store store = session.getStore("imap");
+//            store.connect(username, password);
+//            store.close();
+//            session = null;
+            mailConnection.close();
+
+            return true;
+        } catch (Exception e) {
+            log.error("testImap - error",e);
+            return false;
+        }
+    }
+
+    public Boolean testPop3(SmtpPopSettingsEntity mailSet){
+        try {
+            MailProperties properties = new MailProperties(mailSet);
+            MailConnection mailConnection = MailConnectionUtil.testReceiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return false;
+            }
+            log.error("mailConnection -- testPop3 == "+mailConnection);
+            mailConnection.close();
+
+            return true;
+        } catch (Exception e) {
+            log.error("testPop3 - error",e);
+            return false;
+        }
+    }
+
+    public Boolean testSmtp(SmtpPopSettingsEntity smtpPop){
+        try {
+            Properties properties = new Properties();
+            properties.put("mail.smtp.host", smtpPop.getSmtpServer());
+            properties.put("mail.smtp.port", smtpPop.getSmtpPort());
+            properties.put("mail.smtp.auth", "true");
+            properties.put("mail.user", smtpPop.getEmailAddress());
+            properties.put("mail.smtp.starttls.enable",Integer.valueOf(1).equals(smtpPop.getSmtpTls()) ? "true" : "false");
+            properties.put("mail.smtp.ssl.enable", Integer.valueOf(1).equals(smtpPop.getSmtpSsl()) ? "true" : "false");
+
+            String username = smtpPop.getEmailAddress();
+            String password = smtpPop.getEmailPassword();
+
+            Session session = Session.getInstance(properties, new Authenticator() {
+                protected PasswordAuthentication getPasswordAuthentication() {
+                    return new PasswordAuthentication(username, password);
+                }
+            });
+            Transport transport = session.getTransport("smtp");
+            transport.connect();
+            transport.close();
+            return true;
+        } catch (Exception e) {
+            log.error("testSmtp - error",e);
+            return false;
+        }
+    }
+
+}

+ 135 - 0
java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/UserEmailFolderApiController.java

@@ -0,0 +1,135 @@
+package com.storlead.mail.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.util.LoginUserUtil;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.dto.EmailTemplatesDTO;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.pojo.entity.UserEmailFolderEntity;
+import com.storlead.mail.service.EmailsService;
+import com.storlead.mail.service.UserEmailFolderService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * <p>
+ * 用户文件夹 前端控制器
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@RestController
+@RequestMapping("/user/email/folder")
+@Api(tags = "邮件:邮件自定义文件夹")
+public class UserEmailFolderApiController {
+    @Resource
+    private UserEmailFolderService emailFolderService;
+
+    @Resource
+    private EmailsService emailsService;
+
+    @PostMapping(value = "/page_list")
+    @ApiOperation(value = "获取用户文件夹(分页)" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> pageList(EmailTemplatesDTO dto) {
+
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        Page<UserEmailFolderEntity> page = new Page<>(dto.getPageIndex(),dto.getPageSize());
+        LambdaQueryWrapper<UserEmailFolderEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(UserEmailFolderEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(UserEmailFolderEntity::getIsDelete,Integer.valueOf(0));
+        IPage<UserEmailFolderEntity> pageList = emailFolderService.page(page,queryWrapper);
+        return Result.result(pageList);
+    }
+
+    @PostMapping(value = "/list")
+    @ApiOperation(value = "获取用户文件夹" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> list() {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        LambdaQueryWrapper<UserEmailFolderEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(UserEmailFolderEntity::getOwnerBy,currentUserId);
+        queryWrapper.eq(UserEmailFolderEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.eq(UserEmailFolderEntity::getEnabled,Integer.valueOf(1));
+        List<UserEmailFolderEntity> ls = emailFolderService.list(queryWrapper);
+        return Result.result(ls);
+    }
+
+    @PostMapping(value = "/save")
+    @ApiOperation(value = "绑定文件夹入站规则" )
+    @ApiResponses({
+            @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
+    })
+    public Result<?> save(UserEmailFolderEntity entity) {
+        Long currentUserId = LoginUserUtil.getCurrentUserId();
+        LambdaQueryWrapper<UserEmailFolderEntity> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(UserEmailFolderEntity::getFolderName,entity.getFolderName());
+        queryWrapper.eq(UserEmailFolderEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        queryWrapper.eq(UserEmailFolderEntity::getOwnerBy,currentUserId);
+
+        if (Objects.isNull(entity.getId())) {
+            Integer exit = emailFolderService.count(queryWrapper);
+            if (exit > 0) {
+                return Result.error("文件夹'"+ entity.getFolderName() +"'已存在!");
+            }
+        } else {
+            UserEmailFolderEntity oldFolder = emailFolderService.getOne(queryWrapper);
+            if (Objects.nonNull(oldFolder)) {
+                if (!oldFolder.getId().equals(entity.getId())) {
+                    return Result.error("文件夹'"+ entity.getFolderName() +"'已存在,无法修改!");
+                }
+            }
+        }
+        emailFolderService.saveOrUpdate(entity);
+        return Result.ok();
+    }
+
+    @ApiOperation(value = "删除")
+    @PostMapping("deleteIds")
+    public Result delete(Long [] ids) {
+        if(Objects.isNull(ids)) {
+            return Result.error("参数错误");
+        }
+        /**
+         * 需要修改邮件文件夹标记为''
+         */
+        emailFolderService.lgDelete(Arrays.asList(ids));
+
+        LambdaUpdateWrapper<EmailsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.in(EmailsEntity::getCustomFolderId,Arrays.asList(ids));
+        updateWrapper.set(EmailsEntity::getCustomFolderId,null);
+        emailsService.update(updateWrapper);
+        return Result.ok();
+    }
+
+
+    @ApiOperation("禁用或启用")
+    @PostMapping("enabled")
+    public Result enabled(Long id,Boolean enable) {
+        LambdaUpdateWrapper<UserEmailFolderEntity> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.set(UserEmailFolderEntity::getEnabled,enable);
+        updateWrapper.eq(UserEmailFolderEntity::getId,id);
+        emailFolderService.update(updateWrapper);
+        return Result.ok();
+    }
+}

+ 46 - 0
java/storlead-mail/storlead-mail-biz/pom.xml

@@ -0,0 +1,46 @@
+<?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-mail</artifactId>
+        <version>1.0</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>storlead-mail-biz</artifactId>
+    <packaging>jar</packaging>
+    <name>storlead-mail-biz</name>
+    <description>邮件域实现:Mapper、Service 实现、协议连接与工具。</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mail-spi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-mybatis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.storlead.boot</groupId>
+            <artifactId>storlead-common</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>javax.mail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-compress</artifactId>
+            <version>1.21</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 157 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailReceiver.java

@@ -0,0 +1,157 @@
+package com.storlead.mail;
+
+import com.sun.mail.imap.DefaultFolder;
+import com.sun.mail.imap.IMAPFolder;
+import com.sun.mail.imap.IMAPStore;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import javax.mail.search.*;
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Set;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-24 14:42
+ */
+@Log4j2
+public class QQEmailReceiver {
+
+
+    private static final String UID_FILE_PATH = "uids.txt"; // 存储UID的文件路径
+
+    public static void main(String[] args) {
+        String host = "imap.exmail.qq.com"; // QQ邮箱IMAP服务器地址
+        String port = "993"; // QQ邮箱IMAP服务器端口
+        String userName = "chenkaiqiang@lcjs43.wecom.work"; // 你的QQ邮箱地址
+        String password = "-Chenkq1992"; // 你的QQ邮箱授权码
+        //
+        // pnkxmvlldlrmidif
+        Properties properties = new Properties();
+        properties.put("mail.imap.host", host);
+        properties.put("mail.imap.port", port);
+        properties.put("mail.imap.starttls.enable", "true");
+        properties.put("mail.imap.ssl.trust", "*");
+        properties.put("mail.imap.ssl.enable", "true");
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(userName, password);
+            }
+        };
+
+        Session session = Session.getInstance(properties,auth);
+        try {
+            IMAPStore store =  (IMAPStore) session.getStore("imap");
+            store.connect();
+//            Folder inbox = store.getFolder("Sent Items");
+            IMAPFolder inbox = (IMAPFolder) store.getFolder("INBOX");
+//            Folder inbox = store.getFolder("INBOX");
+            inbox.open(Folder.READ_WRITE);
+            long lastUID = -1;
+            // 加载已处理的UID
+//            if (lastUID == -1) {
+//                // 首次获取所有邮件
+//                 dateStr = "2001-05-28";
+//            } else {
+//                 dateStr = "2024-05-28"; // 替换为你需要的日期
+//            }
+//            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
+//            Date date = dateFormat.parse(dateStr);
+//            SearchTerm searchTerm = new ReceivedDateTerm(ComparisonTerm.GT, date);
+            //<tencent_49C3E81C77FD0CAB1F16CA6F@qq.com>
+//            SearchTerm searchTerm = new HeaderTerm("Message-ID","<tencent_49C3E81C77FD0CAB1F16CA6F@qq.com>");
+
+            SearchTerm searchTerm = new HeaderTerm("Message-ID","<tencent_573CB96038E4DCF91E167FA6@qq.com>");
+            SearchTerm searchTerm1 = new MessageIDTerm("<tencent_573CB96038E4DCF91E167FA6@qq.com>");
+            long start = System.currentTimeMillis();
+
+            Message [] megs = inbox.getMessages();
+            Message [] messages0 = inbox.search(searchTerm);
+            Message [] messages1 = inbox.search(searchTerm,megs);
+            Message [] messages2 = inbox.search(searchTerm1,megs);
+            System.out.println("----耗时"+(System.currentTimeMillis() - start));
+            long maxUID = lastUID;
+            for (Message message : megs) {
+                long uid = inbox.getUID(message);
+                String msguid = message.getHeader("Message-ID")[0];
+                if (message.match(searchTerm1)) {
+                    log.error("找到了 :"+msguid);
+                }
+                log.error("Message-UID = :"+uid);
+                log.error("Message-subject = :"+message.getSubject());
+                log.error("Message-ID = :"+msguid);
+            }
+            inbox.close(false);
+            store.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static Set<Long> loadProcessedUIDs() {
+        Set<Long> uids = new HashSet<>();
+        try (BufferedReader reader = new BufferedReader(new FileReader(UID_FILE_PATH))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                uids.add(Long.parseLong(line));
+            }
+        } catch (IOException e) {
+            // 文件不存在或读取错误时返回空集合
+        }
+        return uids;
+    }
+
+    // 保存已处理的UID
+    private static void saveProcessedUIDs(Set<Long> uids) {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(UID_FILE_PATH))) {
+            for (Long uid : uids) {
+                writer.write(uid.toString());
+                writer.newLine();
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static void saveAttachment(BodyPart bodyPart) throws Exception {
+        String fileName = bodyPart.getFileName();
+        int size = bodyPart.getSize();
+        InputStream is = bodyPart.getInputStream();
+//        File file = new File("attachments/" + fileName); // 存储附件的文件夹路径
+//        try (FileOutputStream fos = new FileOutputStream(file)) {
+//            byte[] buffer = new byte[4096];
+//            int bytesRead;
+//            while ((bytesRead = is.read(buffer)) != -1) {
+//                fos.write(buffer, 0, bytesRead);
+//            }
+//        }
+        System.out.println("Saved attachment: " + fileName);
+    }
+
+
+    private static String parseMultipart(Multipart multipart) throws Exception {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0; i < multipart.getCount(); i++) {
+            BodyPart bodyPart = multipart.getBodyPart(i);
+            if (bodyPart.isMimeType("text/plain")) {
+                stringBuilder.append(bodyPart.getContent());
+            } else if (bodyPart.isMimeType("text/html")) {
+                stringBuilder.append(bodyPart.getContent());
+            } else if (bodyPart.isMimeType("multipart/alternative")) {
+                parseMultipart((Multipart) bodyPart.getContent());
+            } else if (bodyPart.isMimeType("multipart/*")) {
+                parseMultipart((Multipart) bodyPart.getContent());
+            }else if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
+                saveAttachment(bodyPart);
+            }
+        }
+        return stringBuilder.toString();
+    }
+}

+ 53 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailSender.java

@@ -0,0 +1,53 @@
+package com.storlead.mail;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-24 17:08
+ */
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.util.Properties;
+
+public class QQEmailSender {
+
+
+    public static void main(String[] args) {
+        String host = "smtp.exmail.qq.com"; // QQ邮箱SMTP服务器地址
+        String port = "465"; // QQ邮箱SMTP服务器端口
+        String userName = "chenkaiqiang@lcjs43.wecom.work"; // 你的QQ邮箱地址
+//        String password = "-Chenkq1992";  // 你的QQ邮箱授权码
+        String password = "-Chenkq1992";  // 你的QQ邮箱授权码
+
+        Properties properties = new Properties();
+        properties.put("mail.smtp.host", host);
+        properties.put("mail.smtp.port", port);
+        properties.put("mail.smtp.auth", "true");
+        properties.put("mail.user", userName);
+        properties.put("mail.smtp.starttls.enable", "true");
+        properties.put("mail.smtp.ssl.enable", "true");
+
+        Authenticator auth = new Authenticator() {
+            public PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(userName, password);
+            }
+        };
+
+        Session session = Session.getInstance(properties, auth);
+
+        try {
+            Message message = new MimeMessage(session);
+            message.setFrom(new InternetAddress(userName));
+            message.setRecipients(Message.RecipientType.TO, InternetAddress.parse("1217276730@qq.com"));
+            message.setSubject("Test Subject");
+            message.setText("Hello, this is a test email!");
+            Transport.send(message);
+            System.out.println("Email sent successfully!");
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+}
+

+ 129 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnection.java

@@ -0,0 +1,129 @@
+package com.storlead.mail.connection;
+
+import com.sun.mail.imap.IMAPFolder;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import java.util.Objects;
+import java.util.Properties;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 14:55
+ */
+@Log4j2
+public class IMAPConnection implements MailConnection {
+    private  Store store;
+    private  Session session;
+    private  String host;
+    private  String port;
+    private  String user;
+    private  String password;
+    private  Boolean tls;
+    private  Boolean ssl;
+    private  IMAPFolder folder;
+
+    public IMAPConnection(String host, String port, String user, String password,Boolean tls,Boolean ssl) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = tls;
+        this.ssl = ssl;
+    }
+
+    public IMAPConnection(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = true;
+        this.ssl = true;
+    }
+
+    @Override
+    public void connect() throws MessagingException {
+        Properties properties = new Properties();
+        properties.put("mail.store.protocol", "imap");
+        properties.put("mail.imap.host", host);
+        properties.put("mail.imap.port", port);
+        properties.put("mail.imap.starttls.enable",tls);
+        properties.put("mail.imap.ssl.enable", ssl);
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        };
+        this.session = Session.getInstance(properties,auth);
+        this.store = session.getStore("imap");
+        this.store.connect();
+    }
+
+    @Override
+    public void close() {
+        try {
+            if (this.store != null && this.store.isConnected()) {
+                this.store.close();
+            }
+            if (this.folder != null) {
+                this.folder.close();
+            }
+            if (this.session != null) {
+                this.session = null;
+            }
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public Store getStore() {
+        return this.store;
+    }
+
+    @Override
+    public IMAPFolder getFolder(String folderName) {
+        try {
+            if (Objects.isNull(this.store)) {
+                return null;
+            }
+            this.folder = (IMAPFolder) this.store.getFolder(folderName);
+            return  this.folder;
+        }catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public Message getMessageByUid(String uid,String folderName) {
+        try {
+            if (Objects.isNull(this.folder)) {
+                this.folder = getFolder(folderName);
+            }
+            if (!this.folder.isOpen()) {
+                this.folder.open(Folder.READ_ONLY);
+            }
+            Message message = this.folder.getMessageByUID(Long.parseLong(uid));
+            return message;
+        } catch (MessagingException e) {
+            log.error("getMessageByUid -- error --",e);
+            return null;
+        }
+    }
+
+    @Override
+    public String getMessageUid(Message message) {
+        try {
+            Long uid = this.folder.getUID(message);
+            if (Objects.isNull(uid)) {
+                return null;
+            }
+            return String.valueOf(uid);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 27 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnectionFactory.java

@@ -0,0 +1,27 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 14:57
+ */
+public class IMAPConnectionFactory extends MailConnectionFactory {
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public IMAPConnectionFactory(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    @Override
+    public MailConnection createConnection() {
+        return new IMAPConnection(host, port, user, password);
+    }
+}
+

+ 21 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnection.java

@@ -0,0 +1,21 @@
+package com.storlead.mail.connection;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Store;
+
+public interface MailConnection {
+
+    void connect() throws MessagingException;
+
+    void close();
+
+    Store getStore();
+
+    Folder getFolder(String folderName);
+
+    Message getMessageByUid(String uid,String folderName);
+
+    String getMessageUid(Message message);
+}

+ 26 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnectionFactory.java

@@ -0,0 +1,26 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 14:56
+ */
+public abstract class MailConnectionFactory {
+    public abstract MailConnection createConnection();
+
+    public static MailConnectionFactory getFactory(String protocol, String host, String port, String user, String password) {
+        switch (protocol.toLowerCase()) {
+            case "imap":
+                return new IMAPConnectionFactory(host, port, user, password);
+            case "pop3":
+                return new POP3ConnectionFactory(host, port, user, password);
+            default:
+                throw new IllegalArgumentException("Unsupported protocol: " + protocol);
+        }
+    }
+
+    public static SMTPConnectionFactory getSMTPFactory(String host, String port, String user, String password) {
+        return new SMTPConnectionFactory(host, port, user, password);
+    }
+}

+ 126 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3Connection.java

@@ -0,0 +1,126 @@
+package com.storlead.mail.connection;
+
+import com.sun.mail.pop3.POP3Folder;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import javax.mail.search.MessageIDTerm;
+import javax.mail.search.SearchTerm;
+import java.util.Objects;
+import java.util.Properties;
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:14
+ */
+@Log4j2
+public class POP3Connection implements MailConnection {
+    private  Store store;
+    private  Session session;
+    private  String host;
+    private  String port;
+    private  String user;
+    private  String password;
+    private  Boolean tls;
+    private  Boolean ssl;
+    private  POP3Folder folder;
+
+    public POP3Connection(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = true;
+        this.ssl = true;
+    }
+
+    public POP3Connection(String host, String port, String user, String password,Boolean tls,Boolean ssl) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+        this.tls = tls;
+        this.ssl = ssl;
+    }
+    @Override
+    public void connect() throws MessagingException {
+        Properties properties = new Properties();
+        properties.put("mail.store.protocol", "pop3");
+        properties.put("mail.pop3.host", host);
+        properties.put("mail.pop3.port", port);
+        properties.put("mail.pop3.starttls.enable", "true");
+        properties.put("mail.pop3.ssl.enable", "true");
+
+        Authenticator auth = new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        };
+        this.session = Session.getInstance(properties,auth);
+        this.store = session.getStore("pop3");
+        this.store.connect();
+    }
+
+    @Override
+    public void close() {
+        try {
+            if (this.store != null && this.store.isConnected()) {
+                this.store.close();
+            }
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public Store getStore() {
+        return this.store;
+    }
+
+    @Override
+    public POP3Folder getFolder(String folderName) {
+        try {
+            if (Objects.isNull(this.store)) {
+                log.error("store is null --- address ---"+this.user);
+                return null;
+            }
+            this.folder = (POP3Folder)this.store.getFolder(folderName);
+            return this.folder;
+        }catch (Exception e) {
+            log.error("store is null --- Exception ---"+this.user,e);
+            return null;
+        }
+    }
+
+    @Override
+    public Message getMessageByUid(String messageId,String folderName) {
+        try {
+            if (Objects.isNull(this.folder)) {
+                this.folder = getFolder(folderName);
+            }
+            SearchTerm searchTerm = new MessageIDTerm(messageId);
+            if (!this.folder.isOpen()) {
+                this.folder.open(Folder.READ_ONLY);
+            }
+            Message [] messages = folder.search(searchTerm);
+            if (messages.length > 0) {
+                return messages[0];
+            }
+            return null;
+        } catch (MessagingException e) {
+            log.error("getMessageByUid -- error",e);
+            return null;
+        }
+    }
+
+    @Override
+    public String getMessageUid(Message message) {
+        try {
+           return this.folder.getUID(message);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}
+

+ 26 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3ConnectionFactory.java

@@ -0,0 +1,26 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:22
+ */
+public class POP3ConnectionFactory extends MailConnectionFactory {
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public POP3ConnectionFactory(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    @Override
+    public MailConnection createConnection() {
+        return new POP3Connection(host, port, user, password);
+    }
+}

+ 53 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnection.java

@@ -0,0 +1,53 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:23
+ */
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import java.util.Properties;
+
+public class SMTPConnection {
+    private Session session;
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public SMTPConnection(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    public void connect() {
+        Properties properties = new Properties();
+        properties.put("mail.smtp.auth", "true");
+        properties.put("mail.smtp.starttls.enable", "true");
+        properties.put("mail.smtp.host", host);
+        properties.put("mail.smtp.port", port);
+        properties.put("mail.smtp.ssl.enable", "true");
+
+        session = Session.getInstance(properties, new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        });
+    }
+
+    public void sendEmail(String toAddress, String subject, String message) throws MessagingException {
+        Message mimeMessage = new MimeMessage(session);
+        mimeMessage.setFrom(new InternetAddress(user));
+        mimeMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toAddress));
+        mimeMessage.setSubject(subject);
+        mimeMessage.setText(message);
+        Transport.send(mimeMessage);
+    }
+}
+

+ 25 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnectionFactory.java

@@ -0,0 +1,25 @@
+package com.storlead.mail.connection;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 15:23
+ */
+public class SMTPConnectionFactory {
+    private final String host;
+    private final String port;
+    private final String user;
+    private final String password;
+
+    public SMTPConnectionFactory(String host, String port, String user, String password) {
+        this.host = host;
+        this.port = port;
+        this.user = user;
+        this.password = password;
+    }
+
+    public SMTPConnection createConnection() {
+        return new SMTPConnection(host, port, user, password);
+    }
+}

+ 93 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailConnectionUtil.java

@@ -0,0 +1,93 @@
+package com.storlead.mail.connection.client;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:26
+ */
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.MailConnectionFactory;
+import com.storlead.mail.connection.*;
+import com.storlead.mail.connection.config.MailProperties;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Log4j2
+public class MailConnectionUtil {
+
+    public static ConcurrentHashMap<String, MailConnection> chatMailConnectionMap = new ConcurrentHashMap<>();
+    // Method to receive emails using IMAP
+
+    public static MailConnection receiveEmailsConnection(MailProperties properties) {
+        try {
+//            if (Objects.nonNull(chatMailConnectionMap.get(properties.getEmailAddress()+"_"+properties.get))) {
+//                return chatMailConnectionMap.get(properties.getEmailAddress());
+//            }
+            MailConnectionFactory factory = MailConnectionFactory.getFactory(properties.getProtocol(),properties.getHost(),properties.getPort(),properties.getEmailAddress(),properties.getEmailPassword());
+            MailConnection imapConnection  = factory.createConnection();
+            log.error("mailConnection -- testImap == "+properties.getProtocol()+"------"+imapConnection);
+            imapConnection.connect();
+//            chatMailConnectionMap.put(properties.getEmailAddress(),imapConnection);
+            return imapConnection;
+        } catch (MessagingException e) {
+//            e.printStackTrace();
+            log.error("connection error mail --"+properties.getEmailAddress(),e);
+//            chatMailConnectionMap.remove(properties.getEmailAddress());
+            return null;
+        }
+    }
+
+    public static MailConnection testReceiveEmailsConnection(MailProperties properties) {
+        try {
+            MailConnectionFactory factory = MailConnectionFactory.getFactory(properties.getProtocol(),properties.getHost(),properties.getPort(),properties.getEmailAddress(),properties.getEmailPassword());
+            MailConnection imapConnection  = factory.createConnection();
+            log.error("mailConnection -- testImap == "+properties.getProtocol()+"------"+imapConnection);
+            imapConnection.connect();
+            return imapConnection;
+        } catch (MessagingException e) {
+//            e.printStackTrace();
+            log.error("connection error mail --"+properties.getEmailAddress(),e);
+            return null;
+        }
+    }
+
+    public static MailConnection deleteEmailsConnection(MailProperties properties) {
+        try {
+            MailConnectionFactory factory = MailConnectionFactory.getFactory(properties.getProtocol(),properties.getHost(),properties.getPort(),properties.getEmailAddress(),properties.getEmailPassword());
+            MailConnection imapConnection  = factory.createConnection();
+            imapConnection.connect();
+            return imapConnection;
+        } catch (MessagingException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+//    public static void main(String[] args) {
+//        String imapHost = "imap.exmail.qq.com";
+//        String pop3Host = "pop.exmail.qq.com";
+//        String smtpHost = "smtp.exmail.qq.com";
+//        String port = "993";
+//        String smtpPort = "465";
+//        String user = "your_email@example.com"; // Replace with your email
+//        String password = "your_password"; // Replace with your password
+//
+//        try {
+//            System.out.println("Receiving emails using IMAP:");
+//            receiveEmailsIMAP(imapHost, port, user, password);
+//
+//            System.out.println("Receiving emails using POP3:");
+//            receiveEmailsPOP3(pop3Host, port, user, password);
+//
+//            System.out.println("Sending email using SMTP:");
+//            sendEmailSMTP(smtpHost, smtpPort, user, password,
+//                    "recipient@example.com", "Test Subject", "Test Message");
+//
+//        } catch (Exception e) {
+//            e.printStackTrace();
+//        }
+//    }
+}
+

+ 48 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailReceiverUtil.java

@@ -0,0 +1,48 @@
+package com.storlead.mail.connection.client;
+
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.MailConnectionFactory;
+
+import javax.mail.*;
+import java.util.Arrays;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:31
+ */
+
+public class MailReceiverUtil {
+    public static void receiveInboxEmails(String protocol, String host, String port, String user, String password) {
+        receiveEmails(protocol, host, port, user, password, "INBOX");
+    }
+
+    public static void receiveSentEmails(String protocol, String host, String port, String user, String password) {
+        receiveEmails(protocol, host, port, user, password, "Sent");
+    }
+
+    private static void receiveEmails(String protocol, String host, String port, String user, String password, String folderName) {
+        MailConnectionFactory connectionFactory = MailConnectionFactory.getFactory(protocol, host, port, user, password);
+        MailConnection mailConnection = connectionFactory.createConnection();
+        try {
+            mailConnection.connect();
+            Store store = mailConnection.getStore();
+            Folder folder = store.getFolder(folderName);
+            folder.open(Folder.READ_WRITE);
+            Message[] messages = folder.getMessages();
+            for (Message message : messages) {
+                System.out.println(protocol.toUpperCase() + " - Subject: " + message.getSubject());
+                System.out.println(protocol.toUpperCase() + " - From: " + Arrays.toString(message.getFrom()));
+                System.out.println("------------------------------------------------");
+            }
+
+            folder.close(false);
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        } finally {
+            mailConnection.close();
+        }
+    }
+}
+

+ 38 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailSenderUtil.java

@@ -0,0 +1,38 @@
+package com.storlead.mail.connection.client;
+
+import com.storlead.mail.connection.MailConnectionFactory;
+import com.storlead.mail.connection.SMTPConnection;
+import com.storlead.mail.connection.SMTPConnectionFactory;
+
+import javax.mail.MessagingException;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:34
+ */
+public class MailSenderUtil {
+    public static void sendEmail(String host, String port, String user, String password,
+                                 String toAddress, String subject, String messageContent) {
+        SMTPConnectionFactory smtpFactory = MailConnectionFactory.getSMTPFactory(host, port, user, password);
+        SMTPConnection smtpConnection = smtpFactory.createConnection();
+        try {
+            smtpConnection.connect();
+            smtpConnection.sendEmail(toAddress, subject, messageContent);
+        } catch (MessagingException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void main(String[] args) {
+        String smtpHost = "smtp.exmail.qq.com";
+        String smtpPort = "465";
+        String user = "your_email@example.com"; // Replace with your email
+        String password = "your_password"; // Replace with your password
+
+        System.out.println("Sending email using SMTP:");
+        sendEmail(smtpHost, smtpPort, user, password,
+                "recipient@example.com", "Test Subject", "Test Message");
+    }
+}

+ 43 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/config/MailProperties.java

@@ -0,0 +1,43 @@
+package com.storlead.mail.connection.config;
+
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import lombok.Data;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 16:59
+ */
+@Data
+public class MailProperties {
+
+    private String protocol;
+
+    private String host;
+
+    private String port;
+
+    private String emailAddress;
+
+    private String emailPassword;
+
+    private Boolean tls;
+
+    private Boolean ssl;
+
+
+    public MailProperties(SmtpPopSettingsEntity smtpPop) {
+        AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+        String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+        this.host = smtpPop.getReceiveServer() ;
+        this.port = smtpPop.getReceivePort();
+        this.emailAddress = smtpPop.getEmailAddress();
+        this.emailPassword = pass;
+        this.tls = Integer.valueOf(1).equals(smtpPop.getSmtpTls()) ? Boolean.TRUE : Boolean.FALSE;
+        this.ssl = Integer.valueOf(1).equals(smtpPop.getSmtpSsl()) ? Boolean.TRUE : Boolean.FALSE;
+        this.protocol = smtpPop.getProtocolType();
+    }
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/AttachmentMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.AttachmentEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 附件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface AttachmentMapper extends MyBaseMapper<AttachmentEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/ClientSentEmailsMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.ClientSentEmailsEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-12-04
+ */
+public interface ClientSentEmailsMapper extends MyBaseMapper<ClientSentEmailsEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailBlacklistRecordMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱黑名单 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-31
+ */
+public interface EmailBlacklistRecordMapper extends MyBaseMapper<EmailBlacklistRecordEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFileFolderMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailFileFolderEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 文件夹 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-04
+ */
+public interface EmailFileFolderMapper extends MyBaseMapper<EmailFileFolderEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFolderRuleMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailFolderRuleEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 用户邮件文件夹出入规则 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+public interface EmailFolderRuleMapper extends MyBaseMapper<EmailFolderRuleEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFoldersMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailFoldersEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件文件夹关系表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface EmailFoldersMapper extends MyBaseMapper<EmailFoldersEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailTemplatesMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.EmailTemplatesEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件模板 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-28
+ */
+public interface EmailTemplatesMapper extends MyBaseMapper<EmailTemplatesEntity> {
+
+}

+ 107 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailsMapper.java

@@ -0,0 +1,107 @@
+package com.storlead.mail.mapper;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.mail.pojo.MailDTO;
+import com.storlead.mail.pojo.MailListDTO;
+import com.storlead.mail.pojo.vo.*;
+import com.storlead.mail.pojo.vo.*;
+import org.apache.ibatis.annotations.Param;
+
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * 邮件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface EmailsMapper extends MyBaseMapper<EmailsEntity> {
+
+    void updateCleanBindCustomerMail();
+
+    void updateBindCustomerMail();
+
+    void updateBindLiaisonMail();
+
+    /**
+     * 绑定客户邮件关系
+     * @param mailId
+     * @param mailAddress
+     * @param userId
+     */
+    Set<Long> getMailCustomerIdByMailIdAndUserId(@Param("mailId") Long mailId, @Param("mailAddress") String mailAddress, @Param("userId") Long userId);
+
+    /**
+     * 绑定联系人邮箱和客户关系
+     * @param mailId
+     * @param mailAddress
+     * @param userId
+     */
+    List<MailLiaisonVO> getLiaisonMailCustomerIdByMailIdAndUserId(@Param("mailId") Long mailId, @Param("mailAddress") String mailAddress, @Param("userId") Long userId);
+
+    void bindMailCustomerIdByMailId(@Param("mailId") Long mailId,@Param("customerIds") String customerId,@Param("liaisonIds") String liaisonIds);
+
+    EmailsEntity getEmailsByMessageId(@Param("messageId") String messageId,@Param("folder") String folder,@Param("smtpPopId") Long smtpPopId);
+
+    List<String> getEmailMessageIdsByMessageIds(@Param("messageIds") List<String> messageIds,@Param("folder") String folder,@Param("smtpPopId") Long smtpPopId);
+
+    String getEmailMessageIdsByMessageId(@Param("messageId") String messageId,@Param("folder") String folder,@Param("smtpPopId") Long smtpPopId);
+
+    Long getEmailsIdByMessageId(@Param("messageId") String messageId,@Param("smtpPopId") Long smtpPopId);
+
+    IPage<EmailsEntity> pageList(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+
+    IPage<EmailsEntity> pageListCus(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    IPage<EmailsEntity> pageListAllCus(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    IPage<EmailsEntity> pageListNew(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    IPage<EmailsEntity> pageListNewAllCus(IPage<EmailsEntity> page, @Param("dto") MailDTO dto);
+
+    /**
+     * 汇总邮件数量
+     * @param stmpPopIdk
+     * @param ownerBy
+     * @return
+     */
+    NewMailCountTipVO countMailSummaryNum(Long smtpPopId, Long ownerBy);
+
+    /**
+     * 汇总未跟进数
+     * @param stmpPopId
+     * @param ownerBy
+     * @return
+     */
+    NewMailCountTipVO countCustomerAndClueNumber(Long smtpPopId, Long ownerBy);
+
+    /**
+     * 汇总客户邮件数
+     * @param stmpPopId
+     * @param ownerBy
+     * @return
+     */
+    NewMailCountTipVO countCustomerFollowUpMailNum(Long smtpPopId, Long ownerBy);
+
+    LocalDateTime selectCustomerLastFollowUpTime(Long customerId);
+
+    List<EmailsEntity> selectDelayDeleteMail();
+
+    List<UserClickTitleVO> selectUserClickTitle(@Param("ownerBy") Long ownerBy, @Param("title") String title);
+
+    IPage<CustomerEmailVo> selectCustomerEmailPage(Page<CustomerEmailVo> page, @Param("query") MailListDTO dto);
+
+    List<Long> selectCusIdByMailId(@Param("mailIdls") List<Long> mailIdls);
+
+    List<CustomerMailVO> selectCusInfoByCusId(@Param("customerIdls") List<Long> customerIdls);
+
+    Integer countSmtpMailCount(@Param("userId") Long userId);
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/FoldersMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.FoldersEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 文件夹名称 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface FoldersMapper extends MyBaseMapper<FoldersEntity> {
+
+}

+ 23 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailAttachmentMapper.java

@@ -0,0 +1,23 @@
+package com.storlead.mail.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.mail.pojo.entity.MailAttachmentEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * <p>
+ * 邮件附件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-06-14
+ */
+public interface MailAttachmentMapper extends MyBaseMapper<MailAttachmentEntity> {
+
+    IPage<MailAttachmentEntity> pageList(Page<MailAttachmentEntity> page, @Param(Constants.WRAPPER) Wrapper<MailAttachmentEntity> wrapper);
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailDelayPullRecordMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailDelayPullRecordEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮件等待处理记录 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-24
+ */
+public interface MailDelayPullRecordMapper extends MyBaseMapper<MailDelayPullRecordEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailServerPortConfigMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailServerPortConfigEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱端口配置 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-08
+ */
+public interface MailServerPortConfigMapper extends MyBaseMapper<MailServerPortConfigEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailTempAttachmentMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailTempAttachmentEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 临时邮件附件表 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-09
+ */
+public interface MailTempAttachmentMapper extends MyBaseMapper<MailTempAttachmentEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailboxAutoReplySetMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱自动回复内容 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-02-24
+ */
+public interface MailboxAutoReplySetMapper extends MyBaseMapper<MailboxAutoReplySetEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/SmtpPopSettingsMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 邮箱smtp_setting Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+public interface SmtpPopSettingsMapper extends MyBaseMapper<SmtpPopSettingsEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserEmailFolderMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.UserEmailFolderEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 用户文件夹 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+public interface UserEmailFolderMapper extends MyBaseMapper<UserEmailFolderEntity> {
+
+}

+ 16 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserMailTrackRecordMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.mail.mapper;
+
+import com.storlead.mail.pojo.entity.UserMailTrackRecordEntity;
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+
+/**
+ * <p>
+ * 当前收取邮件的最新邮件记录 Mapper 接口
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-19
+ */
+public interface UserMailTrackRecordMapper extends MyBaseMapper<UserMailTrackRecordEntity> {
+
+}

+ 57 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/properties/MailFileProperties.java

@@ -0,0 +1,57 @@
+package com.storlead.mail.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-06-14 18:28
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "file")
+public class MailFileProperties {
+
+    /** 文件大小限制 */
+    private Long maxSize;
+
+    /** 头像大小限制 */
+    private Long avatarMaxSize;
+
+    /** 0:本地,1:OSS */
+    private Integer mode = 0;
+
+    /** 环境 */
+    private String active = "";
+
+    private ElPath mac;
+
+    private String filePath;
+
+    private ElPath linux;
+
+    private ElPath windows;
+
+    public ElPath getPath(){
+        String os = System.getProperty("os.name");
+        if(os.toLowerCase().startsWith("win")) {
+            return windows;
+        } else if(os.toLowerCase().startsWith("mac")){
+            return mac;
+        }
+        return linux;
+    }
+
+    @Data
+    public static class ElPath{
+
+        private String path;
+
+        private String avatar;
+
+        private String jpeg;
+    }
+}

+ 14 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AbstractMailFunctionApiFactory.java

@@ -0,0 +1,14 @@
+package com.storlead.mail.service.impl;
+
+/**
+ * @program: sp-sales-platform
+ * @description: 抽象邮件工厂类
+ * @author: chenkq
+ * @create: 2024-05-29 10:16
+ */
+public abstract class  AbstractMailFunctionApiFactory {
+
+    // 收邮件
+
+    // 发送邮件
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AttachmentServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.AttachmentService;
+import com.storlead.mail.pojo.entity.AttachmentEntity;
+import com.storlead.mail.mapper.AttachmentMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 附件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Service
+public class AttachmentServiceImpl extends MyBaseServiceImpl<AttachmentMapper, AttachmentEntity> implements AttachmentService {
+
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/ClientSentEmailsServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.ClientSentEmailsService;
+import com.storlead.mail.pojo.entity.ClientSentEmailsEntity;
+import com.storlead.mail.mapper.ClientSentEmailsMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-12-04
+ */
+@Service
+public class ClientSentEmailsServiceImpl extends MyBaseServiceImpl<ClientSentEmailsMapper, ClientSentEmailsEntity> implements ClientSentEmailsService {
+
+}

+ 38 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailBlacklistRecordServiceImpl.java

@@ -0,0 +1,38 @@
+package com.storlead.mail.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.mail.service.EmailBlacklistRecordService;
+import com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity;
+import com.storlead.mail.mapper.EmailBlacklistRecordMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 邮箱黑名单 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-31
+ */
+@Service
+public class EmailBlacklistRecordServiceImpl extends MyBaseServiceImpl<EmailBlacklistRecordMapper, EmailBlacklistRecordEntity> implements EmailBlacklistRecordService {
+
+    @Override
+    public Set<String> getBlackEmaillist(Long userId) {
+        LambdaQueryWrapper<EmailBlacklistRecordEntity> queryWrapper = new LambdaQueryWrapper();
+        queryWrapper.eq(EmailBlacklistRecordEntity::getOwnerBy,userId);
+        queryWrapper.eq(EmailBlacklistRecordEntity::getIsDelete,Integer.valueOf(0));
+        List<EmailBlacklistRecordEntity> blacklist = list(queryWrapper);
+        if(CollectionUtils.isEmpty(blacklist)) {
+            return Collections.emptySet();
+        }
+        return blacklist.stream().map(EmailBlacklistRecordEntity::getEmailAddress).collect(Collectors.toSet());
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFileFolderServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.EmailFileFolderService;
+import com.storlead.mail.pojo.entity.EmailFileFolderEntity;
+import com.storlead.mail.mapper.EmailFileFolderMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 文件夹 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-04
+ */
+@Service
+public class EmailFileFolderServiceImpl extends MyBaseServiceImpl<EmailFileFolderMapper, EmailFileFolderEntity> implements EmailFileFolderService {
+
+}

+ 77 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFolderRuleServiceImpl.java

@@ -0,0 +1,77 @@
+package com.storlead.mail.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.mail.service.EmailFolderRuleService;
+import com.storlead.mail.pojo.entity.EmailFolderRuleEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.enums.EmailFolderlRuleEnum;
+import com.storlead.mail.mapper.EmailFolderRuleMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * <p>
+ * 用户邮件文件夹出入规则 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@Service
+public class EmailFolderRuleServiceImpl extends MyBaseServiceImpl<EmailFolderRuleMapper, EmailFolderRuleEntity> implements EmailFolderRuleService {
+
+    @Override
+    public List<EmailFolderRuleEntity> getUserFolderRules(Long userId) {
+        LambdaQueryWrapper<EmailFolderRuleEntity> queryWrapper = new LambdaQueryWrapper();
+        if (Objects.nonNull(userId)) {
+            queryWrapper.eq(EmailFolderRuleEntity::getOwnerBy,userId);
+        }
+        queryWrapper.eq(EmailFolderRuleEntity::getIsDelete,Integer.valueOf(0));
+        queryWrapper.orderByAsc(EmailFolderRuleEntity::getSort);
+        List<EmailFolderRuleEntity> list = this.list(queryWrapper);
+        return list;
+    }
+
+    @Override
+    public void carveUpMailCustomFolder(EmailsEntity emails,List<EmailFolderRuleEntity> rolderRules) {
+        if (CollectionUtils.isEmpty(rolderRules)) {
+            return;
+        }
+        for (EmailFolderRuleEntity rule : rolderRules) {
+            if (EmailFolderlRuleEnum.EMAIL_FROM_EQ.code.equals(rule.getFolderRuleCode())) {
+                // 发件箱等于
+                if (emails.getFrom().equals(rule.getCompareContent())) {
+                    emails.setCustomFolderId(rule.getFolderId());
+                    break;
+                }
+            } else if (EmailFolderlRuleEnum.EMAIL_SUBJECT_CONTAINS.code.equals(rule.getFolderRuleCode())) {
+                // 邮件主题包含
+                if (emails.getSubject().contains(rule.getCompareContent())) {
+                    emails.setCustomFolderId(rule.getFolderId());
+                    break;
+                }
+            } else if (EmailFolderlRuleEnum.EMAIL_FROM_SUFFIX_EQ.code.equals(rule.getFolderRuleCode())) {
+                //发件箱后缀等于
+                String domainRegex = "@(.+)$";
+                // 创建 Pattern 对象
+                Pattern pattern = Pattern.compile(domainRegex);
+                // 创建 Matcher 对象
+                Matcher matcher = pattern.matcher(emails.getFrom());
+                // 查找并提取 @ 后面的部分
+                if (matcher.find()) {
+                    String domain = matcher.group(1);  // 获取 @ 后面的部分
+                    if (rule.getCompareContent().equals(domain)) {
+                        emails.setCustomFolderId(rule.getFolderId());
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFoldersServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.EmailFoldersService;
+import com.storlead.mail.pojo.entity.EmailFoldersEntity;
+import com.storlead.mail.mapper.EmailFoldersMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件文件夹关系表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Service
+public class EmailFoldersServiceImpl extends MyBaseServiceImpl<EmailFoldersMapper, EmailFoldersEntity> implements EmailFoldersService {
+
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailTemplatesServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.EmailTemplatesService;
+import com.storlead.mail.pojo.entity.EmailTemplatesEntity;
+import com.storlead.mail.mapper.EmailTemplatesMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件模板 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-10-28
+ */
+@Service
+public class EmailTemplatesServiceImpl extends MyBaseServiceImpl<EmailTemplatesMapper, EmailTemplatesEntity> implements EmailTemplatesService {
+
+}

+ 1959 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailsServiceImpl.java

@@ -0,0 +1,1959 @@
+package com.storlead.mail.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.framework.common.util.*;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
+import com.storlead.framework.redis.RedisService;
+import com.storlead.mail.connection.MailConnection;
+import com.storlead.mail.connection.client.MailConnectionUtil;
+import com.storlead.mail.connection.config.MailProperties;
+import com.storlead.mail.pojo.entity.*;
+import com.storlead.mail.pojo.vo.CustomerEmailVo;
+import com.storlead.mail.pojo.vo.CustomerMailVO;
+import com.storlead.mail.pojo.vo.NewMailCountTipVO;
+import com.storlead.mail.pojo.vo.UserClickTitleVO;
+import com.storlead.mail.properties.MailFileProperties;
+import com.storlead.mail.enums.EmailBoxEnum;
+import com.storlead.mail.enums.SendStatus;
+import com.storlead.mail.mapper.EmailsMapper;
+import com.storlead.mail.pojo.MailDTO;
+import com.storlead.mail.pojo.MailListDTO;
+import com.storlead.mail.pojo.SendMailDTO;
+import com.storlead.mail.service.*;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.mail.util.EmailHelper;
+import com.storlead.mail.util.EmailSenderWithThreadLocal;
+import com.storlead.mail.util.ReceiveMailQueueThreadPool;
+import com.sun.mail.imap.IMAPFolder;
+import com.sun.mail.pop3.POP3Folder;
+import lombok.extern.log4j.Log4j2;
+import org.apache.commons.compress.utils.FileNameUtils;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.annotation.Resource;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+import java.io.*;
+import java.lang.reflect.Method;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.stream.Collectors;
+
+/**
+ * <p>
+ * 邮件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Log4j2
+@Service
+public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEntity> implements EmailsService {
+
+
+    private final ConcurrentMap<String, Boolean> processedMessages = new ConcurrentHashMap<>();
+
+    private static final ConcurrentHashMap<Long, Lock> userLockMap = new ConcurrentHashMap<>();
+
+    /**
+     * 附件下载控制
+     */
+    private final ConcurrentMap<Long, Boolean> processedDownloadMailIds = new ConcurrentHashMap<>();
+
+    @Resource
+    private SmtpPopSettingsService popSettingsService;
+
+    @Resource
+    private MailFileProperties mailFileProperties;
+
+    @Resource
+    private MailAttachmentService mailAttachmentService;
+
+    @Resource
+    private MailTempAttachmentService tempAttachmentService;
+
+    @Resource
+    private MailDelayPullRecordService delayPullRecordService;
+
+    @Resource
+    private EmailSenderWithThreadLocal senderWithThreadLocal;
+
+    @Resource
+    private RedisService redisService;
+
+    @Resource
+    private EmailBlacklistRecordService blackListService;
+
+    @Resource
+    private EmailFolderRuleService folderRuleService;
+
+    @Resource
+    private Environment environment;
+
+    @Override
+    public void updateSelectBindCustomerMail() {
+        //   this.baseMapper.updateCleanBindCustomerMail();
+        //   this.baseMapper.updateBindLiaisonMail();
+        //   this.baseMapper.updateBindCustomerMail();
+    }
+
+    @Override
+    public SendStatus sendEmail(SendMailDTO dto, SmtpPopSettingsEntity smtpPop) {
+//        String active = environment.getProperty("spring.profiles.active");
+//        if("dev".equals(active)) {
+//            return SendStatus.SUCCESS;
+//        }
+        try {
+            if(Objects.isNull(dto.getRecipientCcls())) {
+                dto.setRecipientCcls(new ArrayList<>());
+            }
+            if(Objects.isNull(dto.getRecipientBccls())) {
+                dto.setRecipientBccls(new ArrayList<>());
+            }
+            if(Objects.isNull(dto.getRecipientls())) {
+                dto.setRecipientls(new ArrayList<>());
+            }
+            String fromAddress = smtpPop.getEmailAddress();
+            String fromNickName = smtpPop.getSenderNickName();
+
+            Session session = getSendEmailsSession(smtpPop);
+            InternetAddress [] froms = new InternetAddress[dto.getRecipientls().size()];
+            for (int i = 0; i < dto.getRecipientls().size(); i++) {
+                froms[i] = new InternetAddress(dto.getRecipientls().get(i));
+            }
+            InternetAddress [] fromsCC = new InternetAddress[dto.getRecipientCcls().size()];
+            for (int i = 0; i < dto.getRecipientCcls().size(); i++) {
+                fromsCC[i] = new InternetAddress(dto.getRecipientCcls().get(i));
+            }
+
+            InternetAddress [] fromsBCC = new InternetAddress[dto.getRecipientBccls().size()];
+            for (int i = 0; i < dto.getRecipientBccls().size(); i++) {
+                fromsBCC[i] = new InternetAddress(dto.getRecipientBccls().get(i));
+            }
+            Transport transport = session.getTransport("smtp");
+            int sizeInBytes = 0;
+            try {
+                MimeMessage message = new MimeMessage(session);
+                if (Objects.nonNull(froms) && froms.length > 0) {
+                    message.setRecipients(Message.RecipientType.TO,froms);
+                }
+                if (Objects.nonNull(fromsCC) && fromsCC.length > 0) {
+                    message.setRecipients(Message.RecipientType.CC,fromsCC);
+                }
+                if (Objects.nonNull(fromsBCC) && fromsBCC.length > 0) {
+                    message.setRecipients(Message.RecipientType.BCC,fromsBCC);
+                }
+                if (CollectionUtils.isEmpty(dto.getFileIds()) && CollectionUtils.isEmpty(dto.getOldfileIds())) {
+                    message.setFrom(new InternetAddress(fromAddress));
+                    message.setSubject(dto.getSubject());
+
+                    Multipart multipart = new MimeMultipart();
+//                    // 消息正文
+                    BodyPart messageBodyPart1 = new MimeBodyPart();
+                    List<MimeBodyPart> imageParts = new ArrayList<>();
+                    String updatedHtmlContent = EmailHelper.convertImageUrlsToCidBodyPart(dto.getContent(),imageParts,mailFileProperties.getPath().getJpeg(),multipart);
+                    messageBodyPart1.setContent(updatedHtmlContent, "text/html; charset=UTF-8");
+                    multipart.addBodyPart(messageBodyPart1);
+                    // 发送完整的消息部分
+                    message.setContent(multipart);
+                    // 获取邮件大小
+                    sizeInBytes = dto.getContent().length();
+
+                } else {
+
+                    message.setFrom(new InternetAddress(fromAddress));
+
+                    message.setDescription(dto.getRemark());
+                    // 设置邮件主题
+                    message.setSubject(dto.getSubject());
+                    // 创建消息部分
+                    Multipart multipart = new MimeMultipart();
+//                    // 消息正文
+                    BodyPart messageBodyPart1 = new MimeBodyPart();
+
+                    List<MimeBodyPart> imageParts = new ArrayList<>();
+                    String updatedHtmlContent = EmailHelper.convertImageUrlsToCidBodyPart(dto.getContent(),imageParts,mailFileProperties.getPath().getJpeg(),multipart);
+                    messageBodyPart1.setContent(updatedHtmlContent, "text/html; charset=UTF-8");
+                    multipart.addBodyPart(messageBodyPart1);
+                    // 设置文本消息部分
+                    if (!CollectionUtils.isEmpty(dto.getFileIds())) {
+                        LambdaQueryWrapper fileWrapper = new LambdaQueryWrapper<MailTempAttachmentEntity>().in(MailTempAttachmentEntity::getId,dto.getFileIds());
+                        List<MailTempAttachmentEntity> tempAttachment = tempAttachmentService.list(fileWrapper);
+                        for (MailTempAttachmentEntity attachment : tempAttachment) {
+                            BodyPart messageBodyPart = new MimeBodyPart();
+                            String filePath =  mailFileProperties.getPath().getPath() + attachment.getFilePath();
+                            String fileName = attachment.getFileName()+"."+attachment.getFileExt();
+                            DataSource fileSource = new FileDataSource(filePath);
+                            messageBodyPart.setDataHandler(new DataHandler(fileSource));
+                            messageBodyPart.setFileName(fileName);
+                            multipart.addBodyPart(messageBodyPart);
+                        }
+                    }
+
+                    if (!CollectionUtils.isEmpty(dto.getOldfileIds())) {
+                        LambdaQueryWrapper fileMailWrapper = new LambdaQueryWrapper<MailTempAttachmentEntity>().in(MailTempAttachmentEntity::getId,dto.getOldfileIds());
+                        List<MailAttachmentEntity> mailAttachments = mailAttachmentService.list(fileMailWrapper);
+                        for (MailAttachmentEntity attachment : mailAttachments) {
+                            BodyPart messageBodyPart = new MimeBodyPart();
+                            String filePath =  mailFileProperties.getPath().getPath() + attachment.getFilePath();
+                            String fileName = attachment.getFileName()+"."+attachment.getFileExt();
+                            DataSource fileSource = new FileDataSource(filePath);
+                            messageBodyPart.setDataHandler(new DataHandler(fileSource));
+                            messageBodyPart.setFileName(fileName);
+                            multipart.addBodyPart(messageBodyPart);
+                        }
+                    }
+                    // 发送完整的消息部分
+                    message.setContent(multipart);
+                    // 获取邮件大小
+                    sizeInBytes = dto.getContent().length();
+                    // 发送消息
+                }
+                /**
+                 * 发送成功,存储邮箱 replyEmailId null
+                 */
+                if (Integer.valueOf(1).equals(dto.getIsReceipt())) {
+                    message.addHeader("Disposition-Notification-To", smtpPop.getEmailAddress());
+                }
+                Long replyMsgId = dto.getReplyMsgId();
+                EmailsEntity email =  saveSendEmails(dto,fromAddress,fromNickName,replyMsgId,Long.valueOf(sizeInBytes));
+                email.setIsOnlyHead(0);
+                email.setSmtpPopId(smtpPop.getId());
+                log.error("senderWithThreadLocal - saveOrUpdate----邮件");
+                if(this.saveOrUpdate(email)) {
+                    if (Objects.nonNull(email.getDelaySendTime())) {
+                        if (!CollectionUtils.isEmpty(dto.getFileIds())) {
+                            LambdaUpdateWrapper<MailTempAttachmentEntity> mt = new LambdaUpdateWrapper<>();
+                            mt.set(MailTempAttachmentEntity::getEmailId, email.getId());
+                            mt.in(MailTempAttachmentEntity::getId, dto.getFileIds());
+                            tempAttachmentService.update(mt);
+                        }
+                    } else {
+                        try {
+                            CompletableFuture<SendStatus> resultFuture = senderWithThreadLocal.sendMailMessage(transport, message, email);
+                            SendStatus result = resultFuture.get();
+                            return result;
+                        } catch (Exception e) {
+                            log.error("email -address --"+smtpPop.getEmailAddress()+"--send - error -",e);
+                            return SendStatus.SERVER_LOGIN_FAIL;
+                        }
+                    }
+                }
+            }catch (Exception e) {
+                log.error("getSendEmailsSession -error",e);
+                return SendStatus.FAILED;
+            }
+        } catch (Exception e) {
+            log.error("send sendMail -error",e);
+            return SendStatus.FAILED;
+        }
+        return SendStatus.SUCCESS;
+    }
+
+    @Override
+    public List<UserClickTitleVO> selectUserClickTitle(Long ownerBy, String title) {
+        return this.baseMapper.selectUserClickTitle(ownerBy,title);
+    }
+
+    @Override
+    public Boolean sendEmail(EmailsEntity emails,SmtpPopSettingsEntity smtpPop) {
+//        String active = environment.getProperty("spring.profiles.active");
+//        if("dev".equals(active)) {
+//            return true;
+//        }
+        try {
+
+            List<String> recipientls = new ArrayList<>();
+            if(StrUtil.isNotBlank(emails.getRecipient())) {
+                recipientls = Arrays.asList(emails.getRecipient().split(","));
+            }
+
+            List<String> recipientCcls = new ArrayList<>();
+            if(StrUtil.isNotBlank(emails.getRecipientCc())) {
+                recipientCcls = Arrays.asList(emails.getRecipientCc().split(","));
+            }
+            List<String> recipientBccls = new ArrayList<>();
+            if(StrUtil.isNotBlank(emails.getRecipientBcc())) {
+                recipientBccls = Arrays.asList(emails.getRecipientBcc().split(","));
+            }
+
+            String fromAddress = smtpPop.getEmailAddress();
+
+            Session session = getSendEmailsSession(smtpPop);
+            InternetAddress [] froms = new InternetAddress[recipientls.size()];
+            for (int i = 0; i < recipientls.size(); i++) {
+                froms[i] = new InternetAddress(recipientls.get(i));
+            }
+            InternetAddress [] fromsCC = new InternetAddress[recipientCcls.size()];
+            for (int i = 0; i < recipientCcls.size(); i++) {
+                fromsCC[i] = new InternetAddress(recipientCcls.get(i));
+            }
+            InternetAddress [] fromsBCC = new InternetAddress[recipientBccls.size()];
+            for (int i = 0; i < recipientBccls.size(); i++) {
+                fromsBCC[i] = new InternetAddress(recipientBccls.get(i));
+            }
+            Transport transport = session.getTransport("smtp");
+            int sizeInBytes = 0;
+            try {
+                Message message = new MimeMessage(session);
+                if (Objects.nonNull(froms) && froms.length > 0) {
+                    message.setRecipients(Message.RecipientType.TO,froms);
+                }
+                if (Objects.nonNull(fromsCC) && fromsCC.length > 0) {
+                    message.setRecipients(Message.RecipientType.CC,fromsCC);
+                }
+                if (Objects.nonNull(fromsBCC) && fromsBCC.length > 0) {
+                    message.setRecipients(Message.RecipientType.BCC,fromsBCC);
+                }
+
+                LambdaQueryWrapper fileWrapper = new LambdaQueryWrapper<MailTempAttachmentEntity>().in(MailTempAttachmentEntity::getEmailId,emails.getId());
+                List<MailTempAttachmentEntity> tempAttachment = tempAttachmentService.list(fileWrapper);
+                if (CollectionUtils.isEmpty(tempAttachment)) {
+                    message.setFrom(new InternetAddress(fromAddress));
+                    message.setSubject(emails.getSubject());
+
+                    Multipart multipart = new MimeMultipart();
+//                    // 消息正文
+                    BodyPart messageBodyPart1 = new MimeBodyPart();
+                    List<MimeBodyPart> imageParts = new ArrayList<>();
+                    String updatedHtmlContent = EmailHelper.convertImageUrlsToCidBodyPart(emails.getContent(),imageParts,mailFileProperties.getPath().getJpeg(),multipart);
+                    messageBodyPart1.setContent(updatedHtmlContent, "text/html; charset=UTF-8");
+                    multipart.addBodyPart(messageBodyPart1);
+                    // 发送完整的消息部分
+                    message.setContent(multipart);
+                } else {
+                    message.setFrom(new InternetAddress(fromAddress));
+                    message.setDescription(emails.getRemark());
+                    // 设置邮件主题
+                    message.setSubject(emails.getSubject());
+                    Multipart multipart = new MimeMultipart();
+//                    // 消息正文
+                    BodyPart messageBodyPart1 = new MimeBodyPart();
+                    List<MimeBodyPart> imageParts = new ArrayList<>();
+                    String updatedHtmlContent = EmailHelper.convertImageUrlsToCidBodyPart(emails.getContent(),imageParts,mailFileProperties.getPath().getJpeg(),multipart);
+                    messageBodyPart1.setContent(updatedHtmlContent, "text/html; charset=UTF-8");
+                    multipart.addBodyPart(messageBodyPart1);
+                    // 设置文本消息部分
+                    for (MailTempAttachmentEntity attachment : tempAttachment) {
+                        BodyPart messageBodyPart = new MimeBodyPart();
+                        String filePath =  mailFileProperties.getPath().getPath() + attachment.getFilePath();
+                        String fileName = attachment.getFileName()+"."+attachment.getFileExt();
+                        DataSource fileSource = new FileDataSource(filePath);
+                        messageBodyPart.setDataHandler(new DataHandler(fileSource));
+                        messageBodyPart.setFileName(fileName);
+                        multipart.addBodyPart(messageBodyPart);
+                    }
+                    // 发送完整的消息部分
+                    message.setContent(multipart);
+                }
+                /**
+                 * 发送成功,存储邮箱 replyEmailId null
+                 */
+                if (Integer.valueOf(1).equals(emails .getIsReceipt())) {
+                    message.addHeader("Disposition-Notification-To", smtpPop.getEmailAddress());
+                }
+                if(this.saveOrUpdate(emails)) {
+                    senderWithThreadLocal.sendMailMessage(transport,message,emails);
+                }
+                return true;
+            }catch (Exception e) {
+                log.error("getSendEmailsSession -error",e);
+                return false;
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            log.error("send sendMail -error",e);
+            return false;
+        }
+    }
+    @Override
+    public void receiveEmails(SmtpPopSettingsEntity smtpPop,Boolean isBackTask) {
+        String active = environment.getProperty("spring.profiles.active");
+//        if("dev".equals(active)) {
+//            return;
+//        }
+        String taskId = smtpPop.getEmailAddress()+"_"+smtpPop.getOwnerBy().toString();
+        ReceiveMailQueueThreadPool instance = ReceiveMailQueueThreadPool.getInstance();
+        if (instance.getTaskInProgressByMail(taskId)) {
+            log.error("有任务正在进行--:>>>>>>>>>>>>>"+smtpPop.getEmailAddress());
+            return;
+        }
+        instance.addTask(taskId,new Runnable() {
+            @Override
+            public void run() {
+                receiveHeadEmails(smtpPop,isBackTask);
+            }
+        });
+
+        addLoadMailContentTask(smtpPop);
+
+        addLoadMailFilesTask(smtpPop);
+    }
+
+    @Override
+    public void receiveHeadEmails(SmtpPopSettingsEntity smtpPop) {
+        receiveHeadEmails(smtpPop,false);
+    }
+
+    @Override
+    public void receiveEmails(SmtpPopSettingsEntity smtpPop) {
+        receiveEmails(smtpPop,false);
+    }
+
+    @Override
+    public void receiveHeadEmails(SmtpPopSettingsEntity smtpPop,Boolean isBackTask) {
+
+        try {
+            List<Message> inboxLs = getMessagels(smtpPop,"INBOX",EmailBoxEnum.INBOX,isBackTask);
+            if (!CollectionUtils.isEmpty(inboxLs)) {
+                List<EmailFolderRuleEntity> folderRules = folderRuleService.getUserFolderRules(smtpPop.getOwnerBy());
+                Set<String> blackList = blackListService.getBlackEmaillist(smtpPop.getOwnerBy());
+                pullHeadMail(inboxLs,smtpPop,EmailBoxEnum.INBOX,blackList,folderRules,isBackTask);
+            }
+//            if (!smtpPop.getEnabled()) {
+//                return;
+//            }
+            List<Message> sentLs = getMessagels(smtpPop,"Sent Items", EmailBoxEnum.SENT,isBackTask);
+            if (!CollectionUtils.isEmpty(sentLs)) {
+                pullHeadMail(sentLs,smtpPop,EmailBoxEnum.SENT,null,null,isBackTask);
+            }
+        }catch (Exception e) {
+            log.error("receiveHeadEmails - error ---"+smtpPop.getEmailAddress(),e);
+        }
+    }
+
+    public List<Message> getMessagels(SmtpPopSettingsEntity smtpPop,String folderName, EmailBoxEnum boxEnum) {
+        return getMessagels(smtpPop,folderName,boxEnum,false);
+    }
+
+    @Override
+    public List<Message> getMessagels(SmtpPopSettingsEntity smtpPop,String folderName, EmailBoxEnum boxEnum,Boolean isBackTask) {
+        try {
+            QueryWrapper queryWrapper = new QueryWrapper<>();
+            queryWrapper.eq("smtp_pop_id", smtpPop.getId());
+            queryWrapper.eq("folder", boxEnum.code);
+            queryWrapper.select("max(recipient_date) as recipient_date");
+            EmailsEntity email = this.getOne(queryWrapper);
+
+            Date beginDate = null;
+            MailProperties properties = new MailProperties(smtpPop);
+            MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+//                if(isBackTask) {
+//                    // 处理异常,并发送通知
+//                    if (Objects.nonNull(smtpPop.getId())) {
+//                        LambdaUpdateWrapper<SmtpPopSettingsEntity> updateWrapper = new LambdaUpdateWrapper<>();
+//                        updateWrapper.set(SmtpPopSettingsEntity::getEnabled,0);
+//                        updateWrapper.eq(SmtpPopSettingsEntity::getId, smtpPop.getId())
+//                                .setSql("connect_error_count = connect_error_count + 1"); // 原值 + 1
+//                        popSettingsService.update(updateWrapper);
+//                        // 禁止后续流程
+////                        smtpPop.setEnabled(false);
+//                    }
+////                    // 发送消息通知提醒邮箱连接异常
+////                    String[] argsArr = {"wecom"};
+////                    Set<Long> userIds = new HashSet<>();
+////                    userIds.add(smtpPop.getOwnerBy());
+////
+////                    Map<String,Object> messageMap = new HashMap<>();
+////                    messageMap.put("messageContent","");
+////                    messageMap.put("titleRemark","["+smtpPop.getEmailAddress()+"]");
+////                    invokeSendMessage(messageMap,userIds,argsArr);
+//                }
+                return null;
+            }
+            Folder inbox = mailConnection.getFolder(folderName);
+            if (Objects.isNull(inbox)) {
+                return null;
+            }
+            inbox.open(Folder.READ_ONLY);
+            if (Objects.nonNull(email)) {
+                beginDate = email.getRecipientDate();
+            }
+            if (beginDate == null) {
+                if (Integer.valueOf(0).equals(smtpPop.getPullDay()) || Objects.isNull(smtpPop.getPullDay())) {
+                    LocalDate currentDate = LocalDate.now();
+                    LocalDate datePullDayAgo = currentDate.minusYears(5);
+                    beginDate = DateUtil.getDateByFormat(datePullDayAgo.toString(), "yyyy-MM-dd");
+                } else {
+                    LocalDate currentDate = LocalDate.now();
+                    LocalDate datePullDayAgo = currentDate.minusDays(smtpPop.getPullDay());
+                    beginDate = DateUtil.getDateByFormat(datePullDayAgo.toString(), "yyyy-MM-dd");
+                }
+            }
+
+            SearchTerm searchTerm = new ReceivedDateTerm(ComparisonTerm.GT, beginDate);
+            Message[] messages = inbox.search(searchTerm);
+
+            List<Message> messagesList = Arrays.asList(messages);
+            if (CollectionUtils.isEmpty(messagesList)) {
+                return null;
+            }
+//            Date finalBeginDate = beginDate;
+//            List<Message> messagels = messagesList.stream().filter(e -> {
+//                try {
+//                    return e.getReceivedDate().compareTo(finalBeginDate) > 0;
+//                } catch (MessagingException ex) {
+//                    return false;
+//                }
+//            }).collect(Collectors.toList());
+//
+//            if (Objects.isNull(messagels)) {
+//                return messagels;
+//            } else if (messagels.size() <= 100) {
+//                return messagels;
+//            } else if(messagels.size() > 100 && messagels.size() <= 1000) {
+//                return messagels.stream().limit(500).collect(Collectors.toList());
+//            } else {
+//                return messagels.stream().limit(1000).collect(Collectors.toList());
+//            }
+            return messagesList;
+        } catch (Exception e) {
+            log.error("error", e);
+            return  null;
+        }
+    }
+
+    public void invokeSendMessage(Map messageMap,Set<Long> toUserIds,String[] argsArr ) {
+        try {
+            Object myClassInstance = SpringContextUtils.getBean("messageService");
+            Class<?> clazz = myClassInstance.getClass();
+
+//            Class<?> clazz = Class.forName("com.storlead.message.service.impl.MessageService");
+
+            Method method = clazz.getMethod("autoMatchEventSendMessage",Map.class,String.class,String.class,Set.class,  String[].class);
+            method.invoke(myClassInstance, messageMap,"999","EMAIL_SMTP_WARN",toUserIds,argsArr);
+        } catch (Exception e) {
+            log.error("invokeSendMessage - error",e);
+        }
+
+    }
+    @Override
+    public void pullHeadMail(List<Message> messages,SmtpPopSettingsEntity smtpPop, EmailBoxEnum boxEnum,Set<String> blackls,List<EmailFolderRuleEntity> folderRules,Boolean isBackTask) {
+//        Lock lock = userLockMap.computeIfAbsent(smtpPop.getOwnerBy(), id -> new ReentrantLock());
+        log.info("==========邮件入库开始=========="+boxEnum.code);
+
+//        if (lock.tryLock()) {
+            try {
+                List<Message> messagels = messages;
+                if (CollectionUtils.isEmpty(messagels)) {
+                    return;
+                }
+                QueryWrapper queryMsgIdWp = new QueryWrapper<>();
+                queryMsgIdWp.select("message_id");
+                queryMsgIdWp.eq("folder", boxEnum.code);
+                queryMsgIdWp.eq("smtp_pop_id", smtpPop.getId());
+                queryMsgIdWp.last("order by recipient_date desc limit 100");
+                List<String> ids = this.listObjs(queryMsgIdWp);
+                List<String> mesageIds = new ArrayList<>();
+                if (!CollectionUtils.isEmpty(ids)) {
+                    mesageIds.addAll(ids);
+                }
+                List<String> messageIds = new ArrayList<>();
+//                if (!CollectionUtils.isEmpty(messagels)) {
+//                    List<String> varIdls = new ArrayList<>();
+//                    for (Message message : messagels) {
+//                        String [] varIds = message.getHeader("Message-ID");
+//                        if (Objects.isNull(varIds)) {
+//                            continue;
+//                        }
+//                        varIdls.add(varIds[0]);
+//                    }
+//                    if (!CollectionUtils.isEmpty(varIdls)) {
+//                        messageIds = this.getEmailMessageIdsByMessageId(varIdls,boxEnum.code, smtpPop.getId());
+//                        if (Objects.isNull(messageIds)) {
+//                            messageIds = new ArrayList<>();
+//                        }
+//                    }
+//                }
+                List<EmailsEntity> entities = new ArrayList<>();
+                for (Message message : messagels) {
+                    String[] varIds = message.getHeader("Message-ID");
+                    if (Objects.isNull(varIds)) {
+                        continue;
+                    }
+                    String messageId = varIds[0];
+                    // 如果 messageId 尚未处理,执行处理逻辑
+                    if (!CollectionUtils.isEmpty(mesageIds) && mesageIds.contains(messageId)) {
+                        continue;
+                    }
+                    String oldMessageId = this.getEmailMessageIdsByMessageId(messageId,boxEnum.code, smtpPop.getId());
+//                    String[] inReplyTo = message.getHeader("In-Reply-To");
+//                    if (inReplyTo != null) {
+//                        String inReplyTo1 = message.getHeader("IN-REPLY-TO") != null ? message.getHeader("In-Reply-To")[0] : null;
+//                        String references1 = message.getHeader("References") != null ? message.getHeader("References")[0] : null;
+//                    }
+//                    Boolean existMessageId = messageIds.contains(messageId);
+                    EmailsEntity entity;
+                    if (Objects.isNull(oldMessageId)) {
+                        entity = convertMessageToEmailVo(message, true);
+                        if (Objects.isNull(entity)) {
+                            continue;
+                        }
+                        entity.setMessageId(messageId);
+                        entity.setSmtpPopId(smtpPop.getId());
+                        entity.setOwnerBy(smtpPop.getOwnerBy());
+                    } else {
+                        continue;
+                    }
+                    if (Objects.isNull(entity)) {
+                        continue;
+                    }
+                    if (!CollectionUtils.isEmpty(folderRules)) {
+                        /** 处理文件夹入栈规则 **/
+                        folderRuleService.carveUpMailCustomFolder(entity, folderRules);
+                    }
+                    mesageIds.add(messageId);
+                    if ("IMAP".equals(smtpPop.getProtocolType())) {
+                        IMAPFolder folder = (IMAPFolder) message.getFolder();
+                        Long msguid = folder.getUID(message);
+                        entity.setMsgUid(msguid.toString());
+                    } else if ("POP3".equals(smtpPop.getProtocolType())) {
+                        POP3Folder folder = (POP3Folder) message.getFolder();
+                        String msguid = folder.getUID(message);
+                        entity.setMsgUid(msguid.toString());
+                    }
+                    if (!CollectionUtils.isEmpty(blackls)) {
+                        if (blackls.contains(entity.getFrom())) {
+                            entity.setIsTrash(1);
+                        }
+                    }
+                    Boolean b = saveOrUpdate(entity);
+                    if (b) {
+                        entities.add(entity);
+                        log.error("==========entity.getId()=========="+entity.getId());
+                    }
+                }
+                log.info("==========邮件入库结束=========="+boxEnum.code);
+                String finalboxCode = boxEnum.code;  // 提前取值
+                Boolean finalIsBackTask = isBackTask;
+                List<Long> finalIsMailIds = entities.stream().map(EmailsEntity::getId).collect(Collectors.toList());
+                log.error("finalIsMailIds = "+finalIsMailIds);
+                if (!CollectionUtils.isEmpty(finalIsMailIds)) {
+                    CompletableFuture.runAsync(() -> {
+                        updateFollowUpTime(entities, smtpPop.getOwnerBy());
+                        // 发送新邮件提醒,绑定客户后,直接查询性能会好
+                        log.error("操作完绑定客户"+ JSON.toJSON(finalIsMailIds)+"__"+isBackTask+"___EmailBoxEnum.code___"+finalboxCode+"____="+EmailBoxEnum.INBOX.code.equals(finalboxCode));
+                        if (!CollectionUtils.isEmpty(finalIsMailIds) && finalIsBackTask && EmailBoxEnum.INBOX.code.equals(finalboxCode)) {
+                            log.error("进入发送提醒");
+                            sendNewMailRemind(finalIsMailIds,smtpPop.getOwnerBy(),smtpPop.getEmailAddress());
+                        }
+                    });
+                    CompletableFuture.runAsync(() -> {
+                        autoReplyMails(entities);
+                    });
+                }
+
+            }catch(Exception e){
+                log.error("pullHeadMail -- error---"+smtpPop.getEmailAddress(), e);
+            }
+    }
+
+    public void sendNewMailRemind(List<Long> mailIds,Long userId,String mailAccount) {
+        if (CollectionUtils.isEmpty(mailIds)) {
+            return;
+        }
+        String messageContent = "您有"+mailIds.size()+"封新邮件,请前往查看!";
+        // 获取新绑定的邮箱
+        List<Long> cusIds = this.baseMapper.selectCusIdByMailId(mailIds);
+        if (!CollectionUtils.isEmpty(cusIds)) {
+            List<CustomerMailVO> customervos = this.baseMapper.selectCusInfoByCusId(cusIds);
+
+            if (!CollectionUtils.isEmpty(customervos)) {
+                StringBuilder sb = new StringBuilder();
+                String moreCus = "";
+                for (CustomerMailVO customer : customervos) {
+                    if (sb.length() > 0) {
+                        moreCus = "等";
+                        sb.append("、");
+                    }
+                    sb.append("【");
+                    sb.append(customer.getCountry());
+                    sb.append("-");
+                    sb.append(customer.getCustomerName());
+                    sb.append("】");
+                }
+                messageContent = "您有来自"+sb+moreCus+"客户的"+mailIds.size()+"封新邮件,请前往查看!";
+            }
+        }
+        try {
+            Map<String,Object> messageMap = new HashMap<>();
+            messageMap.put("messageContent",messageContent);
+
+            Integer mailCount = this.baseMapper.countSmtpMailCount(userId);
+            if (mailCount > 1) {
+                messageMap.put("titleRemark","["+mailAccount+"]");
+            } else {
+                messageMap.put("titleRemark","");
+            }
+            Set<Long> toUserIds = new HashSet<>();
+            toUserIds.add(userId);
+
+           Object myClassInstance = SpringContextUtils.getBean("messageService");
+            Class<?> clazz = myClassInstance.getClass();
+
+//            Class<?> clazz = Class.forName("com.storlead.message.service.impl.MessageService");
+
+            Method method = clazz.getMethod("autoMatchEventSendMessage",Map.class,String.class,String.class,Set.class,  String[].class);
+            String[] argsArr = {"site","wecom","sms","mail"};
+            method.invoke(myClassInstance, messageMap,"11","NEW_EMAIL",toUserIds,argsArr);
+        }catch (Exception e) {
+            log.error("autoMatchEventSendMessage error",e);
+        }
+    }
+
+    public void autoReplyMails(List<EmailsEntity> entities){
+        for (EmailsEntity entity : entities) {
+            try {
+                autoReplyMail(entity);
+            }catch (Exception e) {
+                log.error("---autoReplyMails error---",e);
+            }
+        }
+    }
+
+    public void updateFollowUpTime(List<EmailsEntity> entities,Long ownerBy){
+        for (EmailsEntity entity : entities) {
+            try {
+                // 修改客户跟进实际
+                if (Integer.valueOf(1).equals(entity.getInOutMark())) {
+                    bindLiaisonMailCustomerIdByMailIdAndUserId(entity.getId(), entity.getFrom(), ownerBy, 1);
+                } else {
+                    String recipient = entity.getRecipient();
+                    if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+                        if (StrUtil.isNotBlank(recipient)) {
+                            recipient = recipient + "," + entity.getRecipientCc();
+                        } else {
+                            recipient = entity.getRecipientCc();
+                        }
+                    }
+                    bindLiaisonMailCustomerIdByMailIdAndUserId(entity.getId(), recipient, ownerBy, 2);
+                }
+
+                if ("SENT".equals(entity.getFolder())) {
+                    Object myClassInstance = SpringContextUtils.getBean("customerServiceImpl");
+                    Class<?> clazz = myClassInstance.getClass();
+                    Set<String> mailAddress = new HashSet<>();
+                    if (StrUtil.isNotBlank(entity.getRecipient())) {
+                        List<String> recipientls = Arrays.asList(entity.getRecipient().toLowerCase().split(","));
+                        mailAddress.addAll(recipientls);
+                    }
+                    if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+                        List<String> recipientccls = Arrays.asList(entity.getRecipientCc().toLowerCase().split(","));
+                        mailAddress.addAll(recipientccls);
+                    }
+                    if (!CollectionUtils.isEmpty(mailAddress)) {
+                        LocalDateTime sentDate = (Objects.isNull(entity.getSentDate())) ? LocalDateTime.now() : entity.getSentDate().toInstant()
+                                .atZone(ZoneId.systemDefault()) // 使用系统默认时区
+                                .toLocalDateTime();
+                        Method method = clazz.getMethod("updateFollowUpTimeByEmail", Set.class, LocalDateTime.class);
+                        method.invoke(myClassInstance, mailAddress, sentDate);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("updateFollowUpTimeByEmail --e", e);
+            }
+        }
+    }
+
+    public void autoReplyMail(EmailsEntity entity) {
+        try {
+            SmtpPopSettingsEntity smtpPop =  popSettingsService.getById(entity.getSmtpPopId());
+            if (Integer.valueOf(1).equals(smtpPop.getIsAutoReply()) && smtpPop.getReplyOnTime().before(entity.getSentDate())) {
+                if (entity.getFrom().equals(smtpPop.getEmailAddress())
+                        || "system@storlead.com".equals(entity.getFrom())
+                        || "system@storlead.com".equals(entity.getRecipient())) {
+                    return;
+                }
+                // 发过了不用发
+                String key = smtpPop.getEmailAddress()+"_"+entity.getFrom();
+                Object object =redisService.getCacheObject(key);
+                Long ttl = 0l;
+                if (Objects.nonNull(object)) {
+                     ttl = redisService.getCacheExpire(key, TimeUnit.SECONDS);
+                }
+                if ((Objects.isNull(object) || ttl <= 0) && StrUtil.isNotBlank(smtpPop.getReplyContent())) {
+                    LocalDateTime now = LocalDateTime.now();
+                    // 今天23:59:59
+                    LocalDateTime endOfDay = now.toLocalDate().atTime(23, 59, 59);
+                    // 计算剩余时间(秒)
+                    long timeout = ChronoUnit.SECONDS.between(now, endOfDay);
+                    redisService.setCacheObject(key,"reply",timeout, TimeUnit.SECONDS);
+
+                    AccessKeyEncryptor accessKeyEncryptor = AccessKeyEncryptor.getAccessKeyEncryptor("");
+                    String pass = accessKeyEncryptor.decrypt(smtpPop.getEmailPassword());
+                    smtpPop.setEmailPassword(pass);
+                    EmailsEntity repEmail = new EmailsEntity();
+                    repEmail.setRecipient(entity.getFrom());
+                    repEmail.setSubject(smtpPop.getReplyTitle());
+                    repEmail.setContent(smtpPop.getReplyContent());
+                    this.sendEmail(repEmail,smtpPop);
+                }
+            }
+        }catch (Exception e) {
+            log.error("autoReplyMail errpr ",e);
+        }
+    }
+    @Override
+    public NewMailCountTipVO countMailSummaryNum(Long smtpPopId, Long ownerBy) {
+        return this.baseMapper.countMailSummaryNum(smtpPopId,ownerBy);
+    }
+
+    @Override
+    public NewMailCountTipVO countCustomerAndClueNumber(Long smtpPopId, Long ownerBy) {
+        return this.baseMapper.countCustomerAndClueNumber(smtpPopId,ownerBy);
+    }
+
+    @Override
+    public NewMailCountTipVO countCustomerFollowUpMailNum(Long smtpPopId, Long ownerBy) {
+        return this.baseMapper.countCustomerFollowUpMailNum(smtpPopId,ownerBy);
+    }
+
+    @Override
+    public LocalDateTime getCustomerLastFollowUpTime(Long customerId) {
+        return this.baseMapper.selectCustomerLastFollowUpTime(customerId);
+    }
+
+    @Override
+    public List<EmailsEntity> getDelayDeleteMail() {
+        return this.baseMapper.selectDelayDeleteMail();
+    }
+
+    @Override
+    public IPage<CustomerEmailVo> selectCustomerEmailPage(Page<CustomerEmailVo> page, MailListDTO dto) {
+        return this.baseMapper.selectCustomerEmailPage(page,dto);
+    }
+
+
+    @Override
+    public void receiveBodyContentEmails(SmtpPopSettingsEntity smtpPop,List<EmailsEntity> emailsls,EmailBoxEnum boxEnum) {
+        receiveBodyContent(smtpPop,emailsls,boxEnum);
+    }
+
+    @Override
+    public InputStream loadEmailsFile(SmtpPopSettingsEntity smtpPop, EmailsEntity email,String attachmentName) {
+        try {
+            MailProperties properties = new MailProperties(smtpPop);
+            MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return null;
+            }
+            String folder = "Sent Items";
+            if (EmailBoxEnum.INBOX.code.equals(email.getFolder())) {
+                folder = "INBOX";
+            }
+            Message message = mailConnection.getMessageByUid(email.getMsgUid(),folder);
+            if (Objects.isNull(message)) {
+                return null;
+            }
+            Object content = message.getContent();
+            if (content instanceof Multipart) {
+                Multipart multipart = (Multipart) content;
+                for (int i = 0; i < multipart.getCount(); i++) {
+                    BodyPart bodyPart = multipart.getBodyPart(i);
+                    if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) || bodyPart.isMimeType("application/octet-stream")) {
+                        String fileName = bodyPart.getFileName();
+                        fileName = MimeUtility.decodeText(fileName);
+                        fileName = fileName.replace("\n", "").replace("\r", "");
+                        if (fileName.equals(attachmentName)) {
+                            return bodyPart.getInputStream();
+                        }
+                    }
+                }
+            }
+        }catch (Exception e) {
+            return null;
+        }
+        return null;
+    }
+
+
+    @Override
+    public void loadEmailsFiles(SmtpPopSettingsEntity smtpPop, List<EmailsEntity> emails,List<MailAttachmentEntity> attachments) {
+        try {
+            MailProperties properties = new MailProperties(smtpPop);
+            MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return;
+            }
+            Map<Long,List<MailAttachmentEntity>> attachmentMap = attachments.stream().collect(Collectors.groupingBy(MailAttachmentEntity::getEmailId));
+            for(EmailsEntity email : emails) {
+                List<MailAttachmentEntity> mAttachments = attachmentMap.get(email.getId());
+                if (CollectionUtils.isEmpty(mAttachments)) {
+                    continue;
+                }
+                String folder = "Sent Items";
+                if (EmailBoxEnum.INBOX.code.equals(email.getFolder())) {
+                    folder = "INBOX";
+                }
+                mailConnection.getFolder(folder);
+                Map<String, MailAttachmentEntity> attachmentMapName = mAttachments.stream()
+                        .collect(Collectors.toMap(
+                                attachment -> attachment.getFileName() + "." + attachment.getFileExt(), // 键:拼接 fileName 和 fileExt
+                                attachment -> attachment,
+                                (existing, replacement) -> existing
+                        ));
+                Message message = mailConnection.getMessageByUid(email.getMsgUid(),folder);
+                if (Objects.isNull(message)) {
+                    continue;
+                }
+                Object content = message.getContent();
+                if (content instanceof Multipart) {
+                    Multipart multipart = (Multipart) content;
+                    for (int i = 0; i < multipart.getCount(); i++) {
+                        BodyPart bodyPart = multipart.getBodyPart(i);
+                        if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) || bodyPart.isMimeType("application/octet-stream")) {
+                            String fileName = bodyPart.getFileName();
+                            fileName = MimeUtility.decodeText(fileName);
+                            fileName = fileName.replace("\n", "").replace("\r", "");
+                            MailAttachmentEntity attachment = attachmentMapName.get(fileName);
+                            if (Objects.isNull(attachment)) {
+                                continue;
+                            }
+                            String filePath = mailFileProperties.getPath().getPath()+attachment.getFilePath();
+                            if (new File(filePath).exists()) {
+                                LambdaUpdateWrapper<MailAttachmentEntity> attachmentUpdate = new LambdaUpdateWrapper<>();
+                                attachmentUpdate.set(MailAttachmentEntity::getDownload,Integer.valueOf(1));
+                                attachmentUpdate.eq(MailAttachmentEntity::getId,attachment.getId());
+                                mailAttachmentService.update(attachmentUpdate);
+                            }
+                            try {
+                                File file = new File(filePath);
+                                File parentDir = file.getParentFile();
+                                // 如果父目录不存在,则递归创建
+                                if (parentDir != null && !parentDir.exists()) {
+                                    parentDir.mkdirs();  // mkdirs() 会创建所有必要的父目录
+                                }
+                                try (FileOutputStream output = new FileOutputStream(file)) {
+                                    bodyPart.getInputStream().transferTo(output);
+                                }
+                                LambdaUpdateWrapper<MailAttachmentEntity> attachmentUpdate = new LambdaUpdateWrapper<>();
+                                attachmentUpdate.set(MailAttachmentEntity::getDownload,Integer.valueOf(1));
+                                attachmentUpdate.set(MailAttachmentEntity::getFileSize,file.length());
+                                attachmentUpdate.eq(MailAttachmentEntity::getId,attachment.getId());
+                                mailAttachmentService.update(attachmentUpdate);
+                            }catch (Exception e) {
+                                log.error("error --- ",e);
+                            }
+                        }
+                    }
+                }
+            }
+        }catch (Exception e) {
+            log.error("loadEmailsFiles ---",e);
+            return;
+        }
+        return;
+    }
+
+
+    public static Message[] mergeMessageArrays(Message[] array1, Message[] array2) {
+        // 处理空数组的情况
+        if (array1 == null && array2 == null) {
+            return new Message[0]; // 两个数组都为空,返回空数组
+        } else if (array1 == null) {
+            return array2; // 如果第一个数组为空,返回第二个数组
+        } else if (array2 == null) {
+            return array1; // 如果第二个数组为空,返回第一个数组
+        }
+        // 如果两个数组都不为空,创建一个新的 Message[] 数组,大小为两个数组长度之和
+        Message[] mergedArray = new Message[array1.length + array2.length];
+        // 将第一个数组复制到新数组中
+        System.arraycopy(array1, 0, mergedArray, 0, array1.length);
+        // 将第二个数组复制到新数组的后半部分
+        System.arraycopy(array2, 0, mergedArray, array1.length, array2.length);
+        return mergedArray;
+    }
+
+    public void receiveBodyContent(SmtpPopSettingsEntity smtpPop,List<EmailsEntity> emailsls,EmailBoxEnum boxEnum) {
+        try {
+            MailProperties properties = new MailProperties(smtpPop);
+            MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+            if (Objects.isNull(mailConnection)) {
+                return;
+            }
+            if (!CollectionUtils.isEmpty(emailsls)) {
+                if (Objects.isNull(mailConnection)) {
+                    return;
+                }
+                for (EmailsEntity email : emailsls) {
+                    Message message = boxEnum.code.equals("INBOX") ? mailConnection.getMessageByUid(email.getMsgUid(),"INBOX") : mailConnection.getMessageByUid(email.getMsgUid(),"Sent Items");
+                    if (Objects.isNull(message)) {
+                        continue;
+                    }
+                    if (Integer.valueOf(0).equals(email.getIsOnlyHead())) {
+                        continue;
+                    }
+                    String bodyContent = getEmailBodyContent(message);
+                    LambdaUpdateWrapper<EmailsEntity> update = new LambdaUpdateWrapper<>();
+                    update.set(EmailsEntity::getContent,bodyContent);
+                    update.set(EmailsEntity::getIsOnlyHead,0);
+                    update.eq(EmailsEntity::getId,email.getId());
+                    update(update);
+                    CompletableFuture.runAsync(() -> {
+                        try {
+                            pullMailAttachmentHead(email.getId(),email.getRecipientDate(),message.getContent(),smtpPop);
+                        } catch (Exception e) {
+                            log.error("get message content - error", e);
+                        }
+                    });
+                }
+            }
+        }catch (Exception e) {
+            log.error("receiveHeadContent -- error",e);
+        }
+    }
+
+    @Override
+    public void addLoadMailContentTask(SmtpPopSettingsEntity smtpPop) {
+
+        ReceiveMailQueueThreadPool instance = ReceiveMailQueueThreadPool.getInstance();
+        String taskName = smtpPop.getEmailAddress()+"_"+smtpPop.getOwnerBy().toString()+"_load_content";
+        if (instance.getTaskInProgressByMail(taskName)) {
+            log.error("有任务正在进行--:>>>>>>>>>>>>>"+taskName);
+        } else {
+            instance.addTask(taskName,new Runnable() {
+                @Override
+                public void run() {
+                    loadMailContentTask(smtpPop);
+                }
+            });
+        }
+    }
+
+    @Override
+    public void loadMailContentTask(SmtpPopSettingsEntity smtpPop){
+        try {
+//            while (true) {
+                LambdaQueryWrapper<EmailsEntity> wrapper = new LambdaQueryWrapper<>();
+                wrapper.select(EmailsEntity::getId,EmailsEntity::getMsgUid,EmailsEntity::getFolder
+                        ,EmailsEntity::getRecipientDate,EmailsEntity::getIsOnlyHead,EmailsEntity::getSmtpPopId);
+                wrapper.eq(EmailsEntity::getIsOnlyHead, 1);
+                wrapper.eq(EmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+                wrapper.eq(EmailsEntity::getSmtpPopId, smtpPop.getId());
+                List<EmailsEntity> taskEntities = list(wrapper);
+                if (CollectionUtils.isEmpty(taskEntities)) {
+                    return;
+                }
+                List<EmailsEntity> inboxLs = taskEntities.stream().filter(e -> EmailBoxEnum.INBOX.code.equals(e.getFolder())).collect(Collectors.toList());
+                if (!CollectionUtils.isEmpty(inboxLs)) {
+                    receiveBodyContentEmails(smtpPop, inboxLs, EmailBoxEnum.INBOX);
+                }
+                List<EmailsEntity> sentLs = taskEntities.stream().filter(e -> EmailBoxEnum.SENT.code.equals(e.getFolder())).collect(Collectors.toList());
+                if (!CollectionUtils.isEmpty(sentLs)) {
+                    receiveBodyContentEmails(smtpPop, sentLs, EmailBoxEnum.SENT);
+                }
+//                if (CollectionUtils.isEmpty(sentLs) && CollectionUtils.isEmpty(sentLs)) {
+//                    ret;
+//                }
+//            }
+        } catch (Exception e) {
+            log.error("Error in loadMailFiles", e);
+        }
+    }
+
+    @Override
+    public void addLoadMailFilesTask(SmtpPopSettingsEntity smtpPop) {
+
+        ReceiveMailQueueThreadPool instance = ReceiveMailQueueThreadPool.getInstance();
+        String taskName = smtpPop.getEmailAddress()+"_"+smtpPop.getOwnerBy()+"_load";
+        if (instance.getTaskInProgressByMail(taskName)) {
+            log.error("有任务正在进行--:>>>>>>>>>>>>>"+taskName);
+        } else {
+            instance.addTask(taskName,new Runnable() {
+                @Override
+                public void run() {
+                    loadMailFiles(smtpPop);
+                }
+            });
+        }
+   }
+
+
+    @Override
+    public void loadMailFiles(SmtpPopSettingsEntity smtpPop) {
+        try {
+//            while (true) {
+                // Query for undownloaded attachments
+                LambdaQueryWrapper<MailAttachmentEntity> wrapper = new LambdaQueryWrapper<>();
+                wrapper.eq(MailAttachmentEntity::getSmtpPopId, smtpPop.getId());
+                wrapper.eq(MailAttachmentEntity::getDownload, 0);
+                List<MailAttachmentEntity> attachments = mailAttachmentService.list(wrapper);
+
+                // If no more attachments to download, break the loop
+                if (CollectionUtils.isEmpty(attachments)) {
+                    return;
+                }
+
+                // Get the corresponding email entities
+                List<Long> mailIds = attachments.stream()
+                        .map(MailAttachmentEntity::getEmailId)
+                        .collect(Collectors.toList());
+
+                LambdaQueryWrapper<EmailsEntity> mwrapper = new LambdaQueryWrapper<>();
+                mwrapper.select(EmailsEntity::getId,EmailsEntity::getOwnerBy,EmailsEntity::getFolder,EmailsEntity::getContent,EmailsEntity::getMsgUid);
+                mwrapper.in(EmailsEntity::getId, mailIds);
+                List<EmailsEntity> entities = list(mwrapper);
+
+                // If no corresponding emails, break the loop
+                if (CollectionUtils.isEmpty(entities)) {
+                    return;
+                }
+                // Download the attachments
+                loadEmailsFiles(smtpPop, entities, attachments);
+//            }
+        } catch (Exception e) {
+            log.error("Error in loadMailFiles", e);
+        }
+    }
+
+
+    private static Map<String,String> decodeAndPrintAddresses(Address[] addresses) throws MessagingException, IOException {
+
+        Map<String,String> map = new HashMap<>();
+        if (addresses == null) return new HashMap<>();
+        StringBuilder decodedAddresses = new StringBuilder();
+        for (Address address : addresses) {
+            InternetAddress internetAddress = (InternetAddress) address;
+            String personal = internetAddress.getPersonal();
+            String email = internetAddress.getAddress();
+            if (personal != null) {
+                personal = MimeUtility.decodeText(personal);
+            }
+            map.put(email,(personal != null ? personal : "N/A"));
+//            decodedAddresses.append("[Name: ").append(personal != null ? personal : "N/A").append(", Email: ").append(email).append("] ");
+        }
+        return map;
+    }
+
+    private static String mapKeyToString(Map<String,String> map) {
+        if (map == null) return "";
+        String mapKey = map.entrySet().stream()
+                .map(entry -> entry.getKey())
+                .collect(Collectors.joining(","));
+        if (("N/A").equals(mapKey)) {
+            mapKey = "";
+        }
+        return mapKey;
+    }
+
+    private static String mapValueToString(Map<String,String> map) {
+        if (map == null) return "";
+        String mapKey = map.entrySet().stream()
+                .map(entry -> entry.getValue())
+                .collect(Collectors.joining(","));
+
+        if (("N/A").equals(mapKey)) {
+            mapKey = "";
+        }
+        return mapKey;
+    }
+
+    @Override
+    public Boolean downloadMailAttachment(Long mailId, Date recipientDate,Object messageContent,SmtpPopSettingsEntity smtpPop) {
+        try {
+            String recipient = DateUtils.date2Str(recipientDate, DateUtils.yyyyMMdd);
+            String downloadDir = mailFileProperties.getPath().getPath() + File.separator + smtpPop.getEmailAddress() + File.separator + recipient + File.separator;
+            Object content = messageContent;
+            if (content instanceof Multipart) {
+                Multipart multipart = (Multipart) content;
+                for (int i = 0; i < multipart.getCount(); i++) {
+                    BodyPart bodyPart = multipart.getBodyPart(i);
+                    if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
+                        saveDownloadAttachment(bodyPart, downloadDir, smtpPop.getId(), mailId);
+                    } else if (bodyPart.isMimeType("application/octet-stream")) {
+                        saveDownloadAttachment(bodyPart, downloadDir, smtpPop.getId(), mailId);
+                    }
+                }
+            }
+        }catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public Boolean pullMailAttachmentHead(Long mailId, Date recipientDate,Object mailContent,SmtpPopSettingsEntity smtpPop) {
+        try {
+            if (processedDownloadMailIds.putIfAbsent(mailId, true) == null) {
+                Integer mcount = mailAttachmentService.count(new LambdaQueryWrapper<MailAttachmentEntity>().eq(MailAttachmentEntity::getEmailId,mailId));
+                if(mcount > 0) {
+                    return Boolean.TRUE;
+                }
+                String recipient = DateUtils.date2Str(recipientDate,DateUtils.yyyyMMdd);
+                String downloadDir = mailFileProperties.getPath().getPath()+ File.separator+smtpPop.getEmailAddress() +File.separator + recipient + File.separator;
+                Object content =mailContent;
+                if (content instanceof Multipart) {
+                    Multipart multipart = (Multipart) content;
+                    for (int i = 0; i < multipart.getCount(); i++) {
+                        BodyPart bodyPart = multipart.getBodyPart(i);
+                        if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) || bodyPart.isMimeType("application/octet-stream")) {
+                            saveAttachmentRecord(bodyPart, downloadDir,smtpPop.getId(), mailId);
+                        }
+                    }
+                }
+            }
+            processedDownloadMailIds.remove(mailId);
+        }catch (Exception e) {
+            processedDownloadMailIds.remove(mailId);
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public IPage<EmailsEntity> pageWithScope(Page page, MailDTO dto) {
+        return this.baseMapper.pageListNew(page,dto);
+    }
+
+
+    @Override
+    public IPage<EmailsEntity> pageWithScopeCus(Page page, MailDTO dto) {
+        return this.baseMapper.pageListCus(page,dto);
+    }
+
+    @Override
+    public IPage<EmailsEntity> pageWithScopeAllCus(Page page, MailDTO dto) {
+        return this.baseMapper.pageListNewAllCus(page,dto);
+    }
+
+
+    private void saveAttachmentRecord(BodyPart bodyPart,  String downloadDir,Long smtpPopId,Long emailId) throws MessagingException, IOException {
+        String fileName = bodyPart.getFileName();
+        fileName = MimeUtility.decodeText(fileName);
+        fileName = fileName.replace("\n", "").replace("\r", "");
+        String code =  RandomGenerateHelper.generateRandomString(6);
+        // 如果不存在这创建路径
+        if (!new File(downloadDir).exists()) {
+            new File(downloadDir).mkdirs();
+        }
+        Integer size = bodyPart.getSize();
+        fileName = fileName+"."+code;
+        File file = new File(downloadDir +  File.separator + fileName);
+        saveAttachmentToDatabase(file,code,size,smtpPopId, emailId);
+    }
+
+    public Boolean syncPullEmails(EmailsEntity entity, Message message,SmtpPopSettingsEntity smtpPop) {
+        try {
+            String recipient = DateUtils.date2Str(entity.getRecipientDate(),DateUtils.yyyyMMdd);
+            String downloadDir = mailFileProperties.getPath().getPath()+ File.separator+smtpPop.getEmailAddress() +File.separator + recipient + File.separator;
+            downloadAttachmentFromMessage(message,downloadDir,smtpPop.getId(),entity.getId());
+        }catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public Boolean analysisDownLoadAttachment(EmailsEntity entity, Message message) {
+        try {
+            String recipient = DateUtils.date2Str(entity.getRecipientDate(),DateUtils.yyyyMMdd);
+            String downloadDir = mailFileProperties.getPath().getPath()+ File.separator+entity.getFrom() +File.separator + recipient + File.separator;
+            downloadAttachmentFromMessage(message,downloadDir,0L,entity.getId());
+        }catch (Exception e) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public EmailsEntity convertMessageToEmailVo(Message message,Boolean isPullHead) {
+
+        try {
+            EmailsEntity entity = new EmailsEntity();
+            String subject = Objects.isNull(message.getSubject()) ? "" : MimeUtility.decodeText(message.getSubject());
+            entity.setSubject(subject);
+            if (subject.contains("回复")) {
+                entity.setSubject(subject);
+            }
+            if (Objects.nonNull(message.getFolder())) {
+                if (message.getFolder().getFullName().equals("INBOX")) {
+                    entity.setFolder(EmailBoxEnum.INBOX.code);
+                    entity.setInOutMark(1);
+                }else {
+                    entity.setFolder(EmailBoxEnum.SENT.code);
+                    entity.setInOutMark(2);
+                }
+            }
+
+            Map<String,String>  fromMap = decodeAndPrintAddresses(message.getFrom());
+
+            entity.setFrom(mapKeyToString(fromMap));
+            entity.setFromName(mapValueToString(fromMap));
+
+            Map<String,String>  toMap = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.TO));
+            entity.setRecipient(mapKeyToString(toMap));
+            entity.setRecipientName(mapValueToString(toMap));
+
+            Map<String,String>  toMapCC = decodeAndPrintAddresses(message.getRecipients(Message.RecipientType.CC));
+            if (Objects.nonNull(toMapCC)) {
+                entity.setRecipientCc(mapKeyToString(toMapCC));
+                entity.setRecipientCcName(mapKeyToString(toMapCC));
+            }
+            entity.setEmailSize(Long.valueOf(message.getSize()));
+            if (Objects.nonNull(message.getSentDate())) {
+                entity.setSentDate(message.getSentDate());
+            }
+            if (Objects.nonNull(message.getReceivedDate())) {
+                entity.setRecipientDate(message.getReceivedDate());
+            }
+
+            boolean isSeen = message.isSet(Flags.Flag.SEEN);
+            entity.setIsRead(isSeen ? 1 : 0);
+            if (isPullHead) {
+                try {
+                    Object content = message.getContent();
+                    if (content instanceof String) {
+                        entity.setIsOnlyHead(0);
+                        entity.setContent(content.toString());
+                    } else {
+                        entity.setIsOnlyHead(1);
+                    }
+                } catch (Exception e){
+                    log.error("message.getContent() -- error",e);
+                    return entity;
+                }
+
+            } else {
+                try {
+                    Object content = message.getContent();
+                    String argsStr = "";
+                    if (content instanceof String) {
+                        argsStr = message.getContent().toString();
+                    } else if (content instanceof Multipart) {
+                        Multipart multipart = (Multipart) content;
+                        argsStr = parseMultipart(multipart);
+                        argsStr = parseMultipartImage(argsStr,multipart);
+                    }
+                    entity.setIsOnlyHead(1);
+                    entity.setContent(argsStr);
+                }catch (Exception e) {
+                    log.error("message.getContent() -- error",e);
+                    return entity;
+                }
+            }
+            return entity;
+        }catch (Exception e) {
+            log.error("convertMessageToEmailVo -- error",e);
+            return null;
+        }
+    }
+
+    @Override
+    public Long getEmailIdByMessgeId(String messageId,Long smtpPopId) {
+        return this.baseMapper.getEmailsIdByMessageId(messageId,smtpPopId);
+    }
+
+    @Override
+    public EmailsEntity getEmailsByMessageId(String messageId,String folder,Long smtpPopId) {
+        return this.baseMapper.getEmailsByMessageId(messageId,folder,smtpPopId);
+    }
+
+    @Override
+    public List<String> getEmailMessageIdsByMessageIds(List<String> messageIds,String folder,Long smtpPopId) {
+        return this.baseMapper.getEmailMessageIdsByMessageIds(messageIds,folder,smtpPopId);
+    }
+
+    @Override
+    public String getEmailMessageIdsByMessageId(String messageId,String folder,Long smtpPopId) {
+        return this.baseMapper.getEmailMessageIdsByMessageId(messageId,folder,smtpPopId);
+    }
+
+    @Override
+    public Integer getEmailsByMessageId(String messageId,String fromAddress) {
+        LambdaQueryWrapper<EmailsEntity> w = new LambdaQueryWrapper<>();
+        w.eq(EmailsEntity::getMessageId,messageId);
+        w.eq(EmailsEntity::getFrom,fromAddress);
+        return this.baseMapper.selectCount(w);
+    }
+
+
+    @Override
+    public String getEmailBodyContent(Message message) {
+        try {
+            Object content = message.getContent();
+            String argsStr = "";
+            if (content instanceof String) {
+                argsStr = message.getContent().toString();
+            } else if (content instanceof Multipart) {
+                Multipart multipart = (Multipart) content;
+                argsStr = parseMultipart(multipart);
+                argsStr = parseMultipartImage(argsStr,multipart);
+            }
+            return argsStr;
+        }catch (Exception e) {
+            log.error("convertMessageToEmailVo -- error",e);
+            return "";
+        }
+    }
+
+
+    @Override
+    public void bindLiaisonMailCustomerIdByMailIdAndUserId(Long mailId, String mailAddress, Long userId,Integer inOutMark) {
+        try {
+            if (StrUtil.isBlank(mailAddress)) {
+                return;
+            }
+//            Runnable ttlRunnable = TtlRunnable.get(new Runnable() {
+//                @Override
+//                public void run() {
+                   String lowerMailAddress = mailAddress.toLowerCase();
+//                    Set<Long> cId = emailsMapper.getMailCustomerIdByMailIdAndUserId(mailId,lowerMailAddress,userId);
+//                    Set<Long> lId = new HashSet<>();
+//                    if (Objects.isNull(cId)) {
+//                        new HashSet<>();
+//                    }
+//                    List<MailLiaisonVO> vos = emailsMapper.getLiaisonMailCustomerIdByMailIdAndUserId(mailId,lowerMailAddress,userId);
+//                    if (!CollectionUtils.isEmpty(vos)) {
+//                        Set<Long> mcId = vos.stream().map(e -> e.getCustomerId()).collect(Collectors.toSet());
+//                        if (!CollectionUtils.isEmpty(mcId)) {
+//                            cId.addAll(mcId);
+//                        }
+//                        Set<Long> lIds = vos.stream().map(e -> e.getLiaisonId()).collect(Collectors.toSet());
+//                        if (!CollectionUtils.isEmpty(lIds)) {
+//                            lId.addAll(lIds);
+//                        }
+//                    }
+//                    if (!CollectionUtils.isEmpty(lId) || !CollectionUtils.isEmpty(cId)) {
+//                        String customerId = CollectionUtils.isEmpty(cId) ? null : org.apache.commons.lang.StringUtils.join(cId,",");
+//                        String liaisonIds = CollectionUtils.isEmpty(lId) ? null : org.apache.commons.lang.StringUtils.join(lId,",");
+//                        emailsMapper.bindMailCustomerIdByMailId(mailId,customerId,liaisonIds);
+//                    }
+
+                    Object myClassInstance = SpringContextUtils.getBean("customerMailBingMarkServiceImpl");
+                    Class<?> clazz = myClassInstance.getClass();
+                    try {
+                        Method method = clazz.getMethod("mateBindCustomerLiaisonMail",Long.class,Long.class,String.class,Integer.class);
+                        method.invoke(myClassInstance,mailId,userId,lowerMailAddress,inOutMark);
+                    } catch (Exception e) {
+                        log.error("mateBindCustomerLiaisonFromMail --e",e);
+                    }
+                //}
+//            });
+//            ThreadPoolUtil.execute(ttlRunnable);
+        }catch (Exception e) {
+            log.error("bindLiaisonMailCustomerIdByMailIdAndUserId-------------------",e);
+        }
+    }
+
+    private static Long getMessageId(Message message, Folder folder) throws MessagingException {
+        if (folder instanceof IMAPFolder) {
+            IMAPFolder imapFolder = (IMAPFolder) folder;
+            return  imapFolder.getUID(message);
+        } else {
+            return null;
+        }
+    }
+
+
+
+    private String parseMultipart(Multipart multipart) {
+        StringBuilder stringBuilder = new StringBuilder();
+        try {
+            StringBuilder plainTextContent = new StringBuilder() ;
+            StringBuilder plainHtmlContent = new StringBuilder() ;
+            for (int i = 0; i < multipart.getCount(); i++) {
+                BodyPart bodyPart = multipart.getBodyPart(i);
+                if (bodyPart.isMimeType("text/plain")) {
+                    plainTextContent.append((String) bodyPart.getContent());
+                } else if (bodyPart.isMimeType("text/html")) {
+                    String htmlContent = (String) bodyPart.getContent();
+                    plainHtmlContent.append(htmlContent);
+                } else if (bodyPart.isMimeType("multipart/alternative")) {
+                    stringBuilder.append(parseMultipart((Multipart) bodyPart.getContent()));
+                } else if (bodyPart.isMimeType("multipart/*")) {
+                    stringBuilder.append(parseMultipart((Multipart) bodyPart.getContent()));
+                }
+            }
+            if (StrUtil.isNotBlank(plainHtmlContent)) {
+                stringBuilder.append(plainHtmlContent);
+            } else {
+                stringBuilder.append(plainTextContent);
+            }
+            return stringBuilder.toString();
+        }catch (Exception e) {
+            log.error("parseMultipart - error",e);
+            return stringBuilder.toString();
+        }
+    }
+
+    private String parseMultipartImage(String htmlContent,Multipart multipart) {
+        try {
+            for (int i = 0; i < multipart.getCount(); i++) {
+                BodyPart bodyPart = multipart.getBodyPart(i);
+                String contentType = bodyPart.getContentType();
+                log.debug("contentType -="+contentType);
+                if (Part.INLINE.equalsIgnoreCase(bodyPart.getDisposition()) || bodyPart.getContentType().toLowerCase().startsWith("image/")) {
+                    htmlContent = processHtmlWithBase64Images(htmlContent, bodyPart);
+                } else if (bodyPart.isMimeType("multipart/alternative")) {
+                    htmlContent = parseMultipartImage(htmlContent,(Multipart) bodyPart.getContent());
+                } else if (bodyPart.isMimeType("multipart/*")) {
+                    htmlContent = parseMultipartImage(htmlContent,(Multipart) bodyPart.getContent());
+                } else if (bodyPart.isMimeType("application/octet-stream")) {
+                    htmlContent = processHtmlWithBase64Images(htmlContent, bodyPart);
+               }
+            }
+        }catch (Exception e) {
+           log.error("parseMultipartImage - ERROR",e);
+
+        }
+        return htmlContent;
+    }
+
+    // 处理 HTML 内容中的图片引用,替换为 base64
+//    private String processHtmlWithBase64Images(String htmlContent, Multipart multipart) throws MessagingException, IOException {
+//        // 找到所有 <img> 标签并处理 src="cid:"
+//        String regex = "src=\"cid:(.*?)\"";
+//        java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex);
+//        java.util.regex.Matcher matcher = pattern.matcher(htmlContent);
+//
+//        while (matcher.find()) {
+//            String contentId = matcher.group(1);
+//            BodyPart imagePart = findImagePartByContentId(multipart, contentId);
+//
+//            if (imagePart != null) {
+//                // 转换图片为 Base64 并替换 HTML 中的 src="cid:..."
+//                String base64Image = convertImageToBase64(imagePart);
+//                htmlContent = htmlContent.replace("cid:" + contentId, "data:image/png;base64," + base64Image);
+//            }
+//        }
+//
+//        return htmlContent;
+//    }
+
+    private String processHtmlWithBase64Images(String htmlContent, BodyPart bodyPart) throws MessagingException, IOException {
+        // 找到所有 <img> 标签并处理 src="cid:"
+        String contentId = ((MimeBodyPart) bodyPart).getContentID();
+//        String contentId = ((IMAPBodyPart) bodyPart).getContentID();
+        if (StrUtil.isBlank(contentId)) {
+            if (bodyPart.getHeader("Content-ID") != null) {
+                contentId = bodyPart.getHeader("Content-ID").toString();
+            }
+        }
+        if (StrUtil.isNotBlank(contentId)) {
+            BodyPart imagePart = bodyPart;
+            if (imagePart != null) {
+                // 转换图片为 Base64 并替换 HTML 中的 src="cid:..."
+                contentId = contentId.replaceAll("^<(.+)>$", "$1");
+                String base64Image = convertImageToBase64(imagePart);
+                htmlContent = htmlContent.replace("cid:" + contentId, "data:image/png;base64," + base64Image);
+            }
+        }
+        return htmlContent;
+    }
+
+    // 根据 Content-ID 查找对应的图片部分
+    private BodyPart findImagePartByContentId(Multipart multipart, String contentId) throws MessagingException {
+        for (int i = 0; i < multipart.getCount(); i++) {
+            BodyPart bodyPart = multipart.getBodyPart(i);
+            if (bodyPart.getHeader("Content-ID") != null) {
+                String[] contentIdHeader = bodyPart.getHeader("Content-ID");
+                if (contentIdHeader[0].contains(contentId)) {
+                    return bodyPart; // 找到匹配的图片
+                }
+            }
+        }
+        return null; // 未找到匹配的图片
+    }
+
+    // 将图片转换为 Base64 字符串
+    private String convertImageToBase64(BodyPart bodyPart) throws IOException, MessagingException {
+        InputStream inputStream = bodyPart.getInputStream();
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+
+        byte[] buffer = new byte[1024];
+        int bytesRead;
+        while ((bytesRead = inputStream.read(buffer)) != -1) {
+            outputStream.write(buffer, 0, bytesRead);
+        }
+
+        // 将图片转换为 Base64 编码
+        byte[] imageBytes = outputStream.toByteArray();
+        return Base64.getEncoder().encodeToString(imageBytes);
+    }
+
+    private void downloadAttachmentFromMessage(Message message, String downloadDir,Long smtpPopId,Long emailId) {
+        try {
+            Object content = message.getContent();
+            if (content instanceof Multipart) {
+                Multipart multipart = (Multipart) content;
+                for (int i = 0; i < multipart.getCount(); i++) {
+                    BodyPart bodyPart = multipart.getBodyPart(i);
+                    if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition()) || bodyPart.isMimeType("application/octet-stream")) {
+                        saveDownloadAttachment(bodyPart, downloadDir,smtpPopId,emailId);
+                    }
+                }
+            }
+        }catch (Exception e) {
+            log.error("downloadAttachmentFromMessage -- error = ",e);
+        }
+    }
+
+    private void saveDownloadAttachment(BodyPart bodyPart,  String downloadDir,Long smtpPopId,Long emailId) {
+        try {
+            String fileName = bodyPart.getFileName();
+            fileName = MimeUtility.decodeText(fileName);
+            fileName = fileName.replace("\n", "").replace("\r", "");
+            String code =  RandomGenerateHelper.generateRandomString(6);
+            // 如果不存在这创建路径
+            Integer size = bodyPart.getSize();
+            if (!new File(downloadDir).exists()) {
+                new File(downloadDir).mkdirs();
+            }
+            fileName = fileName+"."+code;
+            File file = new File(downloadDir +  File.separator + fileName);
+            try (FileOutputStream output = new FileOutputStream(file)) {
+                bodyPart.getInputStream().transferTo(output);
+            }
+            saveAttachmentToDatabase(file,code,size,smtpPopId,emailId);
+        }catch (Exception e) {
+            log.error("saveDownloadAttachment -- error",e);
+        }
+
+    }
+
+    private void saveAttachmentHeader(BodyPart bodyPart,  String downloadDir,Long smtpPopId,Long emailId) throws MessagingException, IOException {
+        String fileName = bodyPart.getFileName();
+        fileName = MimeUtility.decodeText(fileName);
+        fileName = fileName.replace("\n", "").replace("\r", "");
+        String code =  RandomGenerateHelper.generateRandomString(6);
+        // 如果不存在这创建路径
+        Integer size = bodyPart.getSize();
+        if (!new File(downloadDir).exists()) {
+            new File(downloadDir).mkdirs();
+        }
+        fileName = fileName+"."+code;
+        File file = new File(downloadDir +  File.separator + fileName);
+        saveAttachmentToDatabase(file,code,size,smtpPopId,emailId);
+    }
+
+
+    private void saveAttachmentToDatabase(File file,String code,Integer size,Long smtpPopId,Long emailId) {
+        String fileName = file.getName().replace("."+code,"");
+        String fName = FileNameUtils.getBaseName(fileName);
+        String fExt = FileNameUtils.getExtension(fileName);
+        LambdaQueryWrapper<MailAttachmentEntity> l =  new LambdaQueryWrapper<>();
+        l.eq(MailAttachmentEntity::getFileName,fName);
+        l.eq(MailAttachmentEntity::getEmailId,emailId);
+        l.eq(MailAttachmentEntity::getFileExt,fExt);
+        l.eq(MailAttachmentEntity::getFileSize,Long.valueOf(size));
+        l.eq(MailAttachmentEntity::getSmtpPopId,smtpPopId);
+        l.last("limit 1");
+        Integer c = mailAttachmentService.count(l);
+        if (c == 0) {
+            MailAttachmentEntity attachment = new MailAttachmentEntity();
+            attachment.setEmailId(emailId);
+            attachment.setSmtpPopId(smtpPopId);
+            attachment.setFileCode(code);
+            attachment.setFileName(fName);
+            attachment.setFilePath(file.getAbsolutePath().replace(mailFileProperties.getPath().getPath(),""));
+            attachment.setFileExt(fExt);
+            attachment.setFileSize(Long.valueOf(size));
+            mailAttachmentService.saveOrUpdate(attachment);
+        }
+    }
+
+    private Session getSendEmailsSession(SmtpPopSettingsEntity smtpPop) {
+
+        Properties properties = new Properties();
+        properties.put("mail.smtp.host", smtpPop.getSmtpServer());
+        properties.put("mail.smtp.port", smtpPop.getSmtpPort());
+        properties.put("mail.smtp.auth", "true");
+        properties.put("mail.user", smtpPop.getEmailAddress());
+        properties.put("mail.smtp.starttls.enable",Integer.valueOf(1).equals(smtpPop.getSmtpTls()) ? "true" : "false");
+        properties.put("mail.smtp.ssl.enable", Integer.valueOf(1).equals(smtpPop.getSmtpSsl()) ? "true" : "false");
+
+        Session session = Session.getInstance(properties, new Authenticator() {
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(smtpPop.getEmailAddress(), smtpPop.getEmailPassword());
+            }
+        });
+        return session;
+    }
+
+    private EmailsEntity saveSendEmails(SendMailDTO dto,String fromAddress,String fromNickname,Long replyMsgId,Long sizeInBytes) {
+        try {
+            EmailsEntity entity = new EmailsEntity();
+            String subject =  MimeUtility.decodeText(dto.getSubject());
+            entity.setSubject(subject);
+            entity.setSentDate(new Date());
+            entity.setIsRead(Integer.valueOf(1));
+            if (Objects.nonNull(dto.getDelaySendTime())) {
+                entity.setDelaySendTime(dto.getDelaySendTime());
+                entity.setStatus(-1);
+            } else {
+                entity.setStatus(0);
+            }
+            if (Objects.nonNull(replyMsgId)) {
+                entity.setReplyMsgId(replyMsgId);
+            }
+            if (Objects.nonNull(dto.getId())) {
+                entity.setId(dto.getId());
+            }
+            if (Objects.nonNull(dto.getIsReceipt())) {
+                entity.setIsReceipt(dto.getIsReceipt());
+            }
+            entity.setEmailSize(sizeInBytes);
+            entity.setFromName(fromNickname);
+            entity.setFrom(fromAddress);
+            if (!CollectionUtils.isEmpty(dto.getRecipientls())) {
+                entity.setRecipient(StringUtil.join(dto.getRecipientls(),","));
+            }
+            if (!CollectionUtils.isEmpty(dto.getRecipientCcls())) {
+                entity.setRecipientCc(StringUtil.join(dto.getRecipientCcls(),","));
+            }
+            if (!CollectionUtils.isEmpty(dto.getRecipientBccls())) {
+                entity.setRecipientBcc(StringUtil.join(dto.getRecipientBccls(),","));
+            }
+            entity.setSubject(dto.getSubject());
+            entity.setContent(dto.getContent());
+            entity.setFolder(EmailBoxEnum.SENTING.code);
+            return entity;
+        }catch (Exception e) {
+            return null;
+        }
+    }
+
+    private EmailsEntity updateSendEmailStatus(Long emailId) {
+        try {
+            EmailsEntity entity = new EmailsEntity();
+            entity.setId(emailId);
+            entity.setStatus(Integer.valueOf(1));
+            entity.setFolder(EmailBoxEnum.SENT.code);
+            updateById(entity);
+            return entity;
+        }catch (Exception e) {
+            return null;
+        }
+    }
+
+    @Override
+    public void analysisMailEml(List<Message> messages,Set<String> cusMails,Long ownerBy) {
+        if (CollectionUtils.isEmpty(messages) || CollectionUtils.isEmpty(cusMails)) {
+            return;
+        }   List<Message> messagels = messages;
+        for (Message message : messagels) {
+            Set<String> cusAddressls = cusMails;
+            try {
+                String[] r = message.getHeader("Message-ID");
+                if (Objects.isNull(r)) {
+                    continue;
+                }
+                String messageId = message.getHeader("Message-ID")[0];
+                Map<String, String> fromMap = decodeAndPrintAddresses(message.getFrom());
+                String fromAddress = mapKeyToString(fromMap);
+                Integer count = this.getEmailsByMessageId(messageId, fromAddress);
+                if (!Integer.valueOf(0).equals(count)) {
+                    log.error("getEmailsByMessageId-----" + messageId);
+                    continue;
+                }
+                EmailsEntity entity = convertMessageToEmailVo(message, false);
+                if (Objects.isNull(entity)) {
+                    continue;
+                }
+                entity.setMessageId(messageId);
+
+                if (StrUtil.isBlank(entity.getFromName())) {
+                    entity.setFromName(StringUtil.extractUsernames(entity.getFrom()));
+                }
+                if (StrUtil.isBlank(entity.getRecipientName())) {
+                    entity.setRecipientName(StringUtil.extractUsernames(entity.getRecipient()));
+                }
+                if (StrUtil.isBlank(entity.getRecipientCcName())) {
+                    entity.setRecipientCcName(StringUtil.extractUsernames(entity.getRecipientCc()));
+                }
+                if (cusAddressls.contains(entity.getFrom())) {
+                    entity.setInOutMark(1);
+                    entity.setFolder("INBOX");
+                } else {
+                    entity.setInOutMark(2);
+                    entity.setFolder("SENT");
+                }
+                if (Objects.isNull(entity.getRecipientDate())) {
+                    entity.setRecipientDate(entity.getSentDate());
+                }
+                entity.setSourceType(1);
+                entity.setOwnerBy(ownerBy);
+                entity.setUpdateBy(1L);
+                entity.setCreateBy(1L);
+                entity.setIsOnlyHead(0);
+
+                Boolean b = saveOrUpdate(entity);
+                if (b) {
+                    analysisDownLoadAttachment(entity, message);
+                    /**
+                     * 加载附件
+                     */
+                    if (Integer.valueOf(1).equals(entity.getInOutMark())) {
+                        bindLiaisonMailCustomerIdByMailIdAndUserId(entity.getId(), entity.getFrom(), 1L, 1);
+                    } else {
+                        String recipient = entity.getRecipient();
+                        if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+                            if (StrUtil.isNotBlank(recipient)) {
+                                recipient = recipient + "," + entity.getRecipientCc();
+                            } else {
+                                recipient = entity.getRecipientCc();
+                            }
+                        }
+                        bindLiaisonMailCustomerIdByMailIdAndUserId(entity.getId(), recipient, 1L, 2);
+                    }
+                }
+            } catch (Exception e) {
+                log.error("analysisMailEml -- error:", e);
+            }
+        }
+
+    }
+
+
+    @Override
+    public List<EmailsEntity> analysisMailEmlls(List<Message> messages,Set<String> internalMails,Long ownerBy) {
+
+        List<EmailsEntity> entities = new ArrayList<>();
+
+        if (CollectionUtils.isEmpty(messages) || CollectionUtils.isEmpty(internalMails)) {
+           return  entities;
+        }
+        List<Message> messagels = messages;
+        for (Message message : messagels) {
+
+            try {
+                String[] r = message.getHeader("Message-ID");
+                if (Objects.isNull(r)) {
+                    continue;
+                }
+                String messageId = message.getHeader("Message-ID")[0];
+                Map<String, String> fromMap = decodeAndPrintAddresses(message.getFrom());
+                String fromAddress = mapKeyToString(fromMap);
+                Integer count = this.getEmailsByMessageId(messageId, fromAddress);
+                if (!Integer.valueOf(0).equals(count)) {
+                    log.error("getEmailsByMessageId-----" + messageId);
+                    continue;
+                }
+                EmailsEntity entity = convertMessageToEmailVo(message, false);
+                if (Objects.isNull(entity)) {
+                    continue;
+                }
+                entity.setMessageId(messageId);
+
+                if (StrUtil.isBlank(entity.getFromName())) {
+                    entity.setFromName(StringUtil.extractUsernames(entity.getFrom()));
+                }
+                if (StrUtil.isBlank(entity.getRecipientName())) {
+                    entity.setRecipientName(StringUtil.extractUsernames(entity.getRecipient()));
+                }
+                if (StrUtil.isBlank(entity.getRecipientCcName())) {
+                    entity.setRecipientCcName(StringUtil.extractUsernames(entity.getRecipientCc()));
+                }
+                /**
+                 * 如果发件箱是内部邮箱,则代表是发出去的邮件
+                 */
+                if (internalMails.contains(entity.getFrom())) {
+                    entity.setInOutMark(2);
+                    entity.setFolder("SENT");
+                } else {
+                    entity.setInOutMark(1);
+                    entity.setFolder("INBOX");
+                }
+                if (Objects.isNull(entity.getRecipientDate())) {
+                    entity.setRecipientDate(entity.getSentDate());
+                }
+                entity.setSourceType(1);
+                entity.setOwnerBy(ownerBy);
+                entity.setUpdateBy(1L);
+                entity.setCreateBy(1L);
+                entity.setIsOnlyHead(0);
+
+                Boolean b = saveOrUpdate(entity);
+                if (b) {
+                    entities.add(entity);
+                }
+            } catch (Exception e) {
+                log.error("analysisMailEml -- error:", e);
+            }
+        }
+        return entities;
+    }
+
+//    public void pullMailRecord(SmtpPopSettingsEntity smtpPop, String folderName, EmailBoxEnum boxEnum, List<Integer> messageIds) {
+//        MailProperties properties = new MailProperties(smtpPop);
+//        MailConnection mailConnection = MailConnectionUtil.receiveEmailsConnection(properties);
+//        if (Objects.isNull(mailConnection)) {
+//            return;
+//        }
+//        try {
+//            Store store = mailConnection.getStore();
+//            Folder inbox = store.getFolder(folderName);
+//            inbox.open(Folder.READ_WRITE);
+//
+//            int[] intArray = messageIds.stream().mapToInt(Integer::intValue).toArray();
+//            Message[] messages  = inbox.getMessages(intArray);
+//            for (Message message : messages) {
+//                String messageId = message.getHeader("Message-ID")[0];
+//                EmailsEntity entity = convertMessageToEmailVo(message,false);
+//                if (Objects.isNull(entity)) {
+//                    return;
+//                }
+//                entity.setSmtpPopId(smtpPop.getId());
+//                entity.setMsgUid(messageId);
+//                entity.setFolder(boxEnum.code);
+//                Boolean b = this.save(entity);
+//                if(b) {
+//                    syncPullEmails(entity,message,folderName,smtpPop);
+//                }
+//            }
+//            mailConnection.close();
+//        }catch (Exception e) {
+//            log.error("receiveEmails - error =",e);
+//            mailConnection.close();
+//        }
+//    }
+
+    public static void main(String[] args) {
+        try {
+            String baseDir = "D:/mail/attachment/rena@renice-tech.com/20240911";
+            String fileName = "Datasheet(128G SLC)-Renice X5 2.5 SATAII SSD V1.2 Version.pdf.KrhPNh";
+            Path filePath = Paths.get(baseDir, fileName);
+            File file = new File(filePath.toUri());
+            File parentDir = file.getParentFile();
+            // 如果父目录不存在,则递归创建
+            if (parentDir != null && !parentDir.exists()) {
+                parentDir.mkdirs();  // mkdirs() 会创建所有必要的父目录
+            }
+            String content = "这是要写入文件的字符串内容";
+
+            try (InputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+                 FileOutputStream output = new FileOutputStream(file)) {
+                inputStream.transferTo(output); // 将字符串流写入文件
+            }
+            System.out.println("-----------------------"+filePath);
+        }catch (Exception e) {
+            log.error(e);
+        }
+
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/FoldersServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.FoldersService;
+import com.storlead.mail.pojo.entity.FoldersEntity;
+import com.storlead.mail.mapper.FoldersMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 文件夹名称 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Service
+public class FoldersServiceImpl extends MyBaseServiceImpl<FoldersMapper, FoldersEntity> implements FoldersService {
+
+}

+ 29 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailAttachmentServiceImpl.java

@@ -0,0 +1,29 @@
+package com.storlead.mail.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.mail.service.MailAttachmentService;
+import com.storlead.mail.pojo.entity.MailAttachmentEntity;
+import com.storlead.mail.mapper.MailAttachmentMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件附件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-06-14
+ */
+@Service
+@Log4j2
+public class MailAttachmentServiceImpl extends MyBaseServiceImpl<MailAttachmentMapper, MailAttachmentEntity> implements MailAttachmentService {
+
+    @Override
+    public IPage<MailAttachmentEntity> pateList(Page<MailAttachmentEntity> page, Wrapper<MailAttachmentEntity> wrapper) {
+        return this.baseMapper.pageList(page,wrapper);
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailDelayPullRecordServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.MailDelayPullRecordService;
+import com.storlead.mail.pojo.entity.MailDelayPullRecordEntity;
+import com.storlead.mail.mapper.MailDelayPullRecordMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮件等待处理记录 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-24
+ */
+@Service
+public class MailDelayPullRecordServiceImpl extends MyBaseServiceImpl<MailDelayPullRecordMapper, MailDelayPullRecordEntity> implements MailDelayPullRecordService {
+
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailServerPortConfigServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.MailServerPortConfigService;
+import com.storlead.mail.pojo.entity.MailServerPortConfigEntity;
+import com.storlead.mail.mapper.MailServerPortConfigMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮箱端口配置 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-08
+ */
+@Service
+public class MailServerPortConfigServiceImpl extends MyBaseServiceImpl<MailServerPortConfigMapper, MailServerPortConfigEntity> implements MailServerPortConfigService {
+
+}

+ 101 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailTempAttachmentServiceImpl.java

@@ -0,0 +1,101 @@
+package com.storlead.mail.service.impl;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.framework.common.util.RandomGenerateHelper;
+import com.storlead.mail.properties.MailFileProperties;
+import com.storlead.mail.service.MailTempAttachmentService;
+import com.storlead.mail.pojo.entity.MailTempAttachmentEntity;
+import com.storlead.mail.mapper.MailTempAttachmentMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.mail.internet.MimeUtility;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.UUID;
+
+/**
+ * <p>
+ * 临时邮件附件表 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-08-09
+ */
+@Service
+public class MailTempAttachmentServiceImpl extends MyBaseServiceImpl<MailTempAttachmentMapper, MailTempAttachmentEntity> implements MailTempAttachmentService {
+
+    @Resource
+    private MailFileProperties mailFileProperties;
+
+    @Override
+    public MailTempAttachmentEntity saveAttachmentToDatabase(File file, String code,Long mailId) {
+
+        String fileName = StrUtil.isBlank(code) ? file.getName() : file.getName().replace("."+code,"");
+        MailTempAttachmentEntity attachment = new MailTempAttachmentEntity();
+        attachment.setEmailId(mailId);
+        attachment.setFileCode(code);
+        attachment.setFileName(FilenameUtils.getBaseName(fileName));
+        attachment.setFilePath(file.getAbsolutePath().replace(mailFileProperties.getPath().getPath(),""));
+        attachment.setFileExt(FilenameUtils.getExtension(fileName));
+        attachment.setFileSize(file.length());
+        this.save(attachment);
+        return attachment;
+    }
+
+    @Override
+    public Long saveTempAttachment(MultipartFile file, String downloadDir, Long mailId) {
+        try {
+            String fileName = file.getOriginalFilename();
+            fileName = MimeUtility.decodeText(fileName);
+            fileName = fileName.replace("\n", "").replace("\r", "");
+            String code =  RandomGenerateHelper.generateRandomString(6);
+            // 如果不存在这创建路径
+            if (!new File(downloadDir).exists()) {
+                new File(downloadDir).mkdirs();
+            }
+            fileName = fileName+"."+code;
+            File filePath = new File(downloadDir +  File.separator + fileName);
+            try (FileOutputStream output = new FileOutputStream(filePath)) {
+                file.getInputStream().transferTo(output);
+            }
+            MailTempAttachmentEntity attachment = saveAttachmentToDatabase(filePath,code, mailId);
+            return attachment.getId();
+        }catch (Exception e) {
+            log.error("--upload -- saveTempAttachment --error --",e);
+            return null;
+        }
+    }
+
+    @Override
+    public MailTempAttachmentEntity saveTempImageAttachment(MultipartFile file, String downloadDir) {
+        try {
+            String fileName = file.getOriginalFilename();
+            fileName = MimeUtility.decodeText(fileName);
+            fileName = fileName.replace("\n", "").replace("\r", "");
+            String extension =  FilenameUtils.getExtension(fileName);
+            String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+            String uniqueId = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
+
+            String newFileName = "image_" + timestamp + "_" + uniqueId + "." + extension;
+            // 如果不存在这创建路径
+            if (!new File(downloadDir).exists()) {
+                new File(downloadDir).mkdirs();
+            }
+            File filePath = new File(downloadDir +  File.separator + newFileName);
+            try (FileOutputStream output = new FileOutputStream(filePath)) {
+                file.getInputStream().transferTo(output);
+            }
+            MailTempAttachmentEntity attachment = saveAttachmentToDatabase(filePath,"", null);
+            return attachment;
+        }catch (Exception e) {
+            log.error("--upload -- saveTempAttachment --error --",e);
+            return null;
+        }
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailboxAutoReplySetServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.MailboxAutoReplySetService;
+import com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity;
+import com.storlead.mail.mapper.MailboxAutoReplySetMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 邮箱自动回复内容 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2025-02-24
+ */
+@Service
+public class MailboxAutoReplySetServiceImpl extends MyBaseServiceImpl<MailboxAutoReplySetMapper, MailboxAutoReplySetEntity> implements MailboxAutoReplySetService {
+
+}

+ 67 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/SmtpPopSettingsServiceImpl.java

@@ -0,0 +1,67 @@
+package com.storlead.mail.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.storlead.framework.common.constant.CommonConstant;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import com.storlead.mail.pojo.entity.SmtpPopSettingsEntity;
+import com.storlead.mail.mapper.SmtpPopSettingsMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>
+ * 邮箱smtp_setting 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-05-28
+ */
+@Service
+public class SmtpPopSettingsServiceImpl extends MyBaseServiceImpl<SmtpPopSettingsMapper, SmtpPopSettingsEntity> implements SmtpPopSettingsService {
+
+    @Override
+    public SmtpPopSettingsEntity getDefaultSmtpPop(Long userId) {
+        LambdaQueryWrapper<SmtpPopSettingsEntity> wrapper = new LambdaQueryWrapper();
+        wrapper.eq(SmtpPopSettingsEntity::getOwnerBy,userId);
+        wrapper.eq(SmtpPopSettingsEntity::getIsDelete,CommonConstant.DEL_FLAG_0);
+        wrapper.eq(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(1));
+        wrapper.last(" limit 1");
+        SmtpPopSettingsEntity entity = this.getOne(wrapper);
+        return entity;
+    }
+
+    @Override
+    public SmtpPopSettingsEntity getSystemSmtpPop(Long popId) {
+        SmtpPopSettingsEntity entity = this.getById(popId);
+        return entity;
+    }
+
+    @Override
+    public List<SmtpPopSettingsEntity> getDefaultSmtpPopls(Set userIds){
+        LambdaQueryWrapper<SmtpPopSettingsEntity> wrapper = new LambdaQueryWrapper();
+        wrapper.in(SmtpPopSettingsEntity::getOwnerBy,userIds);
+        wrapper.eq(SmtpPopSettingsEntity::getUseDefault,Integer.valueOf(1));
+        List<SmtpPopSettingsEntity> entityls = this.list(wrapper);
+        return entityls;
+    }
+    @Override
+    public List<SmtpPopSettingsEntity> getEmailAccount(Long userId) {
+        LambdaQueryWrapper<SmtpPopSettingsEntity> wrapper = new LambdaQueryWrapper();
+        wrapper.eq(SmtpPopSettingsEntity::getOwnerBy,userId);
+        wrapper.eq(SmtpPopSettingsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        return this.list(wrapper);
+    }
+
+    @Override
+    public SmtpPopSettingsEntity getEmailByAccountPopId(Long userId, Long popId) {
+        LambdaQueryWrapper<SmtpPopSettingsEntity> wrapper = new LambdaQueryWrapper();
+        wrapper.eq(SmtpPopSettingsEntity::getOwnerBy,userId);
+        wrapper.eq(SmtpPopSettingsEntity::getId,popId);
+        wrapper.eq(SmtpPopSettingsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.last("limit 1");
+        return this.getOne(wrapper);
+    }
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/UserEmailFolderServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.UserEmailFolderService;
+import com.storlead.mail.pojo.entity.UserEmailFolderEntity;
+import com.storlead.mail.mapper.UserEmailFolderMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 用户文件夹 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-12
+ */
+@Service
+public class UserEmailFolderServiceImpl extends MyBaseServiceImpl<UserEmailFolderMapper, UserEmailFolderEntity> implements UserEmailFolderService {
+
+}

+ 20 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/UserMailTrackRecordServiceImpl.java

@@ -0,0 +1,20 @@
+package com.storlead.mail.service.impl;
+
+import com.storlead.mail.service.UserMailTrackRecordService;
+import com.storlead.mail.pojo.entity.UserMailTrackRecordEntity;
+import com.storlead.mail.mapper.UserMailTrackRecordMapper;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 当前收取邮件的最新邮件记录 服务实现类
+ * </p>
+ *
+ * @author chenkq
+ * @since 2024-11-19
+ */
+@Service
+public class UserMailTrackRecordServiceImpl extends MyBaseServiceImpl<UserMailTrackRecordMapper, UserMailTrackRecordEntity> implements UserMailTrackRecordService {
+
+}

+ 285 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/EmailHelper.java

@@ -0,0 +1,285 @@
+package com.storlead.mail.util;
+
+import lombok.extern.log4j.Log4j2;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.mail.javamail.MimeMessageHelper;
+
+import javax.activation.DataHandler;
+import javax.activation.DataSource;
+import javax.activation.FileDataSource;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.util.ByteArrayDataSource;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-12-13 10:20
+ */
+@Log4j2
+public class EmailHelper {
+    private static final Map<String, String> mimeToExtensionMap = new HashMap<>();
+
+    static {
+        mimeToExtensionMap.put("image/png", "png");
+        mimeToExtensionMap.put("image/jpeg", "jpg");
+        mimeToExtensionMap.put("image/gif", "gif");
+        mimeToExtensionMap.put("application/pdf", "pdf");
+        mimeToExtensionMap.put("text/plain", "txt");
+        // 根据需要继续添加其他 MIME 类型映射
+    }
+    /**
+     * 将 HTML 内容中的 img 标签的 src URL 转换为 cid:imageX 格式。
+     *
+     * @param htmlContent 邮件的 HTML 内容
+     * @param imagePathMap 图片路径的映射
+     * @return 修改后的 HTML 内容
+     * @throws
+     */
+    public static String convertImageUrlsToCid(String htmlContent, Map<String, File> imagePathMap,String filePath) {
+        Document doc = Jsoup.parse(htmlContent);  // 使用 Jsoup 解析 HTML 内容
+
+        // 遍历所有 img 标签
+        for (Element img : doc.select("img")) {
+            String src = img.attr("src");  // 获取 src 属性(图片 URL)
+
+            // 如果 src 是一个有效的 URL,且需要转换为 CID
+            if (src != null && !src.isEmpty()) {
+                // 根据图片的 URL 为每个图片生成唯一的 CID
+                String cid = "image" + imagePathMap.size() + 1;  // 简单的 CID 生成规则,可以改成更复杂的逻辑
+                img.attr("src", "cid:" + cid);  // 将 src 转换为 cid:imageX 格式
+                // 将图片路径和生成的 CID 映射保存
+                String downloadDir = filePath;
+                String imageName = src.replace("/sales/mail/file/images/","/");
+                String url = downloadDir + imageName;
+                imagePathMap.put(cid, new File(url));
+            }
+        }
+
+        return doc.html();  // 返回修改后的 HTML 内容
+    }
+
+    public static String getFileExtensionFromBase64(String base64Data) {
+        // 1. 先检查是否包含 `data:` URL 格式
+        if (base64Data.startsWith("data:")) {
+            // 从 `data:[mediatype];base64,` 中提取 MIME 类型
+            String mimeTypePart = base64Data.split(";")[0];  // 例如:data:image/png
+            String mimeType = mimeTypePart.substring(5);  // 去掉 "data:",例如 "image/png"
+
+            // 2. 查找扩展名
+            return mimeToExtensionMap.getOrDefault(mimeType, "unknown");  // 返回对应的扩展名
+        } else {
+            throw new IllegalArgumentException("Invalid Base64 format. Expected data URL format.");
+        }
+    }
+
+    public static MimeBodyPart createBase64BodyPart(String base64Data, String contentType,String extension)  {
+        // 1. 解码 Base64 数据
+        try {
+
+            String timestamp = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+            String uniqueId = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 8);
+
+            String newFileName = "image_" + timestamp + "_" + uniqueId + "." + extension;
+            byte[] decodedData = Base64.getDecoder().decode(base64Data);
+            // 2. 创建 ByteArrayDataSource,将解码后的数据作为输入流
+            // 3. 创建 MimeBodyPart,并设置数据源
+            MimeBodyPart mimeBodyPart = new MimeBodyPart();
+            DataSource dataSource = new ByteArrayDataSource(decodedData, contentType);
+            mimeBodyPart.setDataHandler(new DataHandler(dataSource));
+            // 4. 设置文件名(可选)
+            mimeBodyPart.setFileName(newFileName);  // 根据需要设置文件名和扩展名
+            return mimeBodyPart;
+        }catch (Exception e) {
+            log.error("convertImageUrlsToCidBodyPart -- error",e);
+            return null;
+        }
+    }
+
+    public static String convertImageUrlsToCidBodyPart(String htmlContent, List<MimeBodyPart> imageParts, String filePath, Multipart multipart) {
+        Document doc = Jsoup.parse(htmlContent);  // 使用 Jsoup 解析 HTML 内容
+
+        Integer i = 0;
+        // 遍历所有 img 标签
+        for (Element img : doc.select("img")) {
+            try {
+                i ++ ;
+
+                String src = img.attr("src");
+                String cid = "image_sp_" + i;
+                if (src.startsWith("data:image/")) {
+                    // 如果 src 是一个有效的 URL,且需要转换为 CID
+                    String mimeTypePart = src.split(";")[0];  // 例如:data:image/png
+                    String mimeType = mimeTypePart.substring(5);    // 去掉 "data:",例如 "image/png"
+                    // 3. 根据 MIME 类型推断扩展名
+                    String extension = mimeToExtensionMap.getOrDefault(mimeType, "unknown");
+                    // 4. 提取 Base64 编码的内容(去掉前缀 "data:image/png;base64,")
+                    String base64Content = src.split(",")[1];
+                    MimeBodyPart imagePart = createBase64BodyPart(base64Content, mimeType,extension);
+                    imagePart.setHeader("Content-ID", cid);
+                    img.attr("src", "cid:" + cid);
+                    imagePart.setDisposition(Part.INLINE);  // 设置为内嵌
+                    multipart.addBodyPart(imagePart);
+                }else if (src.contains("https://sales.storlead.com/sales/mail")){
+                    if (src != null && !src.isEmpty()) {
+                        // 根据图片的 URL 为每个图片生成唯一的 CID
+                        img.attr("src", "cid:" + cid);  // 将 src 转换为 cid:imageX 格式
+                    }
+                    MimeBodyPart imagePart = createImagePartFromURL(src,cid);
+                    multipart.addBodyPart(imagePart);
+
+                } else if (src.contains("/sales/mail/file/images/")){
+                    if (src != null && !src.isEmpty()) {
+                        // 根据图片的 URL 为每个图片生成唯一的 CID
+                        img.attr("src", "cid:" + cid);  // 将 src 转换为 cid:imageX 格式
+                    }
+                    String downloadDir = filePath;
+                    String imageName = src.replace("/sales/mail/file/images/", "");
+                    String imagePath = downloadDir + imageName;
+                    MimeBodyPart imagePart = new MimeBodyPart();
+                    DataSource source = new FileDataSource(imagePath);
+                    imagePart.setDataHandler(new DataHandler(source));
+                    imagePart.setHeader("Content-ID", cid);
+                    imagePart.setDisposition(Part.INLINE);  // 设置为内嵌
+                    multipart.addBodyPart(imagePart);
+
+                }
+                // 获取 src 属性(图片 URL)
+
+            }catch (Exception e ) {
+                log.error("convertImageUrlsToCidBodyPart -- error",e);
+            }
+        }
+        return doc.html();  // 返回修改后的 HTML 内容
+    }
+    public static MimeBodyPart createImagePartFromURL(String imageUrl,String cid) throws Exception {
+        // 从URL加载图片
+        URL url = new URL(imageUrl);
+        try (InputStream inputStream = url.openStream()) {
+            byte[] imageBytes = inputStream.readAllBytes();
+            DataSource dataSource = new ByteArrayDataSource(imageBytes, "image/jpeg");  // 根据图片类型选择MIME类型
+            MimeBodyPart imagePart = new MimeBodyPart();
+            imagePart.setDataHandler(new javax.activation.DataHandler(dataSource));
+            String fileNameWithExtension = Paths.get(url.getPath()).getFileName().toString();
+            String fileNameWithoutExtension = fileNameWithExtension.substring(0, fileNameWithExtension.lastIndexOf('.'));
+            imagePart.setFileName(fileNameWithoutExtension);  // 设置附件名称去掉后缀
+            imagePart.setHeader("Content-ID", cid);
+            imagePart.setDisposition(Part.INLINE);  // 设置为内嵌
+            return imagePart;
+        }
+    }
+
+    public static String convertImageUrlsToBase(String htmlContent, String filePath) {
+        Document doc = Jsoup.parse(htmlContent);  // 使用 Jsoup 解析 HTML 内容
+        String downloadDir = filePath;
+
+        // 遍历所有 img 标签
+        for (Element img : doc.select("img")) {
+            String src = img.attr("src");  // 获取 src 属性(图片 URL)
+            String imageName = src.replace("/sales/mail/file/images/", "");
+            String imgSrc = downloadDir + imageName;
+            String base64Image = convertImageToFileBase64(imgSrc);
+            String imageExtension = imgSrc.substring(imgSrc.lastIndexOf('.') + 1);
+            // 构造 base64 数据 URI
+            String base64DataUri = "data:image/" + imageExtension + ";base64," + base64Image;
+            // 将图片的 src 属性替换为 base64 数据 URI
+            img.attr("src", base64DataUri);
+        }
+
+        return doc.html();  // 返回修改后的 HTML 内容
+    }
+
+    private static String convertImageToBase64(String imageUrl) {
+        try {
+            URL url = new URL(imageUrl);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setDoOutput(true);
+            connection.connect();
+
+            // 从连接中读取图片数据
+            try (InputStream in = connection.getInputStream()) {
+                byte[] imageBytes = in.readAllBytes();  // 读取所有图片字节
+                return Base64.getEncoder().encodeToString(imageBytes);  // 转换为 Base64 编码
+            }
+        }catch (Exception e) {
+            log.error("convertImageToBase64 -- error",e);
+        }
+        return null;
+    }
+    public static String convertImageToFileBase64(String imagePath)  {
+        try {
+        // 读取图片文件
+        byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
+
+        // 使用 Base64 编码将图片字节数组转换为字符串
+        return Base64.getEncoder().encodeToString(imageBytes);
+        }catch (Exception e) {
+            log.error("convertImageToBase64 -- error",e);
+        }
+        return null;
+    }
+    /**
+     * 将图片添加到邮件附件中,并返回一个 Map 映射,包含每个图片的 CID 和对应的 File。
+     *
+     * @param imagePathMap 图片路径的映射
+     * @param helper       MimeMessageHelper
+     * @throws
+     */
+    public static void addImagesToMail(Map<String, File> imagePathMap, MimeMessageHelper helper)  {
+        // 遍历 Map,将每个图片作为附件添加到邮件中
+        try {
+            for (Map.Entry<String, File> entry : imagePathMap.entrySet()) {
+                String cid = entry.getKey();
+                File imageFile = entry.getValue();
+
+                // 添加内嵌图片,Content-ID 用于在 HTML 中引用
+                helper.addInline(cid, imageFile);
+            }
+        }catch (Exception e) {
+        }
+    }
+
+    private static File downloadImage(String imageUrl) {
+
+        try {
+            URL url = new URL(imageUrl);
+            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setDoOutput(true);
+            connection.connect();
+
+            String tempDir = System.getProperty("java.io.tmpdir");
+            String imageName = UUID.randomUUID().toString() + ".jpg";  // 根据实际类型修改
+            File tempImage = new File(tempDir, imageName);
+
+            try (InputStream in = connection.getInputStream();
+                 OutputStream out = new FileOutputStream(tempImage)) {
+                byte[] buffer = new byte[4096];
+                int bytesRead;
+                while ((bytesRead = in.read(buffer)) != -1) {
+                    out.write(buffer, 0, bytesRead);
+                }
+            }
+            return tempImage;
+        }catch (Exception e ) {
+            log.error("downloadImage -- error",e);
+            return null;
+        }
+    }
+}

+ 203 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/EmailSenderWithThreadLocal.java

@@ -0,0 +1,203 @@
+package com.storlead.mail.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.storlead.framework.common.util.SpringContextUtils;
+import com.storlead.mail.pojo.entity.ClientSentEmailsEntity;
+import com.storlead.mail.pojo.entity.EmailsEntity;
+import com.storlead.mail.enums.SendStatus;
+import com.storlead.mail.service.ClientSentEmailsService;
+import com.storlead.mail.service.EmailsService;
+import com.storlead.mail.service.SmtpPopSettingsService;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import javax.mail.*;
+import javax.mail.event.TransportEvent;
+import javax.mail.event.TransportListener;
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-28 10:01
+ */
+@Log4j2
+@Component
+public class EmailSenderWithThreadLocal {
+    @Resource
+    private EmailsService emailsService;
+    @Resource
+    private ClientSentEmailsService sentEmailsService;
+    @Resource
+    private SmtpPopSettingsService popSettingsService;
+
+
+
+    public void asynSendMailMessage(Transport transport, Message message, EmailsEntity email) {
+        try {
+            transport.addTransportListener(new TransportListener() {
+                @Override
+                public void messageDelivered(TransportEvent e) {
+                    onEmailSentSuccess(email);
+                }
+
+                @Override
+                public void messageNotDelivered(TransportEvent e) {
+                    onEmailSentFailure(email);
+                }
+
+                @Override
+                public void messagePartiallyDelivered(TransportEvent e) {
+                    onEmailSentPartialSuccess(email);
+                }
+            });
+            transport.connect();
+            transport.sendMessage(message, message.getAllRecipients());
+            transport.close();
+        }catch (Exception e) {
+            log.error("error-------------",e);
+            e.printStackTrace();
+        }
+    }
+
+    public CompletableFuture<SendStatus> sendMailMessage(Transport transport, Message message, EmailsEntity email) {
+        CompletableFuture<SendStatus> future = new CompletableFuture<SendStatus>();
+
+        TransportListener listener = new TransportListener() {
+            @Override
+            public void messageDelivered(TransportEvent e) {
+                onEmailSentSuccess(email);
+                future.complete(SendStatus.SUCCESS);
+            }
+
+            @Override
+            public void messageNotDelivered(TransportEvent e) {
+                onEmailSentFailure(email);
+                future.complete(SendStatus.FAILED);
+            }
+
+            @Override
+            public void messagePartiallyDelivered(TransportEvent e) {
+                onEmailSentPartialSuccess(email);
+                future.complete(SendStatus.PARTIAL_SUCCESS);
+            }
+        };
+
+        try {
+            transport.addTransportListener(listener); // 注意:应使用addTransportListener而不是addTransportListener(可能原代码有拼写错误)
+            transport.connect();
+            transport.sendMessage(message, message.getAllRecipients());
+            transport.close();
+        } catch (SendFailedException e1) {
+            log.error("邮件发送过程中发生异常", e1);
+            future.complete(SendStatus.SERVER_EXCEPTION_550);
+        } catch (AuthenticationFailedException e) {
+            future.complete(SendStatus.SERVER_LOGIN_FAIL);
+        } catch (MessagingException e) {
+            log.error("邮件发送过程中发生异常", e);
+            future.complete(SendStatus.SERVER_LOGIN_FAIL);
+//            if (e.getMessage().contains("")) {
+//                future.complete(SendStatus.PARTIAL_SUCCESS);
+//            } else if (e.getMessage().contains("")) {
+//                future.complete(SendStatus.PARTIAL_SUCCESS);
+//            } else {
+//            }
+        }
+        return future;
+    }
+
+    private void onEmailSentSuccess(EmailsEntity entity)  {
+        try {
+            try {
+                Object myClassInstance = SpringContextUtils.getBean("customerServiceImpl");
+                Class<?> clazz = myClassInstance.getClass();
+                Set<String> mailAddress = new HashSet<>();
+                if (StrUtil.isNotBlank(entity.getRecipient())) {
+                    List<String> recipientls = Arrays.asList(entity.getRecipient().split(","));
+                    mailAddress.addAll(recipientls);
+                }
+                if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+                    List<String> recipientccls = Arrays.asList(entity.getRecipientCc().split(","));
+                    mailAddress.addAll(recipientccls);
+                }
+                if (!CollectionUtils.isEmpty(mailAddress)) {
+                    LocalDateTime sentDate = (Objects.isNull(entity.getSentDate())) ? LocalDateTime.now() : entity.getSentDate().toInstant()
+                            .atZone(ZoneId.systemDefault()) // 使用系统默认时区
+                            .toLocalDateTime();
+                    Method method = clazz.getMethod("updateFollowUpTimeByEmail",Set.class, LocalDateTime.class);
+                    method.invoke(myClassInstance,mailAddress,sentDate);
+                }
+            } catch (Exception e) {
+                log.error("updateFollowUpTimeByEmail --e",e);
+            }
+
+            emailsService.removeById(entity.getId());
+            // 保存发送日志
+            saveLog(entity);
+        }catch (Exception e1) {
+            log.error("onEmailSentSuccess------------",e1);
+        }
+    }
+
+    private void onEmailSentFailure(EmailsEntity entity) {
+        if (Objects.isNull(emailsService)) {
+            return;
+        }
+        LambdaUpdateWrapper<EmailsEntity> update = new LambdaUpdateWrapper<>();
+        update.set(EmailsEntity::getStatus,Integer.valueOf(2));
+        update.eq(EmailsEntity::getId,entity.getId());
+        emailsService.update(update);
+    }
+
+    private void onEmailSentPartialSuccess(EmailsEntity entity) {
+        try {
+            try {
+                Object myClassInstance = SpringContextUtils.getBean("customerMailBingMarkServiceImpl");
+                Class<?> clazz = myClassInstance.getClass();
+                Set<String> mailAddress = new HashSet<>();
+                if (StrUtil.isNotBlank(entity.getRecipient())) {
+                    List<String> recipientls = Arrays.asList(entity.getRecipient().split(","));
+                    mailAddress.addAll(recipientls);
+                }
+                if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+                    List<String> recipientccls = Arrays.asList(entity.getRecipientCc().split(","));
+                    mailAddress.addAll(recipientccls);
+                }
+                LocalDateTime sentDate = (Objects.isNull(entity.getSentDate())) ? LocalDateTime.now() : entity.getSentDate().toInstant()
+                        .atZone(ZoneId.systemDefault()) // 使用系统默认时区
+                        .toLocalDateTime();
+
+                if (!CollectionUtils.isEmpty(mailAddress)) {
+                    Method method = clazz.getMethod("updateFollowUpTimeByEmail",Set.class, LocalDateTime.class);
+                    method.invoke(myClassInstance,mailAddress,sentDate);
+                }
+            } catch (Exception e) {
+                log.error("updateFollowUpTimeByEmail --e",e);
+            }
+            emailsService.removeById(entity.getId());
+            // 保存客户端的发送日志
+            saveLog(entity);
+        }catch (Exception e1) {
+            log.error("onEmailSentSuccess------------",e1);
+        }
+    }
+
+    private void saveLog(EmailsEntity entity) {
+        try {
+            ClientSentEmailsEntity sentEmailsEntity = new ClientSentEmailsEntity();
+            BeanUtils.copyProperties(entity,sentEmailsEntity);
+            sentEmailsService.save(sentEmailsEntity);
+        }catch (Exception e) {
+            log.error("saveLog-----ClientSentEmails-------",e);
+        }
+    }
+}

+ 174 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/FileExtractor.java

@@ -0,0 +1,174 @@
+package com.storlead.mail.util;
+
+import lombok.extern.java.Log;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.Message;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * @program: storlead-saas-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2026-05-18 11:00
+ */
+@Log4j2
+public class FileExtractor {
+    public static List<Message> processZipFile(String zipFilePath) {
+        List<Message> mimeMessages = new ArrayList<>();
+        try {
+            File file = new File(zipFilePath);
+            try (ZipFile zipFile = new ZipFile(file)) {
+                // 遍历压缩包中的每个条目
+                zipFile.stream().forEach(entry -> {
+                    try {
+                        // 使用指定的编码解码文件名
+//                        String entryName = new String(entry.getName().getBytes(StandardCharsets.ISO_8859_1), encoding);
+                        InputStream inputStream = zipFile.getInputStream(entry);
+                        Session session = Session.getDefaultInstance(System.getProperties());
+                        MimeMessage mimeMessage = new MimeMessage(session, inputStream);
+                        mimeMessages.add(mimeMessage);
+
+                        // 处理文件内容
+                    } catch (Exception e) {
+                        log.error("processZipFile --- error",e);
+                    }
+                });
+            }
+        }catch (Exception e) {
+            log.error("processZipFile -- error",e);
+        }
+        return mimeMessages;
+    }
+
+    // 解压 .zip 文件
+    public static void extractZip(Path zipFilePath){
+        try {
+// 创建一个解压的目录
+            Path extractDir = Paths.get("extracted_files");
+            Files.createDirectories(extractDir);
+
+            // 解压 ZIP 文件
+            try (ZipInputStream zipIn = new ZipInputStream(new FileInputStream(zipFilePath.toFile()))) {
+                ZipEntry entry;
+                while ((entry = zipIn.getNextEntry()) != null) {
+                    // 读取每个文件条目
+                    Path filePath = extractDir.resolve(entry.getName());
+                    // 如果条目是目录,创建目录
+                    if (entry.isDirectory()) {
+                        Files.createDirectories(filePath);
+                    } else {
+                        // 如果是文件,写入内容
+                        try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath.toFile()))) {
+                            byte[] buffer = new byte[1024];
+                            int length;
+                            while ((length = zipIn.read(buffer)) > 0) {
+                                bos.write(buffer, 0, length);
+                            }
+                        }
+                    }
+                    zipIn.closeEntry();
+                }
+            }
+        }catch (Exception e) {
+            log.error("extractZip - error", e);
+        }
+
+    }
+
+//    public static List<Message> processZipInMemory(InputStream zipInputStream)  {
+//        List<Message> mimeMessages = new ArrayList<>();
+//        try {
+//            try (ZipInputStream zipIn = new ZipInputStream(zipInputStream)) {
+//                ZipEntry entry;
+//                while ((entry = zipIn.getNextEntry()) != null) {
+//                    // 如果条目是 .eml 文件,进行处理
+//                    if (entry.getName().endsWith(".eml")) {
+//                        Session session = Session.getDefaultInstance(System.getProperties());
+//                        MimeMessage mimeMessage = new MimeMessage(session, zipIn);
+//                        mimeMessages.add(mimeMessage);
+//                    }
+//                    zipIn.closeEntry();
+//                }
+//            }
+//        }catch (Exception e) {
+//            log.error("processZipInMemory -- error",e);
+//        }
+//        return mimeMessages;
+//    }
+
+    public static List<Message> processZipInMemory(InputStream zipInputStream) {
+        List<Message> mimeMessages = new ArrayList<>();
+        try {
+            processZipRecursively(zipInputStream, mimeMessages);
+        } catch (Exception e) {
+            log.error("Error processing ZIP file recursively", e);
+        }
+        return mimeMessages;
+    }
+
+    private static void processZipRecursively(InputStream inputStream, List<Message> mimeMessages) throws Exception {
+        ZipInputStream zipIn1 = new ZipInputStream(inputStream, StandardCharsets.ISO_8859_1);
+        try (ZipInputStream zipIn = zipIn1) {
+            ZipEntry entry;
+            while ((entry = zipIn.getNextEntry()) != null) {
+                try {
+                    if (entry.getName().endsWith(".eml")) {
+                        // Process EML file
+                        Session session = Session.getDefaultInstance(System.getProperties());
+                        MimeMessage mimeMessage = new MimeMessage(session, zipIn);
+                        mimeMessages.add(mimeMessage);
+                    } else if (entry.getName().toLowerCase().endsWith(".zip")) {
+                        // Recursively process nested ZIP files
+                        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+                        byte[] buffer = new byte[8192];
+                        int bytesRead;
+                        while ((bytesRead = zipIn.read(buffer)) != -1) {
+                            baos.write(buffer, 0, bytesRead);
+                        }
+
+                        ByteArrayInputStream nestedZipStream = new ByteArrayInputStream(baos.toByteArray());
+                        processZipRecursively(nestedZipStream, mimeMessages);
+                    }
+                } finally {
+                    zipIn.closeEntry();
+                }
+            }
+        }
+    }
+    // 逐条处理解压后的文件
+    public static List<Message> processExtractedFiles(Path extractedDir) {
+        List<Message> mimeMessages = new ArrayList<>();
+        try {
+            try (DirectoryStream<Path> stream = Files.newDirectoryStream(extractedDir)) {
+                for (Path entry : stream) {
+                    // 如果是 .eml 文件,则解析
+                    if (entry.toString().endsWith(".eml")) {
+                        try (InputStream emlInputStream = Files.newInputStream(entry)) {
+                            Session session = Session.getDefaultInstance(System.getProperties());
+                            MimeMessage mimeMessage = new MimeMessage(session, emlInputStream);
+                            mimeMessages.add(mimeMessage);
+                        }
+                    } else {
+                        System.out.println("Skipping non-email file: " + entry.getFileName());
+                    }
+                }
+            }
+        }catch (Exception e) {
+            log.error("processExtractedFiles error");
+        }
+        return mimeMessages;
+    }
+}

+ 42 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/FileHelper.java

@@ -0,0 +1,42 @@
+package com.storlead.mail.util;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.Objects;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-09 18:33
+ */
+
+public class FileHelper {
+
+    /**
+     * 获取文件大小
+     *
+     * @author timo
+     * @date 2018/9/3 14:03
+     */
+    public static String formatFileSize(Long size) {
+        if (Objects.isNull(size)) {
+            return "0 B";
+        }
+        if (Long.valueOf(0).equals(size)) {
+            return "0 B";
+        }
+        if (size < 0) {
+            return "0 B";
+        }
+        if (size < 1024) {
+            return size + " B";
+        } else if (size < 1024 * 1024) {
+            return String.format("%.2f KB", size / 1024.0);
+        } else if (size < 1024 * 1024 * 1024) {
+            return String.format("%.2f MB", size / (1024.0 * 1024));
+        } else {
+            return String.format("%.2f GB", size / (1024.0 * 1024 * 1024));
+        }
+    }
+}

+ 50 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/MailFormatPartUtil.java

@@ -0,0 +1,50 @@
+package com.storlead.mail.util;
+
+import javax.mail.BodyPart;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import java.io.InputStream;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-05-30 18:05
+ */
+public class MailFormatPartUtil {
+
+    public static void saveAttachment(BodyPart bodyPart) throws Exception {
+        String fileName = bodyPart.getFileName();
+        int size = bodyPart.getSize();
+        InputStream is = bodyPart.getInputStream();
+//        File file = new File("attachments/" + fileName); // 存储附件的文件夹路径
+//        try (FileOutputStream fos = new FileOutputStream(file)) {
+//            byte[] buffer = new byte[4096];
+//            int bytesRead;
+//            while ((bytesRead = is.read(buffer)) != -1) {
+//                fos.write(buffer, 0, bytesRead);
+//            }
+//        }
+        System.out.println("Saved attachment: " + fileName);
+    }
+
+
+    public static String parseMultipart(Multipart multipart) throws Exception {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (int i = 0; i < multipart.getCount(); i++) {
+            BodyPart bodyPart = multipart.getBodyPart(i);
+            if (bodyPart.isMimeType("text/plain")) {
+                stringBuilder.append(bodyPart.getContent());
+            } else if (bodyPart.isMimeType("text/html")) {
+                stringBuilder.append(bodyPart.getContent());
+            } else if (bodyPart.isMimeType("multipart/alternative")) {
+                parseMultipart((Multipart) bodyPart.getContent());
+            } else if (bodyPart.isMimeType("multipart/*")) {
+                parseMultipart((Multipart) bodyPart.getContent());
+            }else if (Part.ATTACHMENT.equalsIgnoreCase(bodyPart.getDisposition())) {
+                saveAttachment(bodyPart);
+            }
+        }
+        return stringBuilder.toString();
+    }
+}

+ 97 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/ReceiveMailQueueThreadPool.java

@@ -0,0 +1,97 @@
+package com.storlead.mail.util;
+
+import com.storlead.framework.common.thread.ThreadPoolUtil;
+import lombok.extern.log4j.Log4j2;
+
+import java.util.*;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-08-26 16:27
+ */
+@Log4j2
+public class ReceiveMailQueueThreadPool {
+
+
+    private static volatile ReceiveMailQueueThreadPool instance;
+
+    // 用于存储每个人的任务队列
+    private final Map<String, Queue<Runnable>> taskMap;
+
+    public Boolean getTaskInProgressByMail(String mailAddress) {
+        return taskInProgress.getOrDefault(mailAddress, false);
+    }
+
+    // 用于存储每个人当前是否有任务在进行中
+    private final Map<String, Boolean> taskInProgress;
+
+    // 线程池,用于执行任务
+    private final ThreadPoolExecutor executorService;
+
+    public ReceiveMailQueueThreadPool() {
+        this.taskMap = new HashMap<>();
+        this.taskInProgress = new HashMap<>();
+        this.executorService = ThreadPoolUtil.getThreadExecutor();
+    }
+
+    // 双重检查锁的单例获取方法
+    public static ReceiveMailQueueThreadPool getInstance() {
+        if (instance == null) {
+            synchronized (ReceiveMailQueueThreadPool.class) {
+                if (instance == null) {
+                    instance = new ReceiveMailQueueThreadPool();
+                }
+            }
+        }
+        return instance;
+    }
+
+    // 添加任务到某个人的队列
+    public synchronized void addTask(String person, Runnable task) {
+        if (Objects.nonNull(taskMap) && Objects.nonNull(taskMap.get(person))) {
+            return; // 该人有任务正在进行中
+        }
+        taskMap.putIfAbsent(person, new LinkedList<>());
+        taskMap.get(person).offer(task);
+        taskInProgress.putIfAbsent(person, false);
+        processNextTask(person);
+    }
+
+    // 处理某个人的下一个任务
+    private synchronized void processNextTask(String person) {
+        if (taskInProgress.getOrDefault(person, false)) {
+            return; // 该人有任务正在进行中
+        }
+
+        Queue<Runnable> queue = taskMap.get(person);
+        if (queue == null || queue.isEmpty()) {
+            log.error("没有任务了--"+person);
+            return; // 无任务可处理
+        }
+
+        Runnable task = queue.poll();
+        taskInProgress.put(person, true);
+        executorService.submit(() -> {
+            try {
+                task.run(); // 执行任务
+            } finally {
+                taskCompleted(person); // 标记任务完成并处理下一个任务
+            }
+        });
+    }
+
+    // 标记任务完成
+    public synchronized void taskCompleted(String person) {
+        taskInProgress.put(person, false);
+        taskMap.remove(person);
+        processNextTask(person);
+    }
+
+    // 关闭线程池
+    public void shutdown() {
+        executorService.shutdown();
+    }
+}

+ 90 - 0
java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/ZipUtility.java

@@ -0,0 +1,90 @@
+package com.storlead.mail.util;
+
+/**
+ * @program: sp-sales-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2024-07-24 14:55
+ */
+import com.storlead.mail.pojo.entity.MailAttachmentEntity;
+import com.storlead.mail.properties.MailFileProperties;
+import lombok.extern.log4j.Log4j2;
+
+import javax.mail.Message;
+import java.io.*;
+import java.util.List;
+import java.util.zip.*;
+
+@Log4j2
+public class ZipUtility {
+
+    public static void zipFiles(List<MailAttachmentEntity> attachments, String outputZipFile, MailFileProperties mailFileProperties) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(outputZipFile);
+             ZipOutputStream zos = new ZipOutputStream(fos)) {
+
+            for (MailAttachmentEntity attachment : attachments) {
+                String filePath = mailFileProperties.getPath().getPath() + File.separator + attachment.getFilePath();
+                File file = new File(filePath);
+                if (file.exists() && !file.isDirectory()) {
+                    try (FileInputStream fis = new FileInputStream(file)) {
+                        ZipEntry zipEntry = new ZipEntry(attachment.getFileName()+"."+attachment.getFileExt());
+                        zos.putNextEntry(zipEntry);
+
+                        byte[] buffer = new byte[1024];
+                        int length;
+                        while ((length = fis.read(buffer)) > 0) {
+                            zos.write(buffer, 0, length);
+                        }
+                        zos.closeEntry();
+                    }
+                } else {
+                    System.err.println("File not found: " + filePath);
+                }
+            }
+        }
+    }
+
+    public static void zipFiles(File sourceDir, File zipFile) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(zipFile);
+             ZipOutputStream zipOut = new ZipOutputStream(fos)) {
+            File[] files = sourceDir.listFiles();
+            for (File file : files) {
+                try (FileInputStream fis = new FileInputStream(file)) {
+                    ZipEntry zipEntry = new ZipEntry(file.getName());
+                    zipOut.putNextEntry(zipEntry);
+                    byte[] buffer = new byte[1024];
+                    int length;
+                    while ((length = fis.read(buffer)) >= 0) {
+                        zipOut.write(buffer, 0, length);
+                    }
+                    zipOut.closeEntry();
+                }
+            }
+            System.out.println("ZIP file created: " + zipFile.getAbsolutePath());
+        }
+    }
+
+    // 删除临时目录及其内容
+    public static void deleteDirectory(File directory) {
+        File[] files = directory.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                if (file.isDirectory()) {
+                    deleteDirectory(file);
+                } else {
+                    file.delete();
+                }
+            }
+        }
+        directory.delete();
+    }
+
+    public static void saveEmlFile(Message message, String filename) {
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            message.writeTo(fos);  // 将邮件内容写入文件
+            System.out.println("EML file saved as " + filename);
+        } catch (Exception e) {
+            log.error("saveEmlFile--------", e);
+        }
+    }
+}

+ 2 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+#org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+#  com.storlead.sales.mail.properties.FileProperties

+ 35 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/AttachmentMapper.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.mail.mapper.AttachmentMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.AttachmentEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                <result column="sort" property="sort" />
+                    <result column="email_id" property="emailId" />
+                    <result column="file_name" property="fileName" />
+                    <result column="file_url" property="fileUrl" />
+                    <result column="file_size" property="fileSize" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+                sort,
+            id, email_id, file_name, file_url, file_size
+        </sql>
+
+</mapper>

+ 66 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/ClientSentEmailsMapper.xml

@@ -0,0 +1,66 @@
+<?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.mail.mapper.ClientSentEmailsMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.ClientSentEmailsEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="sort" property="sort" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                    <result column="smtp_pop_id" property="smtpPopId" />
+                    <result column="msg_uid" property="msgUid" />
+                    <result column="message_id" property="messageId" />
+                    <result column="subject" property="subject" />
+                    <result column="from" property="from" />
+                    <result column="from_name" property="fromName" />
+                    <result column="recipient" property="recipient" />
+                    <result column="recipient_name" property="recipientName" />
+                    <result column="recipient_cc" property="recipientCc" />
+                    <result column="recipient_cc_name" property="recipientCcName" />
+                    <result column="recipient_bcc" property="recipientBcc" />
+                    <result column="recipient_bcc_name" property="recipientBccName" />
+                    <result column="folder" property="folder" />
+                    <result column="custom_folder_id" property="customFolderId" />
+                    <result column="content" property="content" />
+                    <result column="sent_date" property="sentDate" />
+                    <result column="recipient_date" property="recipientDate" />
+                    <result column="email_size" property="emailSize" />
+                    <result column="remark" property="remark" />
+                    <result column="in_out_mark" property="inOutMark" />
+                    <result column="is_read" property="isRead" />
+                    <result column="is_receipt" property="isReceipt" />
+                    <result column="is_track" property="isTrack" />
+                    <result column="read_remind" property="readRemind" />
+                    <result column="delay_send_time" property="delaySendTime" />
+                    <result column="recipient_read" property="recipientRead" />
+                    <result column="is_only_head" property="isOnlyHead" />
+                    <result column="reply_msg_id" property="replyMsgId" />
+                    <result column="status" property="status" />
+                    <result column="is_star" property="isStar" />
+                    <result column="is_trash" property="isTrash" />
+                    <result column="customer_ids" property="customerIds" />
+                    <result column="liaison_ids" property="liaisonIds" />
+                    <result column="follow_up_id" property="followUpId" />
+                    <result column="follow_up_time" property="followUpTime" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                sort,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+            id, smtp_pop_id, msg_uid, message_id, subject,`from`, from_name, recipient, recipient_name, recipient_cc, recipient_cc_name, recipient_bcc, recipient_bcc_name, folder, custom_folder_id, content, sent_date, recipient_date, email_size, remark, in_out_mark, is_read, is_receipt, is_track, read_remind, delay_send_time, recipient_read, is_only_head, reply_msg_id, status, is_star, is_trash, customer_ids, liaison_ids, follow_up_id, follow_up_time
+        </sql>
+
+</mapper>

+ 32 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailBlacklistRecordMapper.xml

@@ -0,0 +1,32 @@
+<?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.mail.mapper.EmailBlacklistRecordMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.EmailBlacklistRecordEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="sort" property="sort" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                    <result column="email_address" property="emailAddress" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                sort,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+            id, email_address
+        </sql>
+
+</mapper>

+ 33 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFileFolderMapper.xml

@@ -0,0 +1,33 @@
+<?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.mail.mapper.EmailFileFolderMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.EmailFileFolderEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="sort" property="sort" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                    <result column="folder_code" property="folderCode" />
+                    <result column="folder_name" property="folderName" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                sort,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+            id, folder_code, folder_name
+        </sql>
+
+</mapper>

+ 34 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFolderRuleMapper.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.mail.mapper.EmailFolderRuleMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.EmailFolderRuleEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="sort" property="sort" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                <result column="folder_rule_code" property="folderRuleCode" />
+                <result column="folder_id" property="folderId" />
+                <result column="compare_content" property="compareContent" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                sort,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+            id, folder_rule_code, folder_id, compare_content
+        </sql>
+
+</mapper>

+ 33 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFoldersMapper.xml

@@ -0,0 +1,33 @@
+<?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.mail.mapper.EmailFoldersMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.EmailFoldersEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                <result column="sort" property="sort" />
+                    <result column="email_id" property="emailId" />
+                    <result column="folder_id" property="folderId" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+                sort,
+            id, email_id, folder_id
+        </sql>
+
+</mapper>

+ 35 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailTemplatesMapper.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.mail.mapper.EmailTemplatesMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.EmailTemplatesEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="template_type" property="templateType" />
+                <result column="sort" property="sort" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                    <result column="template_title" property="templateTitle" />
+                    <result column="template_content" property="templateContent" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                template_type,
+                update_time,
+                sort,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+            id, template_title, template_content
+        </sql>
+
+</mapper>

+ 807 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailsMapper.xml

@@ -0,0 +1,807 @@
+<?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.mail.mapper.EmailsMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.EmailsEntity" extends="com.storlead.framework.mybatis.mapper.SysBaseFieldMapper.BaseResultMap">
+            <id column="id" property="id" />
+            <result column="smtp_pop_id" property="smtpPopId" />
+            <result column="msg_uid" property="msgUid" />
+            <result column="message_id" property="messageId" />
+            <result column="subject" property="subject" />
+            <result column="from" property="from" />
+            <result column="from_name" property="fromName" />
+            <result column="recipient" property="recipient" />
+            <result column="recipient_name" property="recipientName" />
+            <result column="recipient_cc" property="recipientCc" />
+            <result column="recipient_cc_name" property="recipientCcName" />
+            <result column="recipient_bcc" property="recipientBcc" />
+            <result column="recipient_bcc_name" property="recipientBccName" />
+            <result column="folder" property="folder" />
+            <result column="source_type" property="sourceType" />
+            <result column="custom_folder_id" property="customFolderId" />
+            <result column="content" property="content" />
+            <result column="is_star" property="isStar" />
+            <result column="is_trash" property="isTrash" />
+            <result column="sent_date" property="sentDate" />
+            <result column="recipient_date" property="recipientDate" />
+            <result column="email_size" property="emailSize" />
+            <result column="remark" property="remark" />
+            <result column="is_read" property="isRead" />
+            <result column="reply_msg_id" property="replyMsgId" />
+            <result column="status" property="status" />
+            <result column="is_receipt" property="isReceipt" />
+            <result column="is_track" property="isTrack" />
+            <result column="read_remind" property="readRemind" />
+            <result column="delay_send_time" property="delaySendTime" />
+            <result column="is_only_head" property="isOnlyHead" />
+            <result column="liaison_ids" property="liaisonIds" />
+            <result column="customer_ids" property="customerIds" />
+            <result column="customer_id" property="customerId" />
+            <result column="follow_up_id" property="followUpId" />
+            <result column="follow_up_time" property="followUpTime" />
+            <result column="customer_mail_id" property="customerMailId" />
+            <result column="delete_tag" property="deleteTag" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+            id, smtp_pop_id, msg_uid,source_type,message_id,follow_up_id,follow_up_time,is_receipt,is_only_head,is_track,delete_tag,read_remind,delay_send_time,customer_ids,liaison_ids, is_star,is_trash,subject,`from`, from_name, recipient, recipient_name, recipient_cc, recipient_cc_name, recipient_bcc, recipient_bcc_name, folder,custom_folder_id, content,remark, sent_date, recipient_date, email_size, is_read, reply_msg_id, status, owner_by, create_time, update_time, is_delete, enabled, create_by, update_by, sort
+        </sql>
+
+    <select id="updateCleanBindCustomerMail">
+--         UPDATE emails e
+--         SET e.customer_id = NULL;
+    </select>
+    <select id="updateBindCustomerMail">
+        UPDATE emails e
+            LEFT JOIN customer_company c ON e.from = c.email
+            SET e.customer_id = c.customer_id
+        WHERE c.customer_id IS NOT NULL AND e.customer_id is null and c.is_delete = 0;
+    </select>
+
+    <select id="updateBindLiaisonMail">
+        UPDATE emails e
+            LEFT JOIN liaison l ON e.from = l.email
+            SET e.customer_id = l.customer_id, e.liaison_id = l.id
+        WHERE l.customer_id IS NOT NULL AND (e.customer_id is null or e.liaison_id is null)  and l.is_delete = 0;
+    </select>
+
+    <select id="getMailCustomerIdByMailIdAndUserId" resultType="java.lang.Long">
+        SELECT customer_id FROM customer_company WHERE email is not null AND email != '' AND FIND_IN_SET(LOWER(email),#{mailAddress}) and is_delete = 0
+    </select>
+
+    <select id="getLiaisonMailCustomerIdByMailIdAndUserId" resultType="com.storlead.mail.pojo.vo.MailLiaisonVO">
+       SELECT id as liaisonId,customer_id as customerId FROM liaison WHERE  email is not null AND email != '' AND FIND_IN_SET(LOWER(email),#{mailAddress})  and is_delete = 0
+    </select>
+
+    <select id="bindMailCustomerIdByMailId">
+        update emails
+        set customer_ids= #{customerIds}
+        , liaison_ids = #{liaisonIds}
+        where id =  #{mailId}
+    </select>
+
+    <select id="getEmailsIdByMessageId" resultType="java.lang.Long">
+        select id from emails where message_id = #{messageId} and smtp_pop_id = #{smtpPopId} limit 1
+    </select>
+
+
+    <select id="getEmailsByMessageId" resultType="com.storlead.mail.pojo.entity.EmailsEntity">
+        select * from emails where message_id = #{messageId} and folder = #{folder} and smtp_pop_id = #{smtpPopId} limit 1
+    </select>
+
+    <select id="getEmailMessageIdsByMessageIds" resultType="java.lang.String">
+        select message_id from emails
+         <where>
+             <if test="messageIds != null and messageIds.size > 0">
+                 AND message_id IN
+                 <foreach collection="messageIds" item="messageId" open="(" separator="," close=")">
+                     #{messageId}
+                 </foreach>
+             </if>
+             and folder = #{folder} and smtp_pop_id = #{smtpPopId}
+         </where>
+    </select>
+
+    <select id="getEmailMessageIdsByMessageId" resultType="java.lang.String">
+        select message_id from emails where message_id = #{messageId} and folder = #{folder} and smtp_pop_id = #{smtpPopId}
+    </select>
+
+    <select id="pageList"  resultMap="BaseResultMap">
+        select e.id, e.smtp_pop_id, e.msg_uid,e.message_id,e.is_receipt,e.is_only_head,e.is_track,e.read_remind,
+        e.delay_send_time,e.customer_ids,e.liaison_ids, e.is_star,e.is_trash,e.subject,e.from, e.from_name, e.recipient,
+        e.recipient_name, e.recipient_cc, e.recipient_cc_name,e. recipient_bcc, e.recipient_bcc_name, e.folder,e.remark,
+        e.sent_date, e.recipient_date, e.email_size, e.is_read, e.reply_msg_id, e.status, e.owner_by, e.create_time, e.update_time,
+        e.is_delete, e.enabled, e.create_by, e.update_by, e.sort from  emails as e
+        <where>
+            <if test="(dto.customerName != null and dto.customerName != '') or dto.customerId != null">
+                AND EXISTS (select 1 from customer as c
+                <where>
+                    <if test="dto.customerName != null and dto.customerName != ''">
+                        AND c.customer_name like concat('%',#{dto.customerName},'%')
+                    </if>
+                    <if test="dto.customerId != null">
+                        and c.id = #{dto.customerId}
+                    </if>
+                    <if test="dto.customerType == null">
+                        <if test="dto.bindCustomer != null and dto.bindCustomer == 1">
+                            and c.owner_by = #{dto.ownerBy}
+                        </if>
+                    </if>
+                    and FIND_IN_SET(c.id,e.customer_ids) > 0 )
+                </where>
+            </if>
+            <if test="dto.smtpPopId != null">
+                and e.smtp_pop_id = #{dto.smtpPopId}
+            </if>
+            <if test="dto.emailAddress != null and dto.emailAddress != ''">
+                and (e.from like concat('%',#{dto.emailAddress},'%') or e.recipient like concat('%',#{dto.emailAddress},'%') or e.recipient_cc like concat('%',#{dto.emailAddress},'%'))
+            </if>
+            <if test="dto.folder != null and dto.folder != ''">
+                and e.folder = #{dto.folder}
+            </if>
+            <if test="dto.isStar != null and dto.isStar == 1">
+                and e.is_star = #{dto.isStar}
+            </if>
+            <if test="dto.systemMail != null and dto.systemMail == 1">
+                and e.from = 'system@storlead.com'
+            </if>
+            <if test="dto.systemMail == null">
+                and (e.from is null or e.from <![CDATA[<>]]> 'system@storlead.com')
+            </if>
+            <if test="dto.isTrash != null">
+                and is_trash = #{dto.isTrash}
+            </if>
+            <if test="dto.readStatu != null">
+                and is_read = #{dto.readStatu}
+            </if>
+            <if test="dto.followUpState != null">
+                <if test="dto.followUpState == 0">
+                    and e.follow_up_id is null
+                </if>
+                <if test="dto.followUpState == 1">
+                    and e.follow_up_id is not null
+                </if>
+            </if>
+            <if test="dto.customerType == null">
+                <if test="dto.bindCustomer != null and dto.bindCustomer == 0">
+                    and (e.customer_ids = '' or e.customer_ids is null)
+                </if>
+                <if test="dto.bindCustomer != null and dto.bindCustomer == 1">
+                    and e.customer_ids is not null
+                </if>
+            </if>
+            <if test="dto.blurry != null and dto.blurry != ''">
+                and (subject like concat('%',#{dto.blurry},'%') or content like concat('%',#{dto.blurry},'%') or `from` like concat('%',#{dto.blurry},'%') or recipient like concat('%',#{dto.blurry},'%'))
+            </if>
+        </where>
+        and e.is_delete = 0
+        order by e.recipient_date desc
+    </select>
+
+
+    <select id="pageListCus"  resultMap="BaseResultMap">
+        select e.id, e.smtp_pop_id, e.msg_uid,e.message_id,e.is_receipt,e.is_only_head,e.is_track,e.read_remind,
+        e.delay_send_time,e.customer_ids,e.liaison_ids, e.is_star,e.is_trash,e.subject,e.from, e.from_name, e.recipient,
+        e.recipient_name, e.recipient_cc, e.recipient_cc_name,e. recipient_bcc, e.recipient_bcc_name, e.folder,e.remark,
+        e.sent_date, e.recipient_date, e.email_size, e.is_read, e.reply_msg_id, e.status, e.owner_by, e.create_time, e.update_time,
+        e.is_delete, e.enabled, e.create_by, e.update_by, e.sort,c.id as customer_id from  emails as e
+        left join customer as c
+        on FIND_IN_SET(c.id,e.customer_ids)
+        <where>
+            <if test="dto.ownerBy != null">
+                and e.owner_by = #{dto.ownerBy}
+            </if>
+            <if test="dto.smtpPopId != null">
+                and e.smtp_pop_id = #{dto.smtpPopId}
+            </if>
+            <if test="dto.customerId != null">
+                and c.id = #{dto.customerId}
+            </if>
+            <if test="dto.customerId == null">
+                and e.source_type = 0
+            </if>
+            <if test="dto.folder != null and dto.folder != ''">
+                and e.folder = #{dto.folder}
+            </if>
+            <if test="dto.isStar != null and dto.isStar == 1">
+                and e.is_star = #{dto.isStar}
+            </if>
+            <if test="dto.systemMail != null and dto.systemMail == 1">
+                and e.from = 'system@storlead.com'
+            </if>
+            <if test="dto.systemMail == null">
+                and (e.from is null or e.from <![CDATA[<>]]> 'system@storlead.com')
+            </if>
+            <if test="dto.isTrash != null">
+                and is_trash = #{dto.isTrash}
+            </if>
+            <if test="dto.readStatu != null">
+                and is_read = #{dto.readStatu}
+            </if>
+            <if test="dto.emailAddress != null and dto.emailAddress != ''">
+                and (e.from like concat('%',#{dto.emailAddress},'%') or e.recipient like concat('%',#{dto.emailAddress},'%') or e.recipient_cc like concat('%',#{dto.emailAddress},'%'))
+            </if>
+            <if test="dto.customerName != null and dto.customerName != ''">
+                and c.customer_name like concat('%',#{dto.customerName},'%')
+            </if>
+            <if test="dto.folder != null and dto.folder != ''">
+                and e.folder = #{dto.folder}
+            </if>
+            <if test="dto.followUpState != null">
+                <if test="dto.followUpState == 0">
+                    and e.follow_up_id is null
+                </if>
+                <if test="dto.followUpState == 1">
+                    and c.owner_by = #{dto.ownerBy}
+                    and e.follow_up_id is not null
+                    and c.is_delete  = 0
+                </if>
+                <if test="dto.bindCustomer != null and dto.bindCustomer == 0">
+                    and c.id is null
+                </if>
+                <if test="dto.bindCustomer != null and dto.bindCustomer == 1">
+                    and c.id is not null
+                    and c.owner_by = #{dto.ownerBy}
+                    and c.is_delete  = 0
+                </if>
+            </if>
+            <if test="dto.customerType != null">
+                and c.id is not null
+                and c.owner_by = #{dto.ownerBy}
+                and c.customer_form = #{dto.customerType}
+                <if test="dto.customerLevelDictValue != null and dto.customerLevelDictValue != ''">
+                    and c.customer_level_dict_value = #{dto.customerLevelDictValue}
+                </if>
+
+                <if test="dto.customerSourceDictValue != null and dto.customerSourceDictValue != ''">
+                    and c.customer_source_dict_value = #{dto.customerSourceDictValue}
+                </if>
+
+                <if test="dto.customerTypeDictValue != null and dto.customerTypeDictValue != ''">
+                    and c.customer_type_dict_value = #{dto.customerTypeDictValue}
+                </if>
+                <if test="dto.clueStatusDictValue != null and dto.clueStatusDictValue != ''">
+                    and c.clue_status_dict_value = #{dto.clueStatusDictValue}
+                </if>
+                <if test="dto.continent != null and dto.continent != '' and dto.country != null and dto.country != ''">
+                    and c.continent = #{dto.continent} and c.country = #{dto.country}
+                </if>
+                and c.is_delete = 0
+            </if>
+            <if test="dto.blurry != null and dto.blurry != ''">
+                and (subject like concat('%',#{dto.blurry},'%') or content like concat('%',#{dto.blurry},'%') or `from` like concat('%',#{dto.blurry},'%') or recipient like concat('%',#{dto.blurry},'%'))
+            </if>
+            and e.is_delete = 0
+            <if test="dto.customerType != null">
+                AND e.id IN (
+                    SELECT MAX(e1.id) FROM emails e1
+                    LEFT JOIN customer c1 ON FIND_IN_SET(c1.id, e1.customer_ids)
+                    where e1.is_delete = 0 and c1.is_delete = 0
+                    <if test="dto.ownerBy != null">
+                        and e1.owner_by =  #{dto.ownerBy}
+                    </if>
+                    <if test="dto.folder != null and dto.folder != ''">
+                        and e1.folder = #{dto.folder}
+                    </if>
+                    <if test="dto.isTrash != null">
+                        and e1.is_trash = #{dto.isTrash}
+                    </if>
+                    <if test="dto.smtpPopId != null">
+                        and e1.smtp_pop_id = #{dto.smtpPopId}
+                    </if>
+                    <if test="dto.customerType != null">
+                        and c.customer_form = #{dto.customerType}
+                    </if>
+                    GROUP BY c1.id
+                )
+            </if>
+        </where>
+        order by e.recipient_date desc
+    </select>
+
+
+    <select id="pageListAllCus"  resultMap="BaseResultMap">
+        WITH ranked_emails AS (
+        select e.id, e.smtp_pop_id, e.msg_uid,e.message_id,e.is_receipt,e.is_only_head,e.is_track,e.read_remind,
+        e.delay_send_time,e.customer_ids,e.liaison_ids, e.is_star,e.is_trash,e.subject,e.from, e.from_name, e.recipient,
+        e.recipient_name, e.recipient_cc, e.recipient_cc_name,e. recipient_bcc, e.recipient_bcc_name, e.folder,e.remark,
+        e.sent_date, e.recipient_date, e.email_size, e.is_read, e.reply_msg_id, e.status, e.owner_by, e.create_time,
+        e.update_time,
+        e.is_delete, e.enabled, e.create_by, e.update_by, e.sort,c.id as customer_id
+        ,ROW_NUMBER() OVER (PARTITION BY c.id ORDER BY e.id DESC) AS rn
+        from emails as e
+        left join customer as c
+        on FIND_IN_SET(c.id,e.customer_ids)
+        <where>
+            <if test="dto.ownerBy != null">
+                and e.owner_by = #{dto.ownerBy}
+            </if>
+            <if test="dto.smtpPopId != null">
+                and e.smtp_pop_id = #{dto.smtpPopId}
+            </if>
+            <if test="dto.customerId != null">
+                and c.id = #{dto.customerId}
+            </if>
+            <if test="dto.folder != null and dto.folder != ''">
+                and e.folder = #{dto.folder}
+            </if>
+            <if test="dto.isStar != null and dto.isStar == 1">
+                and e.is_star = #{dto.isStar}
+            </if>
+            <if test="dto.systemMail != null and dto.systemMail == 1">
+                and e.from = 'system@storlead.com'
+            </if>
+            <if test="dto.systemMail == null">
+                and (e.from is null or e.from <![CDATA[<>]]> 'system@storlead.com')
+            </if>
+            <if test="dto.isTrash != null">
+                and is_trash = #{dto.isTrash}
+            </if>
+            <if test="dto.readStatu != null">
+                and is_read = #{dto.readStatu}
+            </if>
+            <if test="dto.emailAddress != null and dto.emailAddress != ''">
+                and (e.from like concat('%',#{dto.emailAddress},'%') or e.recipient like
+                concat('%',#{dto.emailAddress},'%') or e.recipient_cc like concat('%',#{dto.emailAddress},'%'))
+            </if>
+            <if test="dto.customerName != null and dto.customerName != ''">
+                and c.customer_name like concat('%',#{dto.customerName},'%')
+            </if>
+            <if test="dto.folder != null and dto.folder != ''">
+                and e.folder = #{dto.folder}
+            </if>
+            and e.source_type = 0
+            <if test="dto.followUpState != null">
+                <if test="dto.followUpState == 0">
+                    and e.follow_up_id is null
+                </if>
+                <if test="dto.followUpState == 1">
+                    and c.owner_by = #{dto.ownerBy}
+                    and e.follow_up_id is not null
+                    and c.is_delete = 0
+                </if>
+                <if test="dto.bindCustomer != null and dto.bindCustomer == 0">
+                    and c.id is null
+                </if>
+                <if test="dto.bindCustomer != null and dto.bindCustomer == 1">
+                    and c.id is not null
+                    and c.owner_by = #{dto.ownerBy}
+                    and c.is_delete = 0
+                </if>
+            </if>
+            <if test="dto.customerType != null">
+                and c.id is not null
+                and c.owner_by = #{dto.ownerBy}
+                and c.customer_form = #{dto.customerType}
+                <if test="dto.customerLevelDictValue != null and dto.customerLevelDictValue != ''">
+                    and c.customer_level_dict_value = #{dto.customerLevelDictValue}
+                </if>
+
+                <if test="dto.customerSourceDictValue != null and dto.customerSourceDictValue != ''">
+                    and c.customer_source_dict_value = #{dto.customerSourceDictValue}
+                </if>
+
+                <if test="dto.customerTypeDictValue != null and dto.customerTypeDictValue != ''">
+                    and c.customer_type_dict_value = #{dto.customerTypeDictValue}
+                </if>
+                <if test="dto.clueStatusDictValue != null and dto.clueStatusDictValue != ''">
+                    and c.clue_status_dict_value = #{dto.clueStatusDictValue}
+                </if>
+                <if test="dto.continent != null and dto.continent != '' and dto.country != null and dto.country != ''">
+                    and c.continent = #{dto.continent} and c.country = #{dto.country}
+                </if>
+                and c.is_delete = 0
+            </if>
+            <if test="dto.blurry != null and dto.blurry != ''">
+                and (subject like concat('%',#{dto.blurry},'%') or content like concat('%',#{dto.blurry},'%') or `from`
+                like concat('%',#{dto.blurry},'%') or recipient like concat('%',#{dto.blurry},'%'))
+            </if>
+            and e.is_delete = 0
+        </where>
+        order by e.recipient_date desc
+        )
+        SELECT * FROM ranked_emails re
+        WHERE re.rn = 1
+    </select>
+
+
+    <select id="pageListNew"  resultMap="BaseResultMap">
+        select e1.*,cb.id as customer_mail_id,cb.follow_up_id,cb.follow_up_time,GROUP_CONCAT(DISTINCT cb.customer_id ORDER BY e1.id ASC) as customer_ids  from
+            (
+            select e.id, e.smtp_pop_id, e.msg_uid,e.message_id,e.is_receipt,e.is_only_head,e.is_track,e.read_remind,
+            e.delay_send_time, e.is_star,e.is_trash,e.subject,e.from, e.from_name, e.recipient,
+            e.recipient_name, e.recipient_cc, e.recipient_cc_name,e. recipient_bcc, e.recipient_bcc_name, e.folder,e.remark,
+            e.sent_date, e.recipient_date, e.email_size, e.is_read, e.reply_msg_id, e.status, e.owner_by, e.create_time, e.update_time,
+            e.is_delete, e.enabled, e.create_by, e.update_by, e.sort from  emails as e
+            <where>
+                <if test="dto.smtpPopId != null">
+                    and e.smtp_pop_id = #{dto.smtpPopId}
+                </if>
+                <if test="dto.emailAddress != null and dto.emailAddress != ''">
+                    and (e.from like concat('%',#{dto.emailAddress},'%') or e.recipient like concat('%',#{dto.emailAddress},'%') or e.recipient_cc like concat('%',#{dto.emailAddress},'%'))
+                </if>
+                <if test="dto.folder != null and dto.folder != ''">
+                    and e.folder = #{dto.folder}
+                </if>
+                <if test="dto.isStar != null and dto.isStar == 1">
+                    and e.is_star = #{dto.isStar}
+                </if>
+                <if test="dto.systemMail != null and dto.systemMail == 1">
+                    and e.from = 'system@storlead.com'
+                </if>
+                <if test="dto.systemMail == null">
+                    and (e.from is null or e.from <![CDATA[<>]]> 'system@storlead.com')
+                </if>
+                <if test="dto.isTrash != null">
+                    and e.is_trash = #{dto.isTrash}
+                </if>
+                <if test="dto.readStatu != null">
+                    and e.is_read = #{dto.readStatu}
+                </if>
+                <if test="dto.blurry != null and dto.blurry != ''">
+                    and (e.subject like concat('%',#{dto.blurry},'%') or e.content like concat('%',#{dto.blurry},'%'))
+                </if>
+                <if test="dto.folderId != null">
+                    and e.custom_folder_id = #{dto.folderId}
+                </if>
+                <if test="dto.bindCustomer == null and dto.customerId == null">
+                    <if test="dto.folderId == null and dto.isTrash == 0">
+                        and e.custom_folder_id is null
+                    </if>
+                </if>
+                <if test="dto.customerId == null">
+                    and e.source_type = 0
+                </if>
+                and e.is_delete = 0
+            </where>
+        ) as e1
+        left join customer_mail_bing_mark as cb
+        on e1.id = cb.mail_id
+        left join customer as c
+        on cb.customer_id = c.id and c.is_delete = 0
+        <where>
+            <if test="dto.customerId != null">
+                and c.id = #{dto.customerId}
+            </if>
+            <if test="(dto.customerName != null and dto.customerName != '')">
+                and c.customer_name like concat('%',#{dto.customerName},'%')
+                and c.owner_by = #{dto.ownerBy}
+            </if>
+            <if test="dto.customerLevelDictValue != null and dto.customerLevelDictValue != ''">
+                and c.customer_level_dict_value = #{dto.customerLevelDictValue}
+            </if>
+
+            <if test="dto.customerSourceDictValue != null and dto.customerSourceDictValue != ''">
+                and c.customer_source_dict_value = #{dto.customerSourceDictValue}
+            </if>
+
+            <if test="dto.customerTypeDictValue != null and dto.customerTypeDictValue != ''">
+                and c.customer_type_dict_value = #{dto.customerTypeDictValue}
+            </if>
+            <if test="dto.clueStatusDictValue != null and dto.clueStatusDictValue != ''">
+                and c.clue_status_dict_value = #{dto.clueStatusDictValue}
+            </if>
+            <if test="dto.continent != null and dto.continent != '' and dto.country != null and dto.country != ''">
+                and c.continent = #{dto.continent} and c.country = #{dto.country}
+            </if>
+            <if test="dto.customerType != null">
+                and c.customer_form = #{dto.customerType}
+            </if>
+        </where>
+        <if test="dto.customerType == null">
+            GROUP BY e1.id
+        </if>
+        <if test="dto.customerType != null">
+            GROUP BY e1.id,cb.customer_id
+        </if>
+        <if test="dto.followUpState != null and dto.followUpState == 0">
+            <if test="dto.bindCustomer != null and dto.bindCustomer == 0">
+                HAVING customer_ids is null and reply_msg_id is null
+            </if>
+            <if test="dto.bindCustomer != null and dto.bindCustomer == 1">
+                HAVING customer_ids is not null and follow_up_id is null
+            </if>
+        </if>
+        order by e1.recipient_date desc
+    </select>
+
+
+    <select id="pageListNewAllCus"  resultMap="BaseResultMap">
+        WITH ranked_emails AS (
+        select e1.*,cc.customer_id as customer_ids,ROW_NUMBER() OVER (PARTITION BY cc.customer_id ORDER BY e1.id DESC) AS rn
+        from
+        (
+        select e.id, e.smtp_pop_id, e.msg_uid,e.message_id,e.is_receipt,e.is_only_head,e.is_track,e.read_remind,
+        e.delay_send_time, e.is_star,e.is_trash,e.subject,e.from, e.from_name, e.recipient,
+        e.recipient_name, e.recipient_cc, e.recipient_cc_name,e. recipient_bcc, e.recipient_bcc_name, e.folder,e.remark,
+        e.sent_date, e.recipient_date, e.email_size, e.is_read, e.reply_msg_id, e.status, e.owner_by, e.create_time,
+        e.update_time,
+        e.is_delete, e.enabled, e.create_by, e.update_by, e.sort from emails as e
+        <where>
+            <if test="dto.smtpPopId != null">
+                and e.smtp_pop_id = #{dto.smtpPopId}
+            </if>
+            <if test="dto.emailAddress != null and dto.emailAddress != ''">
+                and (e.from like concat('%',#{dto.emailAddress},'%') or e.recipient like
+                concat('%',#{dto.emailAddress},'%') or e.recipient_cc like concat('%',#{dto.emailAddress},'%'))
+            </if>
+            <if test="dto.folder != null and dto.folder != ''">
+                and e.folder = #{dto.folder}
+            </if>
+            <if test="dto.isStar != null and dto.isStar == 1">
+                and e.is_star = #{dto.isStar}
+            </if>
+            <if test="dto.systemMail != null and dto.systemMail == 1">
+                and e.from = 'system@storlead.com'
+            </if>
+            <if test="dto.systemMail == null">
+                and (e.from is null or e.from <![CDATA[<>]]> 'system@storlead.com')
+            </if>
+            <if test="dto.isTrash != null">
+                and e.is_trash = #{dto.isTrash}
+            </if>
+            <if test="dto.readStatu != null">
+                and e.is_read = #{dto.readStatu}
+            </if>
+            <if test="dto.followRemind != null">
+                and e.subject = '跟进提醒'
+            </if>
+            <if test="dto.customerId == null">
+                and e.source_type = 0
+            </if>
+            <if test="dto.blurry != null and dto.blurry != ''">
+                and (e.subject like concat('%',#{dto.blurry},'%') or e.content like concat('%',#{dto.blurry},'%'))
+            </if>
+            and e.is_delete = 0
+        </where>
+        ) as e1
+        left join (
+            SELECT
+            cb.customer_id,
+            cb.mail_id,
+            c.customer_form
+            FROM
+            customer_mail_bing_mark AS cb
+            LEFT JOIN customer AS c ON cb.customer_id = c.id
+            WHERE  c.is_delete = 0
+            AND c.owner_by = #{dto.ownerBy}
+            <if test="dto.customerId != null">
+                and c.id = #{dto.customerId}
+            </if>
+            <if test="(dto.customerName != null and dto.customerName != '')">
+                and c.customer_name like concat('%',#{dto.customerName},'%')
+                and c.owner_by = #{dto.ownerBy}
+            </if>
+            <if test="dto.customerLevelDictValue != null and dto.customerLevelDictValue != ''">
+                and c.customer_level_dict_value = #{dto.customerLevelDictValue}
+            </if>
+
+            <if test="dto.customerSourceDictValue != null and dto.customerSourceDictValue != ''">
+                and c.customer_source_dict_value = #{dto.customerSourceDictValue}
+            </if>
+
+            <if test="dto.customerTypeDictValue != null and dto.customerTypeDictValue != ''">
+                and c.customer_type_dict_value = #{dto.customerTypeDictValue}
+            </if>
+            <if test="dto.clueStatusDictValue != null and dto.clueStatusDictValue != ''">
+                and c.clue_status_dict_value = #{dto.clueStatusDictValue}
+            </if>
+            <if test="dto.customerType != null">
+                and c.customer_form = #{dto.customerType}
+            </if>
+            <if test="dto.continent != null and dto.continent != '' and dto.country != null and dto.country != ''">
+                and c.continent = #{dto.continent} and c.country = #{dto.country}
+            </if>
+        ) as cc
+         on cc.mail_id = e1.id
+         where e1.owner_by = #{dto.ownerBy} and cc.mail_id is not null
+         order by e1.recipient_date desc
+       )
+       SELECT * FROM ranked_emails re
+       WHERE re.rn = 1
+    </select>
+
+    <select id="countMailSummaryNum" resultType="com.storlead.mail.pojo.vo.NewMailCountTipVO">
+        SELECT
+                IFNULL(SUM(CASE WHEN folder = 'INBOX' AND is_read = 0 and is_trash = 0 and `from` <![CDATA[<>]]> 'system@storlead.com' THEN 1 ELSE 0 END),0) AS inboxCount,
+                IFNULL(SUM(CASE WHEN folder = 'SENT' AND is_delete = 0 and is_trash = 0 THEN 1 ELSE 0 END),0) AS sentCount,
+                IFNULL(SUM(CASE WHEN folder = 'SENTING' AND is_delete = 0 and is_trash = 0 THEN 1 ELSE 0 END),0) AS sentingCount,
+                IFNULL(SUM(CASE WHEN folder = 'DRAFT' AND is_delete = 0 and is_trash = 0 THEN 1 ELSE 0 END),0) AS draftCount,
+                IFNULL(SUM(CASE WHEN `subject` = '跟进提醒' AND is_delete = 0 THEN 1 ELSE 0 END),0) AS followUpCount,
+                IFNULL(SUM(CASE WHEN `from` = 'system@storlead.com' AND is_read = 0 THEN 1 ELSE 0 END),0) AS systemCount,
+                IFNULL(SUM(CASE WHEN is_star = 1 THEN 1 ELSE 0 END),0) AS starCount,
+                IFNULL(SUM(CASE WHEN is_trash = 1 THEN 1 ELSE 0 END),0) AS trashCount
+            FROM emails WHERE is_delete = 0 and owner_by = #{ownerBy} and smtp_pop_id = #{smtpPopId} and custom_folder_id is null and source_type = 0
+    </select>
+
+    <select id="countCustomerFollowUpMailNum" resultType="com.storlead.mail.pojo.vo.NewMailCountTipVO">
+        select
+            IFNULL(SUM(CASE WHEN m.customer_ids is null and m.reply_msg_id is null THEN 1 ELSE 0 END),0) AS unknownCount,
+                   IFNULL(SUM(CASE WHEN m.customer_ids is not null and m.follow_up_id is null  THEN 1 ELSE 0 END),0) AS knownCount
+        from (
+        select e1.id,e1.reply_msg_id,cb.follow_up_id as follow_up_id,GROUP_CONCAT(DISTINCT cb.customer_id ORDER BY e1.id ASC) as customer_ids
+        from emails as e1
+        left join customer_mail_bing_mark as cb
+        on e1.id = cb.mail_id
+        left join customer as c
+        on e1.id = cb.mail_id
+        and cb.customer_id = c.id and c.is_delete = 0
+        where e1.owner_by = #{ownerBy} and e1.smtp_pop_id = #{smtpPopId} and e1.from <![CDATA[<>]]> 'system@storlead.com'
+          AND e1.is_delete = 0
+          AND e1.folder = 'INBOX'
+          AND e1.is_trash = 0
+          AND e1.is_delete = 0
+          and e1.source_type = 0
+        GROUP BY e1.id
+        )	as m
+    </select>
+
+    <select id="countCustomerAndClueNumber" resultType="com.storlead.mail.pojo.vo.NewMailCountTipVO">
+        SELECT
+            IFNULL(SUM( CASE WHEN m.customer_form = 10 THEN 1 ELSE 0 END ),0) AS customerCount,
+            IFNULL(SUM( CASE WHEN m.customer_form = 11 THEN 1 ELSE 0 END ),0) AS clueCount
+        FROM
+            (
+                SELECT
+                    cb.customer_id,
+                    c.customer_form,
+                    MAX( cb.mail_id ) AS mail_id
+                FROM
+                    customer_mail_bing_mark AS cb
+                        LEFT JOIN customer AS c ON cb.customer_id = c.id
+                        left join emails as e on e.id = cb.mail_id and e.smtp_pop_id = #{smtpPopId}
+                WHERE c.is_delete = 0
+                 AND c.owner_by = #{ownerBy}
+                 AND e.id is not null
+                GROUP BY
+                    cb.customer_id,
+                    c.customer_form
+            ) AS m
+    </select>
+
+    <select id="selectCustomerLastFollowUpTime" resultType="java.time.LocalDateTime">
+        SELECT
+            max( follow_up_time ) AS follow_up_time
+        FROM
+            (
+                SELECT
+                    max( create_time ) AS follow_up_time
+                FROM
+                    customer_follow_up
+                WHERE
+                    customer_id =  #{customerId}
+                UNION ALL
+                SELECT
+                    max( sent_date ) AS follow_up_time
+                FROM
+                    customer_mail_bing_mark AS mbm
+                        LEFT JOIN emails AS e1 ON e1.id = mbm.mail_id
+                WHERE
+                    mbm.customer_id =  #{customerId}
+                    and e1.source_type = 0
+            ) AS m
+
+    </select>
+
+    <!-- 获取删除后需要删除服务器的邮件 -->
+    <select id="selectDelayDeleteMail" resultMap="BaseResultMap">
+--         SELECT id,smtp_pop_id, msg_uid, message_id,folder,create_time,sent_date,delete_tag FROM emails e
+--             WHERE e.is_only_head = 0 AND e.smtp_pop_id = 10 and delete_tag = 0
+--             AND e.create_time <![CDATA[<]]> DATE_SUB(NOW(), INTERVAL 15 DAY)
+--                 an'd'
+--             AND EXISTS ( SELECT 1 FROM smtp_pop_settings sp WHERE sp.id = e.smtp_pop_id AND sp.smtp_pop_settings = 1)
+--             AND NOT EXISTS ( SELECT 1 FROM mail_attachment ma WHERE ma.email_id = e.id AND ma.download <![CDATA[<>]]> 1) order by sent_date desc limit 10;
+        SELECT
+            id,
+            smtp_pop_id,
+            msg_uid,
+            message_id,
+            folder,
+            create_time,
+            sent_date,
+            delete_tag,
+            is_delete
+        FROM
+            emails e
+        WHERE
+            e.is_only_head = 0
+            AND delete_tag = 0
+            AND recipient_date IS NOT NULL
+            AND ( e.folder = 'INBOX' OR e.folder = 'SENT' )
+            AND ( ( e.recipient_date > '2025-11-13 15:45:09' AND e.recipient_date <![CDATA[<]]> DATE_SUB( NOW( ), INTERVAL 15 DAY ) ) )
+            AND e.delete_tag = 0
+            AND EXISTS ( SELECT 1 FROM smtp_pop_settings sp WHERE sp.id = e.smtp_pop_id )
+            AND NOT EXISTS ( SELECT 1 FROM mail_attachment ma WHERE ma.email_id = e.id AND ma.download <![CDATA[<>]]> 1 )
+
+    </select>
+
+<!--    <select id="selectUserClickTitle" resultType="com.storlead.mail.pojo.vo.UserClickTitleVO">-->
+<!--        SELECT a.id as dataId,a.subject as title,b.click_count AS clickCount FROM emails as a LEFT JOIN user_click_record as b ON a.id = b.data_id and b.click_type = 1-->
+<!--        where a.owner_by = #{ownerBy} and a.is_delete = 0-->
+<!--          <if test="title != null and title != ''">-->
+<!--              and a.`subject` like concat('%',#{title},'%')-->
+<!--          </if>-->
+<!--          order by b.click_count desc limit 10-->
+<!--    </select>-->
+
+    <select id="selectUserClickTitle" resultType="com.storlead.mail.pojo.vo.UserClickTitleVO">
+        SELECT
+        dataId,
+        title,
+        clickCount
+        FROM
+        (
+        SELECT
+        a.id AS dataId,
+        a.SUBJECT AS title,
+        b.click_count AS clickCount,
+        ROW_NUMBER ( ) OVER ( PARTITION BY a.SUBJECT ORDER BY b.click_count DESC ) AS rn
+        FROM
+        emails AS a
+        LEFT JOIN user_click_record AS b ON a.id = b.data_id
+        AND b.click_type = 1
+        WHERE
+        a.owner_by = #{ownerBy}
+        <if test="title != null and title != ''">
+            AND a.`subject` LIKE CONCAT('%',#{title},'%')
+        </if>
+        AND a.is_delete = 0
+        ) AS t
+        WHERE
+        t.rn = 1
+        ORDER BY
+        clickCount DESC
+        LIMIT 10;
+    </select>
+
+    <select id="selectCustomerEmailPage" resultType="com.storlead.mail.pojo.vo.CustomerEmailVo">
+        SELECT
+            cm.mail_id AS mailId,
+            e.from_name AS fromName,
+            e.recipient_date AS recipientDate,
+            e.recipient_name AS recipientName,
+            e.recipient AS recipient,
+            e.subject AS subject,
+            e.in_out_mark AS inOutMark
+        FROM
+            customer_mail_bing_mark AS cm
+            LEFT JOIN emails AS e ON e.id = cm.mail_id
+            WHERE
+            cm.customer_id = #{query.customerId}
+        <if test="query.blurry != null and query.blurry != ''">
+            AND e.`subject` LIKE CONCAT('%',#{query.blurry},'%')
+        </if>
+        <if test="query.liaisonId != null and query.liaisonId != ''">
+            AND FIND_IN_SET(#{query.liaisonId}, cm.`liaison_ids`)
+        </if>
+        <if test="query.beginTime != null and query.endTime != null">
+            and e.recipient_date >= #{query.beginTime}  and e.recipient_date <![CDATA[<=]]> #{query.endTime}
+        </if>
+            AND e.is_delete = 0
+        ORDER BY
+            e.recipient_date
+        DESC
+    </select>
+
+    <select id="selectCusIdByMailId" resultType="java.lang.Long">
+        select customer_id from customer_mail_bing_mark where is_delete = 0
+        and mail_id in
+        <foreach collection="mailIdls" item="mailId" open="(" separator="," close=")">
+            #{mailId}
+        </foreach>
+    </select>
+
+    <select id="selectCusInfoByCusId" resultType="com.storlead.mail.pojo.vo.CustomerMailVO">
+        select customer_name as customerName,country from customer where is_delete = 0
+        and id in
+        <foreach collection="customerIdls" item="customerId" open="(" separator="," close=")">
+            #{customerId}
+        </foreach>
+    </select>
+
+    <select id="countSmtpMailCount" resultType="java.lang.Integer">
+        select count(1) from smtp_pop_settings where is_delete = 0 and owner_by = #{userId}
+    </select>
+</mapper>

+ 33 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/FoldersMapper.xml

@@ -0,0 +1,33 @@
+<?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.mail.mapper.FoldersMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.FoldersEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                <result column="sort" property="sort" />
+                    <result column="user_id" property="userId" />
+                    <result column="folder_name" property="folderName" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+                sort,
+            id, user_id, folder_name
+        </sql>
+
+</mapper>

+ 37 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailAttachmentMapper.xml

@@ -0,0 +1,37 @@
+<?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.mail.mapper.MailAttachmentMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.MailAttachmentEntity" extends="com.storlead.framework.mybatis.mapper.SysBaseFieldMapper.BaseResultMap">
+            <id column="id" property="id" />
+            <result column="email_id" property="emailId" />
+            <result column="file_code" property="fileCode" />
+            <result column="file_name" property="fileName" />
+            <result column="file_path" property="filePath" />
+            <result column="file_size" property="fileSize" />
+            <result column="download" property="download" />
+            <result column="smtp_pop_id" property="smtpPopId" />
+            <result column="file_ext" property="fileExt" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                create_by,
+                create_time,
+                owner_by,
+                update_by,
+                enabled,
+                sort,
+                update_time,
+                is_delete,
+            id, email_id,smtp_pop_id,download,file_code, file_name, file_path, file_size, file_ext
+        </sql>
+
+    <select id="pageList"  resultType="com.storlead.mail.pojo.entity.MailAttachmentEntity">
+        select att.*,e.sent_date from mail_attachment as att left join emails as e on att.email_id = e.id
+         ${ew.customSqlSegment}
+    </select>
+
+
+</mapper>

+ 35 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailDelayPullRecordMapper.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.mail.mapper.MailDelayPullRecordMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.MailDelayPullRecordEntity">
+                    <id column="id" property="id" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="create_time" property="createTime" />
+                <result column="update_time" property="updateTime" />
+                <result column="is_delete" property="isDelete" />
+                <result column="enabled" property="enabled" />
+                <result column="create_by" property="createBy" />
+                <result column="update_by" property="updateBy" />
+                    <result column="smtp_pop_id" property="smtpPopId" />
+                    <result column="msg_uid" property="msgUid" />
+                    <result column="msg_num" property="msgNum" />
+                    <result column="subject" property="subject" />
+                    <result column="folder" property="folder" />
+                    <result column="status" property="status" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+            id, smtp_pop_id, msg_uid, msg_num, subject, folder, status
+        </sql>
+
+</mapper>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است