fix: 单聊机器人初步开发完成

This commit is contained in:
fuzhongyun 2026-02-02 16:32:19 +08:00
parent 3b6471a196
commit 847eb8b5db
3 changed files with 151 additions and 61 deletions

View File

@ -25,11 +25,13 @@ import (
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot" "gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"
dingtalkPkg "ai_scheduler/internal/pkg/dingtalk" dingtalkPkg "ai_scheduler/internal/pkg/dingtalk"
"ai_scheduler/internal/pkg/util"
"github.com/alibabacloud-go/dingtalk/card_1_0" "github.com/alibabacloud-go/dingtalk/card_1_0"
"github.com/alibabacloud-go/tea/tea" "github.com/alibabacloud-go/tea/tea"
"github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/slice"
"github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/log"
"github.com/redis/go-redis/v9"
"xorm.io/builder" "xorm.io/builder"
) )
@ -56,7 +58,7 @@ type DingTalkBotBiz struct {
dingtalkOauth2Client *dingtalkPkg.Oauth2Client dingtalkOauth2Client *dingtalkPkg.Oauth2Client
dingTalkOld *dingtalkPkg.OldClient dingTalkOld *dingtalkPkg.OldClient
dingtalkCardClient *dingtalkPkg.CardClient dingtalkCardClient *dingtalkPkg.CardClient
rdb *utils.Rdb redisCli *redis.Client
issueImpl *impl.IssueImpl issueImpl *impl.IssueImpl
sysImpl *impl.SysImpl sysImpl *impl.SysImpl
} }
@ -103,7 +105,7 @@ func NewDingTalkBotBiz(
dingtalkOauth2Client: dingtalkOauth2Client, dingtalkOauth2Client: dingtalkOauth2Client,
dingTalkOld: dingTalkOld, dingTalkOld: dingTalkOld,
dingtalkCardClient: dingtalkCardClient, dingtalkCardClient: dingtalkCardClient,
rdb: rdb, redisCli: rdb.Rdb,
issueImpl: issueImpl, issueImpl: issueImpl,
sysImpl: sysImpl, sysImpl: sysImpl,
} }
@ -167,12 +169,12 @@ func (d *DingTalkBotBiz) handleSingleChat(ctx context.Context, requireData *enti
// 2. 检查会话状态 (Redis) // 2. 检查会话状态 (Redis)
statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId) 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" { if status == "WAITING_FOR_SYS_CONFIRM" {
// 用户回复了系统名称 // 用户回复了系统名称
sysName := requireData.Req.Text.Content sysName := requireData.Req.Text.Content
d.rdb.Rdb.Del(ctx, statusKey) d.redisCli.Del(ctx, statusKey)
return d.handleWithSpecificSys(ctx, requireData, sysName) return d.handleWithSpecificSys(ctx, requireData, sysName)
} }
@ -283,7 +285,7 @@ func (d *DingTalkBotBiz) fallbackToGroupCreation(ctx context.Context, requireDat
if sys.SysID == 0 { if sys.SysID == 0 {
// 无法明确系统,询问用户 // 无法明确系统,询问用户
statusKey := fmt.Sprintf("ai_bot:session:status:%s", requireData.Req.SenderStaffId) 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, "", "抱歉,我无法确定您咨询的是哪个系统。请告诉我具体系统名称(如:直连天下系统、货易通系统),以便我为您安排对应的技术支持。") entitys.ResText(requireData.Ch, "", "抱歉,我无法确定您咨询的是哪个系统。请告诉我具体系统名称(如:直连天下系统、货易通系统),以便我为您安排对应的技术支持。")
return nil return nil
} }
@ -360,44 +362,30 @@ func (d *DingTalkBotBiz) fallbackToGroupCreationWithSys(ctx context.Context, req
// 合并提问者 // 合并提问者
staffIds = append([]string{requireData.Req.SenderStaffId}, staffIds...) staffIds = append([]string{requireData.Req.SenderStaffId}, staffIds...)
// 4. 拉群 // 4. 发送确认卡片
groupName := fmt.Sprintf("[%s]-%s", classification.IssueTypeName, classification.Summary) 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 { func (d *DingTalkBotBiz) createDefaultGroup(ctx context.Context, requireData *entitys.RequireDataDingTalkBot, reason string) error {
userIds := []string{requireData.Req.SenderStaffId, "17415698414368678"} userIds := []string{requireData.Req.SenderStaffId, "17415698414368678"}
groupName := fmt.Sprintf("[未知]-%s", reason) groupName := fmt.Sprintf("[未知]-%s", reason)
return d.createIssueHandlingGroupAndInitSingle(ctx, requireData.Req.RobotCode, groupName, userIds, "由于无法识别具体问题类型,已为您拉起默认技术支持群。") return d.SendGroupCreationConfirmCard(ctx, &SendGroupCreationConfirmCardParams{
} RobotCode: requireData.Req.RobotCode,
ConversationId: requireData.Req.ConversationId,
// createIssueHandlingGroupAndInitSingle 创建群聊并初始化(单聊专用) SenderStaffId: requireData.Req.SenderStaffId,
func (d *DingTalkBotBiz) createIssueHandlingGroupAndInitSingle(ctx context.Context, robotCode string, groupName string, userIds []string, summary string) error { UserIds: userIds,
// 获取机器人配置 GroupName: groupName,
appKey, err := d.botConfigImpl.GetRobotAppKey(robotCode) Summary: reason,
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)
} }
func (d *DingTalkBotBiz) handleGroupChat(ctx context.Context, requireData *entitys.RequireDataDingTalkBot) (err error) { 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 // 解析 OutTrackId 以获取 SpaceId 和 BotId
spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId) spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId)
// 获取新群聊人员 // 获取操作状态
status := data.CardActionData.CardPrivateData.Params["status"]
if status == "confirm" {
// 获取新群聊人员 (从卡片参数中统一解析)
targetUserIdsStr := data.CardActionData.CardPrivateData.Params["target_user_ids"].(string)
var userIds []string var userIds []string
userIds, err = d.buildNewGroupUserIds(spaceId, botId, data.UserId) if targetUserIdsStr != "" {
if err != nil { userIds = strings.Split(targetUserIdsStr, ",")
return nil, err }
if len(userIds) == 0 {
return nil, errors.New("target_user_ids 参数不能为空")
} }
// 创建群聊及群初始化(异步响应) // 创建群聊及群初始化(异步响应)
if data.CardActionData.CardPrivateData.Params["status"] == "confirm" { util.SafeGo("CreateIssueHandlingGroupAndInit", func() {
go func() {
err := d.createIssueHandlingGroupAndInit(ctx, data.CardActionData.CardPrivateData.Params, spaceId, botId, userIds) err := d.createIssueHandlingGroupAndInit(ctx, data.CardActionData.CardPrivateData.Params, spaceId, botId, userIds)
if err != nil { if err != nil {
log.Errorf("创建群聊及群初始化失败: %v", err) log.Errorf("创建群聊及群初始化失败: %v", err)
} }
}() })
} }
// 构建关闭创建群组卡片按钮的响应 // 构建关闭创建群组卡片按钮的响应
return d.buildCreateGroupCardResp(), nil 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 构建新群聊人员列表 // buildNewGroupUserIds 构建新群聊人员列表
func (d *DingTalkBotBiz) buildNewGroupUserIds(spaceId, botId, groupOwner string) ([]string, error) { func (d *DingTalkBotBiz) buildNewGroupUserIds(spaceId, botId, groupOwner string) ([]string, error) {
// 群id+机器人id确认一个群配置 // 群id+机器人id确认一个群配置

View File

@ -33,6 +33,7 @@ import (
"github.com/alibabacloud-go/dingtalk/card_1_0" "github.com/alibabacloud-go/dingtalk/card_1_0"
"github.com/alibabacloud-go/tea/tea" "github.com/alibabacloud-go/tea/tea"
"github.com/coze-dev/coze-go" "github.com/coze-dev/coze-go"
"github.com/duke-git/lancet/v2/slice"
"github.com/gofiber/fiber/v2/log" "github.com/gofiber/fiber/v2/log"
"xorm.io/builder" "xorm.io/builder"
) )
@ -586,12 +587,16 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec
} }
// 合并所有name、Id // 合并所有name、Id
userNames := make([]string, 0, len(issueOwner)) userNames := make([]string, 0, len(issueOwner))
userIds := make([]*string, 0, len(issueOwner)) userIds := make([]string, 0, len(issueOwner))
for _, owner := range issueOwner { for _, owner := range issueOwner {
userNames = append(userNames, "@"+owner.Name) userNames = append(userNames, "@"+owner.Name)
userIds = append(userIds, tea.String(owner.UserId)) userIds = append(userIds, owner.UserId)
} }
issueOwnerStr := strings.Join(userNames, "、") 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) appKey, err := g.botConfigImpl.GetRobotAppKey(callback.RobotCode)
@ -601,7 +606,6 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec
// 构建卡片 OutTrackId // 构建卡片 OutTrackId
outTrackId := constants.BuildCardOutTrackId(callback.ConversationId, callback.RobotCode) outTrackId := constants.BuildCardOutTrackId(callback.ConversationId, callback.RobotCode)
// 发送钉钉卡片 // 发送钉钉卡片
_, err = g.dingtalkCardClient.CreateAndDeliver( _, err = g.dingtalkCardClient.CreateAndDeliver(
appKey, appKey,
@ -619,7 +623,7 @@ func (g *GroupConfigBiz) shouldCreateIssueHandlingGroup(ctx context.Context, rec
"action_id": tea.String("create_group"), "action_id": tea.String("create_group"),
"button_display": tea.String("true"), "button_display": tea.String("true"),
"group_scope": tea.String(strings.TrimSpace(rec.UserContent.Text)), "group_scope": tea.String(strings.TrimSpace(rec.UserContent.Text)),
// "_CARD_DEBUG_TOOL_ENTRY": tea.String(g.conf.Dingtalk.Card.DebugToolEntryShow), // 调试字段 "target_user_ids": tea.String(strings.Join(targetUserIds, ",")),
}, },
}, },
ImGroupOpenSpaceModel: &card_1_0.CreateAndDeliverRequestImGroupOpenSpaceModel{ 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), OpenSpaceId: tea.String("dtv1.card//im_group." + callback.ConversationId),
ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{ ImGroupOpenDeliverModel: &card_1_0.CreateAndDeliverRequestImGroupOpenDeliverModel{
RobotCode: tea.String(callback.RobotCode), RobotCode: tea.String(callback.RobotCode),
Recipients: append(userIds, tea.String(callback.SenderStaffId)), Recipients: recipientsUsers,
}, },
}) })
if err != nil { if err != nil {

View File

@ -126,7 +126,8 @@ func run() {
qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other) qywxAppBiz := biz.NewQywxAppBiz(configConfig, botGroupQywxImpl, group, other)
groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, botConfigImpl, registry, configConfig, impl.NewReportDailyCacheImpl(db), rdb, manager, cardClient) groupConfigBiz := biz.NewGroupConfigBiz(toolRegis, utils_ossClient, botGroupConfigImpl, botConfigImpl, registry, configConfig, impl.NewReportDailyCacheImpl(db), rdb, manager, cardClient)
macro := do.NewMacro(botGroupImpl, impl.NewReportDailyCacheImpl(db)) 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) cronService = NewCronService(configConfig, dingTalkBotBiz, qywxAppBiz, groupConfigBiz)
} }