Prechádzať zdrojové kódy

邮件解耦,处理外部业务

1811872455@163.com 11 hodín pred
rodič
commit
40f08b499c
22 zmenil súbory, kde vykonal 797 pridanie a 336 odobranie
  1. 4 1
      storlead-api/src/main/java/com/storlead/StorleadMailServiceApplication.java
  2. 10 0
      storlead-api/src/main/resources/application.yml
  3. 0 1
      storlead-mail/src/main/java/com/storlead/sales/mail/dispatch/AutoSendDelayEmailTaskJob.java
  4. 11 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/config/MailIntegrationConfiguration.java
  5. 24 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/config/MailIntegrationProperties.java
  6. 45 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/entity/MailIntegrationTaskEntity.java
  7. 18 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/enums/MailIntegrationTaskStatus.java
  8. 33 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/enums/MailIntegrationTaskType.java
  9. 88 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/external/IntegrationExternalInvoker.java
  10. 87 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/handler/CrmMailSyncHandler.java
  11. 111 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/handler/NewMailRemindHandler.java
  12. 16 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/mapper/MailIntegrationTaskMapper.java
  13. 85 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/scheduler/MailIntegrationTaskScheduler.java
  14. 19 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/service/MailIntegrationTaskService.java
  15. 65 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/service/impl/MailIntegrationTaskServiceImpl.java
  16. 43 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/support/MailEntityResolver.java
  17. 43 0
      storlead-mail/src/main/java/com/storlead/sales/mail/integration/util/MailIdsParser.java
  18. 0 45
      storlead-mail/src/main/java/com/storlead/sales/mail/service/EmailsService.java
  19. 32 240
      storlead-mail/src/main/java/com/storlead/sales/mail/service/impl/EmailsServiceImpl.java
  20. 8 49
      storlead-mail/src/main/java/com/storlead/sales/mail/util/EmailSenderWithThreadLocal.java
  21. 35 0
      storlead-mail/src/main/resources/mapper/MailIntegrationTaskMapper.xml
  22. 20 0
      storlead-mail/src/main/resources/sql/mail_integration_task.sql

+ 4 - 1
storlead-api/src/main/java/com/storlead/StorleadMailServiceApplication.java

@@ -10,7 +10,10 @@ import org.springframework.core.env.Environment;
 import java.net.InetAddress;
 
 @SpringBootApplication(scanBasePackages = "com.storlead")
-@MapperScan("com.storlead.sales.mail.mapper")
+@MapperScan({
+        "com.storlead.sales.mail.mapper",
+        "com.storlead.sales.mail.integration.mapper"
+})
 @Slf4j
 public class StorleadMailServiceApplication {
 

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

@@ -66,6 +66,14 @@ storlead:
       - system_wechat_config
       - system_oss_config
       - sys_app
+      - mail_integration_task
+  mail:
+    integration:
+      enabled: true
+      poll-interval-ms: 30000
+      batch-size: 20
+      # 单体部署且 CRM/消息 Bean 在本进程时 true;仅落库由外部消费时 false
+      invoke-external-beans: true
   datasource:
     # 邮件服务默认走主库 master(未标注 @DS 时生效;标注 @DS 则以注解为准)
     module-default-enabled: true
@@ -73,4 +81,6 @@ storlead:
       - packages:
           - com.storlead.sales.mail.service
           - com.storlead.sales.mail.mapper
+          - com.storlead.sales.mail.integration.service
+          - com.storlead.sales.mail.integration.mapper
         datasource: master

+ 0 - 1
storlead-mail/src/main/java/com/storlead/sales/mail/dispatch/AutoSendDelayEmailTaskJob.java

@@ -88,7 +88,6 @@ public class AutoSendDelayEmailTaskJob implements Job {
 
     public static void main(String[] args) {
         String env = System.getProperty("app.env");
-
         log.error("env ="+env);
     }
 }

+ 11 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/config/MailIntegrationConfiguration.java

@@ -0,0 +1,11 @@
+package com.storlead.sales.mail.integration.config;
+
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@Configuration
+@EnableScheduling
+@EnableConfigurationProperties(MailIntegrationProperties.class)
+public class MailIntegrationConfiguration {
+}

+ 24 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/config/MailIntegrationProperties.java

@@ -0,0 +1,24 @@
+package com.storlead.sales.mail.integration.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@Data
+@ConfigurationProperties(prefix = "storlead.mail.integration")
+public class MailIntegrationProperties {
+
+    /** 是否写入并消费集成任务 */
+    private boolean enabled = true;
+
+    /** 定时拉取间隔(毫秒) */
+    private long pollIntervalMs = 30000L;
+
+    /** 每批处理条数 */
+    private int batchSize = 20;
+
+    /**
+     * 是否通过反射调用 CRM/消息 Bean(单体或未拆服务时 true;
+     * 纯中间表模式、由外部服务消费时可设为 false,本服务仅落库)
+     */
+    private boolean invokeExternalBeans = true;
+}

+ 45 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/entity/MailIntegrationTaskEntity.java

@@ -0,0 +1,45 @@
+package com.storlead.sales.mail.integration.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.storlead.framework.mybatis.entity.SysBaseField;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Accessors(chain = true)
+@TableName("mail_integration_task")
+@ApiModel(value = "MailIntegrationTaskEntity", description = "邮件集成异步任务")
+public class MailIntegrationTaskEntity extends SysBaseField {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    @TableField("task_no")
+    private String taskNo;
+
+    @TableField("task_type")
+    private String taskType;
+
+    @TableField("task_status")
+    private String taskStatus;
+
+    @TableField("mail_ids")
+    private String mailIds;
+
+    @TableField("error_msg")
+    private String errorMsg;
+
+    @TableField("processed_at")
+    private Date processedAt;
+}

+ 18 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/enums/MailIntegrationTaskStatus.java

@@ -0,0 +1,18 @@
+package com.storlead.sales.mail.integration.enums;
+
+import lombok.Getter;
+
+@Getter
+public enum MailIntegrationTaskStatus {
+
+    PENDING("PENDING"),
+    PROCESSING("PROCESSING"),
+    SUCCESS("SUCCESS"),
+    FAILED("FAILED");
+
+    private final String code;
+
+    MailIntegrationTaskStatus(String code) {
+        this.code = code;
+    }
+}

+ 33 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/enums/MailIntegrationTaskType.java

@@ -0,0 +1,33 @@
+package com.storlead.sales.mail.integration.enums;
+
+import lombok.Getter;
+
+/**
+ * 邮件集成任务类型
+ */
+@Getter
+public enum MailIntegrationTaskType {
+
+    /** 绑定客户联络人、更新跟进时间 */
+    CRM_SYNC("CRM_SYNC", 100),
+
+    /** 新邮件站内/企微等提醒 */
+    NEW_REMIND("NEW_REMIND", 200);
+
+    private final String code;
+    private final int sort;
+
+    MailIntegrationTaskType(String code, int sort) {
+        this.code = code;
+        this.sort = sort;
+    }
+
+    public static MailIntegrationTaskType fromCode(String code) {
+        for (MailIntegrationTaskType type : values()) {
+            if (type.code.equals(code)) {
+                return type;
+            }
+        }
+        throw new IllegalArgumentException("Unknown mail integration task type: " + code);
+    }
+}

+ 88 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/external/IntegrationExternalInvoker.java

@@ -0,0 +1,88 @@
+package com.storlead.sales.mail.integration.external;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.framework.common.util.SpringContextUtils;
+import com.storlead.sales.mail.pojo.vo.CustomerMailVO;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import java.lang.reflect.Method;
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * 封装对 CRM、消息中心等外部 Bean 的反射调用(待拆 HTTP 时可替换实现)
+ */
+@Log4j2
+@Component
+public class IntegrationExternalInvoker {
+
+    public void mateBindCustomerLiaisonMail(Long mailId, Long userId, String mailAddress, Integer inOutMark) {
+        if (StrUtil.isBlank(mailAddress)) {
+            return;
+        }
+        try {
+            Object bean = SpringContextUtils.getBean("customerMailBingMarkServiceImpl");
+            Method method = bean.getClass().getMethod(
+                    "mateBindCustomerLiaisonMail", Long.class, Long.class, String.class, Integer.class);
+            method.invoke(bean, mailId, userId, mailAddress.toLowerCase(), inOutMark);
+        } catch (Exception e) {
+            log.error("mateBindCustomerLiaisonMail failed, mailId={}", mailId, e);
+            throw new IllegalStateException("CRM bind failed", e);
+        }
+    }
+
+    public void updateFollowUpTimeByEmail(Set<String> mailAddresses, LocalDateTime sentDate) {
+        if (CollectionUtils.isEmpty(mailAddresses)) {
+            return;
+        }
+        try {
+            Object bean = SpringContextUtils.getBean("customerServiceImpl");
+            Method method = bean.getClass().getMethod("updateFollowUpTimeByEmail", Set.class, LocalDateTime.class);
+            method.invoke(bean, mailAddresses, sentDate);
+        } catch (Exception e) {
+            log.error("updateFollowUpTimeByEmail failed", e);
+            throw new IllegalStateException("CRM follow-up update failed", e);
+        }
+    }
+
+    public void sendNewEmailRemind(Map<String, Object> messageMap, Long userId) {
+        try {
+            Object bean = SpringContextUtils.getBean("messageService");
+            Method method = bean.getClass().getMethod(
+                    "autoMatchEventSendMessage",
+                    Map.class, String.class, String.class, Set.class, String[].class);
+            Set<Long> toUserIds = new HashSet<>();
+            toUserIds.add(userId);
+            String[] channels = {"site", "wecom", "sms", "mail"};
+            method.invoke(bean, messageMap, "11", "NEW_EMAIL", toUserIds, channels);
+        } catch (Exception e) {
+            log.error("autoMatchEventSendMessage NEW_EMAIL failed", e);
+            throw new IllegalStateException("Message send failed", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<CustomerMailVO> listCustomerMailInfo(List<Long> customerIds) {
+        if (CollectionUtils.isEmpty(customerIds)) {
+            return Collections.emptyList();
+        }
+        try {
+            Object bean = SpringContextUtils.getBean("customerServiceImpl");
+            for (Method method : bean.getClass().getMethods()) {
+                if ("listCustomerMailInfoByIds".equals(method.getName())
+                        && method.getParameterCount() == 1
+                        && List.class.isAssignableFrom(method.getParameterTypes()[0])) {
+                    Object result = method.invoke(bean, customerIds);
+                    if (result instanceof List) {
+                        return (List<CustomerMailVO>) result;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.debug("listCustomerMailInfoByIds not available, skip enrich: {}", e.getMessage());
+        }
+        return Collections.emptyList();
+    }
+}

+ 87 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/handler/CrmMailSyncHandler.java

@@ -0,0 +1,87 @@
+package com.storlead.sales.mail.integration.handler;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.sales.mail.entity.EmailsEntity;
+import com.storlead.sales.mail.integration.external.IntegrationExternalInvoker;
+import com.storlead.sales.mail.integration.support.MailEntityResolver;
+import com.storlead.sales.mail.integration.util.MailIdsParser;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
+
+/**
+ * CRM 同步:绑定客户联络人 + 发件箱更新跟进时间
+ */
+@Log4j2
+@Component
+public class CrmMailSyncHandler {
+
+    @Resource
+    private MailEntityResolver mailEntityResolver;
+    @Resource
+    private IntegrationExternalInvoker integrationExternalInvoker;
+
+    public void execute(String mailIdsCsv, Long ownerBy) {
+        List<Long> mailIds = MailIdsParser.parse(mailIdsCsv);
+        if (CollectionUtils.isEmpty(mailIds)) {
+            return;
+        }
+        List<EmailsEntity> entities = mailEntityResolver.resolveByMailIds(mailIds);
+        if (CollectionUtils.isEmpty(entities)) {
+            log.warn("CRM_SYNC: no emails found for ids={}", mailIdsCsv);
+            return;
+        }
+        Long taskOwner = ownerBy != null ? ownerBy : entities.get(0).getOwnerBy();
+        for (EmailsEntity entity : entities) {
+            syncOne(entity, taskOwner);
+        }
+    }
+
+    private void syncOne(EmailsEntity entity, Long ownerBy) {
+        try {
+            if (Integer.valueOf(1).equals(entity.getInOutMark())) {
+                integrationExternalInvoker.mateBindCustomerLiaisonMail(
+                        entity.getId(), ownerBy, entity.getFrom(), 1);
+            } else {
+                String recipient = entity.getRecipient();
+                if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+                    recipient = StrUtil.isNotBlank(recipient)
+                            ? recipient + "," + entity.getRecipientCc()
+                            : entity.getRecipientCc();
+                }
+                integrationExternalInvoker.mateBindCustomerLiaisonMail(
+                        entity.getId(), ownerBy, recipient, 2);
+            }
+            if ("SENT".equals(entity.getFolder())) {
+                updateSentFollowUp(entity);
+            }
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            log.error("CRM_SYNC failed for mailId={}", entity.getId(), e);
+            throw new IllegalStateException(e);
+        }
+    }
+
+    private void updateSentFollowUp(EmailsEntity entity) {
+        Set<String> mailAddress = new HashSet<>();
+        if (StrUtil.isNotBlank(entity.getRecipient())) {
+            mailAddress.addAll(Arrays.asList(entity.getRecipient().toLowerCase().split(",")));
+        }
+        if (StrUtil.isNotBlank(entity.getRecipientCc())) {
+            mailAddress.addAll(Arrays.asList(entity.getRecipientCc().toLowerCase().split(",")));
+        }
+        if (CollectionUtils.isEmpty(mailAddress)) {
+            return;
+        }
+        LocalDateTime sentDate = Objects.isNull(entity.getSentDate())
+                ? LocalDateTime.now()
+                : entity.getSentDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        integrationExternalInvoker.updateFollowUpTimeByEmail(mailAddress, sentDate);
+    }
+}

+ 111 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/handler/NewMailRemindHandler.java

@@ -0,0 +1,111 @@
+package com.storlead.sales.mail.integration.handler;
+
+import com.storlead.sales.mail.entity.EmailsEntity;
+import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
+import com.storlead.sales.mail.integration.external.IntegrationExternalInvoker;
+import com.storlead.sales.mail.integration.support.MailEntityResolver;
+import com.storlead.sales.mail.integration.util.MailIdsParser;
+import com.storlead.sales.mail.mapper.EmailsMapper;
+import com.storlead.sales.mail.pojo.vo.CustomerMailVO;
+import com.storlead.sales.mail.service.SmtpPopSettingsService;
+import cn.hutool.core.util.StrUtil;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@Log4j2
+@Component
+public class NewMailRemindHandler {
+
+    @Resource
+    private MailEntityResolver mailEntityResolver;
+    @Resource
+    private IntegrationExternalInvoker integrationExternalInvoker;
+    @Resource
+    private EmailsMapper emailsMapper;
+    @Resource
+    private SmtpPopSettingsService smtpPopSettingsService;
+
+    public void execute(String mailIdsCsv, Long ownerBy) {
+        List<Long> mailIds = MailIdsParser.parse(mailIdsCsv);
+        if (CollectionUtils.isEmpty(mailIds) || ownerBy == null) {
+            return;
+        }
+        String messageContent = "您有" + mailIds.size() + "封新邮件,请前往查看!";
+        List<Long> cusIds = collectCustomerIds(mailIds);
+        if (!CollectionUtils.isEmpty(cusIds)) {
+            List<CustomerMailVO> customerVos = integrationExternalInvoker.listCustomerMailInfo(cusIds);
+            if (!CollectionUtils.isEmpty(customerVos)) {
+                messageContent = buildCustomerMessage(customerVos, mailIds.size());
+            }
+        }
+        String mailAccount = resolveMailAccount(mailIds);
+        Map<String, Object> messageMap = new HashMap<>();
+        messageMap.put("messageContent", messageContent);
+        Integer mailCount = emailsMapper.countSmtpMailCount(ownerBy);
+        if (mailCount != null && mailCount > 1) {
+            messageMap.put("titleRemark", "[" + mailAccount + "]");
+        } else {
+            messageMap.put("titleRemark", "");
+        }
+        integrationExternalInvoker.sendNewEmailRemind(messageMap, ownerBy);
+    }
+
+    private String resolveMailAccount(List<Long> mailIds) {
+        List<EmailsEntity> entities = mailEntityResolver.resolveByMailIds(mailIds);
+        if (CollectionUtils.isEmpty(entities) || entities.get(0).getSmtpPopId() == null) {
+            return "";
+        }
+        SmtpPopSettingsEntity smtpPop = smtpPopSettingsService.getById(entities.get(0).getSmtpPopId());
+        return smtpPop != null ? smtpPop.getEmailAddress() : "";
+    }
+
+    private String buildCustomerMessage(List<CustomerMailVO> customerVos, int mailCount) {
+        StringBuilder sb = new StringBuilder();
+        String moreCus = "";
+        for (CustomerMailVO customer : customerVos) {
+            if (sb.length() > 0) {
+                moreCus = "等";
+                sb.append("、");
+            }
+            sb.append("【");
+            if (customer.getCountry() != null) {
+                sb.append(customer.getCountry());
+                sb.append("-");
+            }
+            sb.append(customer.getCustomerName());
+            sb.append("】");
+        }
+        return "您有来自" + sb + moreCus + "客户的" + mailCount + "封新邮件,请前往查看!";
+    }
+
+    private List<Long> collectCustomerIds(List<Long> mailIds) {
+        List<EmailsEntity> entities = mailEntityResolver.resolveByMailIds(mailIds);
+        Set<Long> ids = new HashSet<>();
+        for (EmailsEntity entity : entities) {
+            if (entity.getCustomerId() != null) {
+                ids.add(entity.getCustomerId());
+            }
+            if (StrUtil.isNotBlank(entity.getCustomerIds())) {
+                for (String part : entity.getCustomerIds().split(",")) {
+                    if (StrUtil.isBlank(part)) {
+                        continue;
+                    }
+                    try {
+                        ids.add(Long.parseLong(part.trim()));
+                    } catch (NumberFormatException ignored) {
+                    }
+                }
+            }
+        }
+        return new ArrayList<>(ids);
+    }
+}

+ 16 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/mapper/MailIntegrationTaskMapper.java

@@ -0,0 +1,16 @@
+package com.storlead.sales.mail.integration.mapper;
+
+import com.storlead.framework.mybatis.mapper.MyBaseMapper;
+import com.storlead.sales.mail.integration.entity.MailIntegrationTaskEntity;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+public interface MailIntegrationTaskMapper extends MyBaseMapper<MailIntegrationTaskEntity> {
+
+    List<MailIntegrationTaskEntity> selectPendingTasks(@Param("limit") int limit);
+
+    int claimTask(@Param("id") Long id,
+                  @Param("fromStatus") String fromStatus,
+                  @Param("toStatus") String toStatus);
+}

+ 85 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/scheduler/MailIntegrationTaskScheduler.java

@@ -0,0 +1,85 @@
+package com.storlead.sales.mail.integration.scheduler;
+
+import cn.hutool.core.util.StrUtil;
+import com.storlead.sales.mail.integration.config.MailIntegrationProperties;
+import com.storlead.sales.mail.integration.entity.MailIntegrationTaskEntity;
+import com.storlead.sales.mail.integration.enums.MailIntegrationTaskStatus;
+import com.storlead.sales.mail.integration.enums.MailIntegrationTaskType;
+import com.storlead.sales.mail.integration.handler.CrmMailSyncHandler;
+import com.storlead.sales.mail.integration.handler.NewMailRemindHandler;
+import com.storlead.sales.mail.integration.mapper.MailIntegrationTaskMapper;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@Log4j2
+@Component
+public class MailIntegrationTaskScheduler {
+
+    @Resource
+    private MailIntegrationProperties properties;
+    @Resource
+    private MailIntegrationTaskMapper mailIntegrationTaskMapper;
+    @Resource
+    private CrmMailSyncHandler crmMailSyncHandler;
+    @Resource
+    private NewMailRemindHandler newMailRemindHandler;
+
+    @Scheduled(fixedDelayString = "${storlead.mail.integration.poll-interval-ms:30000}")
+    public void processPendingTasks() {
+        if (!properties.isEnabled() || !properties.isInvokeExternalBeans()) {
+            return;
+        }
+        List<MailIntegrationTaskEntity> tasks =
+                mailIntegrationTaskMapper.selectPendingTasks(properties.getBatchSize());
+        if (CollectionUtils.isEmpty(tasks)) {
+            return;
+        }
+        for (MailIntegrationTaskEntity task : tasks) {
+            processOne(task);
+        }
+    }
+
+    private void processOne(MailIntegrationTaskEntity task) {
+        int claimed = mailIntegrationTaskMapper.claimTask(
+                task.getId(),
+                MailIntegrationTaskStatus.PENDING.getCode(),
+                MailIntegrationTaskStatus.PROCESSING.getCode());
+        if (claimed == 0) {
+            return;
+        }
+        try {
+            MailIntegrationTaskType type = MailIntegrationTaskType.fromCode(task.getTaskType());
+            switch (type) {
+                case CRM_SYNC:
+                    crmMailSyncHandler.execute(task.getMailIds(), task.getOwnerBy());
+                    break;
+                case NEW_REMIND:
+                    newMailRemindHandler.execute(task.getMailIds(), task.getOwnerBy());
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported task type: " + task.getTaskType());
+            }
+            markFinished(task.getId(), MailIntegrationTaskStatus.SUCCESS, null);
+        } catch (Exception e) {
+            log.error("mail integration task failed, id={}, type={}", task.getId(), task.getTaskType(), e);
+            String msg = StrUtil.sub(e.getMessage(), 0, 1900);
+            markFinished(task.getId(), MailIntegrationTaskStatus.FAILED, msg);
+        }
+    }
+
+    private void markFinished(Long id, MailIntegrationTaskStatus status, String errorMsg) {
+        MailIntegrationTaskEntity update = new MailIntegrationTaskEntity();
+        update.setId(id);
+        update.setTaskStatus(status.getCode());
+        update.setErrorMsg(errorMsg);
+        update.setProcessedAt(new Date());
+        update.setUpdateTime(new Date());
+        mailIntegrationTaskMapper.updateById(update);
+    }
+}

+ 19 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/service/MailIntegrationTaskService.java

@@ -0,0 +1,19 @@
+package com.storlead.sales.mail.integration.service;
+
+import com.storlead.framework.mybatis.service.MyBaseService;
+import com.storlead.sales.mail.integration.entity.MailIntegrationTaskEntity;
+
+import java.util.List;
+
+public interface MailIntegrationTaskService extends MyBaseService<MailIntegrationTaskEntity> {
+
+    /**
+     * 拉取邮件入库后创建集成任务(CRM 必建;收件箱且后台任务时再建提醒)
+     */
+    void createAfterPullHeadMail(List<Long> mailIds, Long ownerBy, boolean needNewRemind);
+
+    /**
+     * 发件成功/部分成功后创建 CRM 同步任务(邮件可能随后从 emails 表删除)
+     */
+    void createCrmSyncAfterSent(Long mailId, Long ownerBy);
+}

+ 65 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/service/impl/MailIntegrationTaskServiceImpl.java

@@ -0,0 +1,65 @@
+package com.storlead.sales.mail.integration.service.impl;
+
+import cn.hutool.core.util.IdUtil;
+import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.sales.mail.integration.config.MailIntegrationProperties;
+import com.storlead.sales.mail.integration.entity.MailIntegrationTaskEntity;
+import com.storlead.sales.mail.integration.enums.MailIntegrationTaskStatus;
+import com.storlead.sales.mail.integration.enums.MailIntegrationTaskType;
+import com.storlead.sales.mail.integration.mapper.MailIntegrationTaskMapper;
+import com.storlead.sales.mail.integration.service.MailIntegrationTaskService;
+import com.storlead.sales.mail.integration.util.MailIdsParser;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@Log4j2
+@Service
+public class MailIntegrationTaskServiceImpl
+        extends MyBaseServiceImpl<MailIntegrationTaskMapper, MailIntegrationTaskEntity>
+        implements MailIntegrationTaskService {
+
+    @Resource
+    private MailIntegrationProperties mailIntegrationProperties;
+
+    @Override
+    public void createAfterPullHeadMail(List<Long> mailIds, Long ownerBy, boolean needNewRemind) {
+        if (!mailIntegrationProperties.isEnabled() || CollectionUtils.isEmpty(mailIds)) {
+            return;
+        }
+        String mailIdsCsv = MailIdsParser.join(mailIds);
+        saveTask(MailIntegrationTaskType.CRM_SYNC, mailIdsCsv, ownerBy);
+        if (needNewRemind) {
+            saveTask(MailIntegrationTaskType.NEW_REMIND, mailIdsCsv, ownerBy);
+        }
+    }
+
+    @Override
+    public void createCrmSyncAfterSent(Long mailId, Long ownerBy) {
+        if (!mailIntegrationProperties.isEnabled() || mailId == null) {
+            return;
+        }
+        saveTask(MailIntegrationTaskType.CRM_SYNC, String.valueOf(mailId), ownerBy);
+    }
+
+    private void saveTask(MailIntegrationTaskType taskType, String mailIdsCsv, Long ownerBy) {
+        MailIntegrationTaskEntity task = new MailIntegrationTaskEntity();
+        task.setTaskNo("MIT" + IdUtil.fastSimpleUUID());
+        task.setTaskType(taskType.getCode());
+        task.setTaskStatus(MailIntegrationTaskStatus.PENDING.getCode());
+        task.setMailIds(mailIdsCsv);
+        task.setOwnerBy(ownerBy);
+        task.setSort(taskType.getSort());
+        task.setIsDelete(0);
+        task.setEnabled(true);
+        task.setCreateTime(new Date());
+        task.setUpdateTime(new Date());
+        save(task);
+        log.info("mail integration task created: type={}, mailIds={}, ownerBy={}",
+                taskType.getCode(), mailIdsCsv, ownerBy);
+    }
+}

+ 43 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/support/MailEntityResolver.java

@@ -0,0 +1,43 @@
+package com.storlead.sales.mail.integration.support;
+
+import com.storlead.sales.mail.entity.ClientSentEmailsEntity;
+import com.storlead.sales.mail.entity.EmailsEntity;
+import com.storlead.sales.mail.service.ClientSentEmailsService;
+import com.storlead.sales.mail.service.EmailsService;
+import org.springframework.beans.BeanUtils;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+@Component
+public class MailEntityResolver {
+
+    @Resource
+    private EmailsService emailsService;
+    @Resource
+    private ClientSentEmailsService clientSentEmailsService;
+
+    public List<EmailsEntity> resolveByMailIds(List<Long> mailIds) {
+        if (CollectionUtils.isEmpty(mailIds)) {
+            return new ArrayList<>();
+        }
+        List<EmailsEntity> result = new ArrayList<>();
+        for (Long mailId : mailIds) {
+            EmailsEntity email = emailsService.getById(mailId);
+            if (email != null) {
+                result.add(email);
+                continue;
+            }
+            ClientSentEmailsEntity sent = clientSentEmailsService.getById(mailId);
+            if (sent != null) {
+                EmailsEntity copy = new EmailsEntity();
+                BeanUtils.copyProperties(sent, copy);
+                result.add(copy);
+            }
+        }
+        return result;
+    }
+}

+ 43 - 0
storlead-mail/src/main/java/com/storlead/sales/mail/integration/util/MailIdsParser.java

@@ -0,0 +1,43 @@
+package com.storlead.sales.mail.integration.util;
+
+import cn.hutool.core.util.StrUtil;
+import org.springframework.util.CollectionUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public final class MailIdsParser {
+
+    private MailIdsParser() {
+    }
+
+    public static String join(List<Long> mailIds) {
+        if (CollectionUtils.isEmpty(mailIds)) {
+            return "";
+        }
+        return mailIds.stream()
+                .filter(id -> id != null && id > 0)
+                .map(String::valueOf)
+                .collect(Collectors.joining(","));
+    }
+
+    public static List<Long> parse(String mailIds) {
+        if (StrUtil.isBlank(mailIds)) {
+            return Collections.emptyList();
+        }
+        List<Long> result = new ArrayList<>();
+        for (String part : mailIds.split(",")) {
+            if (StrUtil.isBlank(part)) {
+                continue;
+            }
+            try {
+                result.add(Long.parseLong(part.trim()));
+            } catch (NumberFormatException ignored) {
+                // skip invalid segment
+            }
+        }
+        return result;
+    }
+}

+ 0 - 45
storlead-mail/src/main/java/com/storlead/sales/mail/service/EmailsService.java

@@ -175,51 +175,8 @@ public interface EmailsService extends MyBaseService<EmailsEntity> {
     Boolean pullMailAttachmentHead(Long mailId, Date recipientDate,Object messageContent,SmtpPopSettingsEntity smtpPop);
 
 
-    IPage<EmailsEntity> pageWithScope(Page page, MailDTO dto);
-
-    IPage<EmailsEntity> pageWithScopeCus(Page page, MailDTO dto);
-
-    IPage<EmailsEntity> pageWithScopeAllCus(Page page, MailDTO dto);
-
     void pullHeadMail(List<Message> messages, SmtpPopSettingsEntity smtpPop, EmailBoxEnum boxEnum, Set<String> blackls,List<EmailFolderRuleEntity> folderRules,Boolean isBackTask);
 
-    /**
-     * 汇总邮件数量
-     * @param stmpPopId
-     * @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);
-
-    /**
-     * 获取客户最后跟进时间
-     * @param customerId
-     * @return
-     */
-    LocalDateTime getCustomerLastFollowUpTime(Long customerId);
-
-    /**
-     * 解析邮件
-     * @param messages
-     */
-    void analysisMailEml(List<Message> messages,Set<String> cusMails,Long ownerBy);
-
     /**
      * 解析邮件,返回数据
      * @param messages
@@ -230,7 +187,5 @@ public interface EmailsService extends MyBaseService<EmailsEntity> {
 
     List<EmailsEntity> getDelayDeleteMail();
 
-    IPage<CustomerEmailVo> selectCustomerEmailPage(Page<CustomerEmailVo> page,MailListDTO dto);
-
     void updateFollowUpTime(List<EmailsEntity> entities,Long ownerBy);
 }

+ 32 - 240
storlead-mail/src/main/java/com/storlead/sales/mail/service/impl/EmailsServiceImpl.java

@@ -29,6 +29,11 @@ import com.storlead.sales.mail.pojo.vo.*;
 import com.storlead.sales.mail.properties.MailFileProperties;
 import com.storlead.sales.mail.service.*;
 import com.storlead.framework.mybatis.service.impl.MyBaseServiceImpl;
+import com.storlead.sales.mail.integration.external.IntegrationExternalInvoker;
+import com.storlead.sales.mail.integration.handler.CrmMailSyncHandler;
+import com.storlead.sales.mail.integration.handler.NewMailRemindHandler;
+import com.storlead.sales.mail.integration.service.MailIntegrationTaskService;
+import com.storlead.sales.mail.integration.util.MailIdsParser;
 import com.storlead.sales.mail.util.EmailHelper;
 import com.storlead.sales.mail.util.EmailSenderWithThreadLocal;
 import com.storlead.sales.mail.util.ReceiveMailQueueThreadPool;
@@ -116,6 +121,18 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
     @Resource
     private Environment environment;
 
+    @Resource
+    private MailIntegrationTaskService mailIntegrationTaskService;
+
+    @Resource
+    private CrmMailSyncHandler crmMailSyncHandler;
+
+    @Resource
+    private NewMailRemindHandler newMailRemindHandler;
+
+    @Resource
+    private IntegrationExternalInvoker integrationExternalInvoker;
+
     @Override
     public void updateSelectBindCustomerMail() {
         //   this.baseMapper.updateCleanBindCustomerMail();
@@ -465,27 +482,6 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
             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);
@@ -540,20 +536,7 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
         }
     }
 
-    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());
@@ -640,18 +623,11 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
                 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);
-                    });
+                    boolean needNewRemind = Boolean.TRUE.equals(finalIsBackTask)
+                            && EmailBoxEnum.INBOX.code.equals(finalboxCode);
+                    mailIntegrationTaskService.createAfterPullHeadMail(
+                            finalIsMailIds, smtpPop.getOwnerBy(), needNewRemind);
+                    CompletableFuture.runAsync(() -> autoReplyMails(entities));
                 }
 
             }catch(Exception e){
@@ -659,57 +635,11 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
             }
     }
 
-    public void sendNewMailRemind(List<Long> mailIds,Long userId,String mailAccount) {
-        if (CollectionUtils.isEmpty(mailIds)) {
+    public void sendNewMailRemind(List<Long> mailIds, Long userId, String mailAccount) {
+        if (CollectionUtils.isEmpty(mailIds) || userId == null) {
             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);
-        }
+        newMailRemindHandler.execute(MailIdsParser.join(mailIds), userId);
     }
 
     public void autoReplyMails(List<EmailsEntity> entities){
@@ -722,48 +652,13 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
         }
     }
 
-    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);
-            }
+    @Override
+    public void updateFollowUpTime(List<EmailsEntity> entities, Long ownerBy) {
+        if (CollectionUtils.isEmpty(entities)) {
+            return;
         }
+        List<Long> mailIds = entities.stream().map(EmailsEntity::getId).filter(Objects::nonNull).collect(Collectors.toList());
+        crmMailSyncHandler.execute(MailIdsParser.join(mailIds), ownerBy);
     }
 
     public void autoReplyMail(EmailsEntity entity) {
@@ -1186,23 +1081,6 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
         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);
@@ -1405,14 +1283,7 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
 //                        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);
-                    }
+                    integrationExternalInvoker.mateBindCustomerLiaisonMail(mailId, userId, lowerMailAddress, inOutMark);
                 //}
 //            });
 //            ThreadPoolUtil.execute(ttlRunnable);
@@ -1712,85 +1583,6 @@ public class EmailsServiceImpl extends MyBaseServiceImpl<EmailsMapper, EmailsEnt
         }
     }
 
-    @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) {
 

+ 8 - 49
storlead-mail/src/main/java/com/storlead/sales/mail/util/EmailSenderWithThreadLocal.java

@@ -1,9 +1,8 @@
 package com.storlead.sales.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.sales.mail.entity.ClientSentEmailsEntity;
+import com.storlead.sales.mail.integration.service.MailIntegrationTaskService;
 import com.storlead.sales.mail.entity.EmailsEntity;
 import com.storlead.sales.mail.entity.SmtpPopSettingsEntity;
 import com.storlead.sales.mail.enums.EmailBoxEnum;
@@ -20,9 +19,6 @@ 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;
 
@@ -42,6 +38,9 @@ public class EmailSenderWithThreadLocal {
     @Resource
     private SmtpPopSettingsService popSettingsService;
 
+    @Resource
+    private MailIntegrationTaskService mailIntegrationTaskService;
+
 
 
     public void asynSendMailMessage(Transport transport, Message message, EmailsEntity email) {
@@ -119,29 +118,9 @@ public class EmailSenderWithThreadLocal {
 
     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);
+            if (entity.getId() != null && entity.getOwnerBy() != null) {
+                mailIntegrationTaskService.createCrmSyncAfterSent(entity.getId(), entity.getOwnerBy());
             }
-
             emailsService.removeById(entity.getId());
             // 保存发送日志
             saveLog(entity);
@@ -162,28 +141,8 @@ public class EmailSenderWithThreadLocal {
 
     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);
+            if (entity.getId() != null && entity.getOwnerBy() != null) {
+                mailIntegrationTaskService.createCrmSyncAfterSent(entity.getId(), entity.getOwnerBy());
             }
             emailsService.removeById(entity.getId());
             // 保存客户端的发送日志

+ 35 - 0
storlead-mail/src/main/resources/mapper/MailIntegrationTaskMapper.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.sales.mail.integration.mapper.MailIntegrationTaskMapper">
+
+    <resultMap id="BaseResultMap" type="com.storlead.sales.mail.integration.entity.MailIntegrationTaskEntity"
+               extends="com.storlead.frame.mapper.SysBaseFieldMapper.BaseResultMap">
+        <id column="id" property="id"/>
+        <result column="task_no" property="taskNo"/>
+        <result column="task_type" property="taskType"/>
+        <result column="task_status" property="taskStatus"/>
+        <result column="mail_ids" property="mailIds"/>
+        <result column="error_msg" property="errorMsg"/>
+        <result column="processed_at" property="processedAt"/>
+    </resultMap>
+
+    <select id="selectPendingTasks" resultMap="BaseResultMap">
+        SELECT id, task_no, task_type, task_status, mail_ids, error_msg, processed_at,
+               owner_by, create_by, create_time, update_by, update_time, is_delete, enabled, sort
+        FROM mail_integration_task
+        WHERE is_delete = 0
+          AND enabled = 1
+          AND task_status = 'PENDING'
+        ORDER BY sort ASC, create_time ASC
+        LIMIT #{limit}
+    </select>
+
+    <update id="claimTask">
+        UPDATE mail_integration_task
+        SET task_status = #{toStatus},
+            update_time = NOW()
+        WHERE id = #{id}
+          AND task_status = #{fromStatus}
+          AND is_delete = 0
+    </update>
+</mapper>

+ 20 - 0
storlead-mail/src/main/resources/sql/mail_integration_task.sql

@@ -0,0 +1,20 @@
+-- 邮件与 CRM/消息中心集成任务表(在 master 库执行)
+CREATE TABLE IF NOT EXISTS `mail_integration_task` (
+    `id`            BIGINT       NOT NULL AUTO_INCREMENT COMMENT '主键',
+    `task_no`       VARCHAR(64)  DEFAULT NULL COMMENT '任务编号',
+    `task_type`     VARCHAR(32)  NOT NULL COMMENT 'CRM_SYNC | NEW_REMIND',
+    `task_status`   VARCHAR(32)  NOT NULL DEFAULT 'PENDING' COMMENT 'PENDING|PROCESSING|SUCCESS|FAILED',
+    `mail_ids`      VARCHAR(2000) NOT NULL COMMENT '邮件ID,逗号分隔',
+    `error_msg`     VARCHAR(2000) DEFAULT NULL COMMENT '失败原因',
+    `processed_at`  DATETIME     DEFAULT NULL COMMENT '处理完成时间',
+    `owner_by`      BIGINT       DEFAULT NULL COMMENT '邮箱归属用户',
+    `create_by`     BIGINT       DEFAULT NULL,
+    `create_time`   DATETIME     DEFAULT NULL,
+    `update_by`     BIGINT       DEFAULT NULL,
+    `update_time`   DATETIME     DEFAULT NULL,
+    `is_delete`     TINYINT      NOT NULL DEFAULT 0,
+    `enabled`       TINYINT      NOT NULL DEFAULT 1,
+    `sort`          INT          DEFAULT NULL COMMENT '100=CRM_SYNC, 200=NEW_REMIND',
+    PRIMARY KEY (`id`),
+    KEY `idx_task_status_sort` (`task_status`, `sort`, `create_time`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邮件集成异步任务';