From b5a1635a05949dc3dcf5f7bbedec5ac3c052e41a Mon Sep 17 00:00:00 2001 From: ooodc <549496103@qq.com> Date: Sat, 6 Jun 2026 10:57:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E5=8F=91=E9=80=81=E7=9A=84=E9=80=9F=E7=8E=87=E9=99=90=E5=88=B6?= =?UTF-8?q?=EF=BC=8C=E7=A1=AE=E4=BF=9D=E5=90=8C=E4=B8=80=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=B9=8B=E9=97=B4=E7=9A=84=E6=9C=80=E5=B0=8F=E9=97=B4=E9=9A=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/channels/wechat.rs | 45 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/src/channels/wechat.rs b/src/channels/wechat.rs index 7ed697c..fd9cd60 100644 --- a/src/channels/wechat.rs +++ b/src/channels/wechat.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; use std::sync::{ - Arc, + Arc, Mutex, atomic::{AtomicBool, Ordering}, }; use std::time::{Duration, UNIX_EPOCH}; @@ -18,6 +18,44 @@ use crate::bus::message::OutboundEventKind; use crate::channels::base::{Channel, ChannelError}; use crate::config::{LLMProviderConfig, WechatChannelConfig}; +/// Rate limiter: ensures minimum interval between messages to the same chat. +static LAST_SEND: std::sync::LazyLock>> = + std::sync::LazyLock::new(|| Mutex::new(HashMap::new())); + +/// Minimum interval between consecutive messages to the same WeChat user. +const MIN_MSG_INTERVAL_MS: u64 = 1500; + +/// Wait if needed to respect the rate limit, then record the send time. +async fn throttle(chat_id: &str) { + let now = tokio::time::Instant::now(); + let sleep_ms = { + let mut map = LAST_SEND.lock().unwrap(); + let delay = map.get(chat_id).and_then(|last| { + let elapsed = now.duration_since(*last); + if elapsed < Duration::from_millis(MIN_MSG_INTERVAL_MS) { + Some(Duration::from_millis(MIN_MSG_INTERVAL_MS) - elapsed) + } else { + None + } + }); + // Record the expected send time now (before sleep), so concurrent + // callers also see the updated time and don't race. + map.insert(chat_id.to_string(), now); + delay + }; + if let Some(ms) = sleep_ms { + tokio::time::sleep(ms).await; + } +} + +/// Update rate-limit timestamp (without waiting) — used after each chunk. +fn touch_rate_limit(chat_id: &str) { + LAST_SEND + .lock() + .unwrap() + .insert(chat_id.to_string(), tokio::time::Instant::now()); +} + #[derive(Clone)] pub struct WechatChannel { name: String, @@ -313,6 +351,9 @@ impl Channel for WechatChannel { let mut text_sent = false; if !text.is_empty() { + // Rate limit: ensure minimum interval between messages to the same user + throttle(&msg.chat_id).await; + let chunks = split_text(&text, MAX_WECHAT_CHUNK_CHARS); if chunks.len() > 1 { tracing::info!( @@ -335,6 +376,8 @@ impl Channel for WechatChannel { error )) })?; + // Update rate-limit timestamp so the next message or chunk respects the gap + touch_rate_limit(&msg.chat_id); tracing::info!( channel = %self.name, chat_id = %msg.chat_id,