☰
TG Bot 多 Bot 群组收不到命令:排查隐私模式与 Webhook 残留
2026/10/1 13:16:56 网站建设 项目流程

群里已经有三个 Bot 在跑了,你把自己的那个拉进群,满怀期待地敲下/report回车——另一个 Bot 秒回“收到”,你的 Bot 一声不吭。翻日志,干干净净,连一行 update 都没有。这时候大多数人的第一反应是怀疑 token 写错了、怀疑 handler 名字对不上,改了半天发现什么都没变,因为消息压根就没送到你的 Bot 手上。

这个场景的关键词就三个:TG Bot、多 Bot 群组、输入命令接收不到用户输入。它跟代码水平关系不大,八成卡在三个地方——群组里的 Bot 隐私模式、多 Bot 环境下命令的寻址方式、以及你自己服务端没清干净的 webhook 残留。下面我按“先确定消息在哪一层消失”的思路,把整条链路走一遍,包括怎么在 Linux 上用命令行把 Bot 的真实状态问出来,最后给一份可以直接抄的排查清单。不管你是刚学会 setWebhook 的新手,还是手里维护着十几个群的老手,这套流程都能用。

1. 先把“收不到”拆开:消息是在哪一层消失的

1.1 一次典型的多 Bot 群组复现

我遇到过最典型的一次是这样的:一个五百人的技术群,里面已经躺着两个 Bot,一个是群管的公告推送 Bot,一个是某个第三方服务的查询 Bot。我把自己新写的 Bot 拉进去,同时在 BotFather 里确认过 token 没问题,本地私聊测试一切正常。结果在群里敲/help,公告 Bot 回了“无此命令”,我的 Bot 毫无反应。

后面做了三组对照实验,问题范围立刻缩小了:

  • 私聊同一个 Bot 发/help:秒回。说明 token、进程、handler 全都没问题,故障一定在“群组侧”。
  • 在群里发@我的Bot 你好:立刻回了。说明 Bot 确实在群里,网络和权限也没问题,只是它看不到裸命令。
  • 把 Bot 提升为群管理员后再发裸命令/help:回了。

三个动作做完,结论已经很清楚了:这是**隐私模式(Group Privacy)**在作祟,跟代码一行关系都没有。很多人卡在这里,是因为第一反应永远去改代码,而不是先去验证“消息有没有到达 Bot”。

1.2 用三个动作把故障范围压缩到一层

排查这类问题,我习惯按“从外到内”的顺序压缩范围,顺序千万别反,否则容易在自己写的代码里绕半天。

第一个动作是私聊测试。私聊都不回,那问题跟群组无关,去看进程、token、webhook 状态。私聊正常,说明 Bot 本体是活的。

第二个动作是群里显式提及。发一句@你的Bot 测试,或者发/help@你的Bot用户名。这一步能回,说明 Bot 在群里、有读消息的权限、服务端通道也是通的;这一步不回,说明 Bot 可能根本没进群、被管理员限制了、或者群本身不允许 Bot 发言。

第三个动作是看原始 update。在你代码里最靠前的位置把原始 JSON 打出来,或者在服务器上直接调getUpdates。日志里连 update 都没有,跟日志里有 update 但没触发 handler,是两种完全不同的病——前者要查 Telegram 侧的投递,后者要查你自己的过滤条件和路由逻辑。

还有一个很容易被忽略的边界:Bot 看不到其他 Bot 发的消息。如果群里那条“用户输入”其实是另一个 Bot 发出来的(比如某个服务 Bot 转发的指令),你的 Bot 永远不会收到,这是平台层面的硬限制,不是配置问题。排查时先确认消息发送者是不是真人账号,用getUpdates看一下from.is_bot字段就清楚了。

2. Group Privacy:默认开启、却最容易漏掉的那道闸门

2.1 隐私模式下的 Bot 到底能看到什么

Bot 被拉进群组时,默认是带着隐私模式进来的。这个默认值的设计初衷是保护群聊隐私——毕竟没人希望一个记账 Bot 把群里所有闲聊都读走。开启隐私模式时,Bot 只能看到下面这几类消息:

  • 明确指向它的命令,例如/help@你的Bot
  • 群里有人回复它自己发过的消息(reply)
  • 它是一个频道的管理员时,该频道里的消息
  • 服务类消息,比如有人进群、退群、被置顶、群名被改
  • 来自它自己消息上按钮的回调查询(callback query)

