从钉钉到 Dify:基于工作流智能体实现重要通知检索全解析

一、背景

需要借助与大模型,通过钉钉机器人快速检索和查询历史的发文内容。

二、实现方案

  1. 使用钉钉应用的机器人能力,接收用户的提问信息。
  2. 将提问信息发送给AI大模型,提取出时间范围和关键词。
  3. 通过发文检索服务,检索召回时间范围内与关键词匹配的发文信息。
  4. 整理检索结果,封装到交互卡片对象中。
  5. 通过创建交互卡片和更新交互卡片相应Flow Status和Card Data,实现检索结果动态更新投递给用户。

三、时序图

四、实现流程

1. 钉钉机器人

  • 创建应用
  • 机器人配置
  • 消息接收

钉钉通过机器人实现高效的消息推送和接收的效果使用机器人向组织内用户或群内发送和接收消息,实现消息的自动高效触达;使用消息模版或互动卡片自定义阅读体验更好的消息体风格

通过钉钉提供的企业内部应用,创建【发文通知测试】应用。填写应用名称、应用描述、应用图标。

这里填写的是应用的相关信息,真实接收消息和发送消息时,使用的是企业内部应用的机器人,所以应用相关信息不会再消息发送接受中体现,机器人名称和机器人图标才会再聊天窗口展示。

应用创建成功之后可以通过应用名称检索,在应用列表检索结果中点击【发文通知测试】应用,进入应用详情。

应用详情页面展示了基本信息和应用功能。

基本信息-应用信息-应用凭证AgentId、AppKey、AppSecret是用来标识和操作当前应用,将来再消息接收中会传递给业务服务器,用来识别消息是通过哪个应用接收的,可以在业务中根据应用采用不同的处理策略。

应用功能-机器人与消息推送-机器人配置:RobotCode是企业内部应用机器人的唯一标识,在机器人配置中,点击【复制 RobotCode】可以获取到当前机器人的RobotCode,用来在业务中处理更具体的操作。

机器人名称、机器人图标用来在会话中显示与用户交互的机器人。

消息接收模式分为Stream模式和Http模式。使用Stream模式,无需注册公网回调地址。

无论是Stream模式还是Http模式其实回调信息是一样的。因此对于开发业务逻辑来说,是没有太大差异的。考虑到当前消息处理属于简单任务,不需要太高的实时性,简单灵活的HTTP模式更适合当前业务。以下我们使用Http模式来实现。

使用HTTP模式需要配置回调地址,使用 POST 请求接收钉钉推送的消息。

php 复制代码
{
  // 消息发送平台
  "senderPlatform": "Mac",
  // 会话ID
  "conversationId": "cid6EUvB2O8qVF2RYQtH*****==",
  // 被@人的信息
  "atUsers": [
    {
      // 加密的发送者ID
      "dingtalkId": "$:LWCP_v1:$PcdFk0c1XakTWLPSQCaVcRzxeEV*****"
    }
  ],
  // 加密的机器人所在的企业corpId
  "chatbotCorpId": "ding9733d095*******6cb783455b",
  // 加密的机器人ID
  "chatbotUserId": "$:LWCP_v1:$PcdFk0c1XakTWLPSQCaVcRzxeE*****",
  // 加密的消息ID
  "msgId": "msgrK2ckHkMbaiUWt5xQ*****==",
  // 发送者昵称
  "senderNick": "小钉",
  // 是否为管理员
  "isAdmin": true,
  // 企业内部群中@该机器人的成员 userId
  "senderStaffId": "0147282552407*****",
  // 当前会话的Webhook地址过期时间
  "sessionWebhookExpiredTime": 1708332604316,
  // 消息的时间戳,单位毫秒
  "createAt": 1708327204136,
  // 企业内部群的发送者当前群的企业corpId
  "senderCorpId": "ding9733d095*******6cb783455b",
  // 会话类型:1:单聊、2:群聊
  "conversationType": "2",
  // 加密的发送者ID
  "senderId": "$:LWCP_v1:$6GYsn+zrv5WZ77xc2v4zsyXfBv1*****",
  // 群聊时才有的会话标题
  "conversationTitle": "测试群",
  // 是否在@列表中
  "isInAtList": true,
  // 当前会话的Webhook地址
  "sessionWebhook": "https://oapi.dingtalk.com/robot/sendBySession?session=66d7c695a9e1f18782a7cba1d9d*****",
  // 消息文本
  "text": {
    // 机器人接收的消息内容
    "content": " text"
  },
  // 机器人编码
  "robotCode": "dingohropvkloy0*****",
  // 消息类型
  "msgtype": "text"
}

