diff --git a/internal/biz/group_config.go b/internal/biz/group_config.go index 738c4f0..8fb3953 100644 --- a/internal/biz/group_config.go +++ b/internal/biz/group_config.go @@ -32,7 +32,6 @@ import ( "github.com/alibabacloud-go/tea/tea" "github.com/coze-dev/coze-go" "github.com/gofiber/fiber/v2/log" - "github.com/google/uuid" "xorm.io/builder" ) @@ -506,14 +505,16 @@ func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Rec // 未检索到匹配信息,询问是否拉群 if !isRetrieved { + // 构建卡片 OutTrackId + outTrackId := constants.BuildCardOutTrackId("cidwP24PLZhLVOS2dVIkEawLw==", "ding5wwvnf9hxeyjau7t") + // 发送钉钉卡片 - uuid := uuid.New().String() _, err = g.dingtalkCardClient.CreateAndDeliver(dingtalk.AppKey{ AppKey: "ding5wwvnf9hxeyjau7t", AppSecret: "FxXVlTzxrKXvJ8h-9uK0s5TjaBfOJSXumpmrHal-NmQAtku9wOPxcss0Af6WHoAK", }, &card_1_0.CreateAndDeliverRequest{ CardTemplateId: tea.String("faad6d5d-726d-467f-a6ba-28c1930aa5f3.schema"), - OutTrackId: tea.String(uuid), + OutTrackId: tea.String(outTrackId), CallbackType: tea.String("STREAM"), CardData: &card_1_0.CreateAndDeliverRequestCardData{ CardParamMap: map[string]*string{ @@ -525,6 +526,7 @@ func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Rec "button_right": tea.String("忽略"), "button_right_link": tea.String(""), "action_id": tea.String("create_group"), + "button_display": tea.String("true"), "_CARD_DEBUG_TOOL_ENTRY": tea.String("show"), }, }, @@ -534,9 +536,9 @@ func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Rec OpenSpaceId: tea.String("dtv1.card//im_group.cidwP24PLZhLVOS2dVIkEawLw=="), ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ RobotCode: tea.String("ding5wwvnf9hxeyjau7t"), - // Recipients: []*string{ - // tea.String("17415698414368678"), - // }, + Recipients: []*string{ + tea.String("17415698414368678"), + }, }, }) if err != nil { diff --git a/internal/data/constants/dingtalk.go b/internal/data/constants/dingtalk.go index 0dc95fe..88d02d0 100644 --- a/internal/data/constants/dingtalk.go +++ b/internal/data/constants/dingtalk.go @@ -1,6 +1,11 @@ package constants -import "net/url" +import ( + "net/url" + "strings" + + "github.com/google/uuid" +) const DingTalkBseUrl = "https://oapi.dingtalk.com" @@ -79,7 +84,35 @@ const ( }` ) +// 交互卡片回调类型 +const ( + CardActionCallbackTypeAction string = "actionCallback" // 交互卡片回调事件类型 +) + // 交互卡片回调事件类型 const ( CardActionTypeCreateGroup string = "create_group" // 创建群聊 ) + +// dingtalk 卡片 OutTrackId 模板 +const CardOutTrackIdTemplate string = "{space_id}:{bot_id}:{uuid}" + +func BuildCardOutTrackId(spaceId string, botId string) (outTrackId string) { + uuid := uuid.New().String() + + outTrackId = strings.ReplaceAll(CardOutTrackIdTemplate, "{space_id}", spaceId) + outTrackId = strings.ReplaceAll(outTrackId, "{bot_id}", botId) + outTrackId = strings.ReplaceAll(outTrackId, "{uuid}", uuid) + + return +} + +func ParseCardOutTrackId(outTrackId string) (spaceId string, botId string) { + parts := strings.Split(outTrackId, ":") + if len(parts) != 3 { + return + } + spaceId, botId, _ = parts[0], parts[1], parts[2] + + return +} diff --git a/internal/data/impl/bot_group.go b/internal/data/impl/bot_group.go index e0593c4..d8ba58b 100644 --- a/internal/data/impl/bot_group.go +++ b/internal/data/impl/bot_group.go @@ -19,7 +19,7 @@ func NewBotGroupImpl(db *utils.Db) *BotGroupImpl { func (k BotGroupImpl) GetByConversationIdAndRobotCode(staffId string, robotCode string) (*model.AiBotGroup, error) { var data model.AiBotGroup - err := k.Db.Model(k.Model).Where("conversation_id = ? and robot_code = ?", staffId, robotCode).Find(&data).Error + err := k.Db.Model(k.Model).Where("conversation_id = ? and robot_code = ? and status = 1", staffId, robotCode).Find(&data).Error if data.GroupID == 0 { err = sql.ErrNoRows } diff --git a/internal/data/model/ai_bot_group_config.gen.go b/internal/data/model/ai_bot_group_config.gen.go index f839145..8d889a0 100644 --- a/internal/data/model/ai_bot_group_config.gen.go +++ b/internal/data/model/ai_bot_group_config.gen.go @@ -11,6 +11,7 @@ type AiBotGroupConfig struct { ConfigID int32 `gorm:"column:config_id;primaryKey;autoIncrement:true" json:"config_id"` ToolList string `gorm:"column:tool_list;not null" json:"tool_list"` ProductName string `gorm:"column:product_name;not null" json:"product_name"` + IssueOwner string `gorm:"column:issue_owner;comment:群组问题处理人" json:"issue_owner"` // 群组问题处理人 } // TableName AiBotGroupConfig's table name diff --git a/internal/pkg/dingtalk/card_client.go b/internal/pkg/dingtalk/card_client.go index aa498ab..a013a04 100644 --- a/internal/pkg/dingtalk/card_client.go +++ b/internal/pkg/dingtalk/card_client.go @@ -58,3 +58,28 @@ func (c *CardClient) CreateAndDeliver(req AppKey, cardData *card.CreateAndDelive return true, nil } + +// 更新卡片 +func (c *CardClient) UpdateCard(req AppKey, cardData *card.UpdateCardRequest) (bool, error) { + // 获取token + accessToken, err := c.oauth2Client.GetAccessToken(req) + if err != nil { + return false, err + } + + // 调用API + resp, err := c.cli.UpdateCardWithOptions( + cardData, + &card.UpdateCardHeaders{XAcsDingtalkAccessToken: tea.String(accessToken)}, + &util.RuntimeOptions{}, + ) + if err != nil { + return false, err + } + + if resp.Body == nil { + return false, errorcode.ParamErrf("empty response body") + } + + return *resp.Body.Success, nil +} diff --git a/internal/services/dtalk_bot.go b/internal/services/dtalk_bot.go index a2e5f4e..cd59c80 100644 --- a/internal/services/dtalk_bot.go +++ b/internal/services/dtalk_bot.go @@ -4,6 +4,8 @@ import ( "ai_scheduler/internal/biz" "ai_scheduler/internal/config" "ai_scheduler/internal/data/constants" + "ai_scheduler/internal/data/impl" + "ai_scheduler/internal/data/model" "ai_scheduler/internal/entitys" "ai_scheduler/internal/pkg/dingtalk" "context" @@ -15,19 +17,33 @@ import ( "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/card" "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot" "golang.org/x/sync/errgroup" + "xorm.io/builder" ) type DingBotService struct { - config *config.Config - dingTalkBotBiz *biz.DingTalkBotBiz - dingTalkOld *dingtalk.OldClient + config *config.Config + dingTalkBotBiz *biz.DingTalkBotBiz + dingTalkOld *dingtalk.OldClient + dingtalkCardClient *dingtalk.CardClient + botGroupConfigImpl *impl.BotGroupConfigImpl + botGroupImpl *impl.BotGroupImpl } -func NewDingBotService(config *config.Config, dingTalkBotBiz *biz.DingTalkBotBiz, dingTalkOld *dingtalk.OldClient) *DingBotService { +func NewDingBotService( + config *config.Config, + dingTalkBotBiz *biz.DingTalkBotBiz, + dingTalkOld *dingtalk.OldClient, + dingtalkCardClient *dingtalk.CardClient, + botGroupConfigImpl *impl.BotGroupConfigImpl, + botGroupImpl *impl.BotGroupImpl, +) *DingBotService { return &DingBotService{ - config: config, - dingTalkBotBiz: dingTalkBotBiz, - dingTalkOld: dingTalkOld, + config: config, + dingTalkBotBiz: dingTalkBotBiz, + dingTalkOld: dingTalkOld, + dingtalkCardClient: dingtalkCardClient, + botGroupConfigImpl: botGroupConfigImpl, + botGroupImpl: botGroupImpl, } } @@ -147,26 +163,75 @@ func (d *DingBotService) runBackgroundTasks(ctx context.Context, data *chatbot.B return nil } +// **一把梭先搞,后续规范化** func (d *DingBotService) OnCardMessageReceived(ctx context.Context, data *card.CardRequest) (resp *card.CardResponse, err error) { - // 解content - var cardActionData card.PrivateCardActionData - if err = json.Unmarshal([]byte(data.Content), &cardActionData); err != nil { - return nil, err + // 非回调类型暂不接受 + if data.Type != constants.CardActionCallbackTypeAction { + return nil, nil } - // action 处理 - for _, actionId := range cardActionData.CardPrivateData.ActionIdList { + // action 处理 - 这里先只处理第一个匹配的actionId + for _, actionId := range data.CardActionData.CardPrivateData.ActionIdList { switch actionId { case constants.CardActionTypeCreateGroup: - if cardActionData.CardPrivateData.Params["status"] != "confirm" { - continue - } - // 创建群聊 - if _, err = d.dingTalkOld.CreateInternalGroupConversation(ctx, "问题处理群", []string{"17415698414368678", "17101201090101570"}); err != nil { + // 解析 OutTrackId 以获取 SpaceId 和 BotId + spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId) + // 群id+机器人id确认一个群配置 + var botGroup *model.AiBotGroup + botGroup, err = d.botGroupImpl.GetByConversationIdAndRobotCode(spaceId, botId) + if err != nil { return nil, err } + // 获取群配置 + var groupConfig model.AiBotGroupConfig + cond := builder.NewCond().And(builder.Eq{"config_id": botGroup.ConfigID}) + err = d.botGroupConfigImpl.GetOneBySearchToStrut(&cond, &groupConfig) + if err != nil { + return nil, err + } + // 获取处理人列表 + issueOwnerJson := groupConfig.IssueOwner + type issueOwnerType struct { + UserId string `json:"userid"` + Name string `json:"name"` + } + var issueOwner []issueOwnerType + if err = json.Unmarshal([]byte(issueOwnerJson), &issueOwner); err != nil { + return nil, err + } + // 合并所有userid + userIds := []string{data.UserId} // 当前用户为群主 + for _, owner := range issueOwner { + userIds = append(userIds, owner.UserId) + } + + if data.CardActionData.CardPrivateData.Params["status"] == "confirm" { + // 创建群聊 - 这里用的是“统一登录平台”这个应用的接口 + // 不是很关心成功失败,ws中,后续考虑协程去创建 + if _, err = d.dingTalkOld.CreateInternalGroupConversation(ctx, "问题处理群", userIds); err != nil { + return nil, err + } + } + + // 构建关闭创建群组卡片按钮的响应 + resp = d.buildCreateGroupCardResp() + return } } return &card.CardResponse{}, nil } + +// 关闭创建群组卡片按钮 +func (d *DingBotService) buildCreateGroupCardResp() *card.CardResponse { + return &card.CardResponse{ + CardData: &card.CardDataDto{ + CardParamMap: map[string]string{ + "button_display": "false", + }, + }, + CardUpdateOptions: &card.CardUpdateOptions{ + UpdateCardDataByKey: true, + }, + } +}