钉钉与飞书机器人接入企业级智能体:过程及两份排障记录

同一套渠道网关,先接钉钉,再接飞书。两边都踩了坑,但真正需要改代码的只有两处,其余是环境、升级、权限和身份绑定的问题。

把过程记下来,给后面接入的人少走点弯路。

先定接入形态

钉钉只有 Stream 长连接,没有 Webhook 可选。飞书两种都支持:有公网可达的 HTTPS 地址就走 Webhook,没有就选长连接(ws)。

长连接的好处是没有入站连接,不需要公网 IP、域名备案和内网穿透,回调地址的 URL 校验、Encrypt Key、Verification Token 也都不用参与。代价是连接生命周期要自己维护,钉钉还有 3 秒 ACK 的限制,集群部署时只能单副本接收。

这些在渠道表里落到一列 mode,取值 webhook 或 ws,默认 webhook。钉钉不读这列,因为它恒为长连接。

钉钉:从建应用到群里说话

开放平台新建企业内部应用,记下 AppKey 和 AppSecret;开机器人能力,消息接收模式选 Stream(长连接),拿到 RobotCode。

平台侧在渠道管理里填平台类型、AppKey、AppSecret、RobotCode。钉钉不需要回调地址,保存后启动,日志里能看到:

ini 复制代码
运行期装配已建立:channelCode=dd_robot, platform=dingtalk
渠道长连接已启动:channelCode=dd_robot

还差一步身份绑定。外部标识不是昵称,也不是手机号,而是钉钉侧的 userId。最省事的确认方式是让使用者先发一条消息,然后去日志里找 externalUserId,再把它绑到平台账号上。

做完这些,群里 @ 机器人或单聊都能收到回复。

钉钉排障记录

保存渠道时提示"后端接口连接异常"

填完参数点保存,前端弹了一句"后端接口连接异常"。第一反应是接口写错了,但先别急着看代码,确认后端还活着没有。

直接请求后端健康接口,超时;走 nginx 请求,502;看容器状态,后端一直停在启动中,日志里始终没有 "Started DromaraApplication"。

宿主机 16G 内存,同时跑着两套完整的容器编排栈,可用内存只剩 1.5G 左右。那段时间 WSL 和 Docker 守护进程反复重启,整栈被重建了好几次。

停掉其中一套栈,数据卷留着,另一套稳定下来之后接口立刻恢复。未登录请求返回的是正常的 {"code":401,"msg":"登录状态异常"},不再是 502。

能拿到业务响应,就说明网关和后端都健康了。后来再遇到"接口连接异常",先问三件事:后端启动成功了吗,主机资源够吗,网关指向对吗。大部分情况是后端压根没起来。

升级后启动报 column "mode" does not exist

渠道表新加了 mode 列。新库正常,有数据的旧库一启动就报错。

原因是建库脚本只在数据卷为空时首次执行,已有数据的库不会重跑,新列自然不存在。

补一条幂等的增列语句,两个方言都要。PostgreSQL 支持 add column if not exists,直接在原有建库脚本后面追加即可;MySQL 老版本没有这个语法,得先查 information_schema.columns,确认列不存在再执行 alter。两个版本的语句都要写进部署 README。

动了表结构,就要同时给存量库的升级脚本,并写进部署文档。"新库脚本会自动生效"是升级事故里最常见的一类误判。

机器人回复"当前账号尚未绑定平台用户"

渠道配好了,群里 @ 机器人,收到的不是回答,而是一句提示:当前账号尚未绑定平台用户,请联系管理员在「渠道接入 → 外部身份映射」中绑定后再试。

翻日志能看到一条 warn:

ini 复制代码
渠道入站被拒(外部身份未映射):channelCode=dd_robot, externalUserId=xxxxxxxx

映射表里 0 条记录。这不是故障,是设计:外部标识必须显式绑定平台账号,没绑的一律拒绝,不回退默认身份。

