ForcePilot/backend/package/yuxi/channels/policy/debounce.py
Kris 7a972055b7 feat: 新增渠道服务基础框架与核心工具类
新增大量渠道适配器相关的协议、策略、工具类与基础设施代码,包括:
1.  多协议定义:认证、消息、配置、网关等核心接口
2.  策略模块:上下文、群聊、去重、防抖等业务策略
3.  工具集:重试、去重、文本分块、消息格式化等SDK工具
4.  基础设施:外部进程管理、事件广播、熔断机制等
5.  账户与管道系统:账户管理、消息处理管道实现
6.  运行时服务:状态收集、维护任务、日志等后台服务
2026-05-12 00:53:57 +08:00

78 lines
2.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from __future__ import annotations
import asyncio
from collections.abc import Awaitable, Callable
from typing import Any
from yuxi.utils.logging_config import logger
class QueueDebounce:
"""消息队列防抖器
同一 session_key 在 debounce_ms 内的多条连续消息合并为一次批量处理。
适用于群聊中同一用户连续发送多条消息的场景。
用法:
debouncer = QueueDebounce(debounce_ms=500)
await debouncer.enqueue(session_key, message, handler)
工作原理:
1. enqueue(msg) → 将消息加入队列,重置定时器
2. debounce_ms 到期后 → 批量调用 handler(msgs)
3. 期间又来新消息 → 重新重置定时器(重新等待 debounce_ms
性能:使用 asyncio.Task内存占用 O(活跃会话数)
"""
def __init__(self, debounce_ms: int = 500):
self._debounce_s: float = debounce_ms / 1000.0
self._queues: dict[str, list[Any]] = {}
self._timers: dict[str, asyncio.Task] = {}
self._lock = asyncio.Lock()
async def enqueue(
self,
session_key: str,
message: Any,
handler: Callable[[list[Any]], Awaitable[None]],
) -> None:
async with self._lock:
if session_key not in self._queues:
self._queues[session_key] = []
self._queues[session_key].append(message)
old_timer = self._timers.pop(session_key, None)
if old_timer:
old_timer.cancel()
self._timers[session_key] = asyncio.create_task(self._flush_after_delay(session_key, handler))
async def _flush_after_delay(
self,
session_key: str,
handler: Callable[[list[Any]], Awaitable[None]],
) -> None:
await asyncio.sleep(self._debounce_s)
async with self._lock:
msgs = self._queues.pop(session_key, [])
self._timers.pop(session_key, None)
if msgs:
try:
await handler(msgs)
logger.debug(f"[Debounce] Flushed {len(msgs)} messages for {session_key}")
except Exception as e:
logger.error(f"[Debounce] Handler error for {session_key}: {e}")
async def cancel(self, session_key: str) -> None:
async with self._lock:
timer = self._timers.pop(session_key, None)
if timer:
timer.cancel()
self._queues.pop(session_key, None)
@property
def pending_count(self) -> int:
return len(self._queues)