主动推送alertmanager警报到钉钉卡片card(2)

2024年 1月 25日 71.9k 0

通常,提供一个webhook需要一个公网的ip公开以提供入口以便于在调用。而在钉钉的STREAM模式下就不需要公网,我们可以任意通过一台内网的节点来与dingding回调。

在钉钉官方的文档1,文档2,文档3,文档4中大致描述了卡片的调用信息,我们可以通过这些信息将卡片直接通过API发送到钉钉卡片助手。如果我们想发送到指定的群中,并且可以通过互调使用,我们需要查看github的STREAM模式的card_callback示例

在上一篇中对接alertmanager创建钉钉卡片(1)中,我们手动在页面已经创建了钉钉卡片模板。这将会得到的一个模板id。此时我们配合OnCardCallback函数中接收交互的数据,将触发的警报id和传回后端进行逻辑处理。

阅读本篇将了解dingtalk stream-mode 模式下调用卡片和主动推送卡片到群,主动推送卡片到用户id。

本篇文章在linuxea首发: https://www.linuxea.com原文连接: https://www.linuxea.com/3267.html

回调卡片

部分代码如下:构造信息被置顶在const中,而后在dingtalkcard_1_0.CreateAndDeliverRequest中调用

    sendCardRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{
        UserIdType:     tea.Int32(1), // 1(默认):userid模式;2:unionId模式;
        CardTemplateId: tea.String(CARD_TEMPLATE_ID),
        OutTrackId:     tea.String(outTrackId),
        CallbackType:   tea.String("STREAM"), // 采用 Stream 模式接收回调事件
        CardData:       cardData,
        OpenSpaceId:    tea.String(openSpaceId),
    }

如果是群聊,场域信息就是data.ConversationId,并且是IM_GROUP

    if data.ConversationType == "2" { // 群聊
        openSpaceId = fmt.Sprintf("dtv1.card//IM_GROUP.%s", data.ConversationId)
    } else {
        openSpaceId = fmt.Sprintf("dtv1.card//IM_ROBOT.%s", data.SenderStaffId)
    }

CARD_TEMPLATE_ID 是创建卡片完成后的的模板id,位于钉钉后台 -> 卡片平台 -> 模板列表中的模板下方:模板 ID

现在只需要将card_callback示例中的CARD_TEMPLATE_ID变更为创建好的模板id,并且补充clientId, clientSecret就可以任何群内,将创建好的机器人添加后,通过@机器人来回调卡片了。

clientId, clientSecret在钉钉后台 -> 应用开发 -> 钉钉应用 ,点击进入已经创建的应用中,选中凭证与基础信息,复制Client ID和Client Secret

主动调用卡片

每次都要手动@机器人回调在正常情况下是没有问题的。但我们处理的是一个alertmanager警告。因此,我们需要新增一个修改了的OnChatBotMessageReceived函数来主动发起调用,而并非只注册到RegisterChatBotCallbackRouter中去。

我们至少需要明确指定要发的目的是哪里,也就是场域信息。说白了,在这里就是群ID信息

OnChatBotMessageReceived中是通过自动识别来判断场域

通过手动在指定的群内添加创建好的机器人后@机器人可以获取场域

    var openSpaceId string = ""
    if data.ConversationType == "2" { // 群聊
        openSpaceId = fmt.Sprintf("dtv1.card//IM_GROUP.%s", data.ConversationId)
    } else {
        openSpaceId = fmt.Sprintf("dtv1.card//IM_ROBOT.%s", data.SenderStaffId)
    }
    fmt.Printf("is openSpaceId: %s,outTrackId: %s", openSpaceId, outTrackId)

// 本篇文章在linuxea首发: https://www.linuxea.com// 原文连接: https://www.linuxea.com/3267.html

而后将场域存在数据库中,并且填写已经创建了的card_template_id

mysql> select id,
alertmanager_url,groupname,card_template_id,openspace_id,status from `sqlx-demo`.dingtalk_alertmanager_sendinfo;
+----+------------------+-------------+---------------------------------------------+-----------------------------+--------+
| id | alertmanager_url | groupname   | card_template_id                            | openspace_id                | status |
+----+------------------+-------------+---------------------------------------------+-----------------------------+--------+
|  1 | 127.0.0.1:9093   |             | 0b9518ca-9394-40c0-8de4-fe319a45f.schema    | Q9ahOsCD5OP8Zl4OuY0ZXw==    | 1      |
|  2 | 127.0.0.1:9093   |             | 0b9518ca-9394-40c0-8de4-fe319a45f.schema    | cid0jYUU3rhzPhcFf+8+xw==    | 1      |
|  3 | 127.0.0.1:9093   | 六如-测试群   | 0b9518ca-9394-40c0-8de4-fe319a45f.schema    | cidkYDAA6LZPr6Ml6p2xZw==    | 0      |
+----+------------------+-------------+---------------------------------------------+-----------------------------+--------+
3 rows in set

现在OnChatBotMessageReceived被cli.RegisterChatBotCallbackRouter(OnChatBotMessageReceived)直接在注册到ChatBotCallbackRouter,我们在创建一个toChatBotMessage的函数,来主动推送卡片,同时传递message.success,message_id,以及已经写入到数据库的openspace_id和card_template_id,如下:

func toChatBotMessage(mes, idslist1, success, oid, ctid string) error {
    cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{
        CardParamMap: make(map[string]*string),
    }
// 本篇文章在linuxea首发: https://www.linuxea.com
// 原文连接: https://www.linuxea.com/3267.html
    cardData.CardParamMap["message"] = tea.String(mes)
    cardData.CardParamMap["success"] = tea.String(success)
    cardData.CardParamMap["message_id"] = tea.String(idslist1)
    imGroupOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{
        SupportForward: tea.Bool(true),
    }
    imGroupOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
        Extension: make(map[string]*string),
        RobotCode: tea.String(dingtalkClient.ClientID),
    }
    u, _ := uuid.NewUUID()
    outTrackId := u.String()
    openSpaceIds := fmt.Sprintf("dtv1.card//IM_GROUP.%s", oid)
    sendCardRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{
        UserIdType:     tea.Int32(1), // 1(默认):userid模式;2:unionId模式;
        CardTemplateId: tea.String(ctid),
        OutTrackId:     tea.String(outTrackId), //outTrackId
        CallbackType:   tea.String("STREAM"),   // 采用 Stream 模式接收回调事件
        CardData:       cardData,
        OpenSpaceId:    tea.String(openSpaceIds),
    }
    sendCardRequest.ImGroupOpenSpaceModel = imGroupOpenSpaceModel
    sendCardRequest.ImGroupOpenDeliverModel = imGroupOpenDeliverModel
    _, err := dingtalkClient.SendCard(sendCardRequest)
    if err != nil {
        return err
    }
    return nil
}

让OnChatBotMessageReceived,toChatBotMessage的CARD_TEMPLATE_ID保持一致,我们也使用全局变量来将数据库的数据读取后赋值

var CARD_TEMPLATE_ID, AlertManagerURL string

接着遍历从数据库获取到的CARD_TEMPLATE_ID, AlertManagerURL后重新赋值到全局

    for _, v := range res {
        AlertManagerURL = v.AlertManagerURL
        CARD_TEMPLATE_ID = v.CardTemplateId
        // 1.发送消息到卡片填充内容
        // 2.将数据库中的群OpenSpaceId和卡片ID发送到toChatBotMessage
        err = toChatBotMessage(mes, idslistring, success, v.OpenSpaceId, v.CardTemplateId)
        if err != nil {
            zap.L().Error("ActiveDingtalkOnsendCard toChatBotMessage 发送出错:", zap.Error(err))
        }
    }

我们上面的数据库中有3条openspace_id是有数据的,并且2条的status状态是1,那就通过for循环就可以发送到2个群内处理卡片。

卡片回调处理逻辑

处理的逻辑在OnCardCallback函数中,参数的结构体Models.go中,如下:

// 本篇文章在linuxea首发: https://www.linuxea.com
// 原文连接: https://www.linuxea.com/3267.html
type CardRequest struct {
    Content        string `json:"content"`
    CorpId         string `json:"corpId"`
    Extension      string `json:"extension"`
    OutTrackId     string `json:"outTrackId"`
    SpaceId        string `json:"spaceId"`
    SpaceType      string `json:"spaceType"`
    Type           string `json:"type"`
    UserId         string `json:"userId"`
    UserIdType     int    `json:"userIdType"`
    CardActionData PrivateCardActionData
}
type PrivateCardActionData struct {
    CardPrivateData CardPrivateData `json:"cardPrivateData"`
}

type CardPrivateData struct {
    ActionIdList []string       `json:"actionIds"`
    Params       map[string]any `json:"params"`
}

我们通过request.CardActionData.CardPrivateData.Params来获取回调的参数即可。接着,我们将警报内容组合后返回:

mes: mes对应的是message,message对应的是卡片的markdown内容success: success对应的返回值message_id: message_id对应的是告警的id

    if success == "" {
        success = "false"
        response := &card.CardResponse{
            CardData: &card.CardDataDto{
                CardParamMap: map[string]string{
                    "message":    mes,
                    "success":    success,
                    "message_id": idslistring,
                },
            },
        }
        return response, nil
    }

注意:OnChatBotMessageReceived,toChatBotMessage两个函数一个负责主动消息,一个回调返回。在OnChatBotMessageReceived函数中的message,success,message_id的获取接口应该和toChatBotMessage的内容一致。

    cardData.CardParamMap["message"] = tea.String(mes)
    cardData.CardParamMap["success"] = tea.String(success)
    cardData.CardParamMap["message_id"] = tea.String(idslistring)

此时,数据读取后我们就可以进行更多逻辑处理,比如告警合并,以及告警沉默,忽略的逻辑部分了。

卡到单聊

既然发送到群,那也要推送到某个用户的需求。要发送到单聊需要修改,如下:

场域类型 SpaceType SpaceId SpaceId含义
IM群聊 IM_GROUP openConversationId 会话id
IM机器人单聊 IM_ROBOT userId/unionId 员工id

SpaceType的type必须是IM_ROBOT类型,并且SpaceId的场域不在群的ID拼接,而是userid,也就是钉钉后台看到的用户的id号。在github示例的代码中,有一下片段

    if data.ConversationType == "2" { // 群聊
        openSpaceId = fmt.Sprintf("dtv1.card//IM_GROUP.%s", data.ConversationId)
    } else {
        openSpaceId = fmt.Sprintf("dtv1.card//IM_ROBOT.%s", data.SenderStaffId)
    }

    if data.ConversationType == "2" { // 群聊
        sendCardRequest.ImGroupOpenSpaceModel = imGroupOpenSpaceModel
        sendCardRequest.ImGroupOpenDeliverModel = imGroupOpenDeliverModel
    } else {
        sendCardRequest.ImRobotOpenSpaceModel = imRobotOpenSpaceModel
        sendCardRequest.ImRobotOpenDeliverModel = imRobotOpenDeliverModel
    }

如果data.ConversationType等于2那么得到的是IM_GROUP的拼接的群信息类型,否则则是IM_ROBOT的userid的拼接信息类型,因此我们重新写一个函数,如下:

func toChatBotMessageToROBOT(mes, idslist1, success, userids, ctid string) error {
    cardData := &dingtalkcard_1_0.CreateAndDeliverRequestCardData{
        CardParamMap: make(map[string]*string),
    }
    cardData.CardParamMap["message"] = tea.String(mes)
    cardData.CardParamMap["success"] = tea.String(success)
    cardData.CardParamMap["message_id"] = tea.String(idslist1)
    imRobotOpenSpaceModel := &dingtalkcard_1_0.CreateAndDeliverRequestImRobotOpenSpaceModel{
        SupportForward: tea.Bool(true),
    }
    imRobotOpenDeliverModel := &dingtalkcard_1_0.CreateAndDeliverRequestImRobotOpenDeliverModel{
        Extension: make(map[string]*string),
        RobotCode: tea.String(dingtalkClient.ClientID),
        SpaceType: tea.String("IM_ROBOT"),
    }
    u, _ := uuid.NewUUID()
    outTrackId := u.String()
    openSpaceIds := fmt.Sprintf("dtv1.card//IM_ROBOT.%s", userids)
    sendCardRequest := &dingtalkcard_1_0.CreateAndDeliverRequest{
        UserIdType:     tea.Int32(1), // 1(默认):userid模式;2:unionId模式;
        CardTemplateId: tea.String(ctid),
        OutTrackId:     tea.String(outTrackId), //outTrackId
        CallbackType:   tea.String("STREAM"),   // 采用 Stream 模式接收回调事件
        CardData:       cardData,
        OpenSpaceId:    tea.String(openSpaceIds),
    }
    sendCardRequest.ImRobotOpenSpaceModel = imRobotOpenSpaceModel
    sendCardRequest.ImRobotOpenDeliverModel = imRobotOpenDeliverModel
    _, err := dingtalkClient.SendCard(sendCardRequest)
    if err != nil {
        return err
    }
    return nil
}
// 本篇文章在linuxea首发: https://www.linuxea.com
// 原文连接: https://www.linuxea.com/3267.html

现在,我们需要准备一个表来存放userid和组织信息

发送逻辑

在发送到alertmanager中有一个字段是alertgroup,因此。在标签内必须有一个alertgroup的字段

[{ 
    "labels": {
        "alertname": "我只是一个测试s2",
        "severity":"Warning",
        "alertgroup":"ops",
        "instance": "IS-MBG-PRO-jenkins-01",
        "job":"labels.job",
        "name":"labels.name",
        "namespace": "namespace"
    },
    "annotations": {
        "summary": "annotations.summary测试2311",
        "description":"这是description"
    }
}]

其次,alertgroup字段必须是和一个表内的组织关联。比如dingtalk_alertmanager_senduser当下的这个表

mysql> select user,userid,role_group_name,role_name,status
 from `dingtalk_alertmanager_senduser`;
+------+-------------+-----------------+-----------+--------+
| user | userid      | role_group_name | role_name | status |
+------+-------------+-----------------+-----------+--------+
| mark | manager     | ops             | 测试       | 1      |
+------+-------------+-----------------+-----------+--------+

dingtalk_alertmanager_senduser的status字段为1时候,获取当前。查询alertmanagermessage表中的alertgroup和role_group_name都是ops的数据,而后读取userid,调用函数toChatBotMessageToROBOT,将role_group_name等于ops,status为1中的userid值通过机器人推送即可。

参考

https://github.com/open-dingtalk/dingtalk-tutorial-go/blob/main/bot_card_callback/card_callback.gohttps://open-dev.dingtalk.com/apiExplorer?spm=ding_open_doc.document.0.0.2a261887pAnZhf#/?devType=org&api=card_1.0%23DeliverCardhttps://open.dingtalk.com/document/orgapp/open-interface-card-delivery-instancehttps://open.dingtalk.com/document/orgapp/delivery-card-interfacehttps://open.dingtalk.com/document/orgapp/create-and-deliver-cards#h2-zdk-bmv-ve7

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论