|
@@ -9,9 +9,7 @@ import com.storlead.framework.common.result.Result;
|
|
|
import com.storlead.mail.webclient.client.MktMailRemoteClient;
|
|
import com.storlead.mail.webclient.client.MktMailRemoteClient;
|
|
|
import com.storlead.mail.webclient.dto.MktMailStatusChangeRemoteDTO;
|
|
import com.storlead.mail.webclient.dto.MktMailStatusChangeRemoteDTO;
|
|
|
import com.storlead.mail.webclient.dto.MktMailStatusSyncQueryRemoteDTO;
|
|
import com.storlead.mail.webclient.dto.MktMailStatusSyncQueryRemoteDTO;
|
|
|
-import com.storlead.trade.entity.MailStatusSyncCursorEntity;
|
|
|
|
|
import com.storlead.trade.entity.MarketEmailsEntity;
|
|
import com.storlead.trade.entity.MarketEmailsEntity;
|
|
|
-import com.storlead.trade.service.MailStatusSyncCursorService;
|
|
|
|
|
import com.storlead.trade.service.MarketEmailsService;
|
|
import com.storlead.trade.service.MarketEmailsService;
|
|
|
import com.storlead.trade.service.MarketEmailsStatusSyncService;
|
|
import com.storlead.trade.service.MarketEmailsStatusSyncService;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
@@ -19,23 +17,21 @@ import org.springframework.stereotype.Service;
|
|
|
import org.springframework.util.CollectionUtils;
|
|
import org.springframework.util.CollectionUtils;
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
-import java.time.LocalDateTime;
|
|
|
|
|
|
|
+import java.util.ArrayList;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 从邮件服务增量拉取 mkt_mail_status,按 email_id 回写 market_emails。
|
|
|
|
|
|
|
+ * mkt_mail_status → market_emails:
|
|
|
|
|
+ * 查待同步(update_time > status_updated_at)→ 按 mail_id 改业务表 → 回写同步时间。
|
|
|
*/
|
|
*/
|
|
|
@Slf4j
|
|
@Slf4j
|
|
|
@Service
|
|
@Service
|
|
|
@DS(DSConstants.DATASOURCE_TRADE)
|
|
@DS(DSConstants.DATASOURCE_TRADE)
|
|
|
public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSyncService {
|
|
public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSyncService {
|
|
|
|
|
|
|
|
- public static final String SOURCE_MKT_MAIL_STATUS = "mkt_mail_status";
|
|
|
|
|
-
|
|
|
|
|
private static final int BATCH_LIMIT = 200;
|
|
private static final int BATCH_LIMIT = 200;
|
|
|
private static final int MAX_PAGES = 50;
|
|
private static final int MAX_PAGES = 50;
|
|
|
|
|
|
|
|
- /** trade.status:0草稿/待发,1已发送,2发送失败 */
|
|
|
|
|
private static final int TRADE_STATUS_DRAFT = 0;
|
|
private static final int TRADE_STATUS_DRAFT = 0;
|
|
|
private static final int TRADE_STATUS_SENT = 1;
|
|
private static final int TRADE_STATUS_SENT = 1;
|
|
|
private static final int TRADE_STATUS_FAIL = 2;
|
|
private static final int TRADE_STATUS_FAIL = 2;
|
|
@@ -43,34 +39,29 @@ public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSync
|
|
|
@Resource
|
|
@Resource
|
|
|
private MktMailRemoteClient mktMailRemoteClient;
|
|
private MktMailRemoteClient mktMailRemoteClient;
|
|
|
|
|
|
|
|
- @Resource
|
|
|
|
|
- private MailStatusSyncCursorService mailStatusSyncCursorService;
|
|
|
|
|
-
|
|
|
|
|
@Resource
|
|
@Resource
|
|
|
private MarketEmailsService marketEmailsService;
|
|
private MarketEmailsService marketEmailsService;
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
public int syncOnce() {
|
|
public int syncOnce() {
|
|
|
- MailStatusSyncCursorEntity cursor = mailStatusSyncCursorService.getOrInit(SOURCE_MKT_MAIL_STATUS);
|
|
|
|
|
- LocalDateTime since = cursor.getLastStatusUpdatedAt();
|
|
|
|
|
- Long lastId = cursor.getLastId() == null ? 0L : cursor.getLastId();
|
|
|
|
|
-
|
|
|
|
|
int totalUpdated = 0;
|
|
int totalUpdated = 0;
|
|
|
|
|
+ Long lastId = null;
|
|
|
|
|
+
|
|
|
for (int page = 0; page < MAX_PAGES; page++) {
|
|
for (int page = 0; page < MAX_PAGES; page++) {
|
|
|
|
|
+ // 1. 查 mkt_mail_status:有最新修改(update_time > status_updated_at)
|
|
|
MktMailStatusSyncQueryRemoteDTO query = new MktMailStatusSyncQueryRemoteDTO();
|
|
MktMailStatusSyncQueryRemoteDTO query = new MktMailStatusSyncQueryRemoteDTO();
|
|
|
- query.setSince(since);
|
|
|
|
|
- query.setLastId(lastId);
|
|
|
|
|
query.setLimit(BATCH_LIMIT);
|
|
query.setLimit(BATCH_LIMIT);
|
|
|
|
|
+ query.setLastId(lastId);
|
|
|
|
|
|
|
|
Result<List<MktMailStatusChangeRemoteDTO>> result;
|
|
Result<List<MktMailStatusChangeRemoteDTO>> result;
|
|
|
try {
|
|
try {
|
|
|
result = mktMailRemoteClient.statusChanges(query);
|
|
result = mktMailRemoteClient.statusChanges(query);
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.error("拉取营销邮件状态失败 since={} lastId={}", since, lastId, e);
|
|
|
|
|
|
|
+ log.error("查询 mkt_mail_status 待同步数据失败 lastId={}", lastId, e);
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
if (result == null || !result.isSuccess()) {
|
|
if (result == null || !result.isSuccess()) {
|
|
|
- log.warn("拉取营销邮件状态返回失败 message={}", result == null ? null : result.getMessage());
|
|
|
|
|
|
|
+ log.warn("查询 mkt_mail_status 失败 message={}", result == null ? null : result.getMessage());
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
List<MktMailStatusChangeRemoteDTO> changes = result.getResult();
|
|
List<MktMailStatusChangeRemoteDTO> changes = result.getResult();
|
|
@@ -78,52 +69,62 @@ public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSync
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- LocalDateTime batchMaxTime = since;
|
|
|
|
|
- Long batchMaxId = lastId;
|
|
|
|
|
- int pageUpdated = 0;
|
|
|
|
|
|
|
+ List<Long> syncedMailIds = new ArrayList<>();
|
|
|
|
|
+ Long batchLastId = lastId;
|
|
|
for (MktMailStatusChangeRemoteDTO change : changes) {
|
|
for (MktMailStatusChangeRemoteDTO change : changes) {
|
|
|
if (change == null || change.getMailId() == null) {
|
|
if (change == null || change.getMailId() == null) {
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
- if (applyChange(change)) {
|
|
|
|
|
- pageUpdated++;
|
|
|
|
|
|
|
+ if (change.getId() != null) {
|
|
|
|
|
+ batchLastId = change.getId();
|
|
|
}
|
|
}
|
|
|
- if (change.getStatusUpdatedAt() != null) {
|
|
|
|
|
- if (batchMaxTime == null || change.getStatusUpdatedAt().isAfter(batchMaxTime)
|
|
|
|
|
- || (change.getStatusUpdatedAt().equals(batchMaxTime)
|
|
|
|
|
- && change.getId() != null && change.getId() > batchMaxId)) {
|
|
|
|
|
- batchMaxTime = change.getStatusUpdatedAt();
|
|
|
|
|
- batchMaxId = change.getId() == null ? batchMaxId : change.getId();
|
|
|
|
|
- }
|
|
|
|
|
- } else if (change.getId() != null && change.getId() > batchMaxId) {
|
|
|
|
|
- batchMaxId = change.getId();
|
|
|
|
|
|
|
+ // 2. 按邮件 id 修改 market_emails
|
|
|
|
|
+ ApplyResult applyResult = applyToMarketEmails(change);
|
|
|
|
|
+ if (applyResult == ApplyResult.UPDATED) {
|
|
|
|
|
+ totalUpdated++;
|
|
|
|
|
+ syncedMailIds.add(change.getMailId());
|
|
|
|
|
+ } else if (applyResult == ApplyResult.MATCHED_NO_CHANGE) {
|
|
|
|
|
+ // 业务表已对齐,仍需回写同步时间,避免反复拉取
|
|
|
|
|
+ syncedMailIds.add(change.getMailId());
|
|
|
}
|
|
}
|
|
|
|
|
+ // UNMATCHED:尚无 email_id 关联,不 ack,下次继续等
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 本批处理完再推进游标(失败不推进则下次重拉,更新需幂等)
|
|
|
|
|
- since = batchMaxTime;
|
|
|
|
|
- lastId = batchMaxId;
|
|
|
|
|
- mailStatusSyncCursorService.advance(SOURCE_MKT_MAIL_STATUS, since, lastId);
|
|
|
|
|
- totalUpdated += pageUpdated;
|
|
|
|
|
|
|
+ // 3. 同步后回写 mkt_mail_status.status_updated_at
|
|
|
|
|
+ if (!syncedMailIds.isEmpty()) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Result<?> ack = mktMailRemoteClient.ackStatusSync(syncedMailIds);
|
|
|
|
|
+ if (ack == null || !ack.isSuccess()) {
|
|
|
|
|
+ log.warn("回写 mkt_mail_status.status_updated_at 失败 mailIds={} message={}",
|
|
|
|
|
+ syncedMailIds, ack == null ? null : ack.getMessage());
|
|
|
|
|
+ // ack 失败则不再继续翻页,避免漏确认
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("回写 mkt_mail_status.status_updated_at 异常 mailIds={}", syncedMailIds, e);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
if (changes.size() < BATCH_LIMIT) {
|
|
if (changes.size() < BATCH_LIMIT) {
|
|
|
break;
|
|
break;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (batchLastId == null || batchLastId.equals(lastId)) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ lastId = batchLastId;
|
|
|
}
|
|
}
|
|
|
return totalUpdated;
|
|
return totalUpdated;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 按 email_id 回写;发送状态不降级;已读/已回复只升不降。
|
|
|
|
|
- */
|
|
|
|
|
- private boolean applyChange(MktMailStatusChangeRemoteDTO change) {
|
|
|
|
|
|
|
+ private ApplyResult applyToMarketEmails(MktMailStatusChangeRemoteDTO change) {
|
|
|
MarketEmailsEntity existing = marketEmailsService.getOne(new LambdaQueryWrapper<MarketEmailsEntity>()
|
|
MarketEmailsEntity existing = marketEmailsService.getOne(new LambdaQueryWrapper<MarketEmailsEntity>()
|
|
|
.eq(MarketEmailsEntity::getEmailId, change.getMailId())
|
|
.eq(MarketEmailsEntity::getEmailId, change.getMailId())
|
|
|
.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
.eq(MarketEmailsEntity::getIsDelete, CommonConstant.DEL_FLAG_0)
|
|
|
.last("limit 1"));
|
|
.last("limit 1"));
|
|
|
if (existing == null) {
|
|
if (existing == null) {
|
|
|
- log.debug("market_emails 无 email_id={} 记录,跳过", change.getMailId());
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ log.debug("market_emails 无 email_id={},跳过且不 ack", change.getMailId());
|
|
|
|
|
+ return ApplyResult.UNMATCHED;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
Integer mappedStatus = mapSendStatus(change.getSendStatus());
|
|
Integer mappedStatus = mapSendStatus(change.getSendStatus());
|
|
@@ -149,9 +150,9 @@ public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSync
|
|
|
changed = true;
|
|
changed = true;
|
|
|
}
|
|
}
|
|
|
if (!changed) {
|
|
if (!changed) {
|
|
|
- return false;
|
|
|
|
|
|
|
+ return ApplyResult.MATCHED_NO_CHANGE;
|
|
|
}
|
|
}
|
|
|
- return marketEmailsService.update(update);
|
|
|
|
|
|
|
+ return marketEmailsService.update(update) ? ApplyResult.UPDATED : ApplyResult.MATCHED_NO_CHANGE;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static Integer mapSendStatus(Integer sendStatus) {
|
|
private static Integer mapSendStatus(Integer sendStatus) {
|
|
@@ -164,11 +165,9 @@ public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSync
|
|
|
if (sendStatus == 2) {
|
|
if (sendStatus == 2) {
|
|
|
return TRADE_STATUS_FAIL;
|
|
return TRADE_STATUS_FAIL;
|
|
|
}
|
|
}
|
|
|
- // -2草稿 -1定时 0发送中 → trade 0
|
|
|
|
|
return TRADE_STATUS_DRAFT;
|
|
return TRADE_STATUS_DRAFT;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /** 已打开或已读都视为 has_reader=1 */
|
|
|
|
|
private static Integer resolveReader(MktMailStatusChangeRemoteDTO change) {
|
|
private static Integer resolveReader(MktMailStatusChangeRemoteDTO change) {
|
|
|
if (Integer.valueOf(1).equals(change.getOpened()) || Integer.valueOf(1).equals(change.getIsRead())) {
|
|
if (Integer.valueOf(1).equals(change.getOpened()) || Integer.valueOf(1).equals(change.getIsRead())) {
|
|
|
return 1;
|
|
return 1;
|
|
@@ -176,9 +175,6 @@ public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSync
|
|
|
return null;
|
|
return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- /**
|
|
|
|
|
- * 发送状态:成功不降级;失败可升为成功;草稿可变为成功/失败。
|
|
|
|
|
- */
|
|
|
|
|
private static boolean shouldUpgradeSendStatus(Integer current, Integer mapped) {
|
|
private static boolean shouldUpgradeSendStatus(Integer current, Integer mapped) {
|
|
|
if (mapped == null) {
|
|
if (mapped == null) {
|
|
|
return false;
|
|
return false;
|
|
@@ -195,7 +191,12 @@ public class MarketEmailsStatusSyncServiceImpl implements MarketEmailsStatusSync
|
|
|
if (TRADE_STATUS_FAIL == current) {
|
|
if (TRADE_STATUS_FAIL == current) {
|
|
|
return TRADE_STATUS_SENT == mapped;
|
|
return TRADE_STATUS_SENT == mapped;
|
|
|
}
|
|
}
|
|
|
- // 当前为草稿/待发
|
|
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ private enum ApplyResult {
|
|
|
|
|
+ UPDATED,
|
|
|
|
|
+ MATCHED_NO_CHANGE,
|
|
|
|
|
+ UNMATCHED
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|