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

如何在Gin中使用go-telegram-bot-api的ListenForWebhook获取UpdatesChannel

问题描述

我想在Gin框架中结合go-telegram-bot-api使用ListenForWebhook方法。目前的代码能正常运行,但直接使用tgbotapi.Update对象,不符合我的需求。我希望通过tgbotapi.ListenForWebhook()函数的思路,把Webhook数据导入tgbotapi.UpdatesChannel,像轮询模式那样统一接收和处理数据。

当前代码实现

import (
    "github.com/gin-gonic/gin"
    tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)

type TelegramBot struct {
    API   *tgbotapi.BotAPI 
    Upd   tgbotapi.Update
    UpdCh tgbotapi.UpdatesChannel 
    Log   *logger.Logger
}

func (tlg *TelegramBot) InitTg(cfg *config.Config) {
    bot, err := tgapi.NewBot(cfg.Telegram.Token)
    if err != nil {
        tlg.Log.Fatal(err)
    }

    tlg.API = bot
    router := gin.Default()
    if cfg.TelegramMode != "webhook" {
        botUpdate := tgbotapi.NewUpdate(0) 
        botUpdate.Timeout = 60
        tlg.UpdCh = tlg.API.GetUpdatesChan(botUpdate)
        go tlg.Start()
    } else {
        router.POST(cfg.Telegram.Path+"*any", tlg.WebhookHandler)
    }
    go router.Run(cfg.Listen.BindIP + ":" + cfg.Listen.Port)
    
    // TODO fix me :)
    for {}
}

func (tlg *TelegramBot) WebhookHandler(c *gin.Context) {
    defer c.Request.Body.Close()

    bytes, err := ioutil.ReadAll(c.Request.Body)
    if err != nil {
        tlg.Log.Println(err)
        return
    }

    err = json.Unmarshal(bytes, &tlg.Upd)
    if err != nil {
        log.Println(err)
        return
    }
    go tlg.Start()
    c.JSON(http.StatusOK, gin.H{"data": "not you :)"})
}

func (tlg *TelegramBot) Start() {
    // TODO: for webhook
    if tlg.Upd.Message != nil {
        chatID := tlg.Upd.Message.Chat.ID
        tlg.analyzeUpdate(chatID)
    } else if tlg.Upd.CallbackQuery != nil {
        chatID := tlg.Upd.CallbackQuery.Message.Chat.ID
        tlg.analyzeUpdate(chatID)
    }

    // TODO: for polling
    for update := range tlg.UpdCh {
        if update.Message != nil {
            tlg.Upd = update
            chatID := tlg.Upd.Message.Chat.ID
            tlg.analyzeUpdate(chatID)
        } else if update.CallbackQuery != nil {
            tlg.Upd = update
            chatID := update.CallbackQuery.Message.Chat.ID
            tlg.analyzeUpdate(chatID)
        }
    }
}

期望实现

我希望能把Webhook数据导入tgbotapi.UpdatesChannel,统一用轮询模式的逻辑处理,期望的代码结构大概是这样:

router.POST(cfg.Telegram.Path+"*any", func(c *gin.Context) {
    tlg.UpdCh = bot.ListenForWebhook(cfg.Listen.BindIP + ":" + cfg.Listen.Port + cfg.Telegram.Path + bot.Token)
    go tlg.Start()
    c.JSON(http.StatusOK, gin.H{"data": "not you :)"})
})

请问该如何实现这个需求?


解决方案

完全可以实现这个需求,核心思路是提前统一初始化UpdatesChannel,在Webhook处理器中把解析后的Update发送到该通道,让Start函数用一套逻辑处理轮询和Webhook的所有数据。具体修改步骤如下:

1. 统一初始化通道与处理逻辑

不管是轮询还是Webhook模式,都提前创建UpdatesChannel并启动统一处理协程,避免重复初始化:

func (tlg *TelegramBot) InitTg(cfg *config.Config) {
    bot, err := tgbotapi.NewBotAPI(cfg.Telegram.Token)
    if err != nil {
        tlg.Log.Fatal(err)
    }

    tlg.API = bot
    // 初始化带缓冲区的UpdatesChannel,缓冲区大小可根据业务调整
    tlg.UpdCh = make(chan tgbotapi.Update, 100)
    // 启动统一的Update处理逻辑
    go tlg.Start()

    router := gin.Default()
    if cfg.TelegramMode != "webhook" {
        botUpdate := tgbotapi.NewUpdate(0) 
        botUpdate.Timeout = 60
        // 将轮询获取的Update转发到统一通道
        go func() {
            for update := range tlg.API.GetUpdatesChan(botUpdate) {
                tlg.UpdCh <- update
            }
        }()
    } else {
        router.POST(cfg.Telegram.Path+"*any", tlg.WebhookHandler)
        // 设置Telegram Webhook地址(需是公网可访问的URL)
        webhookURL := cfg.Listen.ExternalURL + cfg.Telegram.Path + bot.Token
        _, err := bot.SetWebhook(tgbotapi.NewWebhook(webhookURL))
        if err != nil {
            tlg.Log.Fatal(err)
        }
    }

    // 启动Gin服务,用匿名协程封装便于错误捕获
    go func() {
        if err := router.Run(cfg.Listen.BindIP + ":" + cfg.Listen.Port); err != nil {
            tlg.Log.Fatal(err)
        }
    }()

    // 阻塞主协程,避免程序退出(也可替换为信号监听实现优雅退出)
    select {}
}

2. 修改Webhook处理器,转发数据到通道

删除原处理器中直接调用Start的逻辑,改为将解析后的Update发送到统一通道:

func (tlg *TelegramBot) WebhookHandler(c *gin.Context) {
    var update tgbotapi.Update
    // 用Gin内置的ShouldBindJSON解析请求体,比手动读写更简洁
    if err := c.ShouldBindJSON(&update); err != nil {
        tlg.Log.Println(err)
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    // 将Webhook收到的Update发送到统一通道
    tlg.UpdCh <- update
    c.JSON(http.StatusOK, gin.H{"status": "ok"})
}

3. 简化Start函数,统一处理逻辑

删除原函数中Webhook的单独处理分支,只用通道循环处理所有类型的Update:

func (tlg *TelegramBot) Start() {
    for update := range tlg.UpdCh {
        if update.Message != nil {
            chatID := update.Message.Chat.ID
            tlg.analyzeUpdate(chatID)
        } else if update.CallbackQuery != nil {
            chatID := update.CallbackQuery.Message.Chat.ID
            tlg.analyzeUpdate(chatID)
        }
        // 可在此扩展其他类型的Update处理逻辑
    }
}

额外优化建议

  • 删除TelegramBot结构体中的Upd字段,因为现在所有Update都通过通道传递,不需要单独存储状态。
  • 主协程阻塞用select {}比空循环更优雅,也可以添加信号监听(如syscall.SIGINT、syscall.SIGTERM)实现程序的优雅退出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 20:10:26