去「外部身份映射」加一条就好。上线第一天这个提示一定会被当成 Bug 报上来,提前跟大家说清楚它是安全设计,比事后解释省事。

按手机号查标识返回 errcode=88

为了省事,我们把"按手机号查外部标识"做成了产品功能:渠道列表点图标,输手机号,查出标识并预填到绑定表单。

第一次真调,平台原样返回:

ini 复制代码
errcode=88, subcode=60011
应用尚未开通所需的权限:[qyapi_get_member_by_mobile]
申请链接:https://open-dev.dingtalk.com/appscope/apply?content=xxxxx

链路是通的:登录、换 token、调接口都正常,只是平台侧权限没申请。按报文里的申请链接开通,管理员同意后重查,立刻命中:

ini 复制代码
渠道外部用户标识查询命中:channelCode=dd_robot, platform=dingtalk, mobile=138****0000

这里有两件事值得坚持。第一,不要把平台的错误信息吞掉,errcode、subcode 和申请链接都原样透传,用户往往能自己解决。第二,手机号是敏感信息,日志里必须脱敏,接口也不要记操作日志。

长连接注册地址不跟 API 基址走

把 API 基址改成内网代理后,OpenAPI 调用都正常,长连接却一直连不上,日志里没有有效信息。

翻框架实现才看明白:OpenAPI 基址和长连接注册地址是两个独立字段,后者默认硬编码在公网默认地址上,不会跟着 api_base 走。

装配长连接时把注册地址显式拼出来:以配置的 API 基址为前缀,接上固定的长连接注册路径;API 基址留空时,才交给框架用默认地址。

内网代理、出口受限的环境,一定要确认长连接往哪建连。它的表现是"连不上但没有任何错误信息",属于最难查的一类问题。

图片消息:downloadUrl 被二次编码

钉钉图片消息进来后,先调 /v1.0/robot/messageFiles/download 用 downloadCode 换 downloadUrl,再下载文件。

这里踩过一次:

把下载地址直接交给 WebClient 的字符串 URI 方法,它会按 URL 模板再编码一遍,签名里的 %2F 变成 %252F,OSS 直接返回 403。改成先把下载地址原样构造为 URI,再发起请求就正常了。

另外,钉钉的报文类型比文档里列的杂。遇到没识别的消息类型,框架层会打 WARN 而不是静默丢弃,排障时这条日志很有用。

飞书:建应用、长连接、事件订阅

飞书新建企业自建应用(只有自建应用支持长连接),记下 App ID 和 App Secret;开机器人能力;事件订阅选长连接(如果用 ws),订阅 im.message.receive_v1。

权限按实际用到的能力开,至少要能收发消息、读取消息里的图片和文件资源。权限改完要发版生效,这一步很容易漏。

平台侧填 App ID、App Secret,mode 选 ws。长连接模式下,Encrypt Key、Verification Token、回调地址在界面上直接隐藏,免得有人以为还要配公网回调。

飞书的外部标识是 open_id,绑定方式和钉钉一样。

飞书排障记录

机器人说"只收到图片占位,没有可读的图片内容"

文字消息正常,一发图片就回复这句。第一反应是图片格式不支持,或者应用没有读图片的权限。

但这次日志里没有"权限不足",而是根本没有"渠道图片已装配"这条记录。再往前翻,容器日志里躺着:

arduino 复制代码
org.springframework.core.io.buffer.DataBufferLimitException:
Exceeded limit on max bytes to buffer : 262144

262144 就是 256KB,Spring WebClient 的默认响应缓冲上限。那张飞书图片 337571 字节,下载到一半就失败了。

问题不在飞书,也不在模型。网关把下载失败降级成了 [image] 占位文本,模型只看到占位,自然说没有图片内容。

修法是给媒体下载单独放宽缓冲,统一到 16MB。飞书、企微、钉钉三条下载链路共用这套策略,普通 OpenAPI 请求仍用默认值。

下载通了,类型还得靠魔数

