使用Golang Telego开发Telegram Bot:如何实现FSM?
在Telego中实现有限状态机(FSM)
Telego框架并未像aiogram那样内置FSM功能,需要手动实现状态跟踪逻辑。核心思路是通过维护用户与状态的映射关系,根据用户当前状态来分发和处理消息。
实现步骤与示例代码
1. 定义状态类型
首先自定义状态枚举,明确业务中需要的状态:
type UserState int const ( StateIdle UserState = iota // 空闲状态,无等待操作 StateWaitingForName // 等待用户输入姓名 StateWaitingForAge // 等待用户输入年龄 )
2. 并发安全的状态存储
由于Telego的消息处理是多goroutine并发执行的,需要用互斥锁保证状态存储的线程安全:
import "sync" var ( userStates = make(map[int64]UserState) // key: 用户ID,value: 当前状态 stateMutex sync.RWMutex // 读写锁保护map )
3. 状态操作辅助函数
封装状态的读取和更新逻辑,简化后续代码:
// 设置用户状态 func setUserState(userID int64, state UserState) { stateMutex.Lock() defer stateMutex.Unlock() userStates[userID] = state } // 获取用户当前状态,默认返回空闲状态 func getUserState(userID int64) UserState { stateMutex.RLock() defer stateMutex.RUnlock() if state, exists := userStates[userID]; exists { return state } return StateIdle }
4. 结合Handler实现状态驱动的消息处理
修改基础代码,加入状态判断逻辑,实现一个收集用户信息的流程:
package main import ( "fmt" "os" "sync" "github.com/mymmrac/telego" th "github.com/mymmrac/telego/telegohandler" tu "github.com/mymmrac/telego/telegoutil" ) type UserState int const ( StateIdle UserState = iota StateWaitingForName StateWaitingForAge ) var ( userStates = make(map[int64]UserState) stateMutex sync.RWMutex ) func setUserState(userID int64, state UserState) { stateMutex.Lock() defer stateMutex.Unlock() userStates[userID] = state } func getUserState(userID int64) UserState { stateMutex.RLock() defer stateMutex.RUnlock() if state, exists := userStates[userID]; exists { return state } return StateIdle } func main() { botToken := os.Getenv("TOKEN") bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger()) if err != nil { fmt.Println(err) os.Exit(1) } updates, _ := bot.UpdatesViaLongPolling(nil) bh, _ := th.NewBotHandler(bot, updates) defer bh.Stop() defer bot.StopLongPolling() // 处理/start命令 bh.Handle(func(bot *telego.Bot, update telego.Update) { userID := update.Message.From.ID setUserState(userID, StateIdle) _, _ = bot.SendMessage(tu.Message( tu.ID(update.Message.Chat.ID), fmt.Sprintf("Hello %s! 发送/profile开始完善个人信息", update.Message.From.FirstName), )) }, th.CommandEqual("start")) // 处理/profile命令,进入等待姓名状态 bh.Handle(func(bot *telego.Bot, update telego.Update) { userID := update.Message.From.ID setUserState(userID, StateWaitingForName) _, _ = bot.SendMessage(tu.ID(update.Message.Chat.ID), "请输入你的姓名:") }, th.CommandEqual("profile")) // 处理普通消息,根据用户状态分支处理 bh.Handle(func(bot *telego.Bot, update telego.Update) { if update.Message == nil || update.Message.Text == "" { return } userID := update.Message.From.ID chatID := update.Message.Chat.ID currentState := getUserState(userID) switch currentState { case StateWaitingForName: // 这里可以保存姓名到数据库或其他存储 fmt.Printf("用户%d的姓名:%s\n", userID, update.Message.Text) setUserState(userID, StateWaitingForAge) _, _ = bot.SendMessage(tu.ID(chatID), "请输入你的年龄:") case StateWaitingForAge: // 这里可以保存年龄到数据库或其他存储 fmt.Printf("用户%d的年龄:%s\n", userID, update.Message.Text) setUserState(userID, StateIdle) _, _ = bot.SendMessage(tu.ID(chatID), "个人信息已收集完成!") case StateIdle: _, _ = bot.SendMessage(tu.ID(chatID), "未知指令,发送/start开始,或发送/profile完善信息") } }, th.AnyMessage()) bh.Start() }
扩展说明
- 状态持久化:如果需要bot重启后不丢失用户状态,可以将
userStates替换为数据库(如Redis、SQLite),用用户ID作为键存储状态。 - 复杂状态流转:对于更复杂的业务流程,可以将状态逻辑封装为单独的结构体或函数,避免handler代码过于臃肿。
- 超时处理:可以为状态添加超时机制,比如用户长时间未响应时自动重置为空闲状态,需要结合定时器实现。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