通过msgType识别到消息类型,采用对应的处理策略解析机器人接收到的消息内容。

2. Dify工作流创建智能体

2.1. 创建工作流

首选需要一个Dify账号,进入Dify工作室页面,选择创建空白应用。

在创建空白应用页面选择应用类型,应用名称&图标,应用的简单描述。

我们可以根据需求创建一个工作流应用,应用名就叫【信息提取】

2.2. 创建开始节点

应用创建成功之后,会直接进入到应用编排界面。

我们首先选择【开始】节点,在右侧会展示该节点可以配置的元数据。在【输入字段】项点击【+】添加变量。在这里点击字段类型为文本,设置【变量名称】和【显示名称】以及【最大长度】,是否【必填】。显示名称会默认使用变量名称。点击【保存】就添加了一个名为【query】的变量。api调用时用来接收用户信息。

为了追踪问题,我们可以增加一个非必填的【requestId】字段,用来标识每次api调用的唯一值。

2.3. 创建获取当前日期节点

有了开始节点作为工作流的入口,获取到用户输入信息之后,我们可以添加后续节点【代码执行】通过Python或NodeJS处理一些代码逻辑。比如当前节点获取当前日期

创建节点之后点击节点,页面右侧抽屉会展示当前节点的配置项。

添加标题【获取当前日期】

添加描述【通过Python3获取当前日期,返回字典类型。日期格式为yyyy年MM月dd日】

输入变量不需要

执行代码

css 复制代码
from datetime import date

def main() -> dict:
    return {
        "currentDate": date.today().strftime("%Y年%m月%d日")
    }

输出变量【currentDate】数据类型【String】

失败时重试不需要、异常处理也不需要。

【执行代码】节点配置完成之后,可以点击节点右上角的【运行此步骤】按钮,运行调试代码执行情况。

点击【开始运行】按钮,等待运行结果。如果是SUCCESS状态并且输出栏符合预期,那就可以继续添加下一个节点了。如果是FAIL状态,可以根据错误日志检查问题并调整。

2.4. 创建LLM节点

获取当前日期之后,把数据存入到【currentDate】变量中,作为LLM节点的处理过程中用到的数据。

点击获取当前日期节点配置后继节点,节点类型选择LLM。通过LLM分析开始节点query中的时间范围及关键字。

添加后继节点LLM之后,根据右侧抽屉配置页面配置LLM相关信息。

编辑节点名称为LLM,添加相关描述,添加大语言模型。

通过模型下拉框选者已经配置的模型,如果没有可以通过模型设置,跳转至模型提供商,选者可用的模型提供商。本案例直接使用DeepSeek。

在模型供应商页面,通过搜索可以安装【深度求索】模型。安装成功之后需要配置DeepSeek的API key。

登陆DeepSeek官网可以免费申请账号,申请API key。当前使用场景属于API服务,因此需要充值费用,否则接口返回402错误码。DeepSeek只针对网页版和APP对话免费使用。

将DeepSeek申请的API key输入到模型供应商配置页面,配置成功之后API-KEY字样显示成功标志。此时就可以在LLM节点的模型选项中选择使用深度求索模型了。

在LLM节点选者模型时可以看到有多个模型可以选择,我们这里选择deepseek-chat即可。

  • deepseek - chat:是对话模型,能像和人聊天一样,理解日常问题并给出回答,可用于日常交流、知识问答等场景 。
  • deepseek - coder :为代码生成模型,专注于代码相关任务,比如根据自然语言描述生成代码、代码补全、代码纠错等,助力开发者提升编程效率 。
  • deepseek - reasoner :是推理模型,擅长处理需要逻辑推理的任务,像数学推理、逻辑谜题解答、复杂问题的分析推导等 。