缓冲区修好之后还有第二个问题:飞书图片消息只给 image_key,没有文件名,也没有后缀。装配内容块时需要 MIME,按文件名猜是猜不出来的。

改成读字节头:

类型 文件头
JPEG FF D8 FF
PNG 89 50 4E 47
GIF 47 49 46 38
WebP 52 49 46 46 + WEBP
BMP 42 4D
TIFF 49 49 2A 00 / 4D 4D 00 2A
HEIC 66 74 79 70 + heic / heix / mif1

识别不出来就 WARN,然后按原有降级路径回退成占位文本,不阻断回答。

修完后再发同一张图,日志里是:

ini 复制代码
渠道图片已装配:channelCode=feishu_bot, mediaType=image/jpeg, bytes=337571

机器人这次读到了图片内容,能正常回答。

Webhook 模式的两件事

如果飞书走 Webhook,除了公网 HTTPS 地址,还要处理 URL 校验握手,并填 Encrypt Key 和 Verification Token。长连接模式下这些都用不上,两者不要混配。

消息幂等和防自环由网关统一做,按 message_id 去重、按渠道限流,不需要在平台后台额外配置。

链路跑通后核对这几处

钉钉和飞书都按同一套治理面核查:

看什么 正常表现
渠道来源会话 生成 source=channel 的会话
外部线程映射 群聊或单聊线程指向该会话
消息 多轮问答都在,工具调用类问题也能走通
长连接状态 日志有"运行期装配已建立"和"渠道长连接已启动"
身份映射 外部标识绑到了平台账号
图片 有"渠道图片已装配"日志,bytes 与源文件一致

从入站、身份、会话,到无人值守执行、出站回复,整条链路才算跑通。

回头再看

这几个问题里,真正改代码的只有两处:长连接注册地址,以及媒体下载的缓冲和 MIME 识别。其余都是环境资源、建库升级、安全设计、平台权限。

写渠道接入,代码只是其中一部分。剩下的是把部署、升级、身份和平台配置都提前想到,以及在出问题时能先分层判断:后端起来没有,网关通不通,平台侧权限开了没有,日志里有没有降级记录。

平台的错误信息尽量原样透传,手机号这类敏感字段记得脱敏。这两条在排障时能省掉很多来回。


关于 BizBuddy

BizBuddy 是一个全量开源的企业级智能体平台(MIT License):RuoYi-Vue-Plus(Spring Boot 4.1.1 / JDK 21)做底座,AgentScope Java Harness 做智能体内核,纯 Java 全栈、不含 Python,权限、审计、配额、多渠道接入等能力全部随仓库开源。

欢迎 Star、Issue 与共建。

相关推荐
Apageoflove44 分钟前
把 ComfyUI 改造成无人值守视频产线:状态机、顺序锁与 ComfyUI 插件工程实践
人工智能·github
2601_965454231 小时前
开源项目发布会不用剪视频,AI一键生成宣传片
人工智能·音视频
工具派1 小时前
不重写一行老代码:给 Spring Boot 系统加一层 AI 能力
java·人工智能·spring boot
武子康1 小时前
Coding Agent 的知识也会漂移:用固定快照检查 Vercel Plugin 升级
人工智能·后端·agent
黑妹天下第一乖1 小时前
255 次尝试就能恢复 CA 私钥?这次 TLS 信任链事故,值得每个 AI 开发者警惕
人工智能·python·https·tls·信任链·ca
昇腾CANN1 小时前
10月23日直播丨CANN挑战赛决赛赛题解析
人工智能·昇腾·cann·cann开源
牛战士coding1 小时前
用户点了停止生成,上游还在烧 token 吗?取消传播链路拆解
java·人工智能
长不大的杨瑞杰1 小时前
给 OpenCode v2 接入大模型:Provider、模型选择与 API Key 安全配置实战
人工智能·安全·ai·语言模型
虎鲸不是鱼2 小时前
AI 自动化逆向(九)AI 会记住错的公共常量
人工智能·自动化·大模型·逆向·ai逆向