From d6183594d65be80d929c25d2ddfe227feabdee21 Mon Sep 17 00:00:00 2001 From: "Yuyao Huang (Sam)" Date: Sun, 29 Mar 2026 07:10:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(handler):=20=E6=B7=BB=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8E=BB=E9=87=8D=E5=8A=9F=E8=83=BD=E9=98=B2=E6=AD=A2?= =?UTF-8?q?=E9=A3=9E=E4=B9=A6=E9=87=8D=E5=A4=8D=E6=8A=95=E9=80=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现基于(user_id, content)的消息去重机制,避免飞书在网络抖动时重复投递相同消息。使用10秒时间窗口判断重复消息,超过窗口的旧记录会被自动清理。 --- bot/handler.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/bot/handler.py b/bot/handler.py index cc99990..60701a3 100644 --- a/bot/handler.py +++ b/bot/handler.py @@ -25,6 +25,26 @@ _ws_connected: bool = False _last_message_time: float = 0.0 _reconnect_count: int = 0 +# Deduplication: drop Feishu re-deliveries by (user_id, content) within a short window. +# Feishu retries on network hiccups within ~60s using the same payload. +# We use a 10s window: identical content from the same user within 10s is a re-delivery, +# not a deliberate repeat (user intentional repeats arrive after the bot has already replied). +_recent_messages: dict[tuple[str, str], float] = {} # key: (user_id, content) → timestamp +_DEDUP_WINDOW = 10.0 # seconds + + +def _is_duplicate(user_id: str, content: str) -> bool: + """Return True if this (user, content) pair arrived within the dedup window.""" + now = time.time() + expired = [k for k, ts in _recent_messages.items() if now - ts > _DEDUP_WINDOW] + for k in expired: + del _recent_messages[k] + key = (user_id, content) + if key in _recent_messages: + return True + _recent_messages[key] = now + return False + def get_ws_status() -> dict[str, Any]: """Return WebSocket connection status.""" @@ -79,10 +99,14 @@ def _handle_message(data: P2ImMessageReceiveV1) -> None: logger.info("Empty text after stripping, ignoring") return - logger.info("✉ ...%s → %r", open_id[-8:], text[:80]) - user_id = open_id or chat_id + if _is_duplicate(user_id, text): + logger.info("Dropping duplicate delivery: user=...%s text=%r", user_id[-8:], text[:60]) + return + + logger.info("✉ ...%s → %r", open_id[-8:], text[:80]) + if _main_loop is None: logger.error("Main event loop not set; cannot process message") return