反过来,下面这些它一概看不到:群里的普通聊天文本、别人回复别人的消息、@ 了别人的消息、以及那些没有指向任何人的裸命令。

问题的根子就在这里:很多人心里想的是“我发的是一条命令,Bot 当然该收到”,但隐私模式判断的标准不是“这是不是命令”,而是“这条命令是不是冲着它来的”。群里只有一个 Bot 的时候,用户敲/help,大家(包括 Bot 自己也)默认这就是给它的;一旦群里有三个 Bot,/help到底给谁,就成了一个悬而未决的问题,Telegram 干脆在隐私模式下不保证投递给你。

2.2 在 BotFather 关掉之后,为什么还得把 Bot 踢出重拉

关闭隐私模式的入口在 BotFather:/mybots→ 选你的 Bot →Bot Settings→Group Privacy→Turn off。改完之后 BotFather 会回一句提示,大意是“设置已更新,但需要把 Bot 重新加入群组才能生效”。

注意:这一步是坑最多的地方。改设置本身是立刻生效的,但它作用于“Bot 加入群组的那一刻”。已经躺在群里的 Bot,身上挂的还是加入时的旧配置。所以标准动作是:把 Bot 从群里移除,再重新拉一次。有些客户端存在缓存,移除之后最好等十几秒再拉,或者换一个账号拉进去。

我见过太多人在这里翻车:BotFather 里明明显示的是Privacy: Disabled,群里还是收不到,然后开始怀疑人生。其实就是没重拉。另外一个容易踩的细节是,Group Privacy 是全局设置,对一个 Bot 的所有群生效。你在一个群里为了看全量消息关掉了隐私,结果另一个活跃群的每一条闲聊都会灌进你的 handler,日志一夜之间涨到几个 G,如果还开着“每条消息都回一句”的逻辑,直接社死。所以关隐私之前,务必想清楚你的 Bot 到底是“只响应命令”还是“需要读全量消息”。

2.3 让它当管理员 vs 关闭隐私模式:两种做法的取舍

既然提到管理员,这里必须把两条路拆开讲清楚,因为很多人是随手把 Bot 设成了管理员,然后把它当成“关闭隐私模式的替代方案”,但两者的代价完全不同。

做法效果代价与风险
关闭隐私模式Bot 能收到群里所有普通消息,但没有任何管理权限全局生效,所有群都会收到全量消息;需要把 Bot 踢出重拉才生效
把 Bot 设为管理员隐私模式自动失效,能收到全部消息拥有了删消息、封禁成员等权限,需要给群成员一个合理交代;权限边界变大,一旦 token 泄漏后果更严重
保持隐私模式,改用 @ 寻址只收到指向自己的命令和回复需要教育用户用/cmd@Bot,用户体验略降,但最安全、最省资源

我个人的建议是优先选第三种。理由很实在:一个只在群里响应命令的 Bot,根本没有读全量消息的需求,关掉隐私等于白白消耗自己的处理能力,还平白扩大了数据面。只有当你的 Bot 确实需要做关键词监听、内容审核、消息归档这类事情时,才考虑关隐私或者升管理员——而且这种 Bot 通常本来就该是管理员。

如果你确实要走管理员这条线,记得把默认权限收窄:在群设置里给这个 Bot 的权限只勾选它真正需要的项,不要图省事全开。这个习惯我是在一次事故之后养成的——一个只需要发消息的 Bot 拿到了封禁权限,结果因为一处参数拼错,把一个活跃用户误踢了。

3. 一个群塞了好几个 Bot,命令是分给谁的

3.1 裸命令和 /cmd@YourBot 的分发差异

回到最开始那个现象:群里明明有多个 Bot,为什么手敲/help就是不到我这儿?

先说清楚客户端的行为。在输入框里敲/,Telegram 客户端会聚合当前群里所有 Bot 注册过的命令,弹出一个合并的菜单。这个菜单是从哪来的?就是各个 Bot 通过setMyCommands提交上去的。你从菜单里点选某一项,客户端会自动把命令补成/help@被选中那个Bot。而如果你是手敲/help然后直接回车,发出去的就是一条不带@的裸命令。

裸命令的归属在单 Bot 群里没问题,在多 Bot 群里就存在不确定性,尤其是几个 Bot 注册了同名命令时。官方的做法也很明确:在多 Bot 共存的群里,命令应该带上用户名来指定收件人。所以最稳的写法就一句:永远用/cmd@你的Bot用户名,或者干脆把命令名做得独一无二,从命名上避免撞车。

这里补充一个小技巧:正文里出现@你的Bot用户名也能把它唤醒,这在很多客户端版本上是成立的。但这条行为在不同客户端上偶有差异,我一般只把它当兜底,不作为主方案。命令形式@才是稳的。

3.2 setMyCommands:菜单里没有,用户就只能手敲

这一条是纯粹的工程问题,但在多 Bot 群里被放大得很明显。如果你的 Bot 从来没有调用过setMyCommands,那么用户在群里敲/时,弹出的菜单里根本没有你的命令项。用户找不到,就只好手敲裸命令——于是又回到了上一条的坑里。

所以只要你的 Bot 是给真人用的,注册命令列表几乎是必做项。Python 里一段最简的实现大概长这样:

import requests TOKEN = "你的BotToken" BASE = f"https://api.telegram.org/bot{TOKEN}" commands = [ {"command": "report", "description": "生成今日报表"}, {"command": "status", "description": "查看服务状态"}, {"command": "help", "description": "查看使用说明"}, ] r = requests.post(f"{BASE}/setMyCommands", json={"commands": commands}) print(r.json())

两个细节值得记一下。第一,命令名有格式约束:1 到 32 个字符,只允许小写英文字母、数字和下划线,写中文、写大写字母、写连字符都会被接口拒绝,返回 400。第二,setMyCommands支持scope参数,可以只对某个群生效(BotCommandScopeChat),也可以只对管理员生效(BotCommandScopeChatAdministrators),还能按language_code分语言注册。在多 Bot 群里,把命令名加上你自己的前缀(比如myapp_report)是最省心的做法,既能避免撞名,用户从菜单里点的时候也一定点得到你的那一条。

3.3 那些长得像命令、其实不是命令的输入

这一类问题我统称为“输入形态陷阱”,它们全都表现为“我明明发了命令啊”,但实际上 Telegram 根本没把它当命令处理。

  • 中文命令:/帮助。命令名必须是 ASCII 的那套字符集,客户端通常不会把中文斜杠串识别成命令,服务端也不会按命令处理,隐私模式下直接被丢弃。
  • 全角斜杠:在某些中文输入法下敲出来的/其实是全角/,肉眼几乎看不出差别,但它在协议层面就是一个普通字符。
  • 斜杠前有空格:/help前面多了一个空格,就不算命令开头。
  • 用下划线以外的分隔符:/help-me、/help.me都不符合命令的格式规范。
  • 消息被编辑过:用户先在群里发了一段普通文本,然后编辑成/help。这时你收到的是edited_message而不是message,如果你只处理message,表现就是“命令发出去了但没反应”。

提个醒:这些坑在单人测试时几乎不会遇到,因为你测试的时候会用最规范的输入。真正出问题的是群里那些用中文输入法、习惯性打全角的真实用户。所以你的解析逻辑要能容忍这些变体,或者在 Bot 的使用说明里把命令格式写清楚。

4. 服务端制造的假象:webhook 残留、409 和一个被吃掉的 offset

4.1 getUpdates 报 409 Conflict 背后的真相

前面说的都是群组侧的配置。现在换个方向,说说服务端这一侧经常制造出的“假故障”。最经典的一个是getUpdates返回409 Conflict: terminated by other getUpdates request。

这个错误的含义很直接:同一个 token,同一时刻只允许一种取更新的方式。出现 409,通常意味着下面几种情况之一:

第一种,你的 Bot 之前在某台服务器或者某个托管平台上跑过,webhook 还挂着。Telegram 会把更新推送到那个 webhook 地址,而你在本地用长轮询去拉,自然什么都拉不到,还会报 409。

第二种,同一个 token 同时跑了两份进程。比如你在服务器上留了一个 systemd 服务,又手动python main.py起了一份,两个进程互相抢更新。表现就是“消息有时候能回、有时候不能回”,比完全收不到更难查。

第三种,webhook 和轮询混用。这种情况在小团队里特别常见,因为部署路径是慢慢演进出来的,历史配置没人清理。

这类问题的共同特点是:Bot 本身没问题,代码也没问题,但更新在到达你的进程之前就已经被另一条路吃掉了。所以排查的第一步永远是先问清楚“现在这个 token 到底是用哪种方式在收消息”。

4.2 deleteWebhook、offset 与 allowed_updates 的组合拳

定位到 webhook 残留之后,清理动作很简单:

TOKEN="你的BotToken" # 看现在挂着什么 curl -s "https://api.telegram.org/bot$TOKEN/getWebhookInfo" | jq # 清掉 webhook,同时丢弃积压的旧更新 curl -s -X POST "https://api.telegram.org/bot$TOKEN/deleteWebhook" \ -d "drop_pending_updates=true" | jq

drop_pending_updates这个参数要不要开,取决于你的场景。测试阶段我一般会开,因为积压的旧消息处理起来毫无意义;生产环境里如果消息有业务价值,就不要丢,改成先确认积压量(getWebhookInfo里的pending_update_count)再决定。

除了 webhook,还有两个参数会造成“消息无声无息地消失”,值得单独说:

第一个是 offset 的处理方式。getUpdates返回的每条更新都带一个update_id。你下次请求时要带上offset = 最后一条 update_id + 1,Telegram 才会认为前面那些已经确认收妥、可以从队列里删掉。很多人写轮询循环时随手把 offset 设成一个很大的数字,或者重启脚本时初始化错了,等于主动把没处理的消息全部确认掉——表现就是“服务一重启就丢消息”。正确的做法是把 offset 持久化,或者每次启动前先不带 offset 拉一次,看看积压里有什么(注意这会读到真实数据,测试环境无所谓,生产环境要谨慎)。

第二个是 allowed_updates。默认情况下你会收到所有类型的更新。但如果你之前为了减少噪音,把allowed_updates设成了只有callback_query,那么群里的普通消息、命令消息你一条都收不到。用getWebhookInfo一眼就能看出来allowed_updates字段里到底有没有message。我踩过一次这个坑:一个按钮交互的服务,为了省流量只订阅了回调查询,后来临时加一个文本指令功能,怎么调试都没反应,翻了半小时代码才想起来是订阅列表的问题。

4.3 在 Linux 上用 curl 把 Bot 体检一遍(顺带聊聊列出用户和群组)

我的习惯是:遇到这类问题,先在一台 Linux 机器上把 Bot 的“体检报告”拉出来,别急着改代码。三步走,一分钟内能拿到大部分事实:

TOKEN="你的BotToken" BASE="https://api.telegram.org/bot$TOKEN" # 1. Bot 本体是否正常 curl -s "$BASE/getMe" | jq '{id, username, is_bot}' # 2. 现在是 webhook 还是轮询,有没有报错 curl -s "$BASE/getWebhookInfo" \ | jq '{url, pending_update_count, last_error_date, last_error_message, allowed_updates}' # 3. 更新队列里到底有没有东西(注意这一拉会确认掉更新) curl -s "$BASE/getUpdates?limit=5&timeout=0" \ | jq '.result[] | {id:.update_id, type:.message.chat.type, title:.message.chat.title, from:.message.from.username, is_bot:.message.from.is_bot, text:.message.text}' # 4. 这个群里到底有哪些管理员,其中哪些是 Bot curl -s "$BASE/getChatAdministrators?chat_id=-100xxxxxxxxxx" \ | jq '.result[] | {username:.user.username, is_bot:.user.is_bot, status}'

第四条特别有用。多 Bot 群里排查问题时,你经常需要回答“这个群里除了我还有几个 Bot、谁是管理员、谁开了全量消息”。把管理员列表打出来,一眼就能看出哪些 Bot 是管理员身份(它们必然能收到全量消息),哪些是普通成员(它们受隐私模式限制)。这个动作的本质是“枚举”——先把环境里有哪些身份、各自属于哪个集合列清楚,再谈投递问题。

说到枚举,这事和我平时在 Linux 上查账号权限的思路是一模一样的。排查一台机器上的权限问题,第一步也永远是先把“有哪些用户、各自属于哪些组”列出来,而不是盯着某一条报错发呆:

目的命令说明
列出所有用户getent passwd推荐,会把 LDAP/SSSD 等目录服务里的账号一起列出来
只看用户名getent passwd | cut -d: -f1等价写法awk -F: '{print $1}' /etc/passwd
只列本地文件里的账号cut -d: -f1 /etc/passwd快,但会漏掉域账号
bash 内建快速枚举compgen -u适合在脚本里判断用户是否存在
列出所有群组getent group按组名过滤:getent group docker
只看群组名getent group | cut -d: -f1等价写法cut -d: -f1 /etc/group
查某个用户属于哪些组id -nG alice或groups alice排查权限时用得最多的一条
查当前登录用户who、w、users确认“谁在用这台机器”

这里有个细节值得留意:getent passwd和直接cat /etc/passwd的结果经常不一样,因为前者会去查 NSS 配置里的所有数据源。这跟前面查群管理员是同一个道理——如果只盯着自己手里的那份信息(自己的日志、自己的配置文件),你看到的事实永远是不完整的。多换一个视角去枚举,很多“玄学问题”当场就变成确定性结论了。

5. 一套能照着走的排查链路

5.1 五个检查点,按代价从小到大排

下面这五步是我现在固定用的顺序,从改动成本最低的开始,每一步都能排除掉一大类可能:

  1. 私聊发同样的命令。不回就是 Bot 自身的运行问题,跟群组无关。回,说明本体健康。
  2. 群里发/cmd@你的Bot,再单独发一句@你的Bot 测试。两个都不回,说明 Bot 不在群里、被限制了,或者你的代码对chat.type做了过滤。能回,继续下一步。
  3. 调getWebhookInfo。有 URL 说明 webhook 还挂着;allowed_updates里没有message说明订阅被改过;pending_update_count一直涨说明没人消费。
  4. 在代码最前面打印原始 update。有 update 但没触发业务逻辑,问题在你自己的路由和过滤条件;没有 update,回到第 3 步继续查投递链路。
  5. 检查处理器的过滤条件。chat.type是不是写死了private、文本判断是不是用了精确匹配、命令解析是不是只认 ASCII 斜杠、有没有漏处理edited_message。

5.2 一个只打印不回复的最小验证脚本

排查阶段最忌讳的事情,是在你还没确定消息有没有到达的时候,就去改业务逻辑。我通常会写一个“只打印、绝不回复”的最小脚本,用它单纯确认消息流:

import time import requests TOKEN = "你的BotToken" BASE = f"https://api.telegram.org/bot{TOKEN}" # 先把 webhook 清掉,避免和轮询打架 requests.post(f"{BASE}/deleteWebhook", data={"drop_pending_updates": False}) offset = None while True: params = {"timeout": 30, "allowed_updates": ["message", "edited_message", "callback_query"]} if offset is not None: params["offset"] = offset try: resp = requests.get(f"{BASE}/getUpdates", params=params, timeout=40).json() except requests.exceptions.ReadTimeout: continue for u in resp.get("result", []): offset = u["update_id"] + 1 msg = u.get("message") or u.get("edited_message") or {} chat = msg.get("chat", {}) sender = msg.get("from", {}) print( f"[{u['update_id']}] chat_type={chat.get('type')} " f"title={chat.get('title')} from={sender.get('username')} " f"is_bot={sender.get('is_bot')} text={msg.get('text')!r}" ) time.sleep(0.3)

这个脚本的价值在于:它不做任何业务判断,只回答一个问题——消息到底有没有到达 Bot。如果这个脚本能打印出你在群里发的那条命令,那 Telegram 侧就完全没问题,锅一定在你的业务代码里;如果打印不出来,那你的业务代码怎么改都没用,回去看隐私模式、寻址方式和投递链路。

顺便说一句,这个脚本也顺手解决了“两个进程抢更新”的排查:如果它一边打印、你的正式服务也一边在处理同一条消息,那你就能立刻确认 token 被复用了。

5.3 症状对照表:从现象直接跳到修复动作

现象最可能的原因验证方式修复动作
私聊也不回进程挂了 / token 错 / webhook 冲突调getMe、getWebhookInfo清 webhook、重启进程、核对 token
私聊正常,群里 @ 也不回Bot 不在群里 / 被管理员限制 / 代码过滤了chat.type调getChatAdministrators看 Bot 是否在列重新拉群、检查处理器过滤条件
私聊正常,@ 能回,裸命令不回隐私模式 / 多 Bot 命令歧义把 Bot 设为管理员再试裸命令关隐私并重拉,或统一改用@寻址
部分成员发消息收不到,部分正常对方是 Bot 账号 / 消息被编辑过看from.is_bot、看是否edited_message处理edited_message,接受 Bot 消息不可见的限制
重启服务后丢一批消息offset 初始化错误,消息被确认丢弃打印每次请求的 offset 与返回条数持久化 offset,启动前先探测积压
完全没有任何 update,也没有报错webhook 挂到了别处 /allowed_updates被改看getWebhookInfo的 url 与 allowed_updatesdeleteWebhook,重置 allowed_updates
群里能收,重启后群 ID 对不上白名单普通群升级成超级群,chat_id 变了对比日志里新旧 chat_id更新配置里的 chat_id(会多出-100前缀)
命令菜单里找不到你的命令没调setMyCommands在群里敲/看菜单注册命令列表,命令名加前缀区分

6. 多 Bot 长期共处的约定与自保手段

6.1 命令命名空间:给自己的命令加前缀

多 Bot 群组里最省事的长期方案,是承认“命令名是一种稀缺资源”。/help、/start、/status这几个名字,任何 Bot 都想占,占了就撞。与其每次都靠用户输入@来消歧义,不如从设计上避开:

  • 给命令加产品前缀,例如/myapp_help、/myapp_status。名字长一点,但用户从菜单里点的时候反而更清楚。
  • 一个 Bot 注册的命令不要超过 8 到 10 条。菜单太长,用户根本不会往下翻。
  • 命令的description写清楚用途,因为多 Bot 群里那个合并菜单是按 Bot 分组的,描述就是你的“招牌”。
  • 最重要的几条命令(比如help)保留一份@形式的说明,写在群公告或者 Bot 的欢迎语里,把“多 Bot 群里要用 @”这条规则明确告诉用户。

6.2 让“收不到”自己叫出来:日志、心跳与巡检

这类问题最难受的地方,是它静默。消息没到,就没有任何事件,没有事件就没有日志,人也就不会发现。所以我把“让故障自己叫出来”当成工程的一部分:

第一,原始 update 全量落盘,按update_id去重,按chat_id分文件。不用存太久,七天足够回溯。这一步能让你在事后回答“那条命令到底有没有到”。

第二,定期巡检getWebhookInfo。把pending_update_count和last_error_message采集到监控里,积压量持续上涨、或者出现错误信息,立刻告警。这个指标比“机器人不回消息”要早发现得多。

第三,心跳。让 Bot 每天固定时间往一个私密频道发一条状态消息,包含当天的更新条数、处理失败数、进程运行时长。你会发现这条心跳的沉默,比任何日志都更容易让人察觉到异常。

第四,监控 409。如果你的访问日志里出现 409 Conflict 却没人管,说明有一份你不知道的进程正在消费更新,早晚出事。

6.3 几个真实踩过的坑

最后说几个我在实际运维里踩过、而且这四个坑每一个都表现为“命令收不到”的案例,你可以对照着检查一下自己的环境。

坑一:普通群升级成超级群,chat_id 变了。群聊活跃到一定规模会自动升级为超级群,chat_id从一个小的负数变成一个带-100前缀的长数字。如果你的代码里有群白名单、或者把状态按chat_id存了数据库,升级之后全部失配。表现就是消息确实到了,但被你的白名单过滤掉了。这个坑的可怕之处在于它悄无声息地发生,没有任何通知。

坑二:一个 token 跑在两个环境。测试服务器和线上服务器用了同一个 token,两边的长轮询互相抢更新。表现是消息时有时无、分布完全不均匀。凡是“有时收得到有时收不到”的问题,我第一件事就是确认有没有重复消费。

坑三:群里开了话题(Forum)之后,消息带上了message_thread_id。消息本身照样能收到,但如果你之前把所有状态都按chat_id存,话题模式下不同子话题的消息会混在同一个上下文里;如果你的代码还依赖reply_to_message做关联,回复链路也会变复杂。这不是收不到的问题,但排查时容易误判成“消息没来”。

坑四:Bot 被管理员限制了权限。群管理员可以在群设置里调低 Bot 的剩余权限。权限被砍之后,Bot 依然能收到指向自己的命令,但发不出回复。这时你看到的是“日志里有 update、代码也执行了,用户就是看不到回复”。所以排查时一定要把“收不到”和“回不出去”分开看,看日志里到底有没有sendMessage的失败返回。

我个人在这个问题上最大的体会是:绝大多数“Bot 收不到命令”的情况,答案都不在代码里,而在“这条消息有没有被允许送到我面前”这一层。所以真正高效的排查姿势不是读代码,而是先用最粗暴的方式——私聊、@、getWebhookInfo、getUpdates——把事实摆出来。事实一旦摆齐,剩下的往往只是一两个配置项的事。至于那些一上手就去改 handler 名字、去重写正则的朋友,我建议你下次先花三分钟做前面说的那三步对照实验,能省下至少半天的无效劳动。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询