修正邮件接收时间和发件人展示

This commit is contained in:
andy
2026-07-10 17:36:56 +08:00
parent 4745e20b9a
commit e76cb80f28
25 changed files with 226 additions and 59 deletions

View File

@@ -20,6 +20,7 @@ public record SourceMessageInboxDraft(
boolean duplicatePayloadChanged,
String safeErrorSummary,
LocalDateTime receivedAt,
LocalDateTime capturedAt,
LocalDateTime sourceSentAt,
String senderSummary,
String subject,

View File

@@ -14,6 +14,7 @@ public record CaptureSourceMessageCommand(
String externalConversationId,
String providerFrameId,
String providerSessionId,
Instant sourceReceivedAt,
Instant sourceSentAt,
String senderIdentifier,
String subject,
@@ -23,4 +24,38 @@ public record CaptureSourceMessageCommand(
String schemaVersion,
List<CaptureSourceMessageMedia> mediaItems
) {
public CaptureSourceMessageCommand(
String hotelId,
String provider,
String channel,
String externalMessageId,
String externalConversationId,
String providerFrameId,
String providerSessionId,
Instant sourceSentAt,
String senderIdentifier,
String subject,
String textBody,
String htmlBody,
String payloadJson,
String schemaVersion,
List<CaptureSourceMessageMedia> mediaItems) {
this(
hotelId,
provider,
channel,
externalMessageId,
externalConversationId,
providerFrameId,
providerSessionId,
null,
sourceSentAt,
senderIdentifier,
subject,
textBody,
htmlBody,
payloadJson,
schemaVersion,
mediaItems);
}
}

View File

@@ -10,9 +10,9 @@ import java.util.List;
* @param id 内部 SourceMessage Inbox ID
* @param externalMessageId 外部单封邮件 ID
* @param externalConversationId 外部邮件会话 ID
* @param senderSummary 发送人安全摘要
* @param senderSummary 发件人展示值,当前不打码
* @param subject 邮件主题摘要
* @param receivedAt 本系统接收时间UTC
* @param receivedAt 邮件来源接收时间UTC;缺失时回退本系统接收时间
* @param sourceSentAt 邮件来源发送时间UTC
* @param textBody 完整纯文本正文
* @param htmlBody 完整 HTML 正文,前端展示前必须 sanitize

View File

@@ -36,11 +36,11 @@ public class SourceMessageInboxEntity {
private Boolean duplicatePayloadChanged;
/** 安全错误摘要,不包含正文、附件 URL、Token 或个人敏感信息。 */
private String safeErrorSummary;
/** 本项目接收消息的 UTC 时间。 */
/** 邮件来源接收 UTC 时间,缺失时使用本项目接收消息的 UTC 时间。 */
private LocalDateTime receivedAt;
/** 来源系统提供的邮件发送 UTC 时间,缺失时为空。 */
private LocalDateTime sourceSentAt;
/** 发送人安全摘要,普通查询可展示。 */
/** 发件人展示值,业务人员需要识别真实发件人,当前只清理首尾空白不打码。 */
private String senderSummary;
/** 脱敏后的邮件主题,普通查询可展示。 */
private String subject;

View File

@@ -244,7 +244,7 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
insertPayload(draft, inbox.getId());
if (SourceMessageCaptureStatus.RECEIVED.code().equals(draft.captureStatus())) {
insertBody(draft, inbox.getId());
insertMedia(draft.mediaItems(), inbox.getId(), draft.receivedAt());
insertMedia(draft.mediaItems(), inbox.getId(), draft.capturedAt());
}
return inbox.getId();
}
@@ -340,8 +340,8 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
inbox.setSenderSummary(draft.senderSummary());
inbox.setSubject(draft.subject());
inbox.setSafeSnippet(draft.safeSnippet());
inbox.setCreatedAt(draft.receivedAt());
inbox.setUpdatedAt(draft.receivedAt());
inbox.setCreatedAt(draft.capturedAt());
inbox.setUpdatedAt(draft.capturedAt());
return inbox;
}
@@ -354,7 +354,7 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
payload.setPayloadJson(draft.payloadJson());
payload.setPayloadSha256(draft.payloadSha256());
payload.setSchemaVersion(draft.schemaVersion());
payload.setCreatedAt(draft.receivedAt());
payload.setCreatedAt(draft.capturedAt());
payloadMapper.insert(payload);
}
@@ -368,7 +368,7 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
body.setTextBody(draft.textBody());
body.setHtmlBody(draft.htmlBody());
body.setBodySha256(sha256(draft.textBody() + "\n" + draft.htmlBody()));
body.setCreatedAt(draft.receivedAt());
body.setCreatedAt(draft.capturedAt());
bodyMapper.insert(body);
}

View File

@@ -213,7 +213,7 @@ public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureServ
}
/**
* 构建入库草稿,统一生成安全主题、发送人摘要和正文摘要。
* 构建入库草稿,统一生成安全主题、发件人展示值和正文摘要。
*/
private SourceMessageInboxDraft buildDraft(
CaptureSourceMessageCommand command,
@@ -224,7 +224,9 @@ public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureServ
String payloadSha256,
SourceMessageCaptureStatus captureStatus,
String safeErrorSummary) {
LocalDateTime now = nowUtc();
LocalDateTime capturedAt = nowUtc();
LocalDateTime sourceReceivedAt = toLocalDateTime(command.sourceReceivedAt());
LocalDateTime receivedAt = sourceReceivedAt == null ? capturedAt : sourceReceivedAt;
String textBody = command.textBody() == null ? "" : command.textBody();
String htmlBody = command.htmlBody() == null ? "" : command.htmlBody();
// 邮件主题也可能携带联系方式或带 token 的 URL普通查询只保存可展示摘要。
@@ -244,7 +246,8 @@ public class SourceMessageCaptureServiceImpl implements SourceMessageCaptureServ
captureStatus.code(),
false,
safeErrorSummary,
now,
receivedAt,
capturedAt,
toLocalDateTime(command.sourceSentAt()),
safetySanitizer.senderSummary(command.senderIdentifier()),
safeSubject,

View File

@@ -1,11 +1,10 @@
package cn.nianxx.thhotel.platform.message.service.impl;
import java.util.Locale;
import java.util.regex.Pattern;
import org.springframework.stereotype.Component;
/**
* 来源消息安全摘要工具。列表和普通详情只能使用脱敏后的摘要,不能透出原文或媒体 URL
* 来源消息安全摘要工具。正文和主题仍做安全摘要;发件人展示值只清理首尾空白
*/
@Component
public class SourceMessageSafetySanitizer {
@@ -18,21 +17,10 @@ public class SourceMessageSafetySanitizer {
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
/**
* 生成发送人安全摘要,避免普通列表直接暴露完整邮箱或客户标识
* 生成发送人展示值。业务人员需要识别邮件真实发件人,因此这里只清理首尾空白,不再打码
*/
String senderSummary(String senderIdentifier) {
String value = trimToEmpty(senderIdentifier);
if (value.isEmpty()) {
return "";
}
int atIndex = value.indexOf('@');
if (atIndex > 0 && atIndex < value.length() - 1) {
return value.charAt(0) + "***" + value.substring(atIndex).toLowerCase(Locale.ROOT);
}
if (value.length() <= 2) {
return "*";
}
return value.charAt(0) + "***" + value.charAt(value.length() - 1);
return trimToEmpty(senderIdentifier);
}
/**