Bläddra i källkod

迁移销售模块代码和邮件模块

1811872455@163.com 1 vecka sedan
förälder
incheckning
7449104241
100 ändrade filer med 6444 tillägg och 74 borttagningar
  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. 18 0
      java/storlead-framework/storlead-common/pom.xml
  9. 5 0
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/constant/DSConstants.java
  10. 1 1
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/CloseUtil.java
  11. 1 2
      java/storlead-framework/storlead-common/src/main/java/com/storlead/framework/common/util/FileUtil.java
  12. 5 0
      java/storlead-framework/storlead-mybatis/pom.xml
  13. 113 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceAspect.java
  14. 9 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceConfiguration.java
  15. 40 0
      java/storlead-framework/storlead-mybatis/src/main/java/com/storlead/framework/mybatis/datasource/ModuleDefaultDataSourceProperties.java
  16. 42 0
      java/storlead-mail/pom.xml
  17. 41 0
      java/storlead-mail/storlead-mail-api/pom.xml
  18. 1 1
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/ClientSentEmailsController.java
  19. 9 9
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailFolderRuleApiController.java
  20. 1 1
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailServiceChecker.java
  21. 7 5
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MaiAttachmentApiController.java
  22. 8 8
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailBlacklistRecordApiController.java
  23. 5 5
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailTemplatesApiController.java
  24. 6 6
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailboxAutoReplySetController.java
  25. 13 13
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopMailApiController.java
  26. 12 12
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopSettingsApiController.java
  27. 7 5
      java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/UserEmailFolderApiController.java
  28. 46 0
      java/storlead-mail/storlead-mail-biz/pom.xml
  29. 157 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailReceiver.java
  30. 53 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/QQEmailSender.java
  31. 129 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnection.java
  32. 27 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/IMAPConnectionFactory.java
  33. 21 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnection.java
  34. 26 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/MailConnectionFactory.java
  35. 126 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3Connection.java
  36. 26 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/POP3ConnectionFactory.java
  37. 53 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnection.java
  38. 25 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/SMTPConnectionFactory.java
  39. 93 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailConnectionUtil.java
  40. 48 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailReceiverUtil.java
  41. 38 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/client/MailSenderUtil.java
  42. 43 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/connection/config/MailProperties.java
  43. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/AttachmentMapper.java
  44. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/ClientSentEmailsMapper.java
  45. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailBlacklistRecordMapper.java
  46. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFileFolderMapper.java
  47. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFolderRuleMapper.java
  48. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailFoldersMapper.java
  49. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailTemplatesMapper.java
  50. 107 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/EmailsMapper.java
  51. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/FoldersMapper.java
  52. 23 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailAttachmentMapper.java
  53. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailDelayPullRecordMapper.java
  54. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailServerPortConfigMapper.java
  55. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailTempAttachmentMapper.java
  56. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/MailboxAutoReplySetMapper.java
  57. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/SmtpPopSettingsMapper.java
  58. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserEmailFolderMapper.java
  59. 16 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/mapper/UserMailTrackRecordMapper.java
  60. 57 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/properties/MailFileProperties.java
  61. 14 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AbstractMailFunctionApiFactory.java
  62. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/AttachmentServiceImpl.java
  63. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/ClientSentEmailsServiceImpl.java
  64. 38 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailBlacklistRecordServiceImpl.java
  65. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFileFolderServiceImpl.java
  66. 77 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFolderRuleServiceImpl.java
  67. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailFoldersServiceImpl.java
  68. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailTemplatesServiceImpl.java
  69. 1959 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/EmailsServiceImpl.java
  70. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/FoldersServiceImpl.java
  71. 29 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailAttachmentServiceImpl.java
  72. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailDelayPullRecordServiceImpl.java
  73. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailServerPortConfigServiceImpl.java
  74. 101 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailTempAttachmentServiceImpl.java
  75. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/MailboxAutoReplySetServiceImpl.java
  76. 67 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/SmtpPopSettingsServiceImpl.java
  77. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/UserEmailFolderServiceImpl.java
  78. 20 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/service/impl/UserMailTrackRecordServiceImpl.java
  79. 285 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/EmailHelper.java
  80. 203 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/EmailSenderWithThreadLocal.java
  81. 174 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/FileExtractor.java
  82. 42 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/FileHelper.java
  83. 50 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/MailFormatPartUtil.java
  84. 97 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/ReceiveMailQueueThreadPool.java
  85. 90 0
      java/storlead-mail/storlead-mail-biz/src/main/java/com/storlead/mail/util/ZipUtility.java
  86. 2 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/META-INF/spring.factories
  87. 35 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/AttachmentMapper.xml
  88. 66 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/ClientSentEmailsMapper.xml
  89. 32 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailBlacklistRecordMapper.xml
  90. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFileFolderMapper.xml
  91. 34 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFolderRuleMapper.xml
  92. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailFoldersMapper.xml
  93. 35 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailTemplatesMapper.xml
  94. 807 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/EmailsMapper.xml
  95. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/FoldersMapper.xml
  96. 37 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailAttachmentMapper.xml
  97. 35 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailDelayPullRecordMapper.xml
  98. 34 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailServerPortConfigMapper.xml
  99. 37 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailTempAttachmentMapper.xml
  100. 33 0
      java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailboxAutoReplySetMapper.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

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

