diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java b/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java index bbe12e6..1d16403 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/WecomCallbackController.java @@ -13,15 +13,21 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + @RestController @RequestMapping("/v1/integrations/wecom") public class WecomCallbackController { private static final Logger log = LoggerFactory.getLogger(WecomCallbackController.class); + private static final long MESSAGE_DEDUP_TTL_SECONDS = 600; private final FoundationProperties properties; private final WecomClient wecomClient; private final WecomCryptoService wecomCryptoService; private final AgentOrchestratorService agentOrchestratorService; private final XmlMapper xmlMapper = new XmlMapper(); + private final Map processedMessages = new ConcurrentHashMap<>(); public WecomCallbackController( FoundationProperties properties, @@ -79,6 +85,11 @@ public class WecomCallbackController { return "success"; } + if (isDuplicateMessage(msg)) { + log.info("wecom callback duplicate message ignored fromUser={} msgId={}", msg.getFromUserName(), msg.getMsgId()); + return "success"; + } + log.info("wecom callback invoking agent orchestrator agentId={} fromUser={}", properties.getWecom().getCrmAgentId(), msg.getFromUserName()); String reply = agentOrchestratorService.handleMessage(properties.getWecom().getCrmAgentId(), msg.getFromUserName(), userText); reply = (reply == null || reply.isBlank()) @@ -113,6 +124,30 @@ public class WecomCallbackController { return xmlMapper.readValue(body, WecomIncomingMessage.class); } + private boolean isDuplicateMessage(WecomIncomingMessage msg) { + evictExpiredMessages(); + String key = messageDedupKey(msg); + Long previous = processedMessages.putIfAbsent(key, Instant.now().getEpochSecond()); + return previous != null; + } + + private String messageDedupKey(WecomIncomingMessage msg) { + if (msg.getMsgId() != null && !msg.getMsgId().isBlank()) { + return msg.getMsgId(); + } + return "%s:%s:%s:%s".formatted( + msg.getFromUserName(), + msg.getToUserName(), + msg.getCreateTime(), + msg.getContent() + ); + } + + private void evictExpiredMessages() { + long expireBefore = Instant.now().minusSeconds(MESSAGE_DEDUP_TTL_SECONDS).getEpochSecond(); + processedMessages.entrySet().removeIf(entry -> entry.getValue() < expireBefore); + } + private String preview(String text) { if (text == null || text.isBlank()) { return ""; diff --git a/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java b/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java index baa4fbf..ae8d665 100644 --- a/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java +++ b/src/main/java/com/company/agentfoundation/integration/wecom/WecomIncomingMessage.java @@ -15,6 +15,10 @@ public class WecomIncomingMessage { private String msgType; @JacksonXmlProperty(localName = "Content") private String content; + @JacksonXmlProperty(localName = "CreateTime") + private Long createTime; + @JacksonXmlProperty(localName = "MsgId") + private String msgId; public String getToUserName() { return toUserName; } public void setToUserName(String toUserName) { this.toUserName = toUserName; } @@ -24,4 +28,8 @@ public class WecomIncomingMessage { public void setMsgType(String msgType) { this.msgType = msgType; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } + public Long getCreateTime() { return createTime; } + public void setCreateTime(Long createTime) { this.createTime = createTime; } + public String getMsgId() { return msgId; } + public void setMsgId(String msgId) { this.msgId = msgId; } }