LangChain4j 实战系列第四篇。正篇讲完入库和检索生成后有朋友问你们客服天天泡在飞书里谁没事打开你的 Web 页面聊天问得好——所以这个项目里问答能力的第二个入口就是把 RAG 助手直接搬进飞书。这篇讲这套接入是怎么做的以及 IM 机器人这个形态带来的一堆和 Web 端不一样的坑。项目地址github地址为什么要接飞书先说动机。Web 端chat.html适合演示和调试但真实的企业客服场景里主战场就在 IM 里。客服在飞书群里被 一下就能查知识库比切换浏览器、登录、开页面顺滑太多。而且接 IM 机器人有个白赚的好处问答、引用溯源这些核心能力一行不用改全部复用主链路的 Service。整个接入拆开就是五件事回调入口接飞书事件、验 token、去重异步处理不能阻塞飞书的回调线程会话映射飞书的 chat_id 怎么对上我们内部的 conversationId复用问答链路调同步版 sendMessage卡片回复把 Markdown 答案适配成飞书能渲染的样子老规矩先看泳道图再逐段讲。完整链路泳道图FeishuApiServiceFeishuCardBuilderRagChatService(同步链路)FeishuConversationServiceFeishuMessageHandler[虚拟线程]RedisFeishuEventController飞书开放平台飞书用户FeishuApiServiceFeishuCardBuilderRagChatService(同步链路)FeishuConversationServiceFeishuMessageHandler[虚拟线程]RedisFeishuEventController飞书开放平台飞书用户alt[缓存命中][未命中]完整RAG管道:意图→改写→混合检索→重排→生成(同步等待)机器人 提问不倒绒多少钱一米1POST /api/feishu/event (事件推送)2url_verification? 回challenge3event_callback: 校验 verification_token4SETNX feishu:event:dedup:{eventId} (TTL 5min)5首次事件6handleMessage(event)7立即返回 code0 (不阻塞回调)8群聊检查 mentions,未机器人不回复9extractText() 剥掉 机器人 前缀10/reset /help 特殊命令短路11getOrCreateConversation(chatId)12查 feishu:chat:mapping:{chatId}13expire 续期7天14createConversation(feishu_xxx 或 feishu_group_xxx)15写映射, TTL 7天16sendMessage(conversationId, text, STAFF)17RagChatMessage(content ragReferences)18buildRagCard(答案, 引用列表)19图片URL localhost→外网地址代码块适配,超长截断20replyCardMessage(messageId, cardJson)21tenant_access_token(双检锁缓存,提前5min刷新)22POST /im/v1/messages/{id}/reply (interactive)23卡片消息:答案引用文档脚注24回调入口三件事必须先干FeishuEventController就一个POST /api/feishu/event接口也是 Sa-Token 拦截/api/**时唯一的业务白名单飞书可不会带你的登录 token它靠自己的 verification_token 证明身份。进门三件事顺序不能乱第一件URL 验证挑战。你在飞书后台填回调地址的那一刻飞书先发一个typeurl_verification的请求带个challenge随机串你必须在响应里原样回显它否则地址保存都保存不了。这就是经典的证明这块地是你的。第二件token 校验。event_callback类型的事件取 header 里的 token 和配置的 verification_token 比对对不上直接拒。有个实用妥协未配置 token 时跳过校验方便本地开发调试——但生产必须配上我在配置注释里写死了这条提醒。第三件事件去重。这是不接一次 IM 回调不知道的坑——飞书会重复推送同一个事件。你的接口处理慢了、超时了、甚至网络抖一下它都可能重发。不做去重的后果就是用户问一次机器人回三遍。我的方案很轻拿eventId做 Redis SETNX5 分钟 TTLRBucketStringbucketredissonClient.getBucket(feishu:event:dedup:eventId);booleanisNewbucket.setIfAbsent(1,Duration.ofMinutes(5));注意异常时的方向选择Redis 挂了deduplicateEvent返回 true放行处理。这里我宁可偶发回两遍消息也不能让 Redis 一抖动就吞掉用户的问题——去重是优化丢消息是事故两者的容错方向应该相反。快进快出虚拟线程在这里的用法飞书对回调响应有时间要求你不能在 handler 里同步等一个大模型跑完RAG 一次几十秒很正常否则飞书判定推送失败接着就是上面说的重推。所以handleMessage的全部实现就一行publicvoidhandleMessage(FeishuEventevent){Thread.startVirtualThread(()-doHandleMessage(event));}回调线程立刻返回code0真正的活丢给虚拟线程。Java 21 在这类高IO等待、低CPU的场景下是真的舒服一请求一线程的模型读起来就是同步代码但虚拟线程不要钱开几百个毫不心疼。放在两年前这里得包一层线程池 CompletableFuture代码立刻脏一档。doHandleMessage里还有一个整个流程唯一兜底的 try-catch不管中间炸在哪一步都会尝试给用户回一张抱歉处理消息时出现异常的卡片。IM 机器人最基本的礼貌是永远让用户知道你有反应最忌讳的是用户 了机器人然后石沉大海。消息解析 符号背后的脏活群聊的 判定。群聊消息只有 了机器人才会推送给机器人这是飞书的规则所以判定逻辑我做了简化event 里带了 mentions 字段就认为 了机器人。单聊不需要这步直接都回。文本提取。飞书 text 消息的 content 是个嵌套 JSON 字符串{text:_user_1 不倒绒多少钱}解析出来还要把xxx前缀用正则剥掉不然这个 符号会跟着进知识库检索一圈污染 query。非 text 类型图片、文件目前直接不支持回一张请输入文字内容的引导卡片——明确告诉用户边界比默默忽略强。特殊命令。/reset或中文新对话清会话映射/help回帮助卡片。这个功能看起来小但非常必要RAG 的记忆窗口是按会话累积的用户在一个群里聊了三天八百里地换话题必须有一个重启对话的出口否则历史消息会一直干扰后续检索的指代消解。中文命令“新对话”和斜杠命令并列支持因为真用户不会去记/reset。会话映射一人一会话一群一会话飞书的 chat_id 和我们内部的 conversationId 是两套体系得有人翻译。FeishuConversationService干的就这件事策略if(group.equals(chatType)){userIdfeishu_group_chatId;// 群聊一群共享一个会话上下文}else{userIdfeishu_openId;// 单聊一人一会话}两个设计点群聊为什么要共享上下文因为群里的对话天然是接力的——甲问了有没有不倒绒供应商乙接着 机器人问那它多少钱这个它指的正是甲聊的东西。一群一会话多轮记忆的指代消解才能跨人生效。代价是全群互相能续对方的话题内部群这反而是特性。feishu_前缀是命名空间隔离。飞书用户不注册、不登录直接在 user_id 字段上打前缀和 Web 端真实用户天然分开。将来要做飞书用户数据统计或者下线飞书入口一个 LIKE ‘feishu_%’ 就圈出来了。映射关系本体chatId → conversationId走 RedisTTL 7 天每次命中顺手 expire 续期活跃会话永不掉feishu_conversation_mapping表做持久化备份。这里我其实留了一点冗余——Redis 过期后未重查 DB 而是新建会话行为上等价于会话自动重置7 天没动静的聊天群要个新上下文也合理。这个妥协我认了并把它当特性讲。复用问答链路一个同步方法喂两个入口核心复用的落点就一行RagChatMessageaiMessageragChatService.sendMessage(conversationId,userText,STAFF);RagChatService当初就设计成了两个入口一个内核SSE 流式的sendMessageStream给 Web同步阻塞的sendMessage给飞书意图识别、混合检索、引用收集全都走同一条管道。飞书拿到的RagChatMessage里rag_references列的 JSON 直接解析出来就是引用列表——引用数据在检索环节就落库了接入方只是搬运工。有个决策坦白交代飞书用户我固定给了 STAFF 权限能检索全部文档。理由是机器人部署在企业内网飞书租户里能 到它的本身就是内部员工对外部客户我的设计是引导走 Web 端登录鉴权。这个前提如果哪天不成立比如机器人被拉进含外部人的群这行代码就是第一个要改的地方。为什么飞书不做流式飞书卡片有流式更新能力打字机效果但那需要额外接一套卡片 PATCH 接口 分片节流复杂度翻倍而 IM 用户对等 30 秒来一条完整回复的心理预期远好于网页等 30 秒空白。这是形态决定的取舍不是能力缺失。所以当前实现是同步拿完整答案后一次性回卡片卡片流式更新在待办清单里。卡片构建lark_md 是个方言这是全篇文章里坑浓度最高的一节。AI 生成的是标准 Markdown飞书卡片的 lark_md只认一部分 Markdown直接扔过去渲染出来就是一堆星号和方括号。FeishuCardBuilder#processContentForLarkMd做的适配代码块降级lark_md 对 围栏支持很差CODE_BLOCK_BLOCK_PATTERN把代码块拆出来换成缩进形式图片 URL 内外网替换这个坑不处理图片全是裂的。知识库里图片的地址是 MinIO 内网地址http://localhost:9000/...你机器上的 localhost 飞书服务器可访问不到渲染卡片的是用户手机。所以必须正则扫出把localhost / 127.0.0.1 / host.docker.internal开头的 host 统一换成配置里的external-image-base-url你对外暴露的地址。是不是和 MinerU 提交任务时那次内外网替换神相似同一个坑在项目里摔了两次跨系统引用资源永远要想对方的网络视角。超长截断飞书卡片内容有大小限制我在 28000 字符处硬截断宁可少一段也不让整个卡片发送失败卡片结构是三段式lark_md 正文 →hr分隔 → “ 引用文档n篇“可点击链接列表每条带相关度 87%→ 底部 note 脚注回答仅供参考”。相关度百分比这个score*100的小改造值回票价——它让引用从一列名字变成有置信度的出处”客服同事会先扫一眼相关度再决定信多少。tenant_access_token调飞书 API 的门票回复消息要调飞书 Open API门票是tenant_access_token用 app_id app_secret 换有效期两小时且频繁获取会让旧 token 提前失效——所以必须缓存不能每次调用现换。FeishuApiService里的实现是教科书级的双检锁privatevolatileStringcachedToken;privatevolatilelongtokenExpireTime;publicStringgetTenantAccessToken(){if(cachedToken!nullSystem.currentTimeMillis()tokenExpireTime-TOKEN_REFRESH_MARGIN_MS){returncachedToken;// 快路径无锁}synchronized(this){if(cachedToken!null/* 同上 */){returncachedToken;// 进锁后复查,防重复刷新}returnrefreshToken();}}细节两个字段都volatile过期判断预留5 分钟提前量TOKEN_REFRESH_MARGIN_MS宁可提前刷新不可拿临过期 token 出去丢人HTTP 层用 Hutool 的 HttpRequest回复卡片超时给 15 秒。小结飞书接入这一篇工程含量不大但形态差异带来的细节很多值得记的有这几条回调必须快进快出验 token → SETNX 去重 → 虚拟线程 → 立刻返回重活全异步容错方向要选对去重组件故障时放行宁可重复不可丢任何异常都必须回用户一张错误卡片机器人不能装死会话映射要带命名空间feishu_ 前缀群聊共享上下文是为了跨人指代消解同步/流式两个入口一个内核Service 层设计得当多端接入就只是加一个翻译层网络视角的坑会重复出现MinerU 内外网替换、飞书卡片图片替换本质是同一个教训token 缓存双检锁 提前量IM 平台通用的基本素养至此这个项目的四条线——总览、入库、检索生成、可观测、飞书接入——就都讲完了。代码量不大但我保证每个细节都有它的为什么。后面想继续深挖的可以看意图路由策略的演进或者把卡片流式更新那块债还上。评论区见。
企业数字化 ERP 产品动态
相关推荐
新手入门避坑:3种h5个人博客网站模板成本拆解 新手入门避坑:3种h5个人博客网站模板成本拆解 改个需求建站公司拖一周,这种憋屈事我见得太多了。很多新手入门做 h5 个人博客网站模板 时,总以为找个现成模板就能省事,结果陷入“改代码找外包、改样式加钱”的无底洞。… · 2026/9/28 1:05:44
KNN情感分析实战:从TF-IDF到调参避坑的Python基线指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:05:38
头颅侧位片蝶鞍语义分割数据集构建与训练指南 简介:本资源是面向医学影像AI研究者与深度学习初学者的头颅侧位片蝶鞍区域语义分割专用数据集,聚焦内分泌疾病辅助诊断场景,解决蝶鞍形态异常识别中高质量标注数据稀缺的痛点。数据包共1558个文件,含779张标注用头颅侧位X光JPG图像… · 2026/9/28 1:05:38
3个实战案例教你搞定网络媒体整合营销,告别域名服务器焦虑 3个实战案例教你搞定网络媒体整合营销,告别域名服务器焦虑 你是不是也被域名解析、服务器配置这些词绕晕了?很多做市场推广的朋友,手里有预算、有创意,一遇到技术落地就头大。别急,我整理了3个真实的 实战案例 ,把 网络媒体整合营销… · 2026/9/28 1:40:09
基于OpenCV与LBPH的人脸识别门禁系统:从Haar检测到阈值调优实战 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:39:57
OpenCV实战:快速筛选iOS相册中的相似图片 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:39:57
VSCode+STM32开发环境搭建全攻略:从Keil到GDB调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:39:57
CMSIS-DSP库函数实战:从入门到性能优化与避坑指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/28 1:39:57
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现 简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01
汕头网站建设制作厂家避坑指南:5大注意事项救急 汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习 简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01
制作网页比较方便的软件怎么选?一文搞懂避坑指南 制作网页比较方便的软件怎么选?一文搞懂避坑指南 很多老板一上来就问:做个网站多少钱?但我反问他:你的域名买了吗?服务器租了吗?他一脸懵。这就是典型的“域名服务器搞不懂”。别急,今天咱们不聊虚的,直接 一文搞懂 那些让你头秃的技术名词。… · 2026/9/28 0:00:06
婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 婚恋网站实战案例:避开3个高价坑,省钱50%还能跑赢流量 找婚恋网站建站公司,最怕的就是被坑高价。很多同行跟我吐槽,报价单上写得模棱两可,功能栏里全是“高级定制”、“专属UI”,结果落地全是套壳。今天不聊虚的,直接甩几个我经手的 实战案例… · 2026/9/28 0:00:19
济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 济南做网站多少钱:3个案例拆解,防黑源码下载全攻略 上周济南一个做建材的老板找我,脸都绿了。他的官网首页弹出了赌博广告,后台被植入了挖矿脚本。他慌得问我:“网站被黑挂马不知道怎么办?能不能直接找之前的外包公司要源码下载,看看哪里被动了手脚?… · 2026/9/28 0:00:25