十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

飞书×腾讯会议:从机器人指令到自动创建会议的集成实践

飞书×腾讯会议:从机器人指令到自动创建会议的集成实践 在办公协同里面飞书和腾讯会议都是很多公司的主力工具。飞书管消息、文档、审批腾讯会议管线上会议两边本来各司其职但真放在同一个工作流里痛点就来了HR在飞书里发起面试行政在飞书里订会议室技术同学却要跑到腾讯会议后台手动开会再把会议号和链接复制回飞书群。线下聊的时候永远有人漏看信息线上开会的时候又总有人进错会议。这个项目就是把飞书机器人、飞书云文档、腾讯会议API串起来让用户不用离开飞书发一条消息就能自动创建会议、收到会议卡片、甚至完成日程同步。如果你是公司的运维或业务系统开发或者正准备做企业内部工具集成这篇实践记录里的权限配置、代码示例和踩坑点应该能帮你省至少两三天时间。1. 项目背景与整体设计思路1.1 两个SaaS一台戏先说为什么会有这种需求。很多公司并不是只用一个协作工具消息、文档、审批在飞书线上会议和外部沟通在腾讯会议。日常状态下没什么问题可一旦业务场景跨系统比如运营每周要开例会、HR要给候选人安排远程面试、销售要跟客户约线上演示处理方式就很原始先在飞书群里约定时间然后有人手动打开腾讯会议创建一场会议再把会议号、链接、入会密码复制回群里。参会人如果忘记存还得往回翻聊天记录。我最早接到这个需求时第一反应是“两个平台不都有开放平台吗直接拉通不就行了”真做起来才发现两边都是企业级应用权限、鉴权、数据格式各有各的一套再加上企业内部的机器人审批流事情远比想象中琐碎。但核心目标很明确把“飞书里发起动作”和“腾讯会议里创建会议”变成一条自动链路顺带把回执和提醒也一起解决掉。这个项目还有一个隐藏价值它不只是抄一个会议链接而是把“会议”变成了可编程资源。比如HR可以在飞书多维表格里维护候选人面试安排系统读取状态后自动创建腾讯会议并回写链接销售系统里客户确认了演示时间后端自动开会并把会议信息发到对应的客户群。这些都是手动操作完全做不来的。1.2 整体架构与实际数据流在动手写代码前我先把双方产品的能力边界画清楚。飞书侧能提供的核心能力是机器人、事件订阅、云文档和日程腾讯会议侧能提供的是会议管理API、用户授权和事件回调。整个对接的入口最好是飞书机器人因为员工日常习惯在群里发起动作。我最终定的数据流是这样的用户在飞书群里发一条消息比如/创建会议 今天下午3点 需求评审或者通过一个飞书表单/多维表格提交触发。飞书开放平台通过事件订阅把消息内容推送到我们的后端服务。后端服务先通过飞书API校验发送人身份拿到发送人的open_id和用户信息。后端再调用腾讯会议开放平台API带上预先授权的企业级access_token创建一场会议。腾讯会议返回会议号、会议链接、入会密码等信息。后端把会议信息组装成飞书消息卡片推送到原群里如果时间允许再通过飞书日历API把会议写入参会人日程。后续收到腾讯会议的会议状态回调如会议开始、会议结束后端可以再更新那条消息卡片或者发送提醒。这里有个很容易被忽略的点用户的open_id在飞书体系里属于用户维度而腾讯会议的会议创建者通常是企业管理员预先授权的某个应用身份。也就是说机器人发起的操作是在“应用”层面而不是某个员工个人层面。会议里的主持人默认可能是API调用时指定的userid也可以是企业管理员账号。如果要做成“谁发起的、谁就是主持人”必须额外维护飞书用户和腾讯会议账号的映射关系这个映射通常是通过OAuth授权流程拿到的。1.3 技术选型与协议要点我在这类对接里习惯用Python或Java写后端服务因为飞书和腾讯会议都提供了完整的HTTP API没有SDK也完全能调。项目里我用了Python 3 FastAPI作为接收飞书事件回调的服务框架用requests库调用两边接口。如果你团队以Java为主完全可以用Spring Boot重写逻辑是一样的。协议层面有两点必须提前想清楚飞书开放平台的接口分为tenant_access_token和user_access_token两种。前者是应用身份适合机器人发消息、读通讯录后者是用户身份适合读写某个用户自己的云文档和日程。创建会议这种企业级操作用tenant_access_token配合企业授权即可。腾讯会议的鉴权在老版本里是用app_id、secret_id、secret_key生成签名新版的开放接口则普遍使用OAuth 2.0签发access_token再放到HTTP Header的Authorization里。所以对接前一定先确认你们企业账号拿到的是哪套凭证别把老签名方案套到新接口上。我一个很深的体会是在做技术选型时不要把太多逻辑硬编码到机器人指令解析里。飞书事件的格式和腾讯会议的响应格式都可能会升级中间最好加一层适配器把两边数据结构转换成自己系统的内部模型。否则平台一改字段你的代码就得跟着动。2. 飞书开放平台侧的准备工作2.1 创建企业自建应用并获取密钥飞书侧的准备工作主要是在飞书开放平台后台完成的。进入开发者后台后点击“创建企业自建应用”填写应用名称和描述例如“会议助手”然后上传一个图标。创建完成后在“凭证与基础信息”页面能看到App ID和App Secret这两个参数后面调用所有飞书API都要用。这里要提醒一句App Secret是你的应用密钥绝对不能写在浏览器端也不要提交到Git仓库。我见过有人直接把secret硬编码在前端页面里结果被同事抓去调接口刷消息。正确做法是放到后端环境变量或密钥管理服务里。创建完应用后下一步是给应用添加机器人能力。在应用能力菜单里找到“机器人”点击启用。启用后你的应用会出现在企业群聊的添加机器人列表里这时候用户才能在群里它。2.2 权限申请与可见范围配置飞书接口的权限管控非常细每个API几乎都对应一个权限点。在这个项目里我至少申请了以下几类获取群组中所有消息和机器人消息的权限用来接收用户发起的创建会议指令。获取与发送单聊、群组消息的权限用来让机器人往群里发会议卡片。读取用户信息的权限用来通过open_id换取用户姓名等展示信息。如果后续要做日程同步需要申请读写日历的权限要做云文档表格需要申请云文档相关权限。在权限管理页面勾选后还需要在“版本管理与发布”里创建一个版本提交企业管理员审核。审核通过后应用才真正具备这些权限。这里有个典型的坑开发调试时可以临时使用测试企业或添加测试成员但线上版本必须走正式发布流程否则别人在群里机器人会提示“应用无权限”。另外就是“可见范围”。自建应用默认只对部分成员可见如果全公司都要用记得在权限配置里设置可用范围为全员否则非可用成员根本看不到机器人。2.3 云文档授权凭证的获取这个项目里我们还会把会议纪要、参会名单写到飞书云文档甚至通过飞书多维表格来触发会议创建。飞书云文档的授权凭证获取方式要根据使用场景区分。如果你是纯后端调用想创建或读取某个共享文档通常用tenant_access_token请求云文档接口就够了。获取tenant_access_token的请求非常简单curl -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal \ -H Content-Type: application/json \ -d {app_id: your_app_id, app_secret: your_app_secret}返回体里的tenant_access_token就是应用级凭证有效期一般为2小时过期后你需要重新获取。如果你要操作某个用户自己的云文档比如读取用户私有的多维表格那就需要走网页授权流程拿到user_access_token。流程是先引导用户在浏览器打开飞书授权页用户同意后回调到你配置的重定向地址并附带一个code参数后端再用这个code去POST/open-apis/authen/v2/oauth/token换取user_access_token和refresh_token。很多人在Dify这类平台上首次使用飞书云文档时总是卡在“授权凭证”这一关。本质上就是Dify作为第三方应用需要你把飞书自建应用的App ID和App Secret填进去然后在Dify的授权页面选择飞书跳转到飞书登录授权。授权完成后Dify会把token存在它自己的配置里后续读写云文档就走这个连接。如果你在Dify里看到“授权凭证”报错大概率是你在飞书开放平台创建应用时没有开启对应的云文档权限或者重定向URL没有填成Dify提供的回调地址。3. 腾讯会议侧的准备与鉴权3.1 企业账号与应用创建腾讯会议开放平台和飞书开放平台的管理粒度不太一样。首先要确认你们公司注册的是腾讯会议企业版个人免费版很多API权限是不开放的。用企业管理员账号登录腾讯会议开放平台后在“应用管理”里选择创建“企业自建应用”应用类型一般选“服务端应用”或“企业内部应用”因为我们要在服务端调用会议API。创建应用后会得到一组凭证不同版本平台叫法略有不同常见的是App ID、App Secret或者Secret ID和Secret Key。如果你们的账号还涉及腾讯云API网关可能还需要用API密钥对请求做签名。创建后还有一步很多新手会漏掉配置回调和授权域名。腾讯会议API的OAuth授权流程中授权回调地址必须提前在开放平台后台登记域名白名单也要配置否则请求会被拒绝。3.2 OAuth 2.0 鉴权流程和 token 管理腾讯会议的鉴权我踩过不少坑这里详细拆开讲。企业自建应用获取access_token通常有两种路径第一种是授权码模式。先在浏览器地址栏拼接腾讯会议的授权页URL用户通常是企业管理员登录并点击同意授权后腾讯会议回调你的服务端并带上code。服务端再拿code去换access_token和refresh_token。这个access_token可以理解为某个用户身份下的操作凭证适合需要代表具体员工创建会议的场景。第二种是应用身份模式。如果你只需要系统自动创建会议不想每个员工都去点一次授权可以使用企业应用自己的secret换取token。这个token的权限范围通常由企业管理员的授权策略决定可以做到比个人授权更宽。我在项目里用的是第二种方式因为它更符合自动化场景。拿到access_token后调用腾讯会议API的请求头大概长这样Authorization: Bearer your_access_token Content-Type: application/json有些老接口还要求额外传X-TC-Key、X-TC-Timestamp、X-TC-Nonce、X-TC-Signature这些签名头这是旧版签名方案。如果你们拿到的开放平台文档里有签名逻辑说明还在用旧版接口建议升级到新版OAuth否则后续维护成本很高。token管理上腾讯会议的access_token有效期一般在2小时左右refresh_token有效期更长。我建议用Redis缓存token并且加一把分布式锁。因为如果同一时间有多个请求发现token过期可能会并发刷新token导致前面的token失效。我在联调阶段就遇到过这种问题后来统一改成“刷新前先读缓存刷新后写回缓存再设置一个短暂的分布式锁”才彻底解决。3.3 常用接口与参数解释腾讯会议的会议管理接口设计很直接。核心的几个创建会议POST /v1/meetings查询会议详情GET /v1/meetings/{meeting_id}取消会议POST /v1/meetings/{meeting_id}/cancel获取参会成员列表GET /v1/meetings/{meeting_id}/participants修改会议PUT /v1/meetings/{meeting_id}创建会议的请求体关键字段如下{ topic: 需求评审, start_time: 1700000000000, end_time: 1700003600000, type: 1, userid: zhangsan, settings: { mute_enable: 1, allow_enter_watermark: 1, auto_record_type: none } }这里要注意start_time和end_time要传毫秒级时间戳不是秒也不是日期字符串。type表示会议类型1是预约会议0是即时会议。我们自动创建的场景一般用1。userid是腾讯会议体系里的用户ID不是飞书的open_id。需要提前在企业应用里配置好对应的用户或者通过OAuth授权拿到。settings里有很多可选项比如会中是否静音、是否开启水印、是否自动录制。按需设置即可但要注意如果开启了自动录制存储权限可能会有额外要求。4. 对接流程落地4.1 从飞书机器人触发会议创建实际操作里我推荐用飞书的“事件订阅”接收机器人消息而不是只做简单的关键词回复。事件订阅的好处是你可以拿到完整的消息上下文包括发送者open_id、群聊ID、消息内容。飞书事件订阅的第一步是校验回调地址。配置事件订阅时飞书会向你的回调URL发送一个验证请求请求体里有challenge字段你的服务需要原样返回这个字段校验才能通过。这里附一段FastAPI的处理逻辑from fastapi import FastAPI, Request from fastapi.responses import JSONResponse app FastAPI() app.post(/feishu/event) async def feishu_event(request: Request): payload await request.json() if payload.get(type) url_verification: return JSONResponse({challenge: payload.get(challenge)}) header payload.get(header, {}) event_type header.get(event_type) if event_type im.message.receive_v1: # 这里就是机器人收到消息的事件 message payload.get(event, {}).get(message, {}) content message.get(content, {}) sender payload.get(event, {}).get(sender, {}) open_id sender.get(sender_id, {}).get(open_id, ) # 把内容解析出来判断是否包含创建会议指令 # 然后调用腾讯会议创建接口 pass return JSONResponse({code: 0, msg: success})解析到用户消息后我建议不要写太复杂的语义分析。内部工具规则匹配就够了。比如消息以/创建会议开头后面跟上会议主题和时间就进入创建流程。如果时间缺省默认使用当前时间的下一个整点。4.2 创建会议接口的调用示例拿到用户指令后后端要拼装腾讯会议创建会议的请求。我先封装一个获取腾讯会议access_token的函数然后再写创建会议函数。import requests import time import json MEETING_APP_ID your_meeting_app_id MEETING_APP_SECRET your_meeting_app_secret MEETING_API_BASE https://api.meeting.qq.com/v1 def get_meeting_token(): # 这里按你们实际拿到的企业授权方式实现 # 可能是client_credentials也可能是之前的OAuth token resp requests.post( f{MEETING_API_BASE}/oauth2/token, json{ app_id: MEETING_APP_ID, secret: MEETING_APP_SECRET, grant_type: client_credentials } ) resp.raise_for_status() return resp.json()[access_token] def create_meeting(topic, start_time_ms, end_time_ms, useridadmin): token get_meeting_token() url f{MEETING_API_BASE}/meetings headers { Authorization: fBearer {token}, Content-Type: application/json } payload { topic: topic, start_time: start_time_ms, end_time: end_time_ms, type: 1, userid: userid, settings: { mute_enable: 1, allow_enter_watermark: 1 } } resp requests.post(url, headersheaders, jsonpayload) resp.raise_for_status() return resp.json()调用成功后腾讯会议会返回meeting_id、meeting_code、meeting_url、hosts等字段。其中meeting_code是入会时输入的短号码meeting_url是用户点击即可入会的链接。这些信息要原样保存到数据库并作为后续消息卡片的内容。如果创建失败一定要把腾讯会议返回的error_code和error_msg记录下来。我遇到过最典型的情况是userid不存在或者企业账号没有开通某个API的调用权限。这种错误在腾讯会议后台不一定有明显提示只有看接口返回才能定位。4.3 将会议信息回写成飞书卡片创建会议成功后不能只发一段纯文本信息不够直观。我选择用飞书消息卡片这样可以在卡片上展示会议主题、时间、参会链接还可以放按钮。飞书发送消息接口如下POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_typechat_id请求体里receive_id传群聊IDmsg_type传interactivecontent是一个JSON字符串。注意是字符串不是对象这个很多人第一次都会漏掉。卡片内容可以参考下面这样import json def build_meeting_card(meeting): card { config: {wide_screen_mode: True}, header: { title: {tag: plain_text, content: 会议已创建}, template: blue }, elements: [ {tag: div, text: {tag: lark_md, content: f**会议主题**{meeting[topic]}}}, {tag: div, text: {tag: lark_md, content: f**开始时间**{meeting[start_time_text]}}}, {tag: div, text: {tag: lark_md, content: f**会议号**{meeting[meeting_code]}}}, {tag: div, text: {tag: lark_md, content: f**入会链接**[点击入会]({meeting[meeting_url]})}}, {tag: hr}, {tag: action, actions: [ {tag: button, text: {tag: plain_text, content: 入会}, type: primary, url: meeting[meeting_url]} ]} ] } return json.dumps(card, ensure_asciiFalse)把消息发送成功后用户就能在群里直接看到会议卡片点击按钮入会。相比手动复制粘贴体验提升非常明显。如果还要把会议信息写入飞书云文档比如每天自动生成一份当天的会议汇总表可以用飞书电子表格或多维表格API。这里要注意给机器人发送表格文件和自己用云文档创建表格是两回事。机器人上传Excel文件到群聊需要先通过飞书云文档的上传素材接口拿到file_key再把file_key作为消息内容发送。我在项目里更推荐直接创建飞书电子表格然后在卡片里附上表格链接这样后续还能继续往表格里写入数据。4.4 日程同步与回调接收会议卡片发送后很多同事问“能不能顺便同步到我的飞书日历”。这确实是个很自然的延伸需求。实现方式是用飞书日历API给用户创建日程事件。飞书创建日程的接口路径是POST https://open.feishu.cn/open-apis/calendar/v4/calendars/{calendar_id}/eventscalendar_id通常用用户的主日历。请求体需要传summary标题、start_time、end_time和attendees参会人列表。这里要特别注意的是飞书日历的时间字段结构是嵌套的{ summary: 需求评审, start_time: { timestamp: 1700000000 }, end_time: { timestamp: 1700003600 } }这里的timestamp是秒级时间戳。和腾讯会议接口的毫秒级时间戳很容易混我就是在这里吃过亏明明会议创建成功了日历上显示的时间却早了8小时。后来专门写了一个时间转换工具函数腾讯会议侧用毫秒飞书日历侧用秒彻底告别这类问题。至于腾讯会议的会议状态回调开放平台可以在应用后台配置Webhook地址接收会议开始、会议结束等事件。我在这套系统里实现了收到会议开始事件后自动在飞书群里发一条提醒收到会议结束事件后把会议卡片更新为“已结束”方便后续统计。如果没有Webhook需求也可以定时轮询腾讯会议接口但实时性会差一些而且有被限流的风险。5. 常见问题与避坑实录5.1 腾讯会议摄像头不可用问题排查有同事反馈“腾讯会议不能使用电脑自带摄像头吗”其实这个和对接本身没关系但企业内部推广时一定会被问到。我在项目联调阶段也遇到过还以为是自己改了会议设置导致的问题。排查下来绝大多数情况是系统权限问题。Windows里需要在“隐私与安全性 相机”中允许应用访问摄像头macOS需要在“系统设置 隐私与安全性 摄像头”中勾选腾讯会议。如果权限没问题再看是否被其他软件占用比如钉钉、浏览器视频通话页面都可能独占摄像头。最后可以试试腾讯会议设置里的“视频测试”画面能出就代表设备正常。这个问题之所以容易被归到“对接失败”是因为用户习惯把所有异常都归结到新系统上。所以我在部署文档里专门加了一页“常用客户端问题自查”把摄像头、麦克风、扬声器的问题排查步骤写清楚收到这类报障就按流程走不浪费联调时间。5.2 飞书机器人发表格卡片反复失败飞书机器人发送表格这个功能好多人问。我一开始也想直接让机器人把一个二维表格发到群里后来发现并不像发普通文本那么简单。飞书机器人发送“表格文件”有两种常见方式一种是直接上传Excel文件到群聊另一种是发送飞书云文档的在线表格链接。前者要先把文件上传到飞书并获取file_key再由机器人发送文件消息消息类型是file而不是interactive。后者需要先调用云文档接口创建电子表格再把URL放进消息卡片里。如果你用的是Markdown语法想在卡片里画表格飞书消息卡片目前只支持lark_md不是所有Markdown表格语法都支持所以我建议不要在卡片里放复杂表格只放关键字段。如果真的需要明细数据就生成在线表格并附链接这比硬塞卡片里可靠得多。另外云文档默认权限可能是“组织内可见”如果外部参会人要打开表格需要额外设置协作者权限。否则用户点进去会看到“无权限”的提示。5.3 token过期与并发拉取这个坑我用大标题写在这里因为它最隐蔽。飞书和腾讯会议的token都会过期如果服务是多实例部署多个实例同时发现token过期并去刷新就会导致一会儿用新token一会儿用旧token接口返回401。我的解决方式是在Redis里存token并设置一个刷新锁def refresh_token_with_lock(): token redis.get(meeting_access_token) if token: return token lock_key meeting_access_token_lock got_lock redis.set(lock_key, 1, nxTrue, ex10) if not got_lock: time.sleep(0.5) return redis.get(meeting_access_token) try: new_token get_token_from_meeting_api() redis.set(meeting_access_token, new_token, ex7000) return new_token finally: redis.delete(lock_key)这个模式同样适用于飞书的tenant_access_token。虽然两边token有效期看起来都够长但企业用户量一大早晚会遇到过期问题提前设计好缓存和锁很重要。还有一个容易犯的错在代码里把token刷新和具体业务请求写在同一个事务里。如果业务请求失败不要轻易重试刷新token因为很可能token没问题是参数问题。重试只会导致多次创建会议。我建议创建会议这类操作接口要设计成幂等的在请求体里带上一个业务唯一ID腾讯会议如果收到重复请求要么返回已存在会议要么返回明确错误避免生成一堆“重复会议”。5.4 时区、会议时长和重复会议的计算时区问题在跨团队协作里尤其突出。飞书日历默认用用户本地时区而腾讯会议API的时间戳是UTC毫秒值。如果后端服务器时区设置不是东八区就有可能创建出错误时间的会议。我的处理原则是所有内部存储一律用UTC时间戳只有在展示给用户时才转换成东八区文本。创建会议时把用户输入的时间先解析成东八区时间再转成毫秒时间戳传给腾讯会议创建飞书日历时同样基于这个毫秒值转成秒设置成UTC让飞书自己根据用户时区展示。重复会议比如每周固定的周会也有特殊的接口字段。腾讯会议创建会议时有些版本的重复会议规则是通过recurrence_rule字符串设置的类似FREQWEEKLY;INTERVAL1;BYDAYFR。这个规则的格式和RFC 5545基本一致但它对中文参会者的体验不友好因为一旦需要调整某一次会议时间处理起来特别麻烦。我的建议是第一版先只做单次会议重复会议等稳定运行后再扩展。5.5 一些额外提醒最后说几个零散但很重要的点。第一不要把腾讯会议的App Secret和飞书的App Secret写进日志。我在联调时习惯把所有请求和响应都打印出来有一次不小心把完整请求头打进了日志差点导致密钥泄露。后来赶紧加了日志脱敏凡是包含secret和Authorization的字段一律打码。第二权限最小化原则。飞书应用申请权限时不要看到什么权限就勾什么。只申请当前功能需要的权限后期慢慢加。因为企业内部应用权限太大会有审计风险而且每次改权限都要重新发布版本反而拖慢进度。第三联调时一定要准备一个专用的测试企业或测试群。飞书的发布审核和腾讯会议的企业授权都比较繁琐如果在正式环境里反复调试很容易影响真实用户。我后来单独建了一个“集成测试”群机器人只在这个群里响应测试指令所有报错也只推到这个群里方便我自己看日志。第四接口调用要做好重试和退避。腾讯会议API偶尔会有5xx错误飞书API也有瞬时抖动盲目重试可能导致重复创建会议。我在代码里实现了“最多重试三次每次间隔递增”的策略并且把唯一业务ID透传到腾讯会议创建接口这样即便重试也不会创建出多场会议。我在实际项目中最深的体会是这类跨平台对接最贵的时间不是写代码而是两边权限配置和联调。尤其是飞书的权限版本发布和腾讯会议的OAuth流程文档看着简单真跑起来全是细节。如果你也是刚接手类似需求不用着急先把两边的权限位摸清楚后面的开发就会顺畅很多。
返回列表