@@ -162,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;
 

+ 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

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

@@ -71,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>

+ 1 - 1
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/ClientSentEmailsController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/ClientSentEmailsController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 
 import org.springframework.web.bind.annotation.RequestMapping;

+ 9 - 9
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/EmailFolderRuleApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailFolderRuleApiController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -8,14 +8,14 @@ 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.sales.mail.entity.EmailFolderRuleEntity;
-import com.storlead.sales.mail.entity.EmailsEntity;
-import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-import com.storlead.sales.mail.enums.EmailFolderlRuleEnum;
-import com.storlead.sales.mail.pojo.dto.EmailTemplatesDTO;
-import com.storlead.sales.mail.pojo.dto.FolderRuleDTO;
-import com.storlead.sales.mail.service.EmailFolderRuleService;
-import com.storlead.sales.mail.service.EmailsService;
+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;

+ 1 - 1
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/EmailServiceChecker.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/EmailServiceChecker.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 /**
  * @program: sp-sales-platform

+ 7 - 5
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/MaiAttachmentApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MaiAttachmentApiController.java

@@ -1,13 +1,15 @@
-package com.storlead.sales.controller.mail;
+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.sales.mail.entity.MailTempAttachmentEntity;
-import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-import com.storlead.sales.mail.properties.MailFileProperties;
-import com.storlead.sales.mail.util.FileHelper;
+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;

+ 8 - 8
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/MailBlacklistRecordApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailBlacklistRecordApiController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 import cn.hutool.core.util.StrUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -8,13 +8,13 @@ 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.sales.mail.entity.EmailBlacklistRecordEntity;
-import com.storlead.sales.mail.entity.EmailsEntity;
-import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-import com.storlead.sales.mail.enums.EmailBoxEnum;
-import com.storlead.sales.mail.pojo.dto.EmailTemplatesDTO;
-import com.storlead.sales.mail.service.EmailBlacklistRecordService;
-import com.storlead.sales.mail.service.EmailsService;
+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;

+ 5 - 5
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/MailTemplatesApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailTemplatesApiController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -6,10 +6,10 @@ 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.sales.mail.entity.EmailTemplatesEntity;
-import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-import com.storlead.sales.mail.pojo.dto.EmailTemplatesDTO;
-import com.storlead.sales.mail.service.EmailTemplatesService;
+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;

+ 6 - 6
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/MailboxAutoReplySetController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/MailboxAutoReplySetController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -7,11 +7,11 @@ 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.sales.mail.entity.MailboxAutoReplySetEntity;
-import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-import com.storlead.sales.mail.pojo.dto.EmailTemplatesDTO;
-import com.storlead.sales.mail.service.MailboxAutoReplySetService;
-import com.storlead.sales.mail.service.SmtpPopSettingsService;
+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;

+ 13 - 13
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/SmtpPopMailApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopMailApiController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;//package com.storlead.sales.mail;
+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;
@@ -8,18 +8,18 @@ package com.storlead.sales.controller.mail;//package com.storlead.sales.mail;
 //import com.storlead.framework.common.util.encryptor.AccessKeyEncryptor;
 //import com.storlead.framework.util.LoginUserUtil;
 //import com.storlead.framework.common.result.Result;
-//import com.storlead.sales.mail.connection.MailConnection;
-//import com.storlead.sales.mail.connection.client.MailConnectionUtil;
-//import com.storlead.sales.mail.connection.config.MailProperties;
-//import com.storlead.sales.mail.entity.EmailsEntity;
-//import com.storlead.sales.mail.entity.MailAttachmentEntity;
-//import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-//import com.storlead.sales.mail.pojo.MailDTO;
-//import com.storlead.sales.mail.pojo.SendMailDTO;
-//import com.storlead.sales.mail.properties.MailFileProperties;
-//import com.storlead.sales.mail.service.EmailsService;
-//import com.storlead.sales.mail.service.MailAttachmentService;
-//import com.storlead.sales.mail.service.SmtpPopSettingsService;
+//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;

+ 12 - 12
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/SmtpPopSettingsApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/SmtpPopSettingsApiController.java

@@ -1,4 +1,4 @@
-package com.storlead.sales.controller.mail;
+package com.storlead.mail.controller;
 
 
 import cn.hutool.core.collection.CollectionUtil;
@@ -11,16 +11,16 @@ 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.sales.mail.connection.MailConnection;
-import com.storlead.sales.mail.connection.client.MailConnectionUtil;
-import com.storlead.sales.mail.connection.config.MailProperties;
-import com.storlead.sales.mail.entity.EmailsEntity;
-import com.storlead.sales.mail.entity.MailboxAutoReplySetEntity;
-import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
-import com.storlead.sales.mail.enums.EmailBoxEnum;
-import com.storlead.sales.mail.pojo.vo.MailAcountVO;
-import com.storlead.sales.mail.service.EmailsService;
-import com.storlead.sales.mail.service.SmtpPopSettingsService;
+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;
@@ -62,7 +62,7 @@ public class SmtpPopSettingsApiController {
     @ApiResponses({
             @ApiResponse(code = 200, message = "", response = SmtpPopSettingsEntity.class)
     })
-    public Result<?> pageList(com.storlead.common.object.Page dto) {
+    public Result<?> pageList(com.storlead.framework.mybatis.page.Page dto) {
         Long currentUserId = LoginUserUtil.getCurrentUserId();
         if (Objects.isNull(currentUserId)) {
             return Result.result(null);

+ 7 - 5
java/storlead-sasa/storlead-sales/src/main/java/com/storlead/sales/controller/mail/UserEmailFolderApiController.java → java/storlead-mail/storlead-mail-api/src/main/java/com/storlead/mail/controller/UserEmailFolderApiController.java

@@ -1,5 +1,4 @@
-package com.storlead.sales.controller.mail;
-
+package com.storlead.mail.controller;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -8,9 +7,12 @@ 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.sales.mail.pojo.dto.EmailTemplatesDTO;
-import com.storlead.sales.mail.service.EmailsService;
-import com.storlead.sales.mail.service.UserEmailFolderService;
+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;

+ 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>

+ 34 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailServerPortConfigMapper.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.MailServerPortConfigMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.MailServerPortConfigEntity" extends="com.storlead.framework.mybatis.mapper.SysBaseFieldMapper.BaseResultMap">
+            <id column="id" property="id" />
+            <result column="mail_server_suffix" property="mailServerSuffix" />
+            <result column="pop_port_ssl" property="popPortSsl" />
+            <result column="pop_port" property="popPort" />
+            <result column="smtp_port_ssl" property="smtpPortSsl" />
+            <result column="smtp_port_tls" property="smtpPortTls" />
+            <result column="smtp_port" property="smtpPort" />
+            <result column="imap_port_ssl" property="imapPortSsl" />
+            <result column="imap_port" property="imapPort" />
+            <result column="pop_server" property="popServer" />
+            <result column="imap_server" property="imapServer" />
+            <result column="smtp_server" property="smtpServer" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                owner_by,
+                create_time,
+                update_time,
+                is_delete,
+                enabled,
+                create_by,
+                update_by,
+                sort,
+            id, mail_server_suffix, pop_port_ssl, pop_port,smtp_port_tls, smtp_port_ssl, smtp_port, imap_port_ssl, imap_port, pop_server, imap_server, smtp_server
+        </sql>
+
+</mapper>

+ 37 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailTempAttachmentMapper.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.MailTempAttachmentMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.MailTempAttachmentEntity">
+                    <id column="id" property="id" />
+                <result column="create_by" property="createBy" />
+                <result column="create_time" property="createTime" />
+                <result column="owner_by" property="ownerBy" />
+                <result column="update_by" property="updateBy" />
+                <result column="enabled" property="enabled" />
+                <result column="sort" property="sort" />
+                <result column="update_time" property="updateTime" />
+                <result column="is_delete" property="isDelete" />
+                    <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="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, file_code, file_name, file_path, file_size, file_ext
+        </sql>
+
+</mapper>

+ 33 - 0
java/storlead-mail/storlead-mail-biz/src/main/resources/mapper/MailboxAutoReplySetMapper.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.MailboxAutoReplySetMapper">
+
+        <!-- 通用查询映射结果 -->
+        <resultMap id="BaseResultMap" type="com.storlead.mail.pojo.entity.MailboxAutoReplySetEntity">
+                    <id column="id" property="id" />
+                <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="owner_by" property="ownerBy" />
+                <result column="update_by" property="updateBy" />
+                <result column="sort" property="sort" />
+                    <result column="smtp_pop_id" property="smtpPopId" />
+                    <result column="reply_content" property="replyContent" />
+        </resultMap>
+
+        <!-- 通用查询结果列 -->
+        <sql id="Base_Column_List">
+                create_time,
+                update_time,
+                is_delete,
+                enabled,
+                create_by,
+                owner_by,
+                update_by,
+                sort,
+            id, smtp_pop_id, reply_content
+        </sql>
+
+</mapper>

Vissa filer visades inte eftersom för många filer har ändrats