飞书与腾讯会议API对接实战:会议自动创建、录制回传与待办生成
1. 为什么要在飞书和腾讯会议之间搭一座桥很多团队现在的协作状态是这样的日常沟通、文档协作、审批流全在飞书里跑但一到开会尤其是跟外部客户或者跨部门的大型会议大家还是习惯性地点开腾讯会议。结果就是——会议链接满天飞会议纪要没人整理待办事项散落在各个聊天窗口里开完会等于失忆。我所在的团队大概在半年前也是这个状态。飞书群聊里发一个腾讯会议链接会开完了录播文件在腾讯会议云端聊天记录里的结论被刷屏淹没谁负责什么全靠脑子记。后来我们花了两周时间做了一套飞书和腾讯会议的对接方案核心目标就三个会议自动创建并同步到飞书日历、会议录制和纪要自动回传到飞书群、会议待办自动生成飞书任务。这套方案落地之后最直观的变化是开会前不用再手动复制会议号发群里开会后五分钟内纪要链接和待办清单就自动出现在飞书群里。对于每天至少三场会的团队来说省下来的时间相当可观。这篇文章会从对接的整体架构设计讲起然后拆解API鉴权的具体实现、会议生命周期事件的订阅与处理、录制文件与智能纪要的回传链路最后分享几个我在实操中踩过的坑和对应的解决方案。适合有一定开发基础、正在考虑做飞书和腾讯会议集成的同学参考也适合产品经理了解这套对接的可行性和边界。提示本文涉及的API调用均基于官方公开文档的通用实践具体参数以你实际使用的版本为准。所有代码示例为逻辑示意生产环境需要补充错误处理和重试机制。2. 对接架构的选型与整体数据流设计2.1 三种对接方式的对比与选择依据在动手写代码之前首先要搞清楚飞书和腾讯会议之间到底有哪些连接点。我调研下来可行的对接方式主要有三种对接方式实现原理适用场景维护成本开放平台API直连分别调用飞书开放平台和腾讯会议开放平台的REST API在中间层做数据转换需要深度定制、数据双向同步中高Webhook事件订阅订阅腾讯会议的会议事件回调触发飞书侧的机器人消息或日历操作会议状态变更通知、自动触发后续动作中第三方集成平台使用Zapier、集简云等低代码平台做连接快速验证、轻量级需求低我们最终选择了API直连Webhook事件订阅的组合方案。原因很简单第三方平台虽然上手快但一旦涉及到自定义字段映射、复杂的条件判断、或者需要把会议数据写入飞书多维表格做统计分析就会遇到天花板。而API直连虽然前期投入大一些但后续的扩展性完全掌握在自己手里。具体来说我们的架构分成了四个模块会议创建模块在飞书日历中创建日程时自动调用腾讯会议API生成会议号和入会链接回填到日程描述中。事件订阅模块通过腾讯会议的Webhook接收会议开始、结束、录制完成等事件触发后续处理。数据处理模块将腾讯会议返回的录制文件、智能纪要等内容转换成飞书消息卡片或云文档格式。消息推送模块通过飞书自建机器人将处理后的内容推送到指定群聊或私聊。2.2 数据流的完整链路拆解整个数据流可以拆成两条主线创建线和回传线。创建线的流程是这样的用户在飞书日历中新建一个日程填写会议主题和时间。我们的中间服务监听到日历事件创建后提取会议主题、开始时间、结束时间、参会人列表调用腾讯会议的创建会议接口。腾讯会议返回会议号、入会链接、主持人密码等信息后中间服务再调用飞书日历的更新接口把这些信息写入日程描述同时给参会人发送飞书消息通知。回传线的流程稍微复杂一些腾讯会议在会议结束后会触发录制完成事件Webhook推送到我们的中间服务。中间服务根据事件中的会议ID调用腾讯会议的文件列表接口获取录制文件和智能纪要的下载地址。然后调用飞书云文档的上传接口把录制文件转存到飞书云空间生成分享链接。最后通过飞书机器人把会议主题、录制链接、纪要摘要、待办事项组装成一张消息卡片推送到对应的飞书群。这里有一个关键的设计决策中间服务用什么样的部署方式。我们试过两种方案一种是用飞书低代码平台搭建另一种是用自建的Python服务。低代码平台的优势是开发快、不用管服务器但劣势也很明显——处理大文件上传时容易超时而且调试起来比较麻烦。最终我们选择了自建服务用FastAPI做Web框架部署在一台2核4G的云服务器上日常几十场会议的并发完全够用。2.3 鉴权体系的设计双平台Token管理飞书和腾讯会议的鉴权机制不太一样需要分别处理。飞书开放平台用的是tenant_access_token和user_access_token两套体系。tenant_access_token是以应用身份调用API适合机器人发消息、上传文件这类操作user_access_token是以用户身份调用适合访问用户的日历、云文档等个人资源。tenant_access_token的有效期是2小时需要在过期前刷新。腾讯会议开放平台用的是AppIdSecretKey换取access_token的方式有效期也是2小时。另外腾讯会议的Webhook回调需要验证签名签名算法是基于时间戳和密钥的HMAC-SHA256。我们的做法是在中间服务里维护一个Token管理器用定时任务在Token过期前30分钟自动刷新同时把Token缓存在内存里避免每次API调用都去请求新Token。这里有个细节飞书的tenant_access_token刷新时旧的Token会立即失效所以如果你的服务是多实例部署的需要用一个共享的缓存比如Redis来存储Token否则会出现实例A刷新了Token实例B还在用旧Token导致鉴权失败的情况。# Token管理器的简化逻辑示意 import time import redis import requests class TokenManager: def __init__(self, app_id, app_secret, redis_client): self.app_id app_id self.app_secret app_secret self.redis redis_client self.cache_key ffeishu_token:{app_id} def get_token(self): token self.redis.get(self.cache_key) if token: return token.decode() # 请求新Token resp requests.post( https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal, json{app_id: self.app_id, app_secret: self.app_secret} ) data resp.json() token data[tenant_access_token] # 缓存时预留300秒缓冲 self.redis.setex(self.cache_key, 7200 - 300, token) return token注意腾讯会议的access_token刷新时旧Token不会立即失效但为了保持一致性和安全性建议同样采用提前刷新的策略。3. 会议创建环节的API调用与字段映射3.1 飞书日历事件到腾讯会议参数的转换逻辑飞书日历的事件结构和腾讯会议的创建会议接口字段并不是一一对应的需要做一层映射。我整理了我们实际用到的字段对照表飞书日历字段腾讯会议字段转换逻辑summary日程标题subject会议主题直接映射超过40字符截断start_time.timestampstart_time时间戳直接传递end_time.timestampend_time时间戳直接传递description-不映射用于存放会议号回填attendee_ability-不映射-password自动生成6位数字密码-auto_record根据会议类型判断内部会议开启这里有一个容易忽略的点腾讯会议的创建会议接口要求传入的是秒级时间戳而飞书日历返回的是毫秒级时间戳。如果不做转换会议时间会变成几万年后这个坑我踩过排查了半天才发现是单位问题。另外腾讯会议的会议主题有长度限制超过40个字符会报错。我们的处理方式是如果飞书日程标题超过40字符截取前37个字符加...同时在会议描述里保留完整标题。3.2 参会人同步的两种策略邀请制与密码制腾讯会议的参会人管理有两种模式一种是邀请制需要把参会人的手机号或邮箱添加到会议邀请列表里另一种是密码制任何人只要有会议号和密码就能加入。我们一开始用的是邀请制把飞书日程里的参会人邮箱同步到腾讯会议的邀请列表。但很快发现一个问题外部联系人没有飞书账号也就没有邮箱导致外部客户收不到会议邀请。后来改成了混合策略内部参会人通过邀请制添加确保他们能在腾讯会议客户端直接看到会议。外部参会人不添加到邀请列表而是把会议号和密码通过飞书消息发送给会议组织者由组织者自行转发。这个策略的调整让外部会议的创建成功率从60%提升到了接近100%。如果你也在做类似的对接建议一开始就考虑外部参会人的场景不要等到上线后才发现问题。3.3 会议号回填与日历更新的时序问题创建完腾讯会议后需要把会议号、入会链接、密码回填到飞书日历的日程描述里。这里有一个时序问题飞书日历的事件创建和腾讯会议的创建是异步的如果处理不当会出现日程已经创建但会议号还没回填的情况。我们的解决方案是在中间服务里维护一个任务队列。当监听到飞书日历事件创建后先把任务放入队列立即返回成功响应给飞书避免飞书重试。然后后台Worker从队列中取出任务调用腾讯会议API创建会议再调用飞书日历更新接口回填信息。如果腾讯会议API调用失败任务会重试三次三次都失败则发送告警消息到运维群。# 任务队列的简化处理逻辑 from celery import Celery app Celery(tasks, brokerredis://localhost:6379/0) app.task(bindTrue, max_retries3) def create_meeting_and_update_calendar(self, event_data): try: # 调用腾讯会议API创建会议 meeting_info tencent_meeting_client.create_meeting( subjectevent_data[summary][:40], start_timeevent_data[start_time] // 1000, end_timeevent_data[end_time] // 1000 ) # 回填到飞书日历 feishu_client.update_calendar_event( event_idevent_data[event_id], descriptionf会议号{meeting_info[meeting_code]}\n f入会链接{meeting_info[join_url]}\n f密码{meeting_info[password]} ) except Exception as exc: # 重试采用指数退避策略 raise self.retry(excexc, countdown2 ** self.request.retries)提示飞书日历的更新接口有频率限制建议在任务队列中做限流避免短时间内大量更新触发限流。4. Webhook事件订阅与会议状态流转处理4.1 腾讯会议Webhook的签名验证与事件类型腾讯会议的Webhook回调需要验证签名签名算法是把时间戳、随机字符串、请求体拼接后用SecretKey做HMAC-SHA256加密然后Base64编码。服务端收到回调后用同样的算法计算签名与请求头中的签名比对一致才处理。我们订阅的事件类型主要有四种meeting.started会议开始用于在飞书群里发送会议已开始的提醒。meeting.ended会议结束触发录制文件处理流程。recording.completed录制完成获取录制文件下载地址。smart_summary.completed智能纪要生成完成获取纪要内容。这里有一个坑腾讯会议的Webhook回调有重试机制如果服务端没有在5秒内返回200腾讯会议会重试推送最多重试3次。这意味着你的处理逻辑必须足够快或者采用先响应、后处理的模式。我们的做法是收到回调后先验证签名然后把事件放入消息队列立即返回200后台Worker再慢慢处理。4.2 会议结束事件的触发条件与延迟处理meeting.ended事件并不是在会议结束的瞬间触发的而是有一个延迟。根据我们的实测延迟时间在30秒到2分钟之间。这个延迟是正常的因为腾讯会议需要确认所有参会人都已离开并且完成一些清理工作。但录制文件的生成时间就更长了。recording.completed事件通常在会议结束后5到15分钟才触发具体取决于会议时长和录制文件大小。我们的处理策略是收到meeting.ended事件后先发送一条会议已结束录制文件正在生成中的提示消息到飞书群。等收到recording.completed事件后再发送包含录制链接的完整消息。这里有一个细节需要注意如果会议没有开启录制就不会有recording.completed事件。所以我们的逻辑里加了一个判断如果会议结束后30分钟内没有收到录制完成事件就发送一条本次会议未开启录制的提示避免用户一直等待。4.3 事件幂等处理避免重复推送消息Webhook回调可能会重复推送同一个事件尤其是在网络抖动或者服务端响应超时的情况下。如果不做幂等处理飞书群里就会出现多条重复的会议纪要消息体验很差。我们的做法是在Redis里维护一个事件ID的集合每个事件处理前先检查是否已经处理过。如果已经处理过直接返回200不再执行后续逻辑。事件ID的过期时间设置为24小时足够覆盖腾讯会议的重试周期。# 幂等处理的简化逻辑 def handle_webhook_event(event): event_id event[event_id] # 使用Redis的SETNX实现原子性检查 if not redis.setnx(fevent:{event_id}, 1): # 事件已处理过直接返回 return {code: 0} # 设置过期时间 redis.expire(fevent:{event_id}, 86400) # 处理事件 process_event(event) return {code: 0}注意幂等处理的key一定要包含事件ID不要用会议ID因为同一个会议会有多个事件开始、结束、录制完成等。5. 录制文件与智能纪要的回传链路5.1 录制文件下载与飞书云空间上传腾讯会议的录制文件默认存储在腾讯云上下载地址有时效性通常是7天。我们的做法是收到recording.completed事件后立即调用腾讯会议的文件下载接口把录制文件下载到本地临时目录然后调用飞书云文档的上传接口上传到飞书云空间。这里有一个大文件上传的问题如果会议录制文件超过100MB直接上传可能会超时。飞书云文档的上传接口支持分片上传需要先把文件切成4MB的分片逐个上传最后调用完成上传接口合并。# 分片上传的简化逻辑 def upload_large_file(file_path, file_name): # 初始化分片上传 upload_id feishu_client.init_chunk_upload(file_name) chunk_size 4 * 1024 * 1024 # 4MB with open(file_path, rb) as f: chunk_index 0 while True: chunk f.read(chunk_size) if not chunk: break feishu_client.upload_chunk(upload_id, chunk_index, chunk) chunk_index 1 # 完成上传 file_token feishu_client.complete_chunk_upload(upload_id) return file_token上传完成后飞书会返回一个file_token需要再调用获取文件元信息接口拿到文件的分享链接。这个链接可以直接在飞书消息卡片中展示点击即可在飞书内预览或下载。5.2 智能纪要的格式转换与消息卡片组装腾讯会议的智能纪要返回的是结构化的JSON数据包含会议主题、参会人、发言摘要、待办事项等字段。飞书的消息卡片支持Markdown格式但字段结构需要自己组装。我们设计的消息卡片包含四个部分会议基本信息会议主题、开始时间、结束时间、参会人数。录制文件链接如果有录制展示查看录制按钮。智能纪要摘要截取前200字的会议摘要附上查看完整纪要按钮。待办事项列表把智能纪要中提取的待办事项以复选框的形式展示。这里有一个经验智能纪要的待办事项提取准确率不是100%有时候会把一些讨论内容误判为待办。我们的做法是在消息卡片底部加一个编辑待办的按钮点击后跳转到飞书任务页面用户可以手动调整。5.3 待办事项自动生成飞书任务的实现飞书任务Task的API支持创建任务、设置负责人、设置截止时间。我们把智能纪要中的待办事项提取出来后调用飞书任务API批量创建任务。这里有一个细节待办事项的负责人识别。智能纪要中通常会提到张三负责跟进但格式不固定有时候是张三有时候是张三有时候是张三同学。我们的做法是用正则表达式匹配xxx和xxx负责两种模式匹配到的名字再去飞书通讯录里查找对应的用户ID。如果找不到就把任务创建为无负责人状态并在消息卡片中提示用户手动指派。# 待办事项提取的简化逻辑 import re def extract_todos(summary_text): todos [] # 匹配 xxx 或 xxx负责 的模式 patterns [ r(\w)\s(.), r(\w)负责(.) ] for pattern in patterns: matches re.findall(pattern, summary_text) for match in matches: todos.append({ assignee: match[0], content: match[1].strip() }) return todos提示飞书任务的创建接口有频率限制建议批量创建时加一个100毫秒的间隔避免触发限流。6. 实操中踩过的坑与排查思路6.1 Token过期导致的鉴权失败一次完整的排查过程上线后的第三天运维群里突然收到告警飞书消息推送失败错误码是99991663提示tenant_access_token invalid。我第一反应是Token过期了但检查了Token管理器的日志发现Token在30分钟前刚刚刷新过。排查过程是这样的先看Token管理器的日志确认刷新成功然后看API调用的日志发现失败的那次调用用的Token和刷新后的Token不一致。问题定位到了我们的服务部署了两个实例实例A刷新了Token但实例B还在用内存里缓存的旧Token。解决方案就是把Token缓存从内存迁移到Redis两个实例共享同一个Token。这个问题在单实例部署时不会出现但一旦做了负载均衡就会暴露。如果你也打算做多实例部署建议一开始就用共享缓存。6.2 会议号回填失败日历更新接口的并发限制有一段时间用户反馈说创建日程后会议号有时候能回填有时候不能。排查后发现是飞书日历更新接口的并发限制问题同一个日历的更新操作每秒最多5次。当多个用户同时创建日程时更新请求会排队超过限制的请求会被拒绝。我们的解决方案是在任务队列中增加一个限流器用Redis的令牌桶算法控制更新频率。具体来说每个日历维护一个令牌桶每秒生成5个令牌更新请求需要先获取令牌才能执行。这个改动之后会议号回填的成功率从85%提升到了99.9%。6.3 录制文件下载超时大文件分片与断点续传前面提到过大文件上传需要分片。但下载同样有问题腾讯会议的录制文件下载接口如果文件超过500MB直接下载可能会超时。我们的做法是先用HEAD请求获取文件大小如果超过100MB就采用Range请求分片下载每片10MB下载完一片写入本地文件最后合并。# 分片下载的简化逻辑 def download_large_file(url, save_path): # 获取文件大小 head_resp requests.head(url) file_size int(head_resp.headers[Content-Length]) chunk_size 10 * 1024 * 1024 # 10MB with open(save_path, wb) as f: for start in range(0, file_size, chunk_size): end min(start chunk_size - 1, file_size - 1) headers {Range: fbytes{start}-{end}} resp requests.get(url, headersheaders) f.write(resp.content) return save_path这个方案还有一个好处如果下载中途失败可以从最后一个成功的分片继续下载不用从头开始。6.4 消息卡片按钮无响应飞书卡片回调的配置陷阱飞书消息卡片上的按钮点击后需要配置回调地址。我们一开始把回调地址配置成了内网地址导致点击按钮没有任何反应。排查后发现飞书的卡片回调必须是一个公网可访问的HTTPS地址而且需要在飞书开放平台的后台配置白名单。另外卡片回调的请求体格式和普通消息回调不一样需要单独解析。我们的做法是写一个统一的回调处理器根据请求体中的type字段判断是卡片回调还是消息回调分别处理。7. 这套对接方案还能怎么扩展7.1 会议数据沉淀到飞书多维表格做统计分析我们最近在做的一个扩展是把每次会议的基本信息主题、时长、参会人数、录制文件大小写入飞书多维表格然后利用多维表格的仪表盘功能做统计分析。比如可以直观地看到哪个部门的会议最多、平均会议时长是多少、哪些会议没有开启录制等。这个扩展的实现很简单在会议结束事件的处理逻辑中增加一步调用飞书多维表格的写入接口。需要注意的是多维表格的字段类型要提前定义好日期字段要传时间戳人员字段要传用户ID。7.2 与飞书审批流结合会议室预定与会议创建的联动另一个有意思的扩展方向是跟飞书审批流结合。比如员工在飞书里提交一个会议室预定审批审批通过后自动在腾讯会议创建对应的线上会议并把会议号回填到审批单里。这样线下会议室和线上会议就绑定在一起了参会人既能到现场也能远程接入。这个扩展需要用到飞书审批的实例回调在审批通过的事件中触发会议创建逻辑。审批流的字段映射比日历复杂一些因为审批单的字段是自定义的需要根据审批模板的ID做不同的处理。7.3 常见问题速查表最后整理一份我们运维过程中积累的常见问题速查表方便遇到问题时快速定位问题现象可能原因排查方向消息推送失败错误码99991663Token过期或无效检查Token管理器日志确认刷新是否成功会议号未回填到日历日历更新接口限流检查任务队列的限流配置查看是否有429错误录制文件下载失败下载地址过期或文件过大检查事件处理延迟确认是否在7天内下载卡片按钮点击无反应回调地址未配置或非公网检查飞书开放平台后台的回调配置待办事项负责人识别错误智能纪要格式不固定优化正则表达式增加人工确认环节这套对接方案从最初的简单消息推送到现在完整的会议生命周期管理前后迭代了大概五六个版本。最大的体会是不要试图一次性把所有功能都做完先把最核心的会议创建录制回传跑通然后再逐步增加智能纪要、待办生成、数据分析这些扩展功能。每增加一个功能都要确保有完善的错误处理和告警机制否则出了问题很难排查。另外飞书和腾讯会议的API都在持续更新建议定期关注官方文档的变更日志避免因为接口调整导致服务不可用。我们现在的做法是每个月做一次回归测试确保核心链路始终可用。