feat: 实现消息发送的速率限制,确保同一用户之间的最小间隔
This commit is contained in:
parent
988e77123c
commit
b5a1635a05
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
Arc, Mutex,
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
};
|
};
|
||||||
use std::time::{Duration, UNIX_EPOCH};
|
use std::time::{Duration, UNIX_EPOCH};
|
||||||
@ -18,6 +18,44 @@ use crate::bus::message::OutboundEventKind;
|
|||||||
use crate::channels::base::{Channel, ChannelError};
|
use crate::channels::base::{Channel, ChannelError};
|
||||||
use crate::config::{LLMProviderConfig, WechatChannelConfig};
|
use crate::config::{LLMProviderConfig, WechatChannelConfig};
|
||||||
|
|
||||||
|
/// Rate limiter: ensures minimum interval between messages to the same chat.
|
||||||
|
static LAST_SEND: std::sync::LazyLock<Mutex<HashMap<String, tokio::time::Instant>>> =
|
||||||
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct WechatChannel {
|
pub struct WechatChannel {
|
||||||
name: String,
|
name: String,
|
||||||
@ -313,6 +351,9 @@ impl Channel for WechatChannel {
|
|||||||
let mut text_sent = false;
|
let mut text_sent = false;
|
||||||
|
|
||||||
if !text.is_empty() {
|
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);
|
let chunks = split_text(&text, MAX_WECHAT_CHUNK_CHARS);
|
||||||
if chunks.len() > 1 {
|
if chunks.len() > 1 {
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
@ -335,6 +376,8 @@ impl Channel for WechatChannel {
|
|||||||
error
|
error
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
// Update rate-limit timestamp so the next message or chunk respects the gap
|
||||||
|
touch_rate_limit(&msg.chat_id);
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
channel = %self.name,
|
channel = %self.name,
|
||||||
chat_id = %msg.chat_id,
|
chat_id = %msg.chat_id,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user