通常,提供一个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