选择deepseek-chat模型之后,配置可选参数。让大模型更符合具体业务场景。

  • 温度:控制生成结果的多样性和随机性。数值越小,越严谨;数值越大,越发散。
  • 最大标记:指定生成结果长度的上限。如果生成结果截断,可以调大该参数。
  • Top P:控制生成结果的随机性。数值越小,随机性越弱;数值越大,随机性越强。一般而言,top_p 和temperature 两个参数选择一个进行调整即可。
  • Logprobs:是否返回所输出 token 的对数概率。如果为 true,则在 message的 content 中返回每个输出token 的对数概率。
  • Top Logprobs:一个介于0到20之间的整数N,指定每个输出位置返回输出概率top N的token,且返回这些token 的对数概率。指定此参数时,logprobs 必须为 true。
  • 频率惩罚:介于 -2.0和2.0之间的数字。如果该值为正,那么新 token 会根据其在已有文本中的出现频率受到相应的惩罚,降低模型重复相同内容的可能性。
  • 回复格式:指定模型必须输出的格式。text或json_object
  • 停止序列:最多四个序列,API 将停止生成更多的 token。返回的文本将不包含停止序列。

选择模型和配置参数之后,可以配置上下文、SYSTEM、USER。

  • 上下文:可以导入知识库作为上下文。
  • SYSTEM:为对话提供高层指导。
  • USER:向模型提供指令、查询或任何基于文本的输入。

SYSTEM可以根据一下提示输入,作为高层指导。

bash 复制代码
示例1:
输入:查查24年下半年中国销往海外的陶瓷数量
返回:{"startDate":"2024-07-01","endDate":"2024-12-31","keyWords":"中国、陶瓷、海外","dateDescription":"24年下半年"}

示例2:
输入:22年居然智家的战略
返回:{"startDate":"2022-01-01","endDate":"2022-12-31","keyWords":"居然智家、战略","dateDescription":"22年"}

你是一个智能助手,我们要根据用户输入的问题检索相应的文档,今天是{{#1744792569835.currentDate#}},请你认真分析用户输入的问题,从中抽取需要检索的'开始日期'、'结束日期'、'关键词'、'日期范围描述',时间要精确到日,日期描述是从原文截取出来的片段不能改写,如果没有日期描述文字抽取不到日期则相应字段为空,但关键词字段不得为空,请严格按照上面示例的json格式返回,不要额外加"```json"这种额外的字符。

USER使用开始节点的query变量作为请求内容,以供deepseek分析并返回结果。

输出变量text存储最终deepseek返回结果。

2.5. 创建结束节点

在LLM节点创建并输出text结果之后,创建结束节点作为后继节点。

在结束节点配置页面中设置输出变量为前驱节点LLM的输出结果。

2.6. 运行测试

所有节点都配置之后可以通过编排面板右上角的运行按钮测试。

运行分为四个分项:输入、结果、详情、追踪。

输入

结果

详情

追踪

  • 开始
    • 输入
json 复制代码
{
  "query": "查查24年下半年中国销往海外的陶瓷数量",
  "requestId": null,
  "sys.files": [],
  "sys.user_id": "d4d13728-e759-4416-8130-****",
  "sys.app_id": "a3637c27-9475-4bd1-ae27-****",
  "sys.workflow_id": "82a971ff-8839-4be3-8418-****",
  "sys.workflow_run_id": "28971a8c-75c7-40a6-81dd-****"
}
    • 输出
json 复制代码
{
  "query": "查查24年下半年中国销往海外的陶瓷数量",
  "requestId": null,
  "sys.files": [],
  "sys.user_id": "d4d13728-e759-4416-8130-****",
  "sys.app_id": "a3637c27-9475-4bd1-ae27-****",
  "sys.workflow_id": "82a971ff-8839-4be3-8418-****",
  "sys.workflow_run_id": "28971a8c-75c7-40a6-81dd-****"
}
  • 获取当前日期
    • 输出
json 复制代码
{
  "currentDate": "2025年04月21日"
}
  • LLM
    • 数据处理
swift 复制代码
{
  "model_mode": "chat",
  "prompts": [
    {
      "role": "system",
      "text": "示例1:\n输入:查查24年下半年中国销往海外的陶瓷数量\n返回:{"startDate":"2024-07-01","endDate":"2024-12-31","keyWords":"中国、陶瓷、海外","dateDescription":"24年下半年"}\n\n示例2:\n输入:22年居然智家的战略\n返回:{"startDate":"2022-01-01","endDate":"2022-12-31","keyWords":"居然智家、战略","dateDescription":"22年"}\n\n你是一个智能助手,我们要根据用户输入的问题检索相应的文档,今天是2025年04月21日,请你认真分析用户输入的问题,从中抽取需要检索的'开始日期'、'结束日期'、'关键词'、'日期范围描述',时间要精确到日,日期描述是从原文截取出来的片段不能改写,如果没有日期描述文字抽取不到日期则相应字段为空,但关键词字段不得为空,请严格按照上面示例的json格式返回,不要额外加"```json"这种额外的字符。",
      "files": []
    },
    {
      "role": "user",
      "text": "查查24年下半年中国销往海外的陶瓷数量",
      "files": []
    }
  ],
  "model_provider": "langgenius/deepseek/deepseek",
  "model_name": "deepseek-chat"
}
    • 输出
swift 复制代码
{
  "text": "{\n"startDate":"2024-07-01",\n"endDate":"2024-12-31",\n"keyWords":"中国、陶瓷、海外",\n"dateDescription":"24年下半年"\n}",
  "usage": {
    "prompt_tokens": 230,
    "prompt_unit_price": "2",
    "prompt_price_unit": "0.000001",
    "prompt_price": "0.00046",
    "completion_tokens": 41,
    "completion_unit_price": "8",
    "completion_price_unit": "0.000001",
    "completion_price": "0.000328",
    "total_tokens": 271,
    "total_price": "0.000788",
    "currency": "RMB",
    "latency": 5.418107457924634
  },
  "finish_reason": "stop"
}
  • 结束
    • 输入
swift 复制代码
{
  "text": "{\n"startDate":"2024-07-01",\n"endDate":"2024-12-31",\n"keyWords":"中国、陶瓷、海外",\n"dateDescription":"24年下半年"\n}"
}
    • 输出
swift 复制代码
{
  "text": "{\n"startDate":"2024-07-01",\n"endDate":"2024-12-31",\n"keyWords":"中国、陶瓷、海外",\n"dateDescription":"24年下半年"\n}"
}

2.7. 发布

测试通过之后,点击发布按钮,发布更新当前工作流。

发布成功之后即可获取访问API。通过访问API可以在项目中请求该工作流,通过请求响应提取用户询问的时间范围和关键词。

2.8. 访问API

应用发布成功之后,通过访问API页面可以了解到访问工作流的HTTP方式。

Base URL:api.dify.ai/v1

Authentication:Bearer {API_KEY}

其中API_KEY通过访问API页面右上角的API密钥获取,创建密钥之后通过Authentication鉴权方式发起POST请求。

css 复制代码
curl --location --request POST 'https://api.dify.ai/v1/workflows/run' \
--header 'Authorization: Bearer app-RAyWON89**********U83d' \
--header 'Content-Type: application/json' \
--data-raw '{
    "inputs": {
        "query": "查查24年下半年中国销往海外的陶瓷数量"
    },
    "response_mode": "blocking",
    "user": "Leon"
}'
swift 复制代码
{
    "task_id": "de1c5c8b-17a5-4fb9-8a47-d9cccc0*****",
    "workflow_run_id": "85a585f1-566a-426f-9f16-4f5d073*****",
    "data": {
        "id": "85a585f1-566a-426f-9f16-4f5d073*****",
        "workflow_id": "d18b178c-4389-4f2f-a865-5942de2*****",
        "status": "succeeded",
        "outputs": {
            "text": "{\n"startDate":"2024-07-01",\n"endDate":"2024-12-31",\n"keyWords":"中国、陶瓷、海外",\n"dateDescription":"24年下半年"\n}"
        },
        "error": null,
        "elapsed_time": 5.939426406053826,
        "total_tokens": 271,
        "total_steps": 4,
        "created_at": 1745218034,
        "finished_at": 1745218040
    }
}

3. ElasticSearch检索

通过Dify工作流智能体提取信息之后,使用ES请求查询数据。

json 复制代码
{
    "from": 0,
    "size": 3,
    "query":
    {
        "bool":
        {
            "must":
            [
                {
                    "multi_match":
                    {
                        "query": "中国、陶瓷、海外",
                        "fields":
                        [
                            "content^1.0",
                            "pdfContent^1.0",
                            "title^1.0"
                        ],
                        "type": "best_fields",
                        "operator": "OR",
                        "slop": 0,
                        "prefix_length": 0,
                        "max_expansions": 50,
                        "zero_terms_query": "NONE",
                        "auto_generate_synonyms_phrase_query": true,
                        "fuzzy_transpositions": true,
                        "boost": 1.0
                    }
                },
                {
                    "bool":
                    {
                        "should":
                        [
                            {
                                "terms":
                                {
                                    "userIdList":
                                    [
                                        "011960253007264*****"
                                    ],
                                    "boost": 1.0
                                }
                            },
                            {
                                "terms":
                                {
                                    "deptIdList":
                                    [
                                        "991*****",
                                        "9914****",
                                        "1",
                                        "9917****"
                                    ],
                                    "boost": 1.0
                                }
                            }
                        ],
                        "adjust_pure_negative": true,
                        "boost": 1.0
                    }
                }
            ],
            "adjust_pure_negative": true,
            "boost": 1.0
        }
    },
    "_source":
    {
        "includes":
        [
            "noticeId",
            "sourceId",
            "title",
            "imageUrl",
            "typeId",
            "isTop",
            "isSecret",
            "publisher",
            "publishTime"
        ],
        "excludes":
        []
    }
}

获取ES返回的结果集之后根据情况拼接交互卡片的动态数据。

4. 构造AI交互卡片模板

在ES结果集返回之后需要创建AI交互卡片实例或更新交互卡片变量。

4.1. 创建模板

首先需要构造AI交互卡片模板。

登陆钉钉开放平台之后,选择开放能力-卡片平台-模板管理-新建模板或模板列表。

进入新建模板页面之后,填入模板名称、卡片类型、卡片模板场景、关联应用等信息,然后创建。创建完成之后就能在模板列表中找到了,点击进入模板配置工作台配置模板。

消息卡片类型包括:消息卡片、标准卡片、吊顶卡片、通讯录卡片以及工作台卡片等,此业务使用消息卡片类型。

消息卡片是钉钉互动卡片最常见的类型,其以聊天消息的形式出现在群聊或者单聊中,提供多端一致的信息展示与协同交互能力,拥有最丰富的组件和能力。

卡片模板场景选择AI卡片,可以在IM对话中增加AI场景。

关联应用选择【发文通知测试】应用,意味着在【发文通知测试】应用的机器人对话中,使用此交互卡片与用户交互。

4.2. 配置模板

在交互卡片配置工作台中根据业务需求添加组件及组件内的动态数据。此中内容可以在钉钉开发平台官网了解,此处不在赘述。此处只展示与当前业务相关的动态变量配置。

  1. AI卡片有五种状态:处理中、输出中、执行中、失败、完成
  2. 普通变量、本地变量

AI卡片状态完成状态是必须的,其他四种状态可以配置。依据业务配置处理中状态即可。

  • 卡片状态处理中状态,需要将flowStatus置为1,并且调用创建并发布卡片实例接口(createAndDeliverWithOptions)。
  • 卡片状态完成状态,需要将flowStatus置为3,并调用更新卡片实例接口(updateCardWithOptions)。

普通变量和本地变量是用来控制卡片数据展示和卡片行为的。

富文本组件可以接受接口发送的内容,或者按钮行为通过接口发送的只作为条件计算。

本地变量可以通过卡片行为改变,从而控制卡片其他组件的行为。

4.3. 创建AI交互卡片实例

在机器人接收到消息之后,会通过HTTP接口将消息内容及元数据发送到回调接口中。

此时可以通过业务逻辑,先发送创建AI交互卡片实例,组装请求头和请求内容给交互卡片。通过交互卡片模板唯一标识。

通过模板id和开放空间id可以创建并分发卡片实例,并把实例中需要的数据通过cardData与之绑定,回显渲染。

typescript 复制代码
/**
 * 封装处理中参数,创建并分发处理中卡片给用户
 *
 * @param receiveRobotsMsg 接收机器人消息
 * @param outTrackId 卡片跟踪id
 */
private void sendPendingMessage(ReceiveRobotsMsg receiveRobotsMsg, String outTrackId) {
    LoggerUtils.info(logger, "【钉钉机器人】推送处理中消息 -> receiveRobotsMsg:{}, outTrackId : {}", JSON.toJSONString(receiveRobotsMsg), outTrackId);
    Map<String, String> cardDataCardParamMap = new HashMap<>();
    //AI卡片状态,包含 pending(1)、writing(2)、done(3)、doing(4)、failed(5)
    cardDataCardParamMap.put("flowStatus", "1");
    sendMessage(receiveRobotsMsg, outTrackId, cardDataCardParamMap);
}


/**
 * 发送消息
 *
 * @param receiveRobotsMs      接收的机器人消息
 * @param outTrackId           单条消息id 需要更新卡片时需要
 * @param cardDataCardParamMap 发送的卡片表单数据
 */
public void sendMessage(ReceiveRobotsMsg receiveRobotsMs, String outTrackId, Map<String, String> cardDataCardParamMap) {
    //组装请求头
    CreateAndDeliverHeaders createAndDeliverHeaders = new CreateAndDeliverHeaders();
    try {
        // 调用该接口的访问凭证。
        createAndDeliverHeaders.xAcsDingtalkAccessToken = getAccessToken(receiveRobotsMs.getChatbotCorpId());
        if (StringUtils.isEmpty(createAndDeliverHeaders.xAcsDingtalkAccessToken)) {
            LoggerUtils.error(logger, "【钉钉机器人】推送消息失败:access_token缺失");
            return;
        }
    } catch (DingtalkApiException e) {
        LoggerUtils.error(logger, "【钉钉机器人】推送消息失败:获取access_token异常:{}", e.getMessage());
        return;
    }

    //组装请求内容
    CreateAndDeliverRequest.CreateAndDeliverRequestImRobotOpenSpaceModel imRobotOpenSpaceModel = new CreateAndDeliverRequest.CreateAndDeliverRequestImRobotOpenSpaceModel()
            // 是否支持转发
            .setSupportForward(false);

    CreateAndDeliverRequest.CreateAndDeliverRequestImRobotOpenDeliverModel imRobotOpenDeliverModel = new CreateAndDeliverRequest.CreateAndDeliverRequestImRobotOpenDeliverModel()
            // IM机器人单聊若未设置其他投放属性,需设置spaeType为IM_ROBOT。
            .setSpaceType(SPACE_TYPE)
            // 机器人编码
            .setRobotCode(receiveRobotsMs.getRobotCode());
    CreateAndDeliverRequest.CreateAndDeliverRequestCardData cardData = new CreateAndDeliverRequest.CreateAndDeliverRequestCardData()
            // 卡片模板内容替换参数
            .setCardParamMap(cardDataCardParamMap);

    CreateAndDeliverRequest createAndDeliverRequest =  new CreateAndDeliverRequest()
            // 卡片内容模板ID
            .setCardTemplateId(CARD_TEMPLATE_ID)
            // 外部卡片实例Id
            .setOutTrackId(outTrackId)
            // 表示场域及其场域id,其格式为dtv1.card//spaceType1.spaceId1;spaceType2.spaceId2_1;spaceType2.spaceId2_2;spaceType3.spaceId3
            .setOpenSpaceId("dtv1.card//im_robot." + receiveRobotsMs.getSenderStaffId())
            // 卡片回调的类型
            .setCallbackType("HTTP")
            // 卡片数据
            .setCardData(cardData)
            // IM机器人单聊场域信息。
            .setImRobotOpenSpaceModel(imRobotOpenSpaceModel)
            // IM机器人单聊投放参数
            .setImRobotOpenDeliverModel(imRobotOpenDeliverModel)
            // 用户userId类型:1(默认):userId模式;2:unionId模式
            .setUserIdType(1);
    try {
        // 创建并投放卡片
        createClient().createAndDeliverWithOptions(createAndDeliverRequest, createAndDeliverHeaders, new RuntimeOptions());
    } catch (Exception e) {
        LoggerUtils.error(logger, "【钉钉机器人】推送消息失败:创建钉钉机器人Client异常:{}", e.getMessage());
    }
}

/**
 * 创建client
 * @return
 * @throws Exception
 */
private Client createClient() throws Exception {
    Config config = new Config();
    config.protocol = "https";
    config.regionId = "central";
    return new Client(config);
}

4.4. 更新AI交互卡片变量

更新AI交互卡片与创建AI交互卡片不同

  1. 更新要在创建之后
  2. 更新和创建必须在同一个模板id下使用同一个outTrackId
  3. 更新的flowStatus为3
typescript 复制代码
/**
 * 返回搜索结果
 * @param receiveRobotsMsg
 * @param outTrackId
 */
private void sendDoneMessage(final ReceiveRobotsMsg receiveRobotsMsg, String outTrackId) {
    LoggerUtils.info(logger, "【钉钉机器人】推送完成消息 -> receiveRobotsMsg:{}, outTrackId : {}", JSON.toJSONString(receiveRobotsMsg), outTrackId);
    final String content = receiveRobotsMsg.getText().getContent();
    String corpId = WorkbenchCommonConstant.DEFAULT_CORP_ID;
    String userId = receiveRobotsMsg.getSenderStaffId();
    Set<String> deptIds = this.obtainDeptIds(userId, corpId);
    Map<String, Object> params = ImmutableMap.<String, Object>of("title", content, "queryScene", QuerySceneEnum.ROBOT_RECEIVE_QUERY);
    PageInfo<SimpleNotice> simpleNoticePageInfo = newsSearchService.findByPageForIndexFromElasticsearch(
            1, 3,
            userId, deptIds, params,
            corpId, null);
    LoggerUtils.info(logger, "搜索结果:{}", JSON.toJSONString(simpleNoticePageInfo));
    boolean isEmpty = ObjectUtil.isNull(simpleNoticePageInfo) || CollectionUtils.isEmpty(simpleNoticePageInfo.getList());
    Map<String, String> cardDataCardParamMap = new HashMap<>();
    cardDataCardParamMap.put("flowStatus", "3");
    cardDataCardParamMap.put("failure", "false");
    cardDataCardParamMap.put("isEmpty", Boolean.toString(isEmpty));
    if(isEmpty){
        // 无数据卡片
        String noDataCardTips = String.format("暂未查询到与"%%s%"相关的结果,辛苦您输入更多文件关键词或者标题进行检索。", content);
        cardDataCardParamMap.put("noDataCardTips", noDataCardTips);
    }else{
        // 有数据卡片
        String title = String.format("已为您找到与"%%s%"相关的%d条重要通知:", content, simpleNoticePageInfo.getTotal());
        List<String> itemList = new ArrayList<>();
        for (SimpleNotice simpleNotice : simpleNoticePageInfo.getList()) {
            String url = this.obtainDingTalkUrl(simpleNotice.getNoticeId());
            LoggerUtils.info(logger, "【钉钉机器人】推送完成消息 -> title : {}, dingTalkUrl:{}", simpleNotice.getTitle(), url);
            itemList.add(String.format("[%s](%s)</br><font sizeToken= common_footnote_text_style__font_size colorTokenV2= common_gray2_color >发布时间:%tF</font></br>", simpleNotice.getTitle(), url, DateUtils.parseDateTime(simpleNotice.getPublishTime())));
        }
        String items = StringUtils.join(itemList, "\n***\n");
        LoggerUtils.info(logger, "【钉钉机器人】推送有数据卡片 -> items:{}", items);
        cardDataCardParamMap.put("title", title);
        cardDataCardParamMap.put("items", items);
        cardDataCardParamMap.put("viewMore", getMoreViewDingtalkUrl(content, corpId));
        cardDataCardParamMap.put("showViewMore", String.valueOf(simpleNoticePageInfo.getTotal() > 3));

    }
    updateMessage(receiveRobotsMsg, outTrackId, cardDataCardParamMap);
}




/**
 * 更新消息
 * @param receiveRobotsMsg
 * @param outTrackId
 * @param cardDataCardParamMap
 */
void updateMessage(ReceiveRobotsMsg receiveRobotsMsg, String outTrackId, Map<String, String> cardDataCardParamMap) {
    UpdateCardHeaders updateCardHeaders = new UpdateCardHeaders();
    try {
        updateCardHeaders.xAcsDingtalkAccessToken = getAccessToken(receiveRobotsMsg.getChatbotCorpId());
        if (StringUtils.isEmpty(updateCardHeaders.xAcsDingtalkAccessToken)) {
            LoggerUtils.error(logger, "【钉钉机器人】推送消息失败:access_token缺失");
            return;
        }
    } catch (DingtalkApiException e) {
        LoggerUtils.error(logger, "【钉钉机器人】推送消息失败:获取access_token异常:{}", e.getMessage());
        return;
    }

    UpdateCardRequest.UpdateCardRequestCardUpdateOptions cardUpdateOptions = new UpdateCardRequest.UpdateCardRequestCardUpdateOptions()
            .setUpdateCardDataByKey(false)
            .setUpdatePrivateDataByKey(false);
    UpdateCardRequest.UpdateCardRequestCardData cardData = new UpdateCardRequest.UpdateCardRequestCardData()
            .setCardParamMap(cardDataCardParamMap);
    UpdateCardRequest updateCardRequest = new UpdateCardRequest()
            .setOutTrackId(outTrackId)
            .setCardData(cardData)
            .setCardUpdateOptions(cardUpdateOptions)
            .setUserIdType(1);
    try {
        createClient().updateCardWithOptions(updateCardRequest, updateCardHeaders, new RuntimeOptions());
    } catch (Exception e) {
        LoggerUtils.error(logger, "【钉钉机器人】更新消息失败:创建钉钉机器人Client异常:{}", e.getMessage());
    }
}

更详细的sdk说明可以查阅官方文档,不过文档信息比较分散,以上便是针对当前业务所查阅整理的实现步骤。

望江湖大佬有个中妙计,不吝赐教交流学习。

作者:梁孟源

相关推荐
加班挖坑5 小时前
本地部署graphRAG
llm·openai
Stirner6 小时前
Google A2A 实战理解,相比 MCP 它实际上是任务队列协议
aigc·openai·mcp
新智元8 小时前
国产 Vidu Q1 出道即顶流,登顶 VBench!吉卜力、广告大片、科幻特效全包了
人工智能·openai
新智元9 小时前
AI 永生时代来临!DeepMind「生成幽灵」让逝者赛博重生
人工智能·openai
骑猪兜风23314 小时前
万字长文分析 10 种流行 Agent 框架设计思路,教你如何构建真正可靠的 Agent 系统?
openai·ai编程·cursor
renne1 天前
Open WebUI+MCP搭建个人AI智能体
python·openai
Json_1 天前
睡前小故事数据集分享
人工智能·数据分析·openai
量子位1 天前
人形机器人半马冠军,为什么会选择全尺寸?
人工智能·openai