From 847eb8b5db79fd7ad584e16cc67e57a5a66fe0aa Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Mon, 2 Feb 2026 16:32:19 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=8D=95=E8=81=8A=E6=9C=BA=E5=99=A8?= =?UTF-8?q?=E4=BA=BA=E5=88=9D=E6=AD=A5=E5=BC=80=E5=8F=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/ding_talk_bot.go | 179 ++++++++++++++++++++-------- internal/biz/group_config.go | 30 +++-- internal/services/dtalk_bot_test.go | 3 +- 3 files changed, 151 insertions(+), 61 deletions(-) diff --git a/internal/biz/ding_talk_bot.go b/internal/biz/ding_talk_bot.go index 4820961..f40cc3c 100644 --- a/internal/biz/ding_talk_bot.go +++ b/internal/biz/ding_talk_bot.go @@ -25,11 +25,13 @@ import ( "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot" dingtalkPkg "ai_scheduler/internal/pkg/dingtalk" + "ai_scheduler/internal/pkg/util" "github.com/alibabacloud-go/dingtalk/card_1_0" "github.com/alibabacloud-go/tea/tea" "github.com/duke-git/lancet/v2/slice" "github.com/gofiber/fiber/v2/log" + "github.com/redis/go-redis/v9" "xorm.io/builder" ) @@ -56,7 +58,7 @@ type DingTalkBotBiz struct { dingtalkOauth2Client *dingtalkPkg.Oauth2Client dingTalkOld *dingtalkPkg.OldClient dingtalkCardClient *dingtalkPkg.CardClient - rdb *utils.Rdb + redisCli *redis.Client issueImpl *impl.IssueImpl sysImpl *impl.SysImpl } @@ -103,7 +105,7 @@ func NewDingTalkBotBiz( dingtalkOauth2Client: dingtalkOauth2Client, dingTalkOld: dingTalkOld, dingtalkCardClient: dingtalkCardClient, - rdb: rdb, + redisCli: rdb.Rdb, issueImpl: issueImpl, sysImpl: sysImpl, } @@ -167,12 +169,12 @@ func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *enti // 2. 检查会话状态 (Redis) statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId) - status, _ := d.rdb.Rdb.Get(ctx, statusKey).Result() + status, _ := d.redisCli.Get(ctx, statusKey).Result() if status == "WAITING_FOR_SYS_CONFIRM" { // 用户回复了系统名称 sysName := requireData.Req.Text.Content - d.rdb.Rdb.Del(ctx, statusKey) + d.redisCli.Del(ctx, statusKey) return d.handleWithSpecificSys(ctx, requireData, sysName) } @@ -283,7 +285,7 @@ func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireDat if sys.SysID == 0 { // 无法明确系统,询问用户 statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId) - d.rdb.Rdb.Set(ctx, statusKey, "WAITING_FOR_SYS_CONFIRM", time.Hour) + d.redisCli.Set(ctx, statusKey, "WAITING_FOR_SYS_CONFIRM", time.Hour) entitys.ResText(requireData.Ch, "", "抱歉,我无法确定您咨询的是哪个系统。请告诉我具体系统名称(如:直连天下系统、货易通系统),以便我为您安排对应的技术支持。") return nil } @@ -360,44 +362,30 @@ func (d *DingTalkBotBiz) fallbackToGroupCreationWithSys(ctx context.Context, req // 合并提问者 staffIds = append([]string{requireData.Req.SenderStaffId}, staffIds...) - // 4. 拉群 + // 4. 发送确认卡片 groupName := fmt.Sprintf("[%s]-%s", classification.IssueTypeName, classification.Summary) - return d.createIssueHandlingGroupAndInitSingle(ctx, requireData.Req.RobotCode, groupName, staffIds, classification.Summary) + return d.SendGroupCreationConfirmCard(ctx, &SendGroupCreationConfirmCardParams{ + RobotCode: requireData.Req.RobotCode, + ConversationId: requireData.Req.ConversationId, + SenderStaffId: requireData.Req.SenderStaffId, + UserIds: staffIds, + GroupName: groupName, + Summary: classification.Summary, + }) } -// createDefaultGroup 兜底拉群 +// createDefaultGroup 兜底发送确认卡片 func (d *DingTalkBotBiz) createDefaultGroup(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, reason string) error { userIds := []string{requireData.Req.SenderStaffId, "17415698414368678"} groupName := fmt.Sprintf("[未知]-%s", reason) - return d.createIssueHandlingGroupAndInitSingle(ctx, requireData.Req.RobotCode, groupName, userIds, "由于无法识别具体问题类型,已为您拉起默认技术支持群。") -} - -// createIssueHandlingGroupAndInitSingle 创建群聊并初始化(单聊专用) -func (d *DingTalkBotBiz) createIssueHandlingGroupAndInitSingle(ctx context.Context, robotCode string, groupName string, userIds []string, summary string) error { - // 获取机器人配置 - appKey, err := d.botConfigImpl.GetRobotAppKey(robotCode) - if err != nil { - log.Errorf("get robot app key failed; err: %v", err) - return fmt.Errorf("未找到机器人配置") - } - - // 获取 access_token - accessToken, err := d.dingtalkOauth2Client.GetAccessToken(appKey) - if err != nil { - log.Errorf("get access token failed; err: %v", err) - return fmt.Errorf("获取 access token 失败") - } - appKey.AccessToken = accessToken - - // 创建群聊 - _, openConversationId, err := d.createIssueHandlingGroup(ctx, accessToken, groupName, userIds) - if err != nil { - log.Errorf("create issue handling group failed; err: %v", err) - return fmt.Errorf("创建群聊失败") - } - - // 初始化群聊 - return d.initIssueHandlingGroup(appKey, openConversationId, summary) + return d.SendGroupCreationConfirmCard(ctx, &SendGroupCreationConfirmCardParams{ + RobotCode: requireData.Req.RobotCode, + ConversationId: requireData.Req.ConversationId, + SenderStaffId: requireData.Req.SenderStaffId, + UserIds: userIds, + GroupName: groupName, + Summary: reason, + }) } func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) { @@ -675,27 +663,124 @@ func (d *DingTalkBotBiz) CreateIssueHandlingGroupAndInit(ctx context.Context, da // 解析 OutTrackId 以获取 SpaceId 和 BotId spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId) - // 获取新群聊人员 - var userIds []string - userIds, err = d.buildNewGroupUserIds(spaceId, botId, data.UserId) - if err != nil { - return nil, err - } + // 获取操作状态 + status := data.CardActionData.CardPrivateData.Params["status"] + if status == "confirm" { + // 获取新群聊人员 (从卡片参数中统一解析) + targetUserIdsStr := data.CardActionData.CardPrivateData.Params["target_user_ids"].(string) + var userIds []string + if targetUserIdsStr != "" { + userIds = strings.Split(targetUserIdsStr, ",") + } - // 创建群聊及群初始化(异步响应) - if data.CardActionData.CardPrivateData.Params["status"] == "confirm" { - go func() { + if len(userIds) == 0 { + return nil, errors.New("target_user_ids 参数不能为空") + } + + // 创建群聊及群初始化(异步响应) + util.SafeGo("CreateIssueHandlingGroupAndInit", func() { err := d.createIssueHandlingGroupAndInit(ctx, data.CardActionData.CardPrivateData.Params, spaceId, botId, userIds) if err != nil { log.Errorf("创建群聊及群初始化失败: %v", err) } - }() + }) } // 构建关闭创建群组卡片按钮的响应 return d.buildCreateGroupCardResp(), nil } +type SendGroupCreationConfirmCardParams struct { + RobotCode string + ConversationId string + SenderStaffId string + UserIds []string + GroupName string + Summary string + IsGroupChat bool +} + +// SendGroupCreationConfirmCard 发送创建群聊确认卡片 +func (d *DingTalkBotBiz) SendGroupCreationConfirmCard(ctx context.Context, params *SendGroupCreationConfirmCardParams) error { + // 获取人员姓名用于展示 + var userNames []string + for _, uid := range params.UserIds { + if uid == params.SenderStaffId { + continue + } + user, err := d.botUserImpl.GetByStaffId(uid) + if err == nil && user != nil { + userNames = append(userNames, "@"+user.Name) + } else { + userNames = append(userNames, "@"+uid) + } + } + issueOwnerStr := strings.Join(userNames, "、") + + // 获取应用配置 + appKey, err := d.botConfigImpl.GetRobotAppKey(params.RobotCode) + if err != nil { + return err + } + + // 构建卡片 OutTrackId + outTrackId := constants.BuildCardOutTrackId(params.ConversationId, params.RobotCode) + + // 准备可见人员列表 + var recipients []*string + if params.IsGroupChat { + // 群聊:提问者 + 负责人可见 + for _, uid := range params.UserIds { + recipients = append(recipients, tea.String(uid)) + } + // 确保提问者也在可见列表中 + foundSender := false + for _, uid := range params.UserIds { + if uid == params.SenderStaffId { + foundSender = true + break + } + } + if !foundSender { + recipients = append(recipients, tea.String(params.SenderStaffId)) + } + } else { + // 单聊:仅提问者可见 + recipients = append(recipients, tea.String(params.SenderStaffId)) + } + + // 发送钉钉卡片 + _, err = d.dingtalkCardClient.CreateAndDeliver( + appKey, + &card_1_0.CreateAndDeliverRequest{ + CardTemplateId: tea.String(d.conf.Dingtalk.Card.Template.CreateGroupApprove), + OutTrackId: tea.String(outTrackId), + CallbackType: tea.String("STREAM"), + CardData: &card_1_0.CreateAndDeliverRequestCardData{ + CardParamMap: map[string]*string{ + "title": tea.String("创建群聊提醒"), + "content": tea.String(fmt.Sprintf("**确认创建群聊?**\n\n将邀请以下成员加入群聊:\n\n%s", issueOwnerStr)), + "remark": tea.String("注:如若无需,忽略即可"), + "button_left": tea.String("创建群聊"), + "button_right": tea.String("忽略"), + "action_id": tea.String("create_group"), + "button_display": tea.String("true"), + "group_scope": tea.String(params.Summary), + "target_user_ids": tea.String(strings.Join(params.UserIds, ",")), + }, + }, + ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ + SupportForward: tea.Bool(false), + }, + OpenSpaceId: tea.String("dtv1.card//im_group." + params.ConversationId), + ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ + RobotCode: tea.String(params.RobotCode), + Recipients: recipients, + }, + }) + return err +} + // buildNewGroupUserIds 构建新群聊人员列表 func (d *DingTalkBotBiz) buildNewGroupUserIds(spaceId, botId, groupOwner string) ([]string, error) { // 群id+机器人id确认一个群配置 diff --git a/internal/biz/group_config.go b/internal/biz/group_config.go index 8e97c5e..49f9059 100644 --- a/internal/biz/group_config.go +++ b/internal/biz/group_config.go @@ -33,6 +33,7 @@ import ( "github.com/alibabacloud-go/dingtalk/card_1_0" "github.com/alibabacloud-go/tea/tea" "github.com/coze-dev/coze-go" + "github.com/duke-git/lancet/v2/slice" "github.com/gofiber/fiber/v2/log" "xorm.io/builder" ) @@ -586,12 +587,16 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec } // 合并所有name、Id userNames := make([]string, 0, len(issueOwner)) - userIds := make([]*string, 0, len(issueOwner)) + userIds := make([]string, 0, len(issueOwner)) for _, owner := range issueOwner { userNames = append(userNames, "@"+owner.Name) - userIds = append(userIds, tea.String(owner.UserId)) + userIds = append(userIds, owner.UserId) } issueOwnerStr := strings.Join(userNames, "、") + targetUserIds := append(userIds, callback.SenderStaffId) + recipientsUsers := slice.Map(targetUserIds, func(_ int, item string) *string { + return tea.String(item) + }) // 获取应用配置 appKey, err := g.botConfigImpl.GetRobotAppKey(callback.RobotCode) @@ -601,7 +606,6 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec // 构建卡片 OutTrackId outTrackId := constants.BuildCardOutTrackId(callback.ConversationId, callback.RobotCode) - // 发送钉钉卡片 _, err = g.dingtalkCardClient.CreateAndDeliver( appKey, @@ -611,15 +615,15 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec CallbackType: tea.String("STREAM"), CardData: &card_1_0.CreateAndDeliverRequestCardData{ CardParamMap: map[string]*string{ - "title": tea.String("创建群聊提醒"), - "content": tea.String(fmt.Sprintf("**确认创建群聊?**\n\n将邀请以下成员加入群聊:\n\n%s", issueOwnerStr)), - "remark": tea.String("注:如若无需,忽略即可"), - "button_left": tea.String("创建群聊"), - "button_right": tea.String("忽略"), - "action_id": tea.String("create_group"), - "button_display": tea.String("true"), - "group_scope": tea.String(strings.TrimSpace(rec.UserContent.Text)), - // "_CARD_DEBUG_TOOL_ENTRY": tea.String(g.conf.Dingtalk.Card.DebugToolEntryShow), // 调试字段 + "title": tea.String("创建群聊提醒"), + "content": tea.String(fmt.Sprintf("**确认创建群聊?**\n\n将邀请以下成员加入群聊:\n\n%s", issueOwnerStr)), + "remark": tea.String("注:如若无需,忽略即可"), + "button_left": tea.String("创建群聊"), + "button_right": tea.String("忽略"), + "action_id": tea.String("create_group"), + "button_display": tea.String("true"), + "group_scope": tea.String(strings.TrimSpace(rec.UserContent.Text)), + "target_user_ids": tea.String(strings.Join(targetUserIds, ",")), }, }, ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ @@ -628,7 +632,7 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec OpenSpaceId: tea.String("dtv1.card//im_group." + callback.ConversationId), ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ RobotCode: tea.String(callback.RobotCode), - Recipients: append(userIds, tea.String(callback.SenderStaffId)), + Recipients: recipientsUsers, }, }) if err != nil { diff --git a/internal/services/dtalk_bot_test.go b/internal/services/dtalk_bot_test.go index 0053382..34e3836 100644 --- a/internal/services/dtalk_bot_test.go +++ b/internal/services/dtalk_bot_test.go @@ -126,7 +126,8 @@ func run() { qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other) groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, botConfigImpl, registry, configConfig, impl.NewReportDailyCacheImpl(db), rdb, manager, cardClient) macro := do.NewMacro(botGroupImpl, impl.NewReportDailyCacheImpl(db)) - dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, botGroupConfigImpl, user, botChatHisImpl, impl.NewReportDailyCacheImpl(db), manager, configConfig, sendCardClient, groupConfigBiz, macro, oauth2Client, oldClient, cardClient) + issueImpl := impl.NewIssueImpl(db) + dingTalkBotBiz := biz.NewDingTalkBotBiz(doDo, handle, botConfigImpl, botGroupImpl, botGroupConfigImpl, user, botChatHisImpl, botUserImpl, impl.NewReportDailyCacheImpl(db), manager, configConfig, sendCardClient, groupConfigBiz, macro, oauth2Client, oldClient, cardClient, rdb, issueImpl, sysImpl) // 初始化钉钉机器人服务 cronService = NewCronService(configConfig, dingTalkBotBiz, qywxAppBiz, groupConfigBiz) }