尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

飞书自定义机器人实战:签名校验、消息体与表格推送避坑

发布时间:2026/9/29 8:52:45

资讯中心
01
ARTICLE

飞书自定义机器人实战:签名校验、消息体与表格推送避坑

飞书自定义机器人实战:签名校验、消息体与表格推送避坑
飞书自定义机器人这个功能我第一次用的时候以为十分钟就能搞定结果光签名校验就折腾了一个下午。它看起来只是往群里发消息但真正落地到告警、日报、CI/CD通知这些场景时消息体格式、安全设置、频率限制、人规则每一个细节都能让你多调半小时。如果你正在找一个不依赖复杂应用审核、直接在群聊里推送消息的方案自定义机器人确实是目前最轻的选择。下面我把从创建到发送表格、再到踩坑排查的完整过程拆开讲内容偏实操代码和配置都可以直接拿去改。1. 自定义机器人到底能帮你做什么先搞清楚能力边界1.1 群聊里的轻量级消息通道飞书自定义机器人本质上是群聊里的一个Webhook地址。你不需要去开放平台创建应用也不需要走权限审批只要在群设置里点几下拿到一个URL就能用HTTP POST往这个群发消息。它最核心的价值就是“短平快”监控系统触发告警、流水线构建完成、日报数据生成这些场景不需要用户交互只需要把一段信息送到群里自定义机器人就是最合适的工具。但它也有明显的边界。第一它只能发消息不能读消息也不能获取群成员列表。第二它默认只能发到它所在的群不能跨群发送除非你为每个群都建一个机器人。第三它对消息类型有限制文本、富文本、图片、交互式卡片都支持但文件上传、语音、视频这些需要走应用机器人。很多人一开始想用自定义机器人发Excel附件折腾半天发现根本不支持这就是没提前搞清楚边界。我自己的经验是把自定义机器人当成“只写不读的消息管道”。只要你的需求是单向推送它就能干得又快又稳一旦需要接收用户回复、拉取群信息、上传文件就应该考虑升级成应用机器人。1.2 哪些场景适合自定义机器人哪些不适合适合的场景非常明确。比如Zabbix、Prometheus这类监控工具告警触发后调用Webhook把告警级别、主机、时间推送到飞书群比如GitLab CI或Jenkins构建结束后推送成功或失败的状态再比如每天定时跑一个Python脚本把数据库里的日报数据整理成表格发到群里。这些场景共同点是触发频率不高、消息内容固定、不需要用户回复。不适合的场景也要心里有数。如果你需要机器人接收用户的指令比如在群里输入“查询今日订单”然后机器人返回结果自定义机器人做不到因为它没有事件订阅能力。如果你需要往群里发文件比如PDF报告或Excel附件自定义机器人也不支持只能发图片或卡片。如果你需要特定的人并且让对方收到强提醒自定义机器人可以人但需要拿到正确的user_id或open_id而且不是所有消息类型都支持。所以选型之前先问自己三个问题消息是单向还是双向需不需要发文件要不要跨群三个答案都是“单向、不需要、不需要”那自定义机器人就是最优解。2. 创建与安全配置从拿到Webhook到通过签名校验2.1 群设置里添加机器人的完整流程创建路径其实很短。在飞书群聊右上角点设置找到“群机器人”点击“添加机器人”选择“自定义机器人”。然后填一个名字比如“告警小助手”再选一个头像。下一步就是安全设置这里有三个选项自定义关键词、IP白名单、签名校验。你可以只选一个也可以组合使用。我一般建议至少开签名校验因为Webhook地址一旦泄露别人就能往你群里发垃圾消息。创建完成后飞书会给你一个Webhook地址格式类似https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx。这个地址就是你的发送入口。注意这个地址只显示一次关掉页面就看不到了所以最好立刻复制到密码管理器或者项目的环境变量里。如果忘了只能删掉机器人重新创建。这里有个小细节机器人创建后默认是开启状态但如果你在安全设置里选了“自定义关键词”那么你发送的每条消息里必须包含至少一个关键词否则飞书会直接拒绝。关键词可以是“告警”“日报”“构建”这类业务词也可以是“飞书”这种通用词。我见过有人设了关键词“报警”结果消息里写的是“告警”一直发不出去排查半天才发现是关键词不匹配。2.2 三种安全模式的选择逻辑与签名算法三种安全模式各有适用场景。自定义关键词最简单适合固定模板的消息比如所有告警都带“告警”两个字。但它的缺点是灵活性差如果消息内容动态变化很容易漏掉关键词。IP白名单适合服务器IP固定的场景比如公司内网的监控服务器把出口IP填进去只有这个IP能发消息。但如果你用的是云函数或者动态IP这个方案就不太靠谱。签名校验是最推荐的方式。它的原理是你在创建机器人时得到一个secret每次发送请求时用timestamp和secret计算一个签名放在请求体里。飞书收到后会用同样的算法验证签名验证通过才接收消息。这样即使Webhook地址泄露没有secret也算不出正确的签名。签名算法是HmacSHA256具体步骤是把timestamp和secret用换行符拼接成字符串然后把这个字符串作为key用HmacSHA256计算摘要最后base64编码。这里有个容易搞混的地方很多人的第一反应是用secret作为key把timestamp拼进去作为data但飞书的算法恰好相反。正确的做法是string_to_sign f{timestamp}\n{secret}然后hmac.new(string_to_sign.encode(utf-8), digestmodhashlib.sha256).digest()。注意这里没有传data参数digestmod是sha256。我第一次写的时候就是在这里写错了导致签名一直不通过。2.3 签名校验的代码实现与常见错误下面是一个Python版本的签名函数可以直接复制使用import time import hmac import hashlib import base64 def gen_sign(timestamp, secret): string_to_sign f{timestamp}\n{secret} hmac_code hmac.new( string_to_sign.encode(utf-8), digestmodhashlib.sha256 ).digest() sign base64.b64encode(hmac_code).decode(utf-8) return sign timestamp str(int(time.time())) secret 你的机器人secret sign gen_sign(timestamp, secret) print(timestamp, sign)发送请求时把timestamp和sign放到请求体里{ timestamp: 1710000000, sign: xxxxxxxxxxxxxxxx, msg_type: text, content: { text: 测试消息 } }常见的错误有三个。第一timestamp用的是毫秒飞书要求的是秒级时间戳毫秒会导致签名验证失败。第二签名计算时secret前后有空格或者复制的时候多了换行符导致字符串不一致。第三请求头没有设置Content-Type: application/json飞书会直接返回400。我建议在代码里加一个打印把string_to_sign输出出来和飞书文档里的示例对比一下很快就能定位问题。3. 消息体构造文本、富文本与交互式卡片怎么选3.1 最简文本消息与人的正确姿势文本消息是最简单的类型请求体只有两个字段msg_type和content。msg_type固定为textcontent里放一个text字段就是你要发送的内容。比如{ msg_type: text, content: { text: 服务器CPU使用率超过90%请关注 } }如果你想某个人可以在文本里用at user_idou_xxx名字/at这样的标签。注意这里的user_id推荐使用open_id也就是以ou_开头的字符串。很多人会填错成手机号或者邮箱那样不会生效只会显示成一段普通文字。获取open_id的方式后面会讲。另外如果你想所有人可以用at user_idall所有人/at但前提是机器人有所有人的权限而且不要滥用否则会被群管理员限制。文本消息的优点是简单直接适合告警和短通知。缺点是格式单一不能加粗、不能加链接、不能排版。如果消息里有表格数据纯文本会非常难读这时候就要考虑富文本或卡片。3.2 富文本消息的嵌套结构富文本消息的msg_type是post它的结构比文本复杂得多。一个典型的富文本消息长这样{ msg_type: post, content: { post: { zh_cn: { title: 每日构建报告, content: [ [ {tag: text, text: 项目飞书机器人}, {tag: text, text: 状态}, {tag: text, text: 成功, style: [bold]} ], [ {tag: a, text: 查看详情, href: https://example.com} ] ] } } } }这里的content是一个二维数组外层数组的每个元素代表一行内层数组代表这一行里的多个片段。每个片段可以是一个文本、一个链接、一个人甚至一张图片。style字段可以设置加粗、斜体、下划线等样式。富文本适合需要一定排版但又不至于用卡片的场景比如日报、周报、构建结果汇总。不过富文本的嵌套层级比较深手写很容易出错。我一般会封装一个函数传入标题和行数组自动生成富文本结构。另外要注意富文本里换行是靠数组的行来控制的不是用\n如果你在文本里写了\n它不会换行只会原样显示。3.3 交互式卡片让通知可读性翻倍交互式卡片是三种消息类型里最强大的msg_type是interactive请求体里放card字段。卡片支持标题、颜色、分栏、按钮、图片、表格等组件。一个最简单的卡片{ msg_type: interactive, card: { config: { wide_screen_mode: true }, header: { title: { tag: plain_text, content: 告警通知 }, template: red }, elements: [ { tag: div, text: { tag: lark_md, content: **主机**web-01\n**指标**CPU\n**当前值**95% } }, { tag: action, actions: [ { tag: button, text: { tag: plain_text, content: 查看监控 }, url: https://example.com, type: primary } ] } ] } }卡片的优点不仅是好看更重要的是可读性。告警信息用卡片展示标题带颜色关键字段加粗下面挂一个按钮直接跳转比一堆纯文本高效得多。而且卡片支持表格组件后面讲发送表格时会重点用到。不过卡片的结构也比较复杂建议先从一个简单卡片开始跑通之后再逐步加组件。4. 发送表格到飞书群四种方案及选型对比4.1 方案一Markdown表格转文本这是最土但也最快的方法。飞书的卡片里支持lark_md格式而lark_md支持Markdown表格语法。你可以把表格数据拼成Markdown格式塞到卡片的content里。比如{ tag: div, text: { tag: lark_md, content: | 主机 | CPU | 内存 |\n| --- | --- | --- |\n| web-01 | 95% | 60% |\n| web-02 | 80% | 70% | } }这个方法的好处是零依赖不需要额外权限只要卡片能正常发送就行。缺点是表格样式比较朴素列宽不能控制数据量大时会显得拥挤。而且Markdown表格在飞书里对中文对齐不太友好数字和文字混排时看起来会有点乱。我一般只在数据少于10行时用这个方案。4.2 方案二卡片表格组件飞书卡片原生支持表格组件可以在卡片里定义列和行。示例{ tag: table, columns: [ {name: host, display_name: 主机, data_type: text}, {name: cpu, display_name: CPU, data_type: number}, {name: memory, display_name: 内存, data_type: number} ], rows: [ {host: web-01, cpu: 95, memory: 60}, {host: web-02, cpu: 80, memory: 70} ] }表格组件的好处是样式规整支持排序和分页取决于飞书版本适合数据量稍大的场景。但要注意自定义机器人发送卡片时表格组件需要飞书客户端版本支持旧版本可能显示不出来。另外表格组件的字段名必须和columns里的name一一对应否则会显示空白。我第一次用的时候列名写成了中文行数据里的key用了英文结果表格里全是空值排查了半天。4.3 方案三截图或文件上传如果你确实需要发送Excel或PDF文件自定义机器人做不到必须用应用机器人。应用机器人可以调用飞书的文件上传接口先上传文件拿到file_key再发送文件消息。这个方法需要创建飞书应用、申请权限、获取tenant_access_token流程比自定义机器人复杂很多。如果你的场景是“每天发一份Excel日报”而且不想折腾应用可以考虑先把表格渲染成图片然后用图片消息发送。自定义机器人支持发送图片但需要先上传图片拿到image_key。上传图片的接口是独立的需要用到应用凭证所以严格来说自定义机器人本身也不能直接上传图片只能发送已经在飞书素材库里的图片。这一点很多人会误解。4.4 方案四多维表格自动化推送热词里提到了“飞书多维表格”如果你已经在用多维表格管理数据可以利用它的自动化功能。在多维表格里创建一个自动化流程触发条件可以是“定时”或“记录满足条件”执行动作选择“发送飞书消息”然后选择群聊和机器人。这样你完全不用写代码表格数据变化或定时触发时消息会自动推送到群里。这个方案的缺点是灵活性有限消息格式是固定的不能完全自定义卡片样式。但如果你只是想“有变化就通知”它是最省事的。四种方案的对比方案实现难度样式灵活度数据量限制是否需要应用权限Markdown表格低中小10行否卡片表格组件中高中50行否截图/文件高低无是多维表格自动化低低取决于表格否5. 实战用Python封装一个可复用的飞书机器人客户端5.1 基础封装签名、重试、限流直接写requests调用当然可以但每次都要算签名、处理异常、控制频率很麻烦。我习惯封装一个类把签名、发送、重试、限流都包进去。下面是一个简化版的实现import time import hmac import hashlib import base64 import requests from threading import Lock class FeishuBot: def __init__(self, webhook, secretNone): self.webhook webhook self.secret secret self.lock Lock() self.last_send 0 self.min_interval 3 # 秒控制发送频率 def _gen_sign(self): timestamp str(int(time.time())) string_to_sign f{timestamp}\n{self.secret} hmac_code hmac.new( string_to_sign.encode(utf-8), digestmodhashlib.sha256 ).digest() sign base64.b64encode(hmac_code).decode(utf-8) return timestamp, sign def send(self, msg_type, content, retry3): with self.lock: now time.time() if now - self.last_send self.min_interval: time.sleep(self.min_interval - (now - self.last_send)) self.last_send time.time() payload { msg_type: msg_type, content: content } if self.secret: timestamp, sign self._gen_sign() payload[timestamp] timestamp payload[sign] sign for i in range(retry): try: resp requests.post( self.webhook, jsonpayload, timeout10, headers{Content-Type: application/json} ) if resp.status_code 200: data resp.json() if data.get(code) 0: return True else: print(f发送失败{data}) else: print(fHTTP错误{resp.status_code}) except Exception as e: print(f请求异常{e}) time.sleep(2 ** i) return False这个类里有两个细节值得说。第一限流用了简单的锁和最小间隔因为飞书自定义机器人有频率限制默认每分钟最多20条超过会返回错误码。第二重试用了指数退避第一次失败等2秒第二次等4秒第三次等8秒避免短时间内反复冲击接口。5.2 用模板发送日报表格有了客户端之后发送日报就很简单了。假设你从数据库查出一组数据想用卡片表格组件发送bot FeishuBot(webhook你的webhook, secret你的secret) rows [ {host: web-01, cpu: 95, memory: 60}, {host: web-02, cpu: 80, memory: 70} ] card { config: {wide_screen_mode: True}, header: { title: {tag: plain_text, content: 每日巡检报表}, template: blue }, elements: [ { tag: table, columns: [ {name: host, display_name: 主机, data_type: text}, {name: cpu, display_name: CPU, data_type: number}, {name: memory, display_name: 内存, data_type: number} ], rows: rows } ] } bot.send(interactive, {card: card})注意send方法里我传的是content但卡片消息的请求体结构是{msg_type: interactive, card: {...}}所以这里的content实际上应该是{card: card}。我在封装的时候为了统一把content直接作为请求体的一部分调用时需要注意。如果你自己封装最好把不同消息类型的请求体构造分开处理。5.3 接入监控告警与CI/CD把上面的客户端接入Zabbix或Prometheus非常简单。以Zabbix为例你可以在Zabbix的报警媒介类型里选择Webhook然后把Python脚本放在Zabbix的告警脚本目录脚本接收参数比如告警级别、主机名、消息内容调用FeishuBot发送卡片。CI/CD也是类似在流水线的最后一步加一个Python脚本读取环境变量里的构建状态发送对应的卡片。这里有个经验告警消息一定要带“静默”或“恢复”的标识。很多人只发触发告警不发恢复告警结果群里一堆红色卡片大家都麻木了。我的做法是恢复时发一条绿色卡片标题写“已恢复”这样群里一眼就能看出哪些是当前问题哪些已经处理了。6. 踩坑与排查签名失败、频率限制、user_id获取6.1 签名计算最常见的三个错误签名失败是自定义机器人最高频的问题。我总结下来90%的错误集中在三个地方。第一timestamp用了毫秒。飞书要求的是秒级时间戳你如果用了int(time.time() * 1000)签名一定不对。第二secret复制时带了空格或换行。飞书的secret是一串字符复制的时候很容易多一个换行导致string_to_sign不一致。第三HMAC的key和data搞反了。正确的做法是把string_to_sign作为keydata为空很多人写成了hmac.new(secret.encode(), timestamp.encode())这样算出来的签名完全不对。排查方法很简单把string_to_sign打印出来和飞书文档里的示例对比。如果字符串一样但签名还是不对那就检查编码方式确保用的是UTF-8base64之后不要有多余的换行。6.2 频率限制与消息堆积的处理飞书自定义机器人的频率限制是每个机器人每分钟最多发送20条消息超过之后会返回错误码9499消息不会发送成功。如果你的系统在高峰期会产生大量告警很容易触发限流。我遇到过Zabbix一次性触发50条告警结果后面30条全部失败的情况。解决办法有两个。第一在客户端做聚合把多条告警合并成一条卡片发送。比如把50条告警按主机分组每个主机发一条或者全部合并成一张表格卡片。第二做队列和限流把消息放到队列里控制发送速度比如每秒最多发1条。如果消息量确实很大建议升级到应用机器人应用机器人的频率限制会宽松一些但也不是无限制的。6.3 user_id与copy_user_id的获取方式如果你想某个人需要拿到他的open_id或user_id。获取方式有几种。第一在飞书管理后台的成员管理里找到用户可以看到他的user_id。第二通过飞书开放平台的API用手机号或邮箱换取open_id但这需要应用权限。第三在群聊里如果你有开发者权限可以通过群成员列表接口获取。热词里提到的“copy_user_id”可能是指在飞书客户端里复制用户ID的功能有些版本在用户头像右键菜单里可以复制但并不是所有版本都有。对于自定义机器人来说最稳妥的方式是让用户主动提供open_id或者你在管理后台查到user_id后直接写死在配置里。注意user_id和open_id是不同的文本消息的at标签推荐用open_id以ou_开头。如果你填了user_id有些情况下也能生效但不同租户之间可能会混淆。6.4 其他容易忽略的细节还有一个坑是消息体里的换行。文本消息的text字段里\n是可以正常换行的。但富文本里不能用\n必须用数组的行来控制。卡片里的lark_md可以用\n换行但表格组件的行数据不能包含换行符否则会显示异常。另外自定义机器人发送的消息默认不会触发手机推送除非消息里包含了人。如果你希望告警能强提醒可以在消息里相关负责人。但注意人的频率也不要太高否则会被用户屏蔽。7. 自定义机器人 vs 应用机器人什么时候该换方案7.1 权限与消息能力的差异自定义机器人是群聊级别的它只能在创建它的那个群里发消息不能获取群信息不能读消息不能上传文件。应用机器人是租户级别的需要创建飞书应用、申请权限、发布版本但它能做的事情多得多可以发消息到任意群、可以接收用户消息、可以上传文件、可以获取用户信息、可以操作多维表格。简单来说自定义机器人是“一次性筷子”应用机器人是“全套厨具”。如果你的需求只是单向推送自定义机器人足够用而且省去了应用审核的麻烦。但如果你需要双向交互、文件发送、数据读取就必须走应用机器人。我建议刚开始先用自定义机器人快速验证需求如果发现能力不够再迁移到应用机器人迁移成本并不高主要是把Webhook调用改成API调用加上tenant_access_token的获取和刷新。7.2 迁移到应用机器人的触发条件什么时候该换我总结了几个信号。第一你需要往不同的群发消息而不是固定一个群。第二你需要发送文件比如Excel、PDF、压缩包。第三你需要根据用户回复做后续处理比如在群里输入“确认”然后触发某个操作。第四你需要读取群成员列表或者获取用户的open_id。第五你的消息量很大自定义机器人的限流扛不住。出现以上任何一个信号就可以考虑迁移了。迁移时注意应用机器人需要申请im:message权限发送消息的接口是POST /open-apis/im/v1/messages请求体格式和自定义机器人略有不同但整体思路是一样的。你之前封装的消息构造逻辑大部分可以复用只需要替换发送层。最后分享一个我自己的习惯不管用自定义机器人还是应用机器人我都会把Webhook地址和secret放在环境变量里不写死在代码中。因为一旦泄露别人就能往你群里发消息。另外我会在机器人名字里加上用途比如“告警机器人-生产环境”这样在群设置里一眼就能看出它是干什么的避免误删或者误用。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。