ForcePilot/backend/package/yuxi/channels/adapters/mattermost/debounce.py

46 lines
1.5 KiB
Python
Raw Normal View History

from __future__ import annotations
import hashlib
import time
class InboundDebouncer:
"""入站消息防抖 — 按 channel + thread key 去重。
防止 Mattermost WebSocket 重复推送导致的多次触发
"""
def __init__(self, ttl_ms: int = 1000, max_entries: int = 2000):
self._ttl_ms = ttl_ms
self._max_entries = max_entries
self._entries: dict[str, float] = {}
def should_process(self, channel_id: str, thread_id: str, content: str) -> bool:
key = self._build_key(channel_id, thread_id, content)
now = time.monotonic()
if key in self._entries:
if now - self._entries[key] < self._ttl_ms / 1000:
return False
self._entries[key] = now
self._prune(now)
return True
def _build_key(self, channel_id: str, thread_id: str, content: str) -> str:
raw = f"{channel_id}:{thread_id}:{content[:100]}"
return hashlib.md5(raw.encode()).hexdigest()
def _prune(self, now: float) -> None:
if len(self._entries) > self._max_entries:
cutoff = now - self._ttl_ms / 1000
self._entries = {k: v for k, v in self._entries.items() if v > cutoff}
if len(self._entries) > self._max_entries:
overflow = len(self._entries) - self._max_entries
keys = sorted(self._entries, key=lambda k: self._entries[k])[:overflow]
for k in keys:
del self._entries[k]
def clear(self) -> None:
self._entries.clear()