|
@@ -26,7 +26,9 @@ import com.storlead.sales.mail.mkt.pojo.MktEditMailContentDTO;
|
|
|
import com.storlead.sales.mail.mkt.pojo.MktMailQueryDTO;
|
|
import com.storlead.sales.mail.mkt.pojo.MktMailQueryDTO;
|
|
|
import com.storlead.sales.mail.mkt.pojo.MktOpenStatsVO;
|
|
import com.storlead.sales.mail.mkt.pojo.MktOpenStatsVO;
|
|
|
import com.storlead.sales.mail.mkt.pojo.MktSendMailDTO;
|
|
import com.storlead.sales.mail.mkt.pojo.MktSendMailDTO;
|
|
|
|
|
+import com.storlead.sales.mail.mkt.pojo.MktSendMailResult;
|
|
|
import com.storlead.sales.mail.mkt.service.MktMailAttachmentService;
|
|
import com.storlead.sales.mail.mkt.service.MktMailAttachmentService;
|
|
|
|
|
+import com.storlead.sales.mail.mkt.service.MktMailBlacklistService;
|
|
|
import com.storlead.sales.mail.mkt.service.MktMailMessageService;
|
|
import com.storlead.sales.mail.mkt.service.MktMailMessageService;
|
|
|
import com.storlead.sales.mail.mkt.service.MktMailStatusService;
|
|
import com.storlead.sales.mail.mkt.service.MktMailStatusService;
|
|
|
import com.storlead.sales.mail.mkt.support.MktMailHeaders;
|
|
import com.storlead.sales.mail.mkt.support.MktMailHeaders;
|
|
@@ -47,6 +49,7 @@ import javax.activation.FileDataSource;
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
import javax.mail.BodyPart;
|
|
import javax.mail.BodyPart;
|
|
|
import javax.mail.Message;
|
|
import javax.mail.Message;
|
|
|
|
|
+import javax.mail.MessagingException;
|
|
|
import javax.mail.Multipart;
|
|
import javax.mail.Multipart;
|
|
|
import javax.mail.Session;
|
|
import javax.mail.Session;
|
|
|
import javax.mail.Transport;
|
|
import javax.mail.Transport;
|
|
@@ -93,6 +96,9 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
@Resource
|
|
@Resource
|
|
|
private MktMailStatusService mktMailStatusService;
|
|
private MktMailStatusService mktMailStatusService;
|
|
|
|
|
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private MktMailBlacklistService mktMailBlacklistService;
|
|
|
|
|
+
|
|
|
@Resource
|
|
@Resource
|
|
|
private MailFileProperties mailFileProperties;
|
|
private MailFileProperties mailFileProperties;
|
|
|
|
|
|
|
@@ -139,70 +145,100 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public MktMailMessageEntity send(MktSendMailDTO dto, Long operatorId) {
|
|
|
|
|
- try {
|
|
|
|
|
- if (dto == null || dto.getAccountId() == null) {
|
|
|
|
|
- throw new WebException("发信账户不能为空");
|
|
|
|
|
- }
|
|
|
|
|
- if (CollectionUtils.isEmpty(dto.getRecipientls())) {
|
|
|
|
|
- throw new WebException("收件人不能为空");
|
|
|
|
|
- }
|
|
|
|
|
- dto.setRecipient(dto.getRecipientls().get(0));
|
|
|
|
|
- if (!StringUtils.hasText(dto.getRecipient())) {
|
|
|
|
|
- throw new WebException("收件人不能为空");
|
|
|
|
|
- }
|
|
|
|
|
- if (!StringUtils.hasText(dto.getSubject())) {
|
|
|
|
|
- throw new WebException("主题不能为空");
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ public MktSendMailResult send(MktSendMailDTO dto, Long operatorId) {
|
|
|
|
|
+ if (dto == null || dto.getAccountId() == null) {
|
|
|
|
|
+ throw new WebException("发信账户不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ // 营销发信固定开启打开追踪(忽略入参 isTrack)
|
|
|
|
|
+ dto.setIsTrack(1);
|
|
|
|
|
+ if (CollectionUtils.isEmpty(dto.getRecipientls())) {
|
|
|
|
|
+ throw new WebException("收件人不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ dto.setRecipient(dto.getRecipientls().get(0));
|
|
|
|
|
+ if (!StringUtils.hasText(dto.getRecipient())) {
|
|
|
|
|
+ throw new WebException("收件人不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!StringUtils.hasText(dto.getSubject())) {
|
|
|
|
|
+ throw new WebException("主题不能为空");
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- SmtpPopSettingsEntity account = mailAccountBizService.requireAccount(dto.getAccountId(), MailSceneEnum.MKT);
|
|
|
|
|
-
|
|
|
|
|
- MktMailMessageEntity entity = resolveWritableEntity(dto.getId(), operatorId);
|
|
|
|
|
- fillMessageFields(entity, dto, account, operatorId);
|
|
|
|
|
- ensureTrackToken(entity, dto.getIsTrack());
|
|
|
|
|
- assertTrackReady(entity);
|
|
|
|
|
-
|
|
|
|
|
- boolean delayed = dto.getDelaySendTime() != null && dto.getDelaySendTime().after(new Date());
|
|
|
|
|
- entity.setFolder(MktMailFolderEnum.SENTING.code);
|
|
|
|
|
- entity.setInOutMark(2);
|
|
|
|
|
- if (delayed) {
|
|
|
|
|
- entity.setStatus(STATUS_DELAY);
|
|
|
|
|
- entity.setDelaySendTime(dto.getDelaySendTime());
|
|
|
|
|
- this.saveOrUpdate(entity);
|
|
|
|
|
- mktMailStatusService.ensureStatus(entity);
|
|
|
|
|
- bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
|
|
|
|
|
- copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
|
|
|
|
|
- return entity;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ SmtpPopSettingsEntity account = mailAccountBizService.requireAccount(dto.getAccountId(), MailSceneEnum.MKT);
|
|
|
|
|
|
|
|
- entity.setStatus(STATUS_SENDING);
|
|
|
|
|
- entity.setDelaySendTime(null);
|
|
|
|
|
|
|
+ MktMailMessageEntity entity = resolveWritableEntity(dto.getId(), operatorId);
|
|
|
|
|
+ fillMessageFields(entity, dto, account, operatorId);
|
|
|
|
|
+ ensureTrackToken(entity, dto.getIsTrack());
|
|
|
|
|
+ assertTrackReady(entity);
|
|
|
|
|
+ // 主收件人在黑名单则整封不发;抄送/密送里的黑名单地址剔除后再发
|
|
|
|
|
+ applyRecipientBlacklist(entity, false);
|
|
|
|
|
+
|
|
|
|
|
+ boolean delayed = dto.getDelaySendTime() != null && dto.getDelaySendTime().after(new Date());
|
|
|
|
|
+ entity.setFolder(MktMailFolderEnum.SENTING.code);
|
|
|
|
|
+ entity.setInOutMark(2);
|
|
|
|
|
+ if (delayed) {
|
|
|
|
|
+ // 延时尚未投递,正文需先落库供到期任务使用
|
|
|
|
|
+ entity.setStatus(STATUS_DELAY);
|
|
|
|
|
+ entity.setDelaySendTime(dto.getDelaySendTime());
|
|
|
this.saveOrUpdate(entity);
|
|
this.saveOrUpdate(entity);
|
|
|
mktMailStatusService.ensureStatus(entity);
|
|
mktMailStatusService.ensureStatus(entity);
|
|
|
bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
|
|
bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
|
|
|
- // 正式附件拷入临时表,MIME 与成功后转正式统一按 mailId 处理,避免重复挂载
|
|
|
|
|
copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
|
|
copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
|
|
|
|
|
+ return toSendResult(null, entity);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- SmtpPopSettingsEntity decrypted = decryptAccount(account);
|
|
|
|
|
- boolean async = Boolean.TRUE.equals(dto.getAsync());
|
|
|
|
|
- if (async) {
|
|
|
|
|
- dispatchSmtpSend(entity.getId(), decrypted, true);
|
|
|
|
|
- return entity;
|
|
|
|
|
- }
|
|
|
|
|
- MailSendStatus status = dispatchSmtpSend(entity.getId(), decrypted, false);
|
|
|
|
|
- MktMailMessageEntity latest = this.getById(entity.getId());
|
|
|
|
|
- if (latest != null) {
|
|
|
|
|
- if (status == MailSendStatus.SERVER_LOGIN_FAIL || status == MailSendStatus.FAILED
|
|
|
|
|
- || status == MailSendStatus.SERVER_EXCEPTION_550) {
|
|
|
|
|
- // 状态已由 MktMailSender 回调回写
|
|
|
|
|
- }
|
|
|
|
|
- return latest;
|
|
|
|
|
- }
|
|
|
|
|
- return entity;
|
|
|
|
|
- }catch (Exception e) {
|
|
|
|
|
- log.error("send mail error ----", e);
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ entity.setStatus(STATUS_SENDING);
|
|
|
|
|
+ entity.setDelaySendTime(null);
|
|
|
|
|
+ this.saveOrUpdate(entity);
|
|
|
|
|
+ mktMailStatusService.ensureStatus(entity);
|
|
|
|
|
+ bindTempAttachments(entity.getId(), dto.getFileIds(), operatorId);
|
|
|
|
|
+ copyFormalToTemp(entity.getId(), dto.getOldFileIds(), operatorId);
|
|
|
|
|
+
|
|
|
|
|
+ SmtpPopSettingsEntity decrypted = decryptAccount(account);
|
|
|
|
|
+ boolean async = Boolean.TRUE.equals(dto.getAsync());
|
|
|
|
|
+ MailSendStatus status = dispatchSmtpSend(entity.getId(), decrypted, async, entity.getContent());
|
|
|
|
|
+ MktMailMessageEntity latest = this.getById(entity.getId());
|
|
|
|
|
+ if (latest == null) {
|
|
|
|
|
+ latest = entity;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (async) {
|
|
|
|
|
+ return toSendResult(null, latest);
|
|
|
}
|
|
}
|
|
|
|
|
+ return toSendResult(status, latest);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 主收件人在黑名单则抛出,不发送;抄送、密送中的黑名单地址去掉,不投递到这些邮箱。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param persistStrip 延时任务已落库时,把剔除后的抄送/密送写回
|
|
|
|
|
+ */
|
|
|
|
|
+ private void applyRecipientBlacklist(MktMailMessageEntity entity, boolean persistStrip) {
|
|
|
|
|
+ List<String> to = new ArrayList<>();
|
|
|
|
|
+ to.add(entity.getRecipient());
|
|
|
|
|
+ mktMailBlacklistService.assertRecipientsNotBlacklisted(to);
|
|
|
|
|
+ String cc = emptyIfNull(mktMailBlacklistService.retainDeliverable(entity.getRecipientCc()));
|
|
|
|
|
+ String bcc = emptyIfNull(mktMailBlacklistService.retainDeliverable(entity.getRecipientBcc()));
|
|
|
|
|
+ String oldCc = emptyIfNull(entity.getRecipientCc());
|
|
|
|
|
+ String oldBcc = emptyIfNull(entity.getRecipientBcc());
|
|
|
|
|
+ entity.setRecipientCc(StringUtils.hasText(cc) ? cc : "");
|
|
|
|
|
+ entity.setRecipientBcc(StringUtils.hasText(bcc) ? bcc : "");
|
|
|
|
|
+ if (!persistStrip || entity.getId() == null || (cc.equals(oldCc) && bcc.equals(oldBcc))) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ LambdaUpdateWrapper<MktMailMessageEntity> update = new LambdaUpdateWrapper<>();
|
|
|
|
|
+ update.eq(MktMailMessageEntity::getId, entity.getId());
|
|
|
|
|
+ update.set(MktMailMessageEntity::getRecipientCc, cc);
|
|
|
|
|
+ update.set(MktMailMessageEntity::getRecipientBcc, bcc);
|
|
|
|
|
+ this.update(update);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static String emptyIfNull(String value) {
|
|
|
|
|
+ return value == null ? "" : value;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private MktSendMailResult toSendResult(MailSendStatus status, MktMailMessageEntity mail) {
|
|
|
|
|
+ MktSendMailResult result = new MktSendMailResult();
|
|
|
|
|
+ result.setSendStatus(status);
|
|
|
|
|
+ result.setMail(mail);
|
|
|
|
|
+ return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -252,11 +288,22 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
try {
|
|
try {
|
|
|
|
|
+ applyRecipientBlacklist(entity, true);
|
|
|
|
|
+
|
|
|
SmtpPopSettingsEntity account = mailAccountBizService.requireAccount(entity.getAccountId(), MailSceneEnum.MKT);
|
|
SmtpPopSettingsEntity account = mailAccountBizService.requireAccount(entity.getAccountId(), MailSceneEnum.MKT);
|
|
|
- MailSendStatus status = dispatchSmtpSend(entity.getId(), decryptAccount(account), false);
|
|
|
|
|
|
|
+ MailSendStatus status = dispatchSmtpSend(entity.getId(), decryptAccount(account), false, null);
|
|
|
if (status == MailSendStatus.SUCCESS || status == MailSendStatus.PARTIAL_SUCCESS) {
|
|
if (status == MailSendStatus.SUCCESS || status == MailSendStatus.PARTIAL_SUCCESS) {
|
|
|
success++;
|
|
success++;
|
|
|
}
|
|
}
|
|
|
|
|
+ } catch (WebException bex) {
|
|
|
|
|
+ log.warn("mkt delay send blocked by blacklist, id={}, msg={}", item.getId(), bex.getMessage());
|
|
|
|
|
+ LambdaUpdateWrapper<MktMailMessageEntity> fail = new LambdaUpdateWrapper<>();
|
|
|
|
|
+ fail.eq(MktMailMessageEntity::getId, item.getId());
|
|
|
|
|
+ fail.eq(MktMailMessageEntity::getStatus, STATUS_SENDING);
|
|
|
|
|
+ fail.set(MktMailMessageEntity::getStatus, STATUS_FAIL);
|
|
|
|
|
+ fail.set(MktMailMessageEntity::getFailReason, bex.getMessage());
|
|
|
|
|
+ this.update(fail);
|
|
|
|
|
+ mktMailStatusService.markSendStatus(item.getId(), STATUS_FAIL);
|
|
|
} catch (Exception ex) {
|
|
} catch (Exception ex) {
|
|
|
log.error("mkt delay send fail, id={}", item.getId(), ex);
|
|
log.error("mkt delay send fail, id={}", item.getId(), ex);
|
|
|
LambdaUpdateWrapper<MktMailMessageEntity> fail = new LambdaUpdateWrapper<>();
|
|
LambdaUpdateWrapper<MktMailMessageEntity> fail = new LambdaUpdateWrapper<>();
|
|
@@ -412,11 +459,9 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 对齐 CRM:Session + Transport + MktMailSender(TransportListener 回调)。
|
|
|
|
|
- *
|
|
|
|
|
- * @param async true 时事务提交后异步投递并立即返回;false 同步等待结果
|
|
|
|
|
|
|
+ * @param contentToSend 本次发出的正文;为空则使用库内已有正文(延时任务)
|
|
|
*/
|
|
*/
|
|
|
- private MailSendStatus dispatchSmtpSend(Long mailId, SmtpPopSettingsEntity account, boolean async) {
|
|
|
|
|
|
|
+ private MailSendStatus dispatchSmtpSend(Long mailId, SmtpPopSettingsEntity account, boolean async, String contentToSend) {
|
|
|
if (async) {
|
|
if (async) {
|
|
|
Runnable task = () -> {
|
|
Runnable task = () -> {
|
|
|
try {
|
|
try {
|
|
@@ -424,6 +469,9 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
if (entity == null || !Integer.valueOf(STATUS_SENDING).equals(entity.getStatus())) {
|
|
if (entity == null || !Integer.valueOf(STATUS_SENDING).equals(entity.getStatus())) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (contentToSend != null) {
|
|
|
|
|
+ entity.setContent(contentToSend);
|
|
|
|
|
+ }
|
|
|
Session session = MailTransportSupport.createSmtpSession(account);
|
|
Session session = MailTransportSupport.createSmtpSession(account);
|
|
|
Transport transport = session.getTransport("smtp");
|
|
Transport transport = session.getTransport("smtp");
|
|
|
MimeMessage message = buildMimeMessage(session, entity, account);
|
|
MimeMessage message = buildMimeMessage(session, entity, account);
|
|
@@ -443,7 +491,7 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
} else {
|
|
} else {
|
|
|
task.run();
|
|
task.run();
|
|
|
}
|
|
}
|
|
|
- return MailSendStatus.SUCCESS;
|
|
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
@@ -451,6 +499,9 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
if (entity == null) {
|
|
if (entity == null) {
|
|
|
return MailSendStatus.FAILED;
|
|
return MailSendStatus.FAILED;
|
|
|
}
|
|
}
|
|
|
|
|
+ if (contentToSend != null) {
|
|
|
|
|
+ entity.setContent(contentToSend);
|
|
|
|
|
+ }
|
|
|
Session session = MailTransportSupport.createSmtpSession(account);
|
|
Session session = MailTransportSupport.createSmtpSession(account);
|
|
|
Transport transport = session.getTransport("smtp");
|
|
Transport transport = session.getTransport("smtp");
|
|
|
MimeMessage message = buildMimeMessage(session, entity, account);
|
|
MimeMessage message = buildMimeMessage(session, entity, account);
|
|
@@ -476,12 +527,25 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
private MimeMessage buildMimeMessage(Session session,
|
|
private MimeMessage buildMimeMessage(Session session,
|
|
|
MktMailMessageEntity entity,
|
|
MktMailMessageEntity entity,
|
|
|
SmtpPopSettingsEntity account) throws Exception {
|
|
SmtpPopSettingsEntity account) throws Exception {
|
|
|
- // 定时任务/直接插库场景:发送前补齐 track_token,并保证状态行存在
|
|
|
|
|
|
|
+ // 定时任务/直接插库场景:发送前补齐 track_token(回复绑定与打开追踪共用),并保证状态行存在
|
|
|
prepareTrackBeforeSend(entity);
|
|
prepareTrackBeforeSend(entity);
|
|
|
// 开启追踪时必须能注入像素(避免只有 token、发出去却没有像素)
|
|
// 开启追踪时必须能注入像素(避免只有 token、发出去却没有像素)
|
|
|
assertTrackReady(entity);
|
|
assertTrackReady(entity);
|
|
|
|
|
|
|
|
- MimeMessage message = new MimeMessage(session);
|
|
|
|
|
|
|
+ MimeMessage message = new MimeMessage(session) {
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected void updateMessageID() throws MessagingException {
|
|
|
|
|
+ if (entity.getId() != null && StringUtils.hasText(entity.getTrackToken())) {
|
|
|
|
|
+ String mid = StringUtils.hasText(entity.getMessageId())
|
|
|
|
|
+ ? entity.getMessageId()
|
|
|
|
|
+ : MktMailHeaders.buildMessageId(entity.getId(), entity.getTrackToken(),
|
|
|
|
|
+ account.getEmailAddress());
|
|
|
|
|
+ setHeader("Message-ID", mid);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ super.updateMessageID();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
String fromName = StringUtils.hasText(account.getSenderNickName())
|
|
String fromName = StringUtils.hasText(account.getSenderNickName())
|
|
|
? account.getSenderNickName() : account.getEmailAddress();
|
|
? account.getSenderNickName() : account.getEmailAddress();
|
|
|
message.setFrom(new InternetAddress(account.getEmailAddress(), fromName, "UTF-8"));
|
|
message.setFrom(new InternetAddress(account.getEmailAddress(), fromName, "UTF-8"));
|
|
@@ -498,16 +562,11 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
BodyPart htmlPart = new MimeBodyPart();
|
|
BodyPart htmlPart = new MimeBodyPart();
|
|
|
List<MimeBodyPart> imageParts = new ArrayList<>();
|
|
List<MimeBodyPart> imageParts = new ArrayList<>();
|
|
|
String originalHtml = entity.getContent() == null ? "" : entity.getContent();
|
|
String originalHtml = entity.getContent() == null ? "" : entity.getContent();
|
|
|
- String html = injectTrackPixel(originalHtml, entity);
|
|
|
|
|
- // 像素写入 content 字段,便于库内核对;CID 转换只作用于发出去的 MIME
|
|
|
|
|
- if (entity.getId() != null && !Objects.equals(originalHtml, html)) {
|
|
|
|
|
- entity.setContent(html);
|
|
|
|
|
- LambdaUpdateWrapper<MktMailMessageEntity> contentUpdate = new LambdaUpdateWrapper<>();
|
|
|
|
|
- contentUpdate.eq(MktMailMessageEntity::getId, entity.getId());
|
|
|
|
|
- contentUpdate.set(MktMailMessageEntity::getContent, html);
|
|
|
|
|
- this.update(contentUpdate);
|
|
|
|
|
- log.info("mkt persist track pixel into content, mailId={}", entity.getId());
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 无效果 div.class=token,测回复引用是否保留;另注入打开追踪像素
|
|
|
|
|
+ String withTokenDiv = MktMailHeaders.injectTokenClassDiv(originalHtml, entity.getTrackToken());
|
|
|
|
|
+ String html = injectTrackPixel(withTokenDiv, entity);
|
|
|
|
|
+ // 追踪像素写入内存中的 MIME;投递成功后由回调覆盖库内正文,失败不改正文
|
|
|
|
|
+ entity.setContent(html);
|
|
|
String updatedHtml = EmailHelper.convertImageUrlsToCidBodyPart(
|
|
String updatedHtml = EmailHelper.convertImageUrlsToCidBodyPart(
|
|
|
html, imageParts, mailFileProperties.getPath().getJpeg(), multipart);
|
|
html, imageParts, mailFileProperties.getPath().getJpeg(), multipart);
|
|
|
if (updatedHtml == null) {
|
|
if (updatedHtml == null) {
|
|
@@ -523,9 +582,18 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
message.setContent(multipart);
|
|
message.setContent(multipart);
|
|
|
- // 携带本地主键,收信从服务器拉回时按此头更新原记录,不新增
|
|
|
|
|
|
|
+ // 携带本地主键,收信从服务器拉回时按此头更新原记录,不新增(客户回复通常不会带回)
|
|
|
if (entity.getId() != null) {
|
|
if (entity.getId() != null) {
|
|
|
message.setHeader(MktMailHeaders.LOCAL_MAIL_ID, String.valueOf(entity.getId()));
|
|
message.setHeader(MktMailHeaders.LOCAL_MAIL_ID, String.valueOf(entity.getId()));
|
|
|
|
|
+ if (!StringUtils.hasText(entity.getMessageId()) && StringUtils.hasText(entity.getTrackToken())) {
|
|
|
|
|
+ String mid = MktMailHeaders.buildMessageId(entity.getId(), entity.getTrackToken(),
|
|
|
|
|
+ account.getEmailAddress());
|
|
|
|
|
+ entity.setMessageId(mid);
|
|
|
|
|
+ LambdaUpdateWrapper<MktMailMessageEntity> midUpdate = new LambdaUpdateWrapper<>();
|
|
|
|
|
+ midUpdate.eq(MktMailMessageEntity::getId, entity.getId());
|
|
|
|
|
+ midUpdate.set(MktMailMessageEntity::getMessageId, mid);
|
|
|
|
|
+ this.update(midUpdate);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
message.saveChanges();
|
|
message.saveChanges();
|
|
|
return message;
|
|
return message;
|
|
@@ -551,19 +619,18 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 实际 SMTP 发送前补齐追踪:支持定时任务 / 第三方直接插库。
|
|
|
|
|
- * is_track=1 且无 track_token 时生成并回写库;同步 ensure 状态行。
|
|
|
|
|
|
|
+ * 实际 SMTP 发送前:保证有 track_token(回复绑定 Message-ID 与打开像素共用);
|
|
|
|
|
+ * is_track=1 时才注入像素。同步 ensure 状态行。
|
|
|
*/
|
|
*/
|
|
|
private void prepareTrackBeforeSend(MktMailMessageEntity entity) {
|
|
private void prepareTrackBeforeSend(MktMailMessageEntity entity) {
|
|
|
if (entity == null || entity.getId() == null) {
|
|
if (entity == null || entity.getId() == null) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
- if (isTrackOn(entity) && !StringUtils.hasText(entity.getTrackToken())) {
|
|
|
|
|
|
|
+ if (!StringUtils.hasText(entity.getTrackToken())) {
|
|
|
String token = UUID.randomUUID().toString().replace("-", "");
|
|
String token = UUID.randomUUID().toString().replace("-", "");
|
|
|
entity.setTrackToken(token);
|
|
entity.setTrackToken(token);
|
|
|
LambdaUpdateWrapper<MktMailMessageEntity> update = new LambdaUpdateWrapper<>();
|
|
LambdaUpdateWrapper<MktMailMessageEntity> update = new LambdaUpdateWrapper<>();
|
|
|
update.eq(MktMailMessageEntity::getId, entity.getId());
|
|
update.eq(MktMailMessageEntity::getId, entity.getId());
|
|
|
- update.set(MktMailMessageEntity::getIsTrack, 1);
|
|
|
|
|
update.set(MktMailMessageEntity::getTrackToken, token);
|
|
update.set(MktMailMessageEntity::getTrackToken, token);
|
|
|
this.update(update);
|
|
this.update(update);
|
|
|
log.info("mkt prepare track_token before send, mailId={}", entity.getId());
|
|
log.info("mkt prepare track_token before send, mailId={}", entity.getId());
|
|
@@ -748,12 +815,10 @@ public class MktMailMessageServiceImpl extends MyBaseServiceImpl<MktMailMessageM
|
|
|
private void ensureTrackToken(MktMailMessageEntity entity, Integer isTrack) {
|
|
private void ensureTrackToken(MktMailMessageEntity entity, Integer isTrack) {
|
|
|
int track = isTrack == null ? (entity.getIsTrack() == null ? 0 : entity.getIsTrack()) : isTrack;
|
|
int track = isTrack == null ? (entity.getIsTrack() == null ? 0 : entity.getIsTrack()) : isTrack;
|
|
|
entity.setIsTrack(track);
|
|
entity.setIsTrack(track);
|
|
|
- if (track == 1 && !StringUtils.hasText(entity.getTrackToken())) {
|
|
|
|
|
|
|
+ // track_token 始终保留:打开像素与回复绑定 Message-ID 共用;像素仅在 is_track=1 时注入
|
|
|
|
|
+ if (!StringUtils.hasText(entity.getTrackToken())) {
|
|
|
entity.setTrackToken(UUID.randomUUID().toString().replace("-", ""));
|
|
entity.setTrackToken(UUID.randomUUID().toString().replace("-", ""));
|
|
|
}
|
|
}
|
|
|
- if (track != 1) {
|
|
|
|
|
- entity.setTrackToken(null);
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private SmtpPopSettingsEntity decryptAccount(SmtpPopSettingsEntity account) {
|
|
private SmtpPopSettingsEntity decryptAccount(SmtpPopSettingsEntity account) {
|