1. 为什么要把飞书和腾讯会议这两套系统打通
1.1 一个每天都在发生的低效场景
我先说一个真实到不能再真实的场景。我所在的团队内部沟通全部走飞书,群聊、文档、审批都在里面,但对外会议基本都用腾讯会议,客户评审、跨团队例会、线上培训,一天下来能有好几场。最让人抓狂的操作是:每到开会前,总有人要在飞书群里发一条带会议链接的消息。他得先切到腾讯会议客户端,手动创建一个会议,等它生成会议号,复制入会链接,再回到飞书粘贴到群里。运气好的时候,链接和会议号一次粘贴成功;运气不好,链接带了一堆参数,发出来被群里的换行截断,参会的人点进去又说无效。
这种重复劳动看起来不起眼,但每天都在消耗团队注意力,而且特别容易出错。后来我们决定做一件事:把飞书和腾讯会议对接起来,让用户不用再打开腾讯会议客户端,直接在飞书群里@机器人,用一句话发起会议。这个需求听起来不算复杂,实际落地过程中却牵扯出飞书开放平台、腾讯会议企业API、服务端鉴权、消息卡片、事件订阅一整条链路。这篇文章就把我们完整的对接过程、代码思路和踩过的坑写出来,给同样想打通这两套系统的团队一个参考。
1.2 对接完成之后的变化
项目第一版上线后,日常使用体验发生了明显变化:
- 原先要打开腾讯会议客户端、手动创建会议、复制链接、回飞书粘贴,现在只需要在群里输入
/会议 明天14:00 客户需求对齐一句话。 - 机器人自动创建会议,并把会议主题、时间、会议号、入会链接整理成一张卡片发回群里,参会人只需要点卡片上的按钮就能入会。
- 会议结束后,服务端还能拉取会议时长和参会人列表,把会议纪要自动同步到飞书云文档,整个闭环不再依赖人工。
这套能力后面也延伸到了飞书日历场景,用户可以直接在日历事件里绑定腾讯会议链接,省掉了以前来回切换系统的麻烦。
2. 对接前必须想清楚的架构链路
2.1 飞书和腾讯会议开放平台的能力差异
先说飞书。飞书开放平台的模型是企业自建应用,开发者可以在后台创建一个应用,给它开机器人能力、申请各种API权限、配置事件订阅。整个体系围绕“应用”展开,应用既可以通过 webhook 接收消息事件,也可以主动调用 API 发送消息、读写云文档、操作通讯录。对我们这种场景来说,飞书主要负责三件事:接收用户消息、展示会议卡片、调用后续文档能力。
腾讯会议开放平台则更像一个纯 REST API 池子。它不关心你是不是要做机器人,只给你提供会议管理、录制管理、直播管理这类接口。开发者需要先在腾讯会议开放平台注册应用,拿到一组凭证,然后通过签名换取访问令牌,再调用对应接口创建会议、查询会议号。
两边的核心差异我整理成一张表,方便后续设计架构时对齐:
| 维度 | 飞书自建应用 | 腾讯会议企业API |
|---|---|---|
| 开发者入口 | 飞书开放平台开发者后台 | 腾讯会议开放平台控制台 |
| 能力载体 | 应用 + 机器人 + 事件订阅 | 应用凭证 + REST API |
| 鉴权方式 | Tenant Access Token | 应用签名 + 访问令牌 |
| 主要能力 | IM、云文档、通讯录、日历 | 会议创建、会议查询、录制管理 |
| 消息推送 | 支持 Webhook 和长连接 | 不支持,需要主动轮询或回调 |
这个差异决定了整体架构不能简单做一个“双向同步”,而是要明确哪个系统是入口,哪个系统是被调用的服务。
2.2 整体调用流程是怎么设计的
我们的最终链路是这样的:
- 用户在飞书群里 @ 机器人,发送一条包含会议意图的消息。
- 飞书后台通过事件订阅,把消息内容推送到我们的应用服务。
- 应用服务解析消息内容,提取会议主题、开始时间、参会人等字段。
- 应用服务调用腾讯会议开放平台的鉴权接口,获取访问令牌。
- 应用服务调用腾讯会议“创建会议”接口,拿到会议号和入会链接。
- 应用服务把腾讯会议返回的数据拼装成飞书消息卡片。
- 飞书开放平台把卡片消息发送到原始群聊。
看起来步骤很多,实际调用链只有三个角色:飞书负责接收和展示,腾讯会议负责创建会议,我们自己写的应用服务负责逻辑编排和数据转换。
2.3 动手前需要准备的清单
开始写代码之前,有几个前置条件必须准备好:
- 一个飞书企业管理员账号,用于在开发者后台创建自建应用并完成发布。
- 一个腾讯会议开放平台账号,最好是企业版权限,因为个人版账号很多会议管理 API 都用不了。
- 一台有公网地址的开发服务器,或者使用飞书推荐的长连接模式(这样不一定需要公网回调地址)。
- 企业内部的应用标识信息,因为腾讯会议创建会议时一般需要传入企业用户 ID 或 meeting userid。
如果团队之前没碰过这两类开放平台,建议先把文档里“创建应用”的章节过一遍,我后面也会把每一步的关键细节写出来。
3. 飞书侧配置:自建应用和机器人的完整过程
3.1 创建自建应用
登录飞书开放平台开发者后台,选择“创建企业自建应用”,填写应用名称、图标、描述,提交后系统会生成一个 App ID 和 App Secret,这两个值相当于飞书应用的登录凭证,后面服务端换取的tenant_access_token就是依赖它们。
这一步有两点容易忽略:
一是应用创建完成后默认处于“测试状态”,如果想让全公司的人都能使用,必须走一遍版本发布流程。建议先在小范围测试群里试用,确认没问题后再申请发布。
二是 App Secret 只会完整展示一次,后续想再查看可能需要重置密钥。开发阶段一定要把 App ID、App Secret 存到服务端的配置中心或者环境变量里,不要写死在代码仓库中。
3.2 开启机器人和事件订阅
在应用的“添加应用能力”里找到机器人,启用之后,应用才会出现在飞书群的 @ 列表里。启用机器人时记得设置一个相对好记的名字,比如“会议助手”,这样用户输入指令时不会找错对象。
接下来配置事件订阅。飞书提供了两种接收方式:一种是传统的 Webhook 回调地址,需要你在服务端提供一个公网可访问的 URL;另一种是基于 WebSocket 的长连接模式,飞书 SDK 会主动建立连接并推送事件。我们最终选了长连接模式,主要原因是公司内网服务器没有固定的公网地址,长连接省去了回调地址的暴露和签名验证环节。
在“事件与回调”配置页,添加事件im.message.receive_v1(接收消息事件),这个事件会在群成员 @ 机器人并发送消息时触发。保存后飞书会推送一条url_verification验证消息(Webhook 模式),需要服务端返回一个叫challenge的字段,验证通过后事件订阅才会真正生效。
3.3 申请必要的权限范围
飞书 API 的权限申请是最容易卡住的环节,很多接口调用报错都是因为权限没开。
我们这次至少需要以下几类权限:
| 权限标识 | 说明 |
|---|---|
im:message | 读取用户发给机器人的消息内容 |
im:message:send_as_bot | 作为机器人发送消息 |
im:chat:readonly | 读取群基础信息,用于获取 chat_id 对应的群名称 |
contact:user.base:readonly | 读取用户基本信息,用于把 open_id 映射为用户名 |
docx:document | 云文档相关权限,后续做会议纪要同步时使用 |
申请后在开发者后台提交上线,等待管理员审核即可。特别注意,飞书权限是按“应用”维度生效的,不是在接口调用时临时获取的,如果某天新增了接口需求,需要回到后台补充权限并重新发布版本。
3.4 测试机器人是否正常接收消息
配置完成后,在飞书群里 @ 机器人发一条“Hello”,服务端会收到一条事件推送。我们在这一步的逻辑很简单:收到消息事件后,先读取header.event_type,确认是im.message.receive_v1,再读取event.message.content,里面是一段 JSON 字符串,存放消息文本内容。
实测下来,长连接模式下的消息到达延迟在几十毫秒级别,完全够用。唯一需要注意的是,飞书长连接 SDK 内部会做断线重连,不能简单地在服务启动后就不管了,建议把连接状态监控暴露一个健康检查接口,方便运维观察。
4. 腾讯会议企业 API 的接入准备
4.1 获取应用凭证
进入腾讯会议开放平台,创建一个“企业应用”,创建完成后可以获得三个关键凭证:App ID、Secret ID、Secret Key。App ID 用于标识应用,Secret ID 和 Secret Key 用于生成调用签名。
这里有个细节让我们当时绕了一圈:腾讯会议 API 调用时,很多接口要求X-TC-Key请求头直接放 App ID,而不是 Tencent Cloud Account 的 ID,两者长得像但含义完全不同。如果你用错了 ID,签名校验会直接报错,排查时容易误以为是密钥问题。
4.2 签名机制是绕不开的一关
腾讯会议企业 API 使用的是 HMAC 签名机制,而不是简单的 Token。签名生成需要把多个字段按固定顺序拼接,然后用 Secret Key 做 HMAC-SHA256 运算。每个请求都需要生成一次签名,核心字段包括:
appId:应用 IDsecretId:应用 Secret IDtimestamp:当前 Unix 时间戳nonce:随机字符串或数字ver:版本号,一般固定为 1
拼接规则是类似appId=xxx&secretId=xxx×tamp=xxx&nonce=xxx&ver=1这样的 URL 编码形式,然后使用 Secret Key 计算哈希值,最终把签名放到请求头X-TC-Signature中。实际编码时建议直接用官方提供的签名工具类或者参考语言对应的 SDK,而不是自己手写拼接逻辑,因为字段顺序和编码方式很容易出错。
4.3 获取访问令牌并缓存
签名通过后,调用腾讯会议的鉴权接口获取访问令牌,后续创建会议的请求都需要携带这个 Token。令牌有一个有效期,短则一小时,长则一天,不同环境不一样。千万不要每次调用会议接口都重新换一次 Token,腾讯会议侧对接口调用频率控制得比较严格,频繁换取容易触发限流。
我们的做法是在服务端加了一层缓存,以 App ID 为键缓存访问令牌,在过期前 5 分钟进行一次预刷新。这个策略后来在会议并发场景下效果不错,没有因为 Token 过期导致创建会议失败。
5. 核心功能落地:从机器人指令到自动建会
5.1 服务端接收飞书消息的代码骨架
我用 Go 语言做了服务端,长连接接事件订阅用的飞书官方 SDK,消息处理的骨架大概是这样的:
func handleMessage(ctx context.Context, event *lark.EventV2) error { eventType := event.Header.EventType() if eventType != "im.message.receive_v1" { return nil } msg := event.Event["message"].(map[string]interface{}) chatID := msg["chat_id"].(string) content := msg["content"].(string) msgType := msg["message_type"].(string) if msgType != "text" { return nil } // content 是 JSON 字符串,解析后拿到 text 字段 var data struct { Text string `json:"text"` } _ = json.Unmarshal([]byte(content), &data) go handleCommand(ctx, chatID, data.Text) return nil }需要注意,飞书推送的content字段是字符串,里面嵌套着 JSON,必须先反序列化一次才能拿到用户发的文本内容。如果用户在消息里 @ 了机器人,文本内容里会带上机器人的 open_id 前缀,解析指令时需要过滤掉。
5.2 解析指令并调用腾讯会议 API
我们的指令设计成了两种格式:一个是指令加参数的/会议命令,另一个是纯文本的自然语言解析,用正则提取时间和主题。自然语言的准确性暂不做保证,正则优先匹配“明天/今天/具体日期+时间+主题”的模式。
解析完成后,调用腾讯会议创建会议接口。腾讯会议创建会议的接口路径是 POST/v1/meetings,请求体示例:
{ "title": "客户需求对齐", "start_time": "1719907200", "end_time": "1719910800", "userid": "zhangsan", "type": 1, "settings": { "mute_enable": 1, "allow_enter_watermark": true, "auto_record": 1 } }几个关键字段的注意点:
start_time和end_time是 Unix 时间戳,单位是秒,而且是 UTC 时间戳。如果直接传 JavaScript 的Date.now()毫秒值,或者传了本地时间的秒值,会导致会议时间偏差,差 8 个小时的情况我们遇到过不止一次。userid必须传入腾讯会议侧的会议发起人 ID,一般是企业通讯录里的用户名,不是飞书的 open_id。如果两边身份体系没有打通,这里需要做一层映射,否则会议创建成功但发起人不匹配,后续无法用接口操作会议。type表示会议类型,0 代表普通预约会议,1 代表固定会议号会议,2 代表网络研讨会。具体看场景需求,内外部沟通我们一般用 1,方便用户记住会议号。
调用成功后,接口会返回包括meeting_id、meeting_code、join_url在内的信息。
5.3 把会议信息封装成飞书卡片
腾讯会议返回的内网数据格式比较原始,直接文本丢到群里也能看,但体验一般。我们最终做的是飞书消息卡片,格式类似:
{ "msg_type": "interactive", "card": { "header": { "title": {"tag": "plain_text", "content": "会议创建成功"} }, "elements": [ {"tag": "div", "text": {"tag": "lark_md", "content": "**主题**:客户需求对齐\n**时间**:6月20日 14:00-15:00"}}, {"tag": "div", "text": {"tag": "lark_md", "content": "**会议号**:123456789"}}, {"tag": "action", "actions": [ {"tag": "button", "text": {"tag": "plain_text", "content": "加入会议"}, "url": "https://meeting.tencent.com/dm/link..."} ]} ] } }发送接口是飞书的 POST/open-apis/im/v1/messages,需要在 URL 参数里指定receive_id_type=chat_id,请求体里的receive_id填群聊的 chat_id。同样要带上飞书的tenant_access_token,这个 token 是用 App ID 和 App Secret 换取的。
卡片发出去后,用户点“加入会议”按钮就能直接跳转到腾讯会议客户端,整个流程不需要手动复制任何内容。
6. 实测过程中遇到的那些坑
6.1 飞书事件验证一直不通过
第一次配置 Webhook 回调时,飞书后台提示“验证 URL 失败”。原因是我返回的响应体结构不对。飞书要求回调接口在收到url_verification事件时,直接返回一段 JSON,里面必须包含challenge字段,并且Content-Type必须是application/json。当时我写的是纯文本返回,导致验证一直失败。
如果你用的是长连接模式,不会遇到这个问题,但如果团队服务器有公网地址、又想用 Webhook,这个细节要多留意。
6.2 腾讯会议创建会议返回 401
创建会议的请求一直返 401,最初以为是 Token 没带上,排查后发现问题出在签名上。签名里的nonce我用的是一个固定字符串,而不是每次请求生成随机值。腾讯会议侧的校验会把nonce作为签名内容的一部分,如果两次请求 nonce 相同,哪怕时间戳变化,也会被判定为非法请求。解决办法是每次请求都重新生成 nonce,推荐用 UUID 或随机数。
6.3 会议时间差 8 小时
这是最经典的坑。我最初在前端写了一个测试脚本,把参数里的时间直接传成了本地时间的new Date()毫秒值,结果腾讯会议后台显示的会议时间比预期晚了 8 个小时。原因就是start_time需要的是 UTC 时间戳。后排问题时,我们用了一个笨办法:把服务端日志里的请求体打印出来,再和腾讯会议后台的实际时间串在一起对比,才定位到时间戳单位问题。
提醒一下,对接时所有时间参数尽量统一用 UTC 时间戳,前端展示时再转本地时区,不要在服务端做任何时区换算。
6.4 用户身份映射不一致
飞书侧拿到的用户 ID 是open_id,腾讯会议侧需要的是会议室系统里的userid。两者不是一个体系,如果企业内部没有统一身份源,就需要在服务端维护一个映射表,或者通过飞书通讯录里的企业邮箱/工号字段,去和腾讯会议的企业通讯录匹配。这个映射表在初期问题不大,但随着人员变动会变得很头疼,建议尽早接统一身份源。
6.5 飞书发送消息偶发下拉限流
上线初期,群消息发送偶尔会失败,错误信息是“请求过多”。排查后发现是飞书开放平台对机器人发消息有频控,尤其是同一群聊里短时间连续发多条消息时。我们的解决办法是在发送侧做了一个简单的带令牌桶的限流,并且对发送失败的消息做了三级重试:第一级 5 秒重试,第二级 30 秒重试,第三级转入死信队列由人工处理。
另外,如果服务端收到飞书事件后有大量异步任务,建议先入队再处理,不要直接在事件回调线程里执行耗时操作。
7. 后续可以继续扩展的几个方向
7.1 会议纪要自动同步飞书云文档
会议结束后,腾讯会议侧会生成录制文件和参会人记录。我们可以通过腾讯会议的“查询会议”接口拿到会议持续时间、参会人列表,再结合飞书云文档的异步任务接口,把一份自动生成的会议纪要写入飞书文档。实现起来有几个前置条件:需要飞书应用有云文档写权限,同时云文档需要被设置为“组织内部可阅读”,否则生成的文档默认只有机器人自己能看,影响实际使用。
7.2 给机器人加上 AI 能力,对接 Dify
最近很多团队在做 AI 知识库,飞书云文档作为企业内部知识的承载者扮演了重要角色。我在实践过程中发现一个经常被问到的点:初次使用 Dify 接入飞书云文档时,授权凭证到底去哪里拿。
实际上就是在飞书开放平台创建一个自建应用,在权限管理里开通docx:document和drive:drive相关权限,然后把应用的 App ID 和 App Secret 填到 Dify 的飞书云文档集成页面里,Dify 会引导你完成授权流程。如果你已经按照这篇文章建好了一个飞书应用,只需要在原有应用上补权限,重新发布即可,不需要再额外建一套应用。
有了 AI 能力之后,会议助手的对话就不再只是简单的指令解析了,它可以做到:用户发一句“帮我总结上次的客户会议”,机器人自动去腾讯会议拉取最近一次会议的参会名单和时间,再结合飞书云文档里的历史记录,生成一段结构化总结。我们目前已经在公司内部尝试这个方向。
7.3 从飞书日历事件一键拉起会议
比起在群里发指令,很多用户更习惯在飞书日历里建日程。如果日历事件能一键带上腾讯会议入会链接,体验会更顺滑。实现方式是通过飞书日历的事件订阅接口,监听某个日程的创建事件,然后把腾讯会议创建的入会链接写入事件的location字段。要注意的是,飞书日历事件更新接口的权限和消息模块权限相互独立,需要单独申请。
上面这些就是我在飞书和腾讯会议对接过程中,从架构设计到落地实现再到排错的全过程。最后再分享一点个人心得:这类系统对接项目,真正的复杂度往往不在代码本身,而在两端平台的口径差异上——时间戳单位、ID 体系、权限模型、签名规则,每个点都可能让排查变成一个下午的事。建议正式开始前把双方的官方文档通读一遍,尤其是权限范围和参数含义部分,然后先做最小闭环验证,再逐步加复杂功能,这样后面踩坑的密度会小很多。