新增 Zalo OA 官方账号完整集成能力,包含: 1. 基础通信能力:消息编解码、目标归一化、文本分块 2. 安全与校验:Webhook 签名验证、DM 策略管理、配对流程 3. 辅助工具:重复事件去重、请求限流、异常告警 4. 管理功能:账号多实例管理、配置验证、健康诊断 5. 扩展能力:媒体托管、视觉识别、TTS 语音合成 6. 运维支持:审计日志、状态监控、目录同步
65 lines
2.0 KiB
Python
65 lines
2.0 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
from enum import Enum, auto
|
|
from typing import Any
|
|
|
|
DEDUP_WINDOW_MS_DEFAULT = 300000
|
|
|
|
|
|
class EventState(Enum):
|
|
CLAIMED = auto()
|
|
COMMITTED = auto()
|
|
|
|
|
|
class EventDeduplicator:
|
|
def __init__(self, window_ms: int = DEDUP_WINDOW_MS_DEFAULT):
|
|
self._window_ms = window_ms
|
|
self._events: dict[str, tuple[float, EventState]] = {}
|
|
|
|
def _make_key(self, raw_payload: dict[str, Any]) -> str:
|
|
sender = raw_payload.get("sender", {})
|
|
message = raw_payload.get("message", {})
|
|
event_name = raw_payload.get("event_name", "")
|
|
sender_id = sender.get("id", "unknown")
|
|
msg_id = message.get("msg_id", "")
|
|
return f"{event_name}:{sender_id}:{msg_id}"
|
|
|
|
def claim(self, raw_payload: dict[str, Any]) -> bool:
|
|
key = self._make_key(raw_payload)
|
|
now = time.monotonic()
|
|
window_s = self._window_ms / 1000
|
|
|
|
if key in self._events:
|
|
claimed_at, state = self._events[key]
|
|
if now - claimed_at < window_s:
|
|
return False
|
|
del self._events[key]
|
|
|
|
self._events[key] = (now, EventState.CLAIMED)
|
|
return True
|
|
|
|
def commit(self, raw_payload: dict[str, Any]):
|
|
key = self._make_key(raw_payload)
|
|
if key in self._events:
|
|
created_at, _ = self._events[key]
|
|
self._events[key] = (created_at, EventState.COMMITTED)
|
|
|
|
def release(self, raw_payload: dict[str, Any]):
|
|
key = self._make_key(raw_payload)
|
|
self._events.pop(key, None)
|
|
self._cleanup()
|
|
|
|
def is_committed(self, raw_payload: dict[str, Any]) -> bool:
|
|
key = self._make_key(raw_payload)
|
|
if key in self._events:
|
|
_, state = self._events[key]
|
|
return state == EventState.COMMITTED
|
|
return False
|
|
|
|
def _cleanup(self):
|
|
boundary = time.monotonic() - self._window_ms / 1000
|
|
expired = [k for k, (v, _) in self._events.items() if v < boundary]
|
|
for k in expired:
|
|
del self._events[k]
|