YPZ vor 3 Tagen
Ursprung
Commit
f2c3e2e987

+ 18 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketEmailsController.java

@@ -32,6 +32,24 @@ public class MarketEmailsController {
         return marketEmailsService.getList(marketEmailsDTO);
     }
 
+    @PostMapping("/open-list")
+    @ApiOperation("营销: 打开邮件的客户列表")
+    public Result<Object> openList(@RequestBody MarketEmailsDTO marketEmailsDTO) {
+        return marketEmailsService.getOpenList(marketEmailsDTO);
+    }
+
+    @PostMapping("/protect")
+    @ApiOperation("营销: 保护邮件(按 id 或 idList)")
+    public Result<Object> protect(@RequestBody MarketEmailsDTO marketEmailsDTO) {
+        return marketEmailsService.protect(marketEmailsDTO);
+    }
+
+    @PostMapping("/suspend")
+    @ApiOperation("营销: 暂停邮件(按 id 或 idList)")
+    public Result<Object> suspend(@RequestBody MarketEmailsDTO marketEmailsDTO) {
+        return marketEmailsService.suspend(marketEmailsDTO);
+    }
+
     @PostMapping("/add")
     @ApiOperation("营销: 营销邮件添加")
     public Result<?> add(@RequestBody MarketEmailsDTO marketEmailsDTO) {

+ 1 - 13
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/controller/MarketingCampaignSopDetailController.java

@@ -368,19 +368,7 @@ public class MarketingCampaignSopDetailController {
     @PostMapping("/edit")
     @ApiOperation(value = "编辑营销活动SOP明细", notes = "编辑营销活动SOP明细")
     public Result<?> edit(@RequestBody MarketingCampaignSopDetailDTO dto) {
-        if (ObjectUtils.isEmpty(dto.getId())) {
-            return Result.error("id不能为空");
-        }
-        MarketingCampaignSopDetailEntity entity = new MarketingCampaignSopDetailEntity();
-        BeanUtils.copyProperties(dto, entity);
-        boolean isOk = marketingCampaignSopDetailService.updateById(entity);
-        if (!isOk) {
-            return Result.error("修改失败");
-        }
-        if (!ObjectUtils.isEmpty(dto.getEmailContent())){
-
-        }
-        return Result.result(entity);
+        return marketingCampaignSopDetailService.editMarketingCampaignSopDetail(dto);
     }
 
     /**

+ 27 - 4
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/dto/MarketEmailsDTO.java

@@ -1,13 +1,17 @@
 package com.storlead.trade.dto;
 
+import com.alibaba.fastjson.annotation.JSONField;
 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.fasterxml.jackson.annotation.JsonFormat;
 import com.storlead.framework.mybatis.page.Page;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
 
+import java.util.Date;
 import java.util.List;
 
 @Data
@@ -26,15 +30,15 @@ public class MarketEmailsDTO extends Page {
     @TableField("marketing_campaign_id")
     private Long marketingCampaignId;
 
-    @ApiModelProperty(value = "邮件模板id")
-    @TableField("sop_detail_id")
-    private Long sopDetailId;
+    @ApiModelProperty(value = "营销活动关联邮件模板id")
+    @TableField("marketing_campaign_sop_detail_id")
+    private Long marketingCampaignSopDetailId;
 
     @ApiModelProperty(value = "对应的邮件id")
     @TableField("email_id")
     private Long emailId;
 
-    @ApiModelProperty(value = "状态:0草稿,1已发送,2发送失败")
+    @ApiModelProperty(value = "发送状态:-2草稿 -1待定时 0发送中 1成功 2失败;收信记录一般为1")
     @TableField("status")
     private Integer status;
 
@@ -48,4 +52,23 @@ public class MarketEmailsDTO extends Page {
 
     @ApiModelProperty(value = "id列表(批量操作用)")
     private List<Long> idList;
+
+    @ApiModelProperty(value = "是否保护(0未保护、1保护)")
+    @TableField("has_protect")
+    private Integer hasProtect;
+
+    @ApiModelProperty(value = "是否暂停(0未暂停、1暂停)")
+    @TableField("has_suspend")
+    private Integer hasSuspend;
+
+    @ApiModelProperty(value = "发件人邮箱")
+    @TableField("recipient")
+    private String recipient;
+
+    @ApiModelProperty(value = "首次打开时间")
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    @TableField("first_open_time")
+    private Date firstOpenTime;
 }

+ 23 - 4
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/entity/MarketEmailsEntity.java

@@ -27,15 +27,15 @@ public class MarketEmailsEntity extends SysBaseField {
     @TableField("marketing_campaign_id")
     private Long marketingCampaignId;
 
-    @ApiModelProperty(value = "邮件模板id")
-    @TableField("sop_detail_id")
-    private Long sopDetailId;
+    @ApiModelProperty(value = "营销活动关联邮件模板id")
+    @TableField("marketing_campaign_sop_detail_id")
+    private Long marketingCampaignSopDetailId;
 
     @ApiModelProperty(value = "对应的邮件id")
     @TableField("email_id")
     private Long emailId;
 
-    @ApiModelProperty(value = "状态:0草稿,1已发送,2发送失败")
+    @ApiModelProperty(value = "发送状态:-2草稿 -1待定时 0发送中 1成功 2失败;收信记录一般为1")
     @TableField("status")
     private Integer status;
 
@@ -58,4 +58,23 @@ public class MarketEmailsEntity extends SysBaseField {
     @TableField("content")
     private String content;
 
+    @ApiModelProperty(value = "是否保护(0未保护、1保护)")
+    @TableField("has_protect")
+    private Integer hasProtect;
+
+    @ApiModelProperty(value = "是否暂停(0未暂停、1暂停)")
+    @TableField("has_suspend")
+    private Integer hasSuspend;
+
+    @ApiModelProperty(value = "发件人邮箱")
+    @TableField("recipient")
+    private String recipient;
+
+    @ApiModelProperty(value = "首次打开时间")
+    @JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @JSONField(format ="yyyy-MM-dd HH:mm:ss")
+    @TableField("first_open_time")
+    private Date firstOpenTime;
+
 }

+ 17 - 1
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketEmailsService.java

@@ -18,7 +18,23 @@ public interface MarketEmailsService extends MyBaseService<MarketEmailsEntity> {
 
     Result<Object> edit(MarketEmailsDTO marketEmailsDTO);
 
+    /**
+     * 打开邮件的客户列表:按 marketingCampaignId 过滤 hasReader=1 的记录,
+     * 按 recipient 聚合(统计点击次数、取最早首次打开时间)。
+     */
+    Result<Object> getOpenList(MarketEmailsDTO marketEmailsDTO);
+
+    /**
+     * 保护邮件(按 id 或 idList,hasProtect 置 1)
+     */
+    Result<Object> protect(MarketEmailsDTO marketEmailsDTO);
+
+    /**
+     * 暂停邮件(按 id 或 idList,hasSuspend 置 1)
+     */
+    Result<Object> suspend(MarketEmailsDTO marketEmailsDTO);
+
     @Async
     void addSendTime(MarketingCampaignEntity marketingCampaignEntity);
 
-}
+}

+ 4 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/MarketingCampaignSopDetailService.java

@@ -1,10 +1,14 @@
 package com.storlead.trade.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.storlead.framework.common.result.Result;
+import com.storlead.trade.dto.MarketingCampaignSopDetailDTO;
 import com.storlead.trade.entity.MarketingCampaignSopDetailEntity;
 
 /**
  * 营销活动SOP明细 Service接口
  */
 public interface MarketingCampaignSopDetailService extends IService<MarketingCampaignSopDetailEntity> {
+
+    Result<?> editMarketingCampaignSopDetail(MarketingCampaignSopDetailDTO dto);
 }

+ 3 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/CustomerTradeServiceImpl.java

@@ -56,6 +56,9 @@ public class CustomerTradeServiceImpl
         if (!ObjectUtils.isEmpty(dto.getId())) {
             wrapper.eq(CustomerEntity::getId, dto.getId());
         }
+        if (!CollectionUtils.isEmpty(dto.getIdList())){
+            wrapper.in(CustomerEntity::getId, dto.getIdList());
+        }
         if (!ObjectUtils.isEmpty(dto.getCustomerCode())) {
             wrapper.eq(CustomerEntity::getCustomerCode, dto.getCustomerCode());
         }

+ 140 - 3
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketEmailsServiceImpl.java

@@ -21,6 +21,7 @@ import com.storlead.trade.entity.*;
 import com.storlead.trade.mapper.MarketEmailsMapper;
 import com.storlead.trade.service.*;
 import com.storlead.trade.vo.MarketEmailsCustomerVO;
+import com.storlead.trade.vo.MarketEmailsOpenVO;
 import com.storlead.trade.vo.ScheduleStageVO;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
@@ -53,6 +54,7 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
     @Resource
     private CustomerTradeService customerTradeService;
     @Resource
+    @org.springframework.context.annotation.Lazy
     private MarketingCampaignSopDetailService marketingCampaignSopDetailEntityService;
     @Resource
     private SopDetailService sopDetailService;
@@ -76,8 +78,8 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
         if (!ObjectUtils.isEmpty(dto.getMarketingCampaignId())) {
             wrapper.eq(MarketEmailsEntity::getMarketingCampaignId, dto.getMarketingCampaignId());
         }
-        if (!ObjectUtils.isEmpty(dto.getSopDetailId())) {
-            wrapper.eq(MarketEmailsEntity::getSopDetailId, dto.getSopDetailId());
+        if (!ObjectUtils.isEmpty(dto.getMarketingCampaignSopDetailId())) {
+            wrapper.eq(MarketEmailsEntity::getMarketingCampaignSopDetailId, dto.getMarketingCampaignSopDetailId());
         }
         if (!ObjectUtils.isEmpty(dto.getEmailId())) {
             wrapper.eq(MarketEmailsEntity::getEmailId, dto.getEmailId());
@@ -118,6 +120,135 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
         return Result.ok();
     }
 
+    @Override
+    public Result<Object> getOpenList(MarketEmailsDTO dto) {
+        if (dto == null) {
+            dto = new MarketEmailsDTO();
+        }
+        // 打开邮件的客户 = hasReader=1 且 recipient 不为空
+        // 策略:单活动下打开邮件的邮件数通常几百~几千,先查全集在内存里按 recipient 分组 + 分页
+        LambdaQueryWrapper<MarketEmailsEntity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.eq(MarketEmailsEntity::getHasReader, 1);
+        if (!ObjectUtils.isEmpty(dto.getMarketingCampaignId())) {
+            wrapper.eq(MarketEmailsEntity::getMarketingCampaignId, dto.getMarketingCampaignId());
+        }
+        if (dto.getRecipient() != null && !dto.getRecipient().trim().isEmpty()) {
+            wrapper.eq(MarketEmailsEntity::getRecipient, dto.getRecipient().trim());
+        }
+        wrapper.isNotNull(MarketEmailsEntity::getRecipient);
+        wrapper.ne(MarketEmailsEntity::getRecipient, "");
+        // 只查需要的列,减少数据传输
+        wrapper.select(MarketEmailsEntity::getRecipient,
+                MarketEmailsEntity::getFirstOpenTime,
+                MarketEmailsEntity::getHasProtect,
+                MarketEmailsEntity::getHasSuspend);
+        List<MarketEmailsEntity> all = this.list(wrapper);
+        // 按 recipient 聚合
+        java.util.Map<String, com.storlead.trade.vo.MarketEmailsOpenVO> aggMap = new java.util.LinkedHashMap<>();
+        if (!CollectionUtils.isEmpty(all)) {
+            for (MarketEmailsEntity e : all) {
+                String r = e.getRecipient();
+                if (r == null || r.isEmpty()) {
+                    continue;
+                }
+                com.storlead.trade.vo.MarketEmailsOpenVO vo = aggMap.computeIfAbsent(r, k -> {
+                    com.storlead.trade.vo.MarketEmailsOpenVO v = new com.storlead.trade.vo.MarketEmailsOpenVO();
+                    v.setRecipient(k);
+                    v.setClickCount(0L);
+                    return v;
+                });
+                vo.setClickCount(vo.getClickCount() + 1);
+                // 取最早首次打开时间
+                if (e.getFirstOpenTime() != null) {
+                    if (vo.getFirstOpenTime() == null || e.getFirstOpenTime().before(vo.getFirstOpenTime())) {
+                        vo.setFirstOpenTime(e.getFirstOpenTime());
+                    }
+                }
+                // MAX(has_protect) / MAX(has_suspend) 语义
+                if (e.getHasProtect() != null && e.getHasProtect() == 1) {
+                    vo.setHasProtect(1);
+                }
+                if (e.getHasSuspend() != null && e.getHasSuspend() == 1) {
+                    vo.setHasSuspend(1);
+                }
+            }
+        }
+        // 按首次打开时间倒序
+        java.util.List<com.storlead.trade.vo.MarketEmailsOpenVO> sorted = new java.util.ArrayList<>(aggMap.values());
+        sorted.sort((a, b) -> {
+            if (a.getFirstOpenTime() == null && b.getFirstOpenTime() == null) return 0;
+            if (a.getFirstOpenTime() == null) return 1;
+            if (b.getFirstOpenTime() == null) return -1;
+            return b.getFirstOpenTime().compareTo(a.getFirstOpenTime());
+        });
+        // 内存分页
+        long pageIndex = dto.getPageIndex() != null && dto.getPageIndex() > 0 ? dto.getPageIndex() : 1L;
+        long pageSize = dto.getPageSize() != null && dto.getPageSize() > 0 ? dto.getPageSize() : 50L;
+        long total = sorted.size();
+        long from = Math.min((pageIndex - 1) * pageSize, total);
+        long to = Math.min(from + pageSize, total);
+        java.util.List<com.storlead.trade.vo.MarketEmailsOpenVO> pageList = sorted.subList((int) from, (int) to);
+        com.baomidou.mybatisplus.core.metadata.IPage<com.storlead.trade.vo.MarketEmailsOpenVO> page =
+                new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(pageIndex, pageSize, total);
+        page.setRecords(pageList);
+        return Result.result(page);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Result<Object> protect(MarketEmailsDTO dto) {
+        if (dto == null) {
+            return Result.error("请求体不能为空");
+        }
+        List<Long> ids = new ArrayList<>();
+        if (!ObjectUtils.isEmpty(dto.getId())) {
+            ids.add(dto.getId());
+        }
+        if (!CollectionUtils.isEmpty(dto.getIdList())) {
+            ids.addAll(dto.getIdList());
+        }
+        if (ids.isEmpty()) {
+            return Result.error("id 或 idList 不能为空");
+        }
+        LambdaUpdateWrapper<MarketEmailsEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.in(MarketEmailsEntity::getId, ids);
+        wrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.set(MarketEmailsEntity::getHasProtect, dto.getHasProtect());
+        boolean ok = this.update(wrapper);
+        if (!ok) {
+            return Result.error("保护失败");
+        }
+        return Result.ok();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Result<Object> suspend(MarketEmailsDTO dto) {
+        if (dto == null) {
+            return Result.error("请求体不能为空");
+        }
+        List<Long> ids = new ArrayList<>();
+        if (!ObjectUtils.isEmpty(dto.getId())) {
+            ids.add(dto.getId());
+        }
+        if (!CollectionUtils.isEmpty(dto.getIdList())) {
+            ids.addAll(dto.getIdList());
+        }
+        if (ids.isEmpty()) {
+            return Result.error("id 或 idList 不能为空");
+        }
+        LambdaUpdateWrapper<MarketEmailsEntity> wrapper = new LambdaUpdateWrapper<>();
+        wrapper.in(MarketEmailsEntity::getId, ids);
+        wrapper.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0);
+        wrapper.set(MarketEmailsEntity::getHasSuspend, dto.getHasSuspend());
+        boolean ok = this.update(wrapper);
+        if (!ok) {
+            return Result.error("暂停失败");
+        }
+        return Result.ok();
+    }
+
     @Override
     @Transactional(rollbackFor = Throwable.class)
     public Result<Object> edit(MarketEmailsDTO dto) {
@@ -247,10 +378,16 @@ public class MarketEmailsServiceImpl extends MyBaseServiceImpl<MarketEmailsMappe
                     if (ObjectUtils.isEmpty(sopDetail)) {
                         continue;
                     }
+                    //取marketingCampaignSopDetailEntityList中marketingId=marketingCampaignEntity.getId()且sopDetailId=sopDetail.getId()的记录
+                    MarketingCampaignSopDetailEntity marketingCampaignSopDetail = marketingCampaignSopDetailEntityList.stream()
+                            .filter(detail -> detail.getMarketingId().equals(marketingCampaignEntity.getId())
+                                    && detail.getSopDetailId().equals(sopDetail.getId()))
+                            .findFirst()
+                            .orElse(null);
                     MarketEmailsEntity marketEmail = new MarketEmailsEntity();
                     marketEmail.setMarketingCampaignId(marketingCampaignEntity.getId());
                     marketEmail.setCustomerId(customerVO.getCustomerId());
-                    marketEmail.setSopDetailId(sopDetail.getId());
+                    marketEmail.setMarketingCampaignSopDetailId(marketingCampaignSopDetail.getId());
                     marketEmail.setExpectSendTime(scheduleStage.getBeijingDatetime());
                     marketEmailList.add(marketEmail);
                 }

+ 116 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/service/impl/MarketingCampaignSopDetailServiceImpl.java

@@ -1,12 +1,33 @@
 package com.storlead.trade.service.impl;
 
 import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.storlead.framework.common.constant.DSConstants;
+import com.storlead.framework.common.result.Result;
+import com.storlead.mail.pojo.entity.MktMailMessageEntity;
+import com.storlead.mail.service.MktMailMessageService;
+import com.storlead.trade.dto.CustomerDTO;
+import com.storlead.trade.dto.MarketingCampaignSopDetailDTO;
+import com.storlead.trade.entity.CustomerEntity;
+import com.storlead.trade.entity.MarketEmailsEntity;
 import com.storlead.trade.entity.MarketingCampaignSopDetailEntity;
 import com.storlead.trade.mapper.MarketingCampaignSopDetailMapper;
+import com.storlead.trade.service.CustomerTradeService;
+import com.storlead.trade.service.MarketEmailsService;
 import com.storlead.trade.service.MarketingCampaignSopDetailService;
 import com.storlead.trade.service.impl.support.TradeDataSourceServiceImpl;
+import com.storlead.trade.vo.CustomerVO;
+import org.springframework.beans.BeanUtils;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.util.ObjectUtils;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * 营销活动SOP明细 Service实现
@@ -16,4 +37,99 @@ import org.springframework.stereotype.Service;
 public class MarketingCampaignSopDetailServiceImpl
         extends TradeDataSourceServiceImpl<MarketingCampaignSopDetailMapper, MarketingCampaignSopDetailEntity>
         implements MarketingCampaignSopDetailService {
+    @Resource
+    private MarketEmailsService marketEmailsService;
+    @Resource
+    private CustomerTradeService customerService;
+    @Resource
+    private MktMailMessageService mktMailMessageService;
+
+    @Override
+    public Result<?> editMarketingCampaignSopDetail(MarketingCampaignSopDetailDTO dto) {
+        if (ObjectUtils.isEmpty(dto.getId())) {
+            return Result.error("id不能为空");
+        }
+        MarketingCampaignSopDetailEntity entity = new MarketingCampaignSopDetailEntity();
+        BeanUtils.copyProperties(dto, entity);
+        boolean isOk = this.updateById(entity);
+        if (!isOk) {
+            return Result.error("修改失败");
+        }
+        if (!ObjectUtils.isEmpty(dto.getEmailContent())){
+            //写入每个客户的邮件内容
+            this.updateEmailContent(dto.getId(), dto.getEmailContent());
+        }
+        return Result.result(entity);
+    }
+
+    private void updateEmailContent(Long id, String emailContent) {
+        //查询营销活动关联的所有客户
+//        MarketingCampaignSopDetailEntity entity = this.getById(id);
+        LambdaQueryWrapper<MarketEmailsEntity> marketEmailsEntityLambdaQueryWrapper = new LambdaQueryWrapper<>();
+        marketEmailsEntityLambdaQueryWrapper.eq(MarketEmailsEntity::getMarketingCampaignSopDetailId, id);
+        marketEmailsEntityLambdaQueryWrapper.eq(MarketEmailsEntity::getIsDelete, 0);
+        List<MarketEmailsEntity> marketEmailsEntityList = marketEmailsService.list(marketEmailsEntityLambdaQueryWrapper);
+
+        List<Long> customerIdList = marketEmailsEntityList.stream().map(MarketEmailsEntity::getCustomerId).collect(Collectors.toList());
+        CustomerDTO dto = new CustomerDTO();
+        dto.setIdList(customerIdList);
+        dto.setPageIndex(1);
+        dto.setPageSize(customerIdList.size());
+        IPage<CustomerVO> customerPageList = customerService.getList(dto);
+        if (customerPageList == null || customerPageList.getRecords() == null || customerPageList.getRecords().size() == 0) {
+            return;
+        }
+        List<CustomerVO> customerVOList = customerPageList.getRecords();
+
+
+//        List<CustomerEntity> customerEntityList = customerService.listByIds(customerIdList);
+        Map<Long,CustomerVO> customerEntityMap = customerVOList.stream().collect(Collectors.toMap(CustomerVO::getId, customerVO -> customerVO));
+        for (MarketEmailsEntity marketEmailsEntity : marketEmailsEntityList) {
+            CustomerVO customerEntity = customerEntityMap.get(marketEmailsEntity.getCustomerId());
+            if (customerEntity != null) {
+                //修改emailContent中全部的${customerName}$为customerEntity.getCustomerName()
+                emailContent = emailContent.replace("${customerName}$", customerEntity.getCustomerName());
+                marketEmailsEntity.setContent(emailContent);
+            }
+            if (!ObjectUtils.isEmpty(marketEmailsEntity.getCustomerId()) && !ObjectUtils.isEmpty(customerEntityMap.get(marketEmailsEntity.getCustomerId()).getCustomerCompany()) && !ObjectUtils.isEmpty(customerEntityMap.get(marketEmailsEntity.getCustomerId()).getCustomerCompany().getEmail())){
+                marketEmailsEntity.setRecipient(customerEntityMap.get(marketEmailsEntity.getCustomerId()).getCustomerCompany().getEmail());
+            }
+
+        }
+        boolean isOk = marketEmailsService.updateBatchById(marketEmailsEntityList);
+        if (!isOk) {
+            return;
+        }
+
+        //异步写入邮件发送时间及内容
+        this.insertMailMessage(marketEmailsEntityList,customerEntityMap);
+    }
+
+    @Async
+    public void insertMailMessage(List<MarketEmailsEntity> marketEmailsEntityList, Map<Long, CustomerVO> customerEntityMap) {
+        for (MarketEmailsEntity marketEmailsEntity : marketEmailsEntityList) {
+            if (ObjectUtils.isEmpty(marketEmailsEntity.getCustomerId()) || ObjectUtils.isEmpty(customerEntityMap.get(marketEmailsEntity.getCustomerId()).getCustomerCompany()) || ObjectUtils.isEmpty(customerEntityMap.get(marketEmailsEntity.getCustomerId()).getCustomerCompany().getEmail())){
+                continue;
+            }
+            if (!ObjectUtils.isEmpty(marketEmailsEntity.getEmailId())){
+                LambdaUpdateWrapper<MktMailMessageEntity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
+                lambdaUpdateWrapper.eq(MktMailMessageEntity::getId, marketEmailsEntity.getEmailId());
+//                lambdaUpdateWrapper.set(MktMailMessageEntity::getStatus, -1);
+                lambdaUpdateWrapper.set(MktMailMessageEntity::getContent, marketEmailsEntity.getContent());
+                mktMailMessageService.update(lambdaUpdateWrapper);
+            }else {
+                MktMailMessageEntity mktMailMessageEntity = new MktMailMessageEntity();
+                mktMailMessageEntity.setStatus(-1);
+                mktMailMessageEntity.setDelaySendTime(marketEmailsEntity.getExpectSendTime());
+                mktMailMessageEntity.setContent(marketEmailsEntity.getContent());
+                mktMailMessageEntity.setRecipient(customerEntityMap.get(marketEmailsEntity.getCustomerId()).getCustomerCompany().getEmail());
+                mktMailMessageEntity.setIsTrack(1);
+                mktMailMessageService.save(mktMailMessageEntity);
+                if (mktMailMessageEntity.getId() != null) {
+                    marketEmailsEntity.setEmailId(mktMailMessageEntity.getId());
+                }
+            }
+        }
+        boolean isOk = marketEmailsService.updateBatchById(marketEmailsEntityList);
+    }
 }

+ 39 - 0
java/storlead-sasa/storlead-trade/src/main/java/com/storlead/trade/vo/MarketEmailsOpenVO.java

@@ -0,0 +1,39 @@
+package com.storlead.trade.vo;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 打开邮件的客户列表响应 VO(按 recipient 聚合)
+ *
+ * @author Generated
+ */
+@Data
+@ApiModel(value = "MarketEmailsOpenVO", description = "打开邮件的客户(按发件人邮箱聚合)")
+public class MarketEmailsOpenVO implements Serializable {
+
+    @ApiModelProperty(value = "发件人邮箱(客户邮箱)")
+    private String recipient;
+
+    @ApiModelProperty(value = "点击次数(同 recipient 下的邮件打开记录数)")
+    private Long clickCount;
+
+    @ApiModelProperty(value = "首次打开时间")
+    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @JSONField(format = "yyyy-MM-dd HH:mm:ss")
+    private Date firstOpenTime;
+
+    @ApiModelProperty(value = "是否保护(0未保护、1保护)")
+    private Integer hasProtect;
+
+    @ApiModelProperty(value = "是否暂停(0未暂停、1暂停)")
+    private Integer hasSuspend;
+}