补齐前端P0查询接口
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package cn.nianxx.thhotel.platform.message.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 邮件会话中的单封邮件详情。该结果会返回完整 text/html 和媒体外链,只能用于受控会话详情场景。
|
||||
*
|
||||
* @param id 内部 SourceMessage Inbox ID
|
||||
* @param externalMessageId 外部单封邮件 ID
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param senderSummary 发送人安全摘要
|
||||
* @param subject 邮件主题摘要
|
||||
* @param receivedAt 本系统接收时间,UTC
|
||||
* @param sourceSentAt 邮件来源发送时间,UTC
|
||||
* @param textBody 完整纯文本正文
|
||||
* @param htmlBody 完整 HTML 正文,前端展示前必须 sanitize
|
||||
* @param htmlSanitizeRequired 是否要求前端 sanitize HTML
|
||||
* @param inlineImages 内联图片外链
|
||||
* @param attachments 附件外链
|
||||
* @param relatedOrders 关联订单摘要
|
||||
* @param relatedTasks 关联任务摘要
|
||||
*/
|
||||
public record SourceMessageConversationMessageResult(
|
||||
String id,
|
||||
@JsonProperty("external_message_id")
|
||||
String externalMessageId,
|
||||
@JsonProperty("external_conversation_id")
|
||||
String externalConversationId,
|
||||
@JsonProperty("sender_summary")
|
||||
String senderSummary,
|
||||
String subject,
|
||||
@JsonProperty("received_at")
|
||||
OffsetDateTime receivedAt,
|
||||
@JsonProperty("source_sent_at")
|
||||
OffsetDateTime sourceSentAt,
|
||||
@JsonProperty("text_body")
|
||||
String textBody,
|
||||
@JsonProperty("html_body")
|
||||
String htmlBody,
|
||||
@JsonProperty("html_sanitize_required")
|
||||
Boolean htmlSanitizeRequired,
|
||||
@JsonProperty("inline_images")
|
||||
List<SourceMessageOriginalMediaResponse> inlineImages,
|
||||
List<SourceMessageOriginalMediaResponse> attachments,
|
||||
@JsonProperty("related_orders")
|
||||
List<SourceMessageRelatedOrderResult> relatedOrders,
|
||||
@JsonProperty("related_tasks")
|
||||
List<SourceMessageRelatedTaskResult> relatedTasks
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.nianxx.thhotel.platform.message.common.result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SourceMessage 邮件会话详情结果。包含会话摘要和同会话所有邮件原文。
|
||||
*
|
||||
* @param conversation 会话摘要
|
||||
* @param messages 同一会话内全部邮件,按接收时间正序返回
|
||||
*/
|
||||
public record SourceMessageConversationResult(
|
||||
SourceMessageConversationSummaryResult conversation,
|
||||
List<SourceMessageConversationMessageResult> messages
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package cn.nianxx.thhotel.platform.message.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* 邮件会话摘要。用于前端在会话详情顶部展示来源链路信息。
|
||||
*
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param channel 来源渠道
|
||||
* @param externalConversationId 外部邮件会话 ID
|
||||
* @param subject 当前定位消息的主题摘要
|
||||
* @param messageCount 会话内邮件数量
|
||||
* @param firstReceivedAt 会话第一封入库时间,UTC
|
||||
* @param lastReceivedAt 会话最后一封入库时间,UTC
|
||||
*/
|
||||
public record SourceMessageConversationSummaryResult(
|
||||
@JsonProperty("hotel_id")
|
||||
String hotelId,
|
||||
String channel,
|
||||
@JsonProperty("external_conversation_id")
|
||||
String externalConversationId,
|
||||
String subject,
|
||||
@JsonProperty("message_count")
|
||||
Long messageCount,
|
||||
@JsonProperty("first_received_at")
|
||||
OffsetDateTime firstReceivedAt,
|
||||
@JsonProperty("last_received_at")
|
||||
OffsetDateTime lastReceivedAt
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package cn.nianxx.thhotel.platform.message.common.result;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* SourceMessage 关联业务上下文。由业务模块实现提供,平台会话服务只做聚合展示。
|
||||
*
|
||||
* @param relatedOrders 关联订单摘要列表
|
||||
* @param relatedTasks 关联任务摘要列表
|
||||
*/
|
||||
public record SourceMessageRelatedContextResult(
|
||||
List<SourceMessageRelatedOrderResult> relatedOrders,
|
||||
List<SourceMessageRelatedTaskResult> relatedTasks
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.nianxx.thhotel.platform.message.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* SourceMessage 会话中关联订单摘要。平台层只定义通用展示字段,具体来源由业务模块提供。
|
||||
*
|
||||
* @param orderId 订单 ID
|
||||
* @param displayOrderKey 前端展示用订单号或临时订单号
|
||||
* @param orderStatus 订单状态
|
||||
*/
|
||||
public record SourceMessageRelatedOrderResult(
|
||||
@JsonProperty("order_id")
|
||||
String orderId,
|
||||
@JsonProperty("display_order_key")
|
||||
String displayOrderKey,
|
||||
@JsonProperty("order_status")
|
||||
String orderStatus
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package cn.nianxx.thhotel.platform.message.common.result;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* SourceMessage 会话中关联任务摘要。用于前端从邮件原文跳转到任务详情。
|
||||
*
|
||||
* @param taskId 任务 ID
|
||||
* @param orderId 任务当前挂靠订单 ID
|
||||
* @param taskType 系统主任务类型
|
||||
* @param taskSubtype 任务 subtype
|
||||
* @param taskStatus 任务状态
|
||||
* @param cardName 任务卡名称
|
||||
*/
|
||||
public record SourceMessageRelatedTaskResult(
|
||||
@JsonProperty("task_id")
|
||||
String taskId,
|
||||
@JsonProperty("order_id")
|
||||
String orderId,
|
||||
@JsonProperty("task_type")
|
||||
String taskType,
|
||||
@JsonProperty("task_subtype")
|
||||
String taskSubtype,
|
||||
@JsonProperty("task_status")
|
||||
String taskStatus,
|
||||
@JsonProperty("card_name")
|
||||
String cardName
|
||||
) {
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package cn.nianxx.thhotel.platform.message.control;
|
||||
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageConversationResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageOriginalResponse;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageOriginalAccessRequest;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageConversationService;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageOriginalService;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryResponse;
|
||||
@@ -25,15 +27,18 @@ public class SourceMessageController {
|
||||
|
||||
private final SourceMessageQueryService queryService;
|
||||
private final SourceMessageOriginalService originalService;
|
||||
private final SourceMessageConversationService conversationService;
|
||||
|
||||
/**
|
||||
* 注入 SourceMessage 查询与原文读取服务,Controller 不直接访问 Mapper 或 Repository。
|
||||
*/
|
||||
public SourceMessageController(
|
||||
SourceMessageQueryService queryService,
|
||||
SourceMessageOriginalService originalService) {
|
||||
SourceMessageOriginalService originalService,
|
||||
SourceMessageConversationService conversationService) {
|
||||
this.queryService = queryService;
|
||||
this.originalService = originalService;
|
||||
this.conversationService = conversationService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,6 +90,15 @@ public class SourceMessageController {
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "source message not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定 SourceMessage 所在邮件会话完整详情。该接口由后端内部记录原文读取审计,前端不传原文 key。
|
||||
*/
|
||||
@GetMapping("/{id}/conversation")
|
||||
public SourceMessageConversationResult conversation(@PathVariable Long id) {
|
||||
return conversationService.getConversation(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "source message not found"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求头文本是否有效,避免空白调用方或场景进入审计记录。
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,9 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HexFormat;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Repository;
|
||||
@@ -90,6 +92,24 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询同一外部会话下的全部 SourceMessage 安全快照,按接收时间正序返回。
|
||||
*/
|
||||
@Override
|
||||
public List<SourceMessageInboxSnapshot> findByExternalConversationId(String hotelId, String externalConversationId) {
|
||||
if (!hasText(hotelId) || !hasText(externalConversationId)) {
|
||||
return List.of();
|
||||
}
|
||||
return inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
|
||||
.eq(SourceMessageInboxEntity::getHotelId, trim(hotelId))
|
||||
.eq(SourceMessageInboxEntity::getExternalConversationId, trim(externalConversationId))
|
||||
.orderByAsc(SourceMessageInboxEntity::getReceivedAt)
|
||||
.orderByAsc(SourceMessageInboxEntity::getId))
|
||||
.stream()
|
||||
.map(this::toSnapshot)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 SourceMessage 幂等键读取已有记录,用于重复投递判断。
|
||||
*/
|
||||
@@ -160,6 +180,30 @@ public class MybatisSourceMessageInboxRepository implements SourceMessageInboxRe
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量统计会话消息数。该方法只访问 Inbox 索引表,不读取正文或媒体 URL。
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Long> countByExternalConversationIds(String hotelId, List<String> externalConversationIds) {
|
||||
if (!hasText(hotelId) || externalConversationIds == null || externalConversationIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
List<String> safeConversationIds = externalConversationIds.stream()
|
||||
.filter(this::hasText)
|
||||
.map(this::trim)
|
||||
.distinct()
|
||||
.toList();
|
||||
if (safeConversationIds.isEmpty()) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<String, Long> counts = new LinkedHashMap<>();
|
||||
inboxMapper.selectList(Wrappers.<SourceMessageInboxEntity>lambdaQuery()
|
||||
.eq(SourceMessageInboxEntity::getHotelId, trim(hotelId))
|
||||
.in(SourceMessageInboxEntity::getExternalConversationId, safeConversationIds))
|
||||
.forEach(entity -> counts.merge(entity.getExternalConversationId(), 1L, Long::sum));
|
||||
return counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入 Inbox 主记录及原始 payload;RECEIVED 状态额外保存正文和媒体引用。
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -25,6 +26,11 @@ public interface SourceMessageInboxRepository {
|
||||
*/
|
||||
List<SourceMessageInboxSnapshot> findByIds(List<Long> ids);
|
||||
|
||||
/**
|
||||
* 按外部邮件会话 ID 查询同一会话全部 Inbox 安全快照。
|
||||
*/
|
||||
List<SourceMessageInboxSnapshot> findByExternalConversationId(String hotelId, String externalConversationId);
|
||||
|
||||
/**
|
||||
* 按酒店、来源、渠道、外部邮件 ID 查询幂等记录。
|
||||
*/
|
||||
@@ -47,6 +53,11 @@ public interface SourceMessageInboxRepository {
|
||||
*/
|
||||
List<Long> findIdsBySafeKeyword(String hotelId, String keyword, int limit);
|
||||
|
||||
/**
|
||||
* 批量统计外部邮件会话下的 SourceMessage 数量,用于任务和订单前端摘要。
|
||||
*/
|
||||
Map<String, Long> countByExternalConversationIds(String hotelId, List<String> externalConversationIds);
|
||||
|
||||
/**
|
||||
* 插入 Inbox 及其 payload/body/media 子记录,返回内部 SourceMessage ID。
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.nianxx.thhotel.platform.message.service;
|
||||
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageConversationResult;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* SourceMessage 邮件会话详情服务。该服务会返回完整正文、HTML 和媒体外链,并写入内部读取审计。
|
||||
*/
|
||||
public interface SourceMessageConversationService {
|
||||
|
||||
/**
|
||||
* 读取指定 SourceMessage 所在邮件会话的完整详情。
|
||||
*
|
||||
* @param sourceMessageId 当前定位的 SourceMessage Inbox ID
|
||||
* @return 找到时返回同一会话全部邮件详情;不存在时为空
|
||||
*/
|
||||
Optional<SourceMessageConversationResult> getConversation(Long sourceMessageId);
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageSummaryRespons
|
||||
import cn.nianxx.thhotel.platform.message.common.request.SourceMessageQueryRequest;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -43,4 +44,13 @@ public interface SourceMessageQueryService {
|
||||
* @return 匹配到的安全摘要列表,不返回正文、HTML、附件 URL 或 payload
|
||||
*/
|
||||
List<SourceMessageSummaryResponse> getSummariesByIds(List<Long> inboxIds);
|
||||
|
||||
/**
|
||||
* 批量统计外部邮件会话下的 SourceMessage 数量,供业务摘要字段展示。
|
||||
*
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param externalConversationIds 外部邮件会话 ID 列表
|
||||
* @return 会话 ID 到消息数量的映射
|
||||
*/
|
||||
Map<String, Long> countByExternalConversationIds(String hotelId, List<String> externalConversationIds);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package cn.nianxx.thhotel.platform.message.service;
|
||||
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageRelatedContextResult;
|
||||
|
||||
/**
|
||||
* SourceMessage 关联业务上下文 Provider。业务模块实现该接口,平台会话服务只做聚合展示。
|
||||
*/
|
||||
public interface SourceMessageRelatedContextProvider {
|
||||
|
||||
/**
|
||||
* 查询单封 SourceMessage 关联的业务摘要。
|
||||
*
|
||||
* @param hotelId 酒店上下文 ID
|
||||
* @param sourceMessageId SourceMessage Inbox ID
|
||||
* @return 关联订单和任务摘要;没有关联时返回空列表结果
|
||||
*/
|
||||
SourceMessageRelatedContextResult findRelatedContext(String hotelId, Long sourceMessageId);
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
package cn.nianxx.thhotel.platform.message.service.impl;
|
||||
|
||||
import cn.nianxx.thhotel.platform.common.time.UtcTimeFormatter;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageInboxSnapshot;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalAccessAuditDraft;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalContent;
|
||||
import cn.nianxx.thhotel.platform.message.common.dto.SourceMessageOriginalMediaItem;
|
||||
import cn.nianxx.thhotel.platform.message.common.enums.SourceMessageOriginalAccessResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageConversationMessageResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageConversationResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageConversationSummaryResult;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageOriginalMediaResponse;
|
||||
import cn.nianxx.thhotel.platform.message.common.result.SourceMessageRelatedContextResult;
|
||||
import cn.nianxx.thhotel.platform.message.repository.SourceMessageInboxRepository;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageConversationService;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageRelatedContextProvider;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* SourceMessage 邮件会话详情服务实现。该服务负责受控读取原文并写入后端内部审计。
|
||||
*/
|
||||
@Service
|
||||
public class SourceMessageConversationServiceImpl implements SourceMessageConversationService {
|
||||
|
||||
private static final String INTERNAL_ACTOR_ID = "system:source-message-conversation";
|
||||
private static final String ACCESS_SCENE = "source-message-conversation";
|
||||
private static final String MEDIA_TYPE_INLINE_IMAGE = "INLINE_IMAGE";
|
||||
private static final String MEDIA_TYPE_ATTACHMENT = "ATTACHMENT";
|
||||
|
||||
private final SourceMessageInboxRepository inboxRepository;
|
||||
private final List<SourceMessageRelatedContextProvider> relatedContextProviders;
|
||||
|
||||
/**
|
||||
* 注入 SourceMessage 持久化端口和业务关联上下文 Provider 列表。
|
||||
*/
|
||||
public SourceMessageConversationServiceImpl(
|
||||
SourceMessageInboxRepository inboxRepository,
|
||||
List<SourceMessageRelatedContextProvider> relatedContextProviders) {
|
||||
this.inboxRepository = inboxRepository;
|
||||
this.relatedContextProviders = relatedContextProviders == null ? List.of() : relatedContextProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定 SourceMessage 所在会话的完整原文链路,并为每封邮件写入内部读取审计。
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
public Optional<SourceMessageConversationResult> getConversation(Long sourceMessageId) {
|
||||
Optional<SourceMessageInboxSnapshot> sourceOptional = inboxRepository.findById(sourceMessageId);
|
||||
if (sourceOptional.isEmpty()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
SourceMessageInboxSnapshot source = sourceOptional.get();
|
||||
List<SourceMessageInboxSnapshot> conversationMessages = findConversationMessages(source);
|
||||
SourceMessageConversationSummaryResult conversation = toConversationSummary(source, conversationMessages);
|
||||
List<SourceMessageConversationMessageResult> messages = conversationMessages.stream()
|
||||
.map(this::toConversationMessage)
|
||||
.toList();
|
||||
return Optional.of(new SourceMessageConversationResult(conversation, messages));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询同一外部会话邮件;外部会话 ID 缺失时降级为单封邮件详情。
|
||||
*/
|
||||
private List<SourceMessageInboxSnapshot> findConversationMessages(SourceMessageInboxSnapshot source) {
|
||||
if (source.externalConversationId() == null || source.externalConversationId().isBlank()) {
|
||||
return List.of(source);
|
||||
}
|
||||
List<SourceMessageInboxSnapshot> messages = inboxRepository.findByExternalConversationId(
|
||||
source.hotelId(),
|
||||
source.externalConversationId());
|
||||
return messages.isEmpty() ? List.of(source) : messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成会话顶部摘要,时间统一转为 UTC OffsetDateTime。
|
||||
*/
|
||||
private SourceMessageConversationSummaryResult toConversationSummary(
|
||||
SourceMessageInboxSnapshot source,
|
||||
List<SourceMessageInboxSnapshot> messages) {
|
||||
SourceMessageInboxSnapshot first = messages.get(0);
|
||||
SourceMessageInboxSnapshot last = messages.get(messages.size() - 1);
|
||||
return new SourceMessageConversationSummaryResult(
|
||||
source.hotelId(),
|
||||
source.channel(),
|
||||
source.externalConversationId(),
|
||||
source.subject(),
|
||||
(long) messages.size(),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(first.receivedAt()),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(last.receivedAt()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Inbox 快照转换为会话邮件详情,并附带原文、媒体外链和业务关联摘要。
|
||||
*/
|
||||
private SourceMessageConversationMessageResult toConversationMessage(SourceMessageInboxSnapshot message) {
|
||||
SourceMessageOriginalContent originalContent = readOriginalAndAudit(message);
|
||||
SourceMessageRelatedContextResult relatedContext = findRelatedContext(message);
|
||||
return new SourceMessageConversationMessageResult(
|
||||
message.id().toString(),
|
||||
message.externalMessageId(),
|
||||
message.externalConversationId(),
|
||||
message.senderSummary(),
|
||||
message.subject(),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(message.receivedAt()),
|
||||
UtcTimeFormatter.toUtcOffsetDateTime(message.sourceSentAt()),
|
||||
originalContent.textBody(),
|
||||
originalContent.htmlBody(),
|
||||
true,
|
||||
filterMedia(originalContent.mediaItems(), MEDIA_TYPE_INLINE_IMAGE),
|
||||
filterMedia(originalContent.mediaItems(), MEDIA_TYPE_ATTACHMENT),
|
||||
relatedContext.relatedOrders(),
|
||||
relatedContext.relatedTasks());
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取原文并写入内部审计。会话接口不依赖前端传原文读取 key。
|
||||
*/
|
||||
private SourceMessageOriginalContent readOriginalAndAudit(SourceMessageInboxSnapshot message) {
|
||||
SourceMessageOriginalContent content = inboxRepository.findOriginalContent(message.id())
|
||||
.orElse(new SourceMessageOriginalContent(message.id(), null, null, List.of()));
|
||||
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
||||
inboxRepository.insertOriginalAccessAudit(new SourceMessageOriginalAccessAuditDraft(
|
||||
message.id(),
|
||||
INTERNAL_ACTOR_ID,
|
||||
ACCESS_SCENE,
|
||||
SourceMessageOriginalAccessResult.GRANTED.code(),
|
||||
now));
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* 聚合所有业务模块提供的关联上下文。当前 reservation 模块会提供订单和任务摘要。
|
||||
*/
|
||||
private SourceMessageRelatedContextResult findRelatedContext(SourceMessageInboxSnapshot message) {
|
||||
List<SourceMessageRelatedContextResult> contexts = relatedContextProviders.stream()
|
||||
.map(provider -> provider.findRelatedContext(message.hotelId(), message.id()))
|
||||
.toList();
|
||||
return new SourceMessageRelatedContextResult(
|
||||
contexts.stream().flatMap(context -> context.relatedOrders().stream()).toList(),
|
||||
contexts.stream().flatMap(context -> context.relatedTasks().stream()).toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按媒体类型筛选并转换原文媒体引用。
|
||||
*/
|
||||
private List<SourceMessageOriginalMediaResponse> filterMedia(
|
||||
List<SourceMessageOriginalMediaItem> mediaItems,
|
||||
String mediaType) {
|
||||
if (mediaItems == null || mediaItems.isEmpty()) {
|
||||
return List.of();
|
||||
}
|
||||
return mediaItems.stream()
|
||||
.filter(item -> mediaType.equals(item.mediaType()))
|
||||
.map(this::toMediaResponse)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换媒体内部 DTO 为接口响应对象。
|
||||
*/
|
||||
private SourceMessageOriginalMediaResponse toMediaResponse(SourceMessageOriginalMediaItem item) {
|
||||
return new SourceMessageOriginalMediaResponse(
|
||||
item.mediaType(),
|
||||
item.fileName(),
|
||||
item.contentType(),
|
||||
item.sizeBytes(),
|
||||
item.externalUrl(),
|
||||
item.externalMediaId());
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import cn.nianxx.thhotel.platform.message.common.result.SourceMessagePageResult;
|
||||
import cn.nianxx.thhotel.platform.message.repository.SourceMessageInboxRepository;
|
||||
import cn.nianxx.thhotel.platform.message.service.SourceMessageQueryService;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -72,6 +73,14 @@ public class SourceMessageQueryServiceImpl implements SourceMessageQueryService
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量统计邮件会话消息数,只访问 SourceMessage Inbox 索引表。
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Long> countByExternalConversationIds(String hotelId, List<String> externalConversationIds) {
|
||||
return inboxRepository.countByExternalConversationIds(hotelId, externalConversationIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化页码,缺失或非法页码统一回到第一页。
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user