"""message_id 幂等去重:基于 OrderedDict 实现 LRU + 容量上限。 飞书在网络抖动时会重发事件推送,不能依赖 event_id,必须用 message_id 本地去重。 """ from __future__ import annotations import logging import threading from collections import OrderedDict from typing import Optional logger = logging.getLogger(__name__) class MessageDedup: """线程安全的 LRU message_id 去重器。 飞书事件推送在网络抖动时会重发,必须用 message_id 在本地去重, 不能依赖 event_id(官方文档明确说明 event_id 不保证幂等)。 """ def __init__(self, max_size: int = 2000) -> None: if max_size <= 0: raise ValueError("max_size 必须为正数") self._max_size = max_size self._seen: OrderedDict[str, None] = OrderedDict() self._lock = threading.Lock() def check_and_mark(self, message_id: str) -> bool: """若 message_id 首次出现则记录并返回 True;已存在则返回 False(重复)。""" if not message_id: return False with self._lock: if message_id in self._seen: # 命中:移到末尾(LRU) self._seen.move_to_end(message_id) return False self._seen[message_id] = None if len(self._seen) > self._max_size: evicted_k, _ = self._seen.popitem(last=False) logger.debug("去重缓存淘汰最旧 message_id: %s", evicted_k) return True def __len__(self) -> int: with self._lock: return len(self._seen) def stats(self) -> dict: with self._lock: return {"size": len(self._seen), "max_size": self._max_size}