You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Teloxide-rs:如何实现多用户共享机器人同一状态?

问题分析

你当前的核心问题是混淆了用户私有对话状态和全局共享状态:teloxide的Dialogue组件设计初衷就是为每个用户(chat_id)维护独立的会话状态,所以不管用内存还是Redis存储,每个用户的State都是隔离的,这就导致玩家A修改的是自己的状态,玩家B查询的是自己的,自然不一致。

SQLite本身和Redis一样,是可以存储全局共享数据的,你之前的问题不是存储方案的问题,而是用错了存储的使用场景——对话存储是给用户私有会话用的,不是存全局数据的。

解决方案

要实现多用户共享同一状态,你需要单独维护全局共享存储,和用户的私有对话状态分开。下面给出两种可行方案:

方案1:内存全局存储(适合测试/小规模场景)

用Arc<Mutex<T>>来包装全局状态,实现多线程安全的共享访问:

修改后的核心代码

use teloxide::{
    prelude::*,
    utils::command::BotCommands,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};

// 定义全局共享的任务状态结构
#[derive(Clone, Default, Serialize, Deserialize)]
struct GlobalTaskState {
    tasks: std::collections::HashMap<String, bool>, // 任务名 -> 是否完成
}

type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;

#[derive(Clone, BotCommands)]
#[command(rename_rule = "lowercase", description = "支持的命令:")]
enum Command {
    #[command(description = "标记任务为完成,格式:/mark task_1")]
    Mark { task: String },
    #[command(description = "查询任务状态,格式:/query task_1")]
    Query { task: String },
}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();
    log::info!("启动游戏协调机器人...");

    let bot = Bot::from_env();
    // 创建全局共享状态,用Arc<Mutex>保证线程安全
    let global_state = Arc::new(Mutex::new(GlobalTaskState::default()));

    let handler = Update::filter_message()
        .filter_command::<Command>()
        .endpoint(handle_command);

    Dispatcher::builder(bot, handler)
        .dependencies(dptree::deps![global_state]) // 注入全局状态
        .enable_ctrlc_handler()
        .build()
        .dispatch()
        .await;
}

async fn handle_command(
    bot: Bot,
    msg: Message,
    cmd: Command,
    global_state: Arc<Mutex<GlobalTaskState>>,
) -> HandlerResult {
    match cmd {
        Command::Mark { task } => {
            let mut state = global_state.lock().unwrap();
            state.tasks.insert(task.clone(), true);
            bot.send_message(msg.chat.id, format!("任务 {} 已标记为完成", task)).await?;
        }
        Command::Query { task } => {
            let state = global_state.lock().unwrap();
            let status = state.tasks.get(&task).map(|&done| if done { "已完成" } else { "未完成" }).unwrap_or("不存在");
            bot.send_message(msg.chat.id, format!("任务 {} 的状态:{}", task, status)).await?;
        }
    }
    Ok(())
}

方案2:Redis全局存储(适合生产/多实例场景)

如果需要持久化或者多机器人实例共享状态,直接用Redis存储全局数据,不需要依赖对话存储:

修改后的核心依赖(Cargo.toml)

保留原teloxide的基础特性,新增redis crate直接操作:

[dependencies]
teloxide = { version = "0.12", features = ["macros"] }
log = "0.4"
pretty_env_logger = "0.4"
tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] }
serde = "1.0.152"
redis = { version = "0.23", features = ["tokio-comp"] }

核心代码示例

use teloxide::{
    prelude::*,
    utils::command::BotCommands,
};
use serde::{Deserialize, Serialize};
use redis::{Client, Commands};

type HandlerResult = Result<(), Box<dyn std::error::Error + Send + Sync>>;

#[derive(Clone, BotCommands)]
#[command(rename_rule = "lowercase", description = "支持的命令:")]
enum Command {
    #[command(description = "标记任务为完成,格式:/mark task_1")]
    Mark { task: String },
    #[command(description = "查询任务状态,格式:/query task_1")]
    Query { task: String },
}

#[tokio::main]
async fn main() {
    pretty_env_logger::init();
    log::info!("启动游戏协调机器人...");

    let bot = Bot::from_env();
    // 连接Redis
    let redis_client = Client::open("redis://127.0.0.1:6379").unwrap();
    let redis_conn = redis_client.get_async_connection().await.unwrap();

    let handler = Update::filter_message()
        .filter_command::<Command>()
        .endpoint(handle_command);

    Dispatcher::builder(bot, handler)
        .dependencies(dptree::deps![redis_conn]) // 注入Redis连接
        .enable_ctrlc_handler()
        .build()
        .dispatch()
        .await;
}

async fn handle_command(
    bot: Bot,
    msg: Message,
    cmd: Command,
    mut redis_conn: redis::aio::Connection,
) -> HandlerResult {
    match cmd {
        Command::Mark { task } => {
            // 存储任务状态到Redis,用任务名作为key
            redis::cmd("SET").arg(&task).arg("done").query_async(&mut redis_conn).await?;
            bot.send_message(msg.chat.id, format!("任务 {} 已标记为完成", task)).await?;
        }
        Command::Query { task } => {
            // 从Redis查询任务状态
            let status: Option<String> = redis::cmd("GET").arg(&task).query_async(&mut redis_conn).await?;
            let status_text = match status {
                Some(s) if s == "done" => "已完成",
                _ => "未完成或不存在",
            };
            bot.send_message(msg.chat.id, format!("任务 {} 的状态:{}", task, status_text)).await?;
        }
    }
    Ok(())
}
关键总结
  • 不要用Dialogue存储全局共享数据,它是为用户私有会话设计的。
  • 全局状态需要单独维护,内存场景用Arc<Mutex>,持久化/多实例场景用Redis/SQLite。
  • teloxide本身不需要内置全局状态方案,因为可以通过依赖注入轻松集成通用存储。

内容的提问来源于stack exchange,提问作者beyarkay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 16:35:23