1. 增加钉钉IM客户端,增加创建并投放卡片方法 2.规范化多处配置获取代码 3.增加获取入群二维码链接方法 4.调整加群流程为 确认加群-创建群聊-将机器人添加到新群-获取新群分享链接-点击跳转至新群
This commit is contained in:
parent
a0b76f1581
commit
534da15898
|
|
@ -309,7 +309,7 @@ func (r *Handle) getUserDingtalkUnionId(ctx context.Context, accessToken, sessio
|
||||||
creatorName := session.UserName
|
creatorName := session.UserName
|
||||||
|
|
||||||
// 获取创建者uid 用户名 -> dingtalk uid
|
// 获取创建者uid 用户名 -> dingtalk uid
|
||||||
creatorId, err := r.dingtalkContactClient.SearchUserOne(accessToken, creatorName)
|
creatorId, err := r.dingtalkContactClient.SearchUserOne(dingtalk.AppKey{AccessToken: accessToken}, creatorName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("search dingtalk user one failed: %v", err)
|
log.Warnf("search dingtalk user one failed: %v", err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ func (g *GroupConfigBiz) handleMatch(ctx context.Context, rec *entitys.Recognize
|
||||||
case constants.TaskTypeCozeWorkflow:
|
case constants.TaskTypeCozeWorkflow:
|
||||||
return g.handleCozeWorkflow(ctx, rec, pointTask)
|
return g.handleCozeWorkflow(ctx, rec, pointTask)
|
||||||
case constants.TaskTypeKnowle: // 知识库V2版本
|
case constants.TaskTypeKnowle: // 知识库V2版本
|
||||||
return g.handleKnowledgeV2(ctx, rec, pointTask)
|
return g.handleKnowledgeV2(ctx, rec, groupConfig)
|
||||||
// return g.handleKnowledgeV3(ctx, rec, pointTask)
|
// return g.handleKnowledgeV3(ctx, rec, pointTask)
|
||||||
default:
|
default:
|
||||||
return g.otherTask(ctx, rec)
|
return g.otherTask(ctx, rec)
|
||||||
|
|
@ -473,7 +473,7 @@ func (g *GroupConfigBiz) GetReportCache(ctx context.Context, day time.Time, tota
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleKnowledgeV2 处理知识库V2版本
|
// handleKnowledgeV2 处理知识库V2版本
|
||||||
func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Recognize, pointTask *model.AiBotTool) (err error) {
|
func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Recognize, groupConfig *model.AiBotGroupConfig) (err error) {
|
||||||
req := l_request.Request{
|
req := l_request.Request{
|
||||||
Method: "POST",
|
Method: "POST",
|
||||||
Url: "http://127.0.0.1:9600/query",
|
Url: "http://127.0.0.1:9600/query",
|
||||||
|
|
@ -499,12 +499,24 @@ func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Rec
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// var DingtalkGroupCaseOwner = map[string][]string{
|
|
||||||
// "cidwP24PLZhLVOS2dVIkEawLw==": {"付仲云", "贺泽琨", "任志远"},
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 未检索到匹配信息,询问是否拉群
|
// 未检索到匹配信息,询问是否拉群
|
||||||
if !isRetrieved {
|
if !isRetrieved {
|
||||||
|
// 获取群问题处理人
|
||||||
|
type issueOwnerType struct {
|
||||||
|
UserId string `json:"userid"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
var issueOwner []issueOwnerType
|
||||||
|
if err = json.Unmarshal([]byte(groupConfig.IssueOwner), &issueOwner); err != nil {
|
||||||
|
return fmt.Errorf("解析群问题处理人失败,err: %v", err)
|
||||||
|
}
|
||||||
|
// 合并所有name
|
||||||
|
var userNames []string
|
||||||
|
for _, owner := range issueOwner {
|
||||||
|
userNames = append(userNames, "@"+owner.Name)
|
||||||
|
}
|
||||||
|
issueOwnerStr := strings.Join(userNames, "、")
|
||||||
|
|
||||||
// 构建卡片 OutTrackId
|
// 构建卡片 OutTrackId
|
||||||
outTrackId := constants.BuildCardOutTrackId("cidwP24PLZhLVOS2dVIkEawLw==", "ding5wwvnf9hxeyjau7t")
|
outTrackId := constants.BuildCardOutTrackId("cidwP24PLZhLVOS2dVIkEawLw==", "ding5wwvnf9hxeyjau7t")
|
||||||
|
|
||||||
|
|
@ -519,7 +531,7 @@ func (g *GroupConfigBiz) handleKnowledgeV2(ctx context.Context, rec *entitys.Rec
|
||||||
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
CardData: &card_1_0.CreateAndDeliverRequestCardData{
|
||||||
CardParamMap: map[string]*string{
|
CardParamMap: map[string]*string{
|
||||||
"title": tea.String("群创建提醒"),
|
"title": tea.String("群创建提醒"),
|
||||||
"content": tea.String("**确认创建群聊?**\n\n将邀请以下成员加入群聊:\n\n@张三、@李四"),
|
"content": tea.String(fmt.Sprintf("**确认创建群聊?**\n\n将邀请以下成员加入群聊:\n\n%s", issueOwnerStr)),
|
||||||
"remark": tea.String("注:如若无需,忽略即可"),
|
"remark": tea.String("注:如若无需,忽略即可"),
|
||||||
"button_left": tea.String("点击进群"),
|
"button_left": tea.String("点击进群"),
|
||||||
"button_left_link": tea.String(""),
|
"button_left_link": tea.String(""),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dingtalk
|
package dingtalk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
|
||||||
errorcode "ai_scheduler/internal/data/error"
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
|
@ -11,15 +10,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type CardClient struct {
|
type CardClient struct {
|
||||||
config *config.Config
|
|
||||||
cli *card.Client
|
cli *card.Client
|
||||||
oauth2Client *Oauth2Client
|
oauth2Client *Oauth2Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCardClient(config *config.Config, oauth2Client *Oauth2Client) (*CardClient, error) {
|
func NewCardClient(oauth2Client *Oauth2Client) (*CardClient, error) {
|
||||||
cfg := &openapi.Config{
|
cfg := &openapi.Config{
|
||||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
|
||||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
|
||||||
Protocol: tea.String("https"),
|
Protocol: tea.String("https"),
|
||||||
RegionId: tea.String("central"),
|
RegionId: tea.String("central"),
|
||||||
}
|
}
|
||||||
|
|
@ -28,13 +24,13 @@ func NewCardClient(config *config.Config, oauth2Client *Oauth2Client) (*CardClie
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CardClient{config: config, cli: c, oauth2Client: oauth2Client}, nil
|
return &CardClient{cli: c, oauth2Client: oauth2Client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建并投放卡片
|
// 创建并投放卡片
|
||||||
func (c *CardClient) CreateAndDeliver(req AppKey, cardData *card.CreateAndDeliverRequest) (bool, error) {
|
func (c *CardClient) CreateAndDeliver(appKey AppKey, cardData *card.CreateAndDeliverRequest) (bool, error) {
|
||||||
// 获取token
|
// 获取token
|
||||||
accessToken, err := c.oauth2Client.GetAccessToken(req)
|
accessToken, err := c.oauth2Client.GetAccessToken(appKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -60,9 +56,9 @@ func (c *CardClient) CreateAndDeliver(req AppKey, cardData *card.CreateAndDelive
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新卡片
|
// 更新卡片
|
||||||
func (c *CardClient) UpdateCard(req AppKey, cardData *card.UpdateCardRequest) (bool, error) {
|
func (c *CardClient) UpdateCard(appKey AppKey, cardData *card.UpdateCardRequest) (bool, error) {
|
||||||
// 获取token
|
// 获取token
|
||||||
accessToken, err := c.oauth2Client.GetAccessToken(req)
|
accessToken, err := c.oauth2Client.GetAccessToken(appKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dingtalk
|
package dingtalk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
|
||||||
errorcode "ai_scheduler/internal/data/error"
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
|
|
||||||
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
|
@ -11,14 +10,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContactClient struct {
|
type ContactClient struct {
|
||||||
config *config.Config
|
|
||||||
cli *contact.Client
|
cli *contact.Client
|
||||||
|
oauth2Client *Oauth2Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContactClient(config *config.Config) (*ContactClient, error) {
|
func NewContactClient(oauth2Client *Oauth2Client) (*ContactClient, error) {
|
||||||
cfg := &openapi.Config{
|
cfg := &openapi.Config{
|
||||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
|
||||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
|
||||||
Protocol: tea.String("https"),
|
Protocol: tea.String("https"),
|
||||||
RegionId: tea.String("central"),
|
RegionId: tea.String("central"),
|
||||||
}
|
}
|
||||||
|
|
@ -26,7 +23,7 @@ func NewContactClient(config *config.Config) (*ContactClient, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &ContactClient{config: config, cli: c}, nil
|
return &ContactClient{cli: c, oauth2Client: oauth2Client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SearchUserReq struct {
|
type SearchUserReq struct {
|
||||||
|
|
@ -40,15 +37,23 @@ type SearchUserResp struct {
|
||||||
Body interface{}
|
Body interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ContactClient) SearchUserOne(accessToken string, name string) (string, error) {
|
func (c *ContactClient) SearchUserOne(appKey AppKey, name string) (string, error) {
|
||||||
headers := &contact.SearchUserHeaders{}
|
// 获取token
|
||||||
headers.XAcsDingtalkAccessToken = tea.String(accessToken)
|
accessToken, err := c.oauth2Client.GetAccessToken(appKey)
|
||||||
resp, err := c.cli.SearchUserWithOptions(&contact.SearchUserRequest{
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.cli.SearchUserWithOptions(
|
||||||
|
&contact.SearchUserRequest{
|
||||||
FullMatchField: tea.Int32(1),
|
FullMatchField: tea.Int32(1),
|
||||||
QueryWord: tea.String(name),
|
QueryWord: tea.String(name),
|
||||||
Offset: tea.Int32(0),
|
Offset: tea.Int32(0),
|
||||||
Size: tea.Int32(1),
|
Size: tea.Int32(1),
|
||||||
}, headers, &util.RuntimeOptions{})
|
},
|
||||||
|
&contact.SearchUserHeaders{XAcsDingtalkAccessToken: tea.String(accessToken)},
|
||||||
|
&util.RuntimeOptions{},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package dingtalk
|
||||||
|
|
||||||
|
import (
|
||||||
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
|
|
||||||
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
im "github.com/alibabacloud-go/dingtalk/im_1_0"
|
||||||
|
util "github.com/alibabacloud-go/tea-utils/v2/service"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImClient struct {
|
||||||
|
cli *im.Client
|
||||||
|
oauth2Client *Oauth2Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImClient(oauth2Client *Oauth2Client) (*ImClient, error) {
|
||||||
|
cfg := &openapi.Config{
|
||||||
|
Protocol: tea.String("https"),
|
||||||
|
RegionId: tea.String("central"),
|
||||||
|
}
|
||||||
|
c, err := im.NewClient(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ImClient{cli: c, oauth2Client: oauth2Client}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建并投放卡片
|
||||||
|
func (c *ImClient) AddRobotToConversation(appKey AppKey, imData *im.AddRobotToConversationRequest) (string, error) {
|
||||||
|
// 获取token
|
||||||
|
accessToken, err := c.oauth2Client.GetAccessToken(appKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用API
|
||||||
|
resp, err := c.cli.AddRobotToConversationWithOptions(
|
||||||
|
imData,
|
||||||
|
&im.AddRobotToConversationHeaders{XAcsDingtalkAccessToken: tea.String(accessToken)},
|
||||||
|
&util.RuntimeOptions{},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Body == nil {
|
||||||
|
return "", errorcode.ParamErrf("empty response body")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *resp.Body.ChatBotUserId, nil
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dingtalk
|
package dingtalk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
|
||||||
errorcode "ai_scheduler/internal/data/error"
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -13,14 +12,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotableClient struct {
|
type NotableClient struct {
|
||||||
config *config.Config
|
|
||||||
cli *notable.Client
|
cli *notable.Client
|
||||||
|
oauth2Client *Oauth2Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotableClient(config *config.Config) (*NotableClient, error) {
|
func NewNotableClient(oauth2Client *Oauth2Client) (*NotableClient, error) {
|
||||||
cfg := &openapi.Config{
|
cfg := &openapi.Config{
|
||||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
|
||||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
|
||||||
Protocol: tea.String("https"),
|
Protocol: tea.String("https"),
|
||||||
RegionId: tea.String("central"),
|
RegionId: tea.String("central"),
|
||||||
}
|
}
|
||||||
|
|
@ -28,7 +25,7 @@ func NewNotableClient(config *config.Config) (*NotableClient, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &NotableClient{config: config, cli: c}, nil
|
return &NotableClient{cli: c, oauth2Client: oauth2Client}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateRecordReq struct {
|
type UpdateRecordReq struct {
|
||||||
|
|
@ -43,9 +40,13 @@ type UpdateRecordsserResp struct {
|
||||||
Body interface{}
|
Body interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *NotableClient) UpdateRecord(accessToken string, req *UpdateRecordReq) (bool, error) {
|
func (c *NotableClient) UpdateRecord(appKey AppKey, req *UpdateRecordReq) (bool, error) {
|
||||||
headers := ¬able.UpdateRecordsHeaders{}
|
// 获取token
|
||||||
headers.XAcsDingtalkAccessToken = tea.String(accessToken)
|
accessToken, err := c.oauth2Client.GetAccessToken(appKey)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := c.cli.UpdateRecordsWithOptions(
|
resp, err := c.cli.UpdateRecordsWithOptions(
|
||||||
tea.String(req.BaseId),
|
tea.String(req.BaseId),
|
||||||
tea.String(req.SheetId),
|
tea.String(req.SheetId),
|
||||||
|
|
@ -63,7 +64,10 @@ func (c *NotableClient) UpdateRecord(accessToken string, req *UpdateRecordReq) (
|
||||||
Id: tea.String(req.RecordId),
|
Id: tea.String(req.RecordId),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, headers, &util.RuntimeOptions{})
|
},
|
||||||
|
¬able.UpdateRecordsHeaders{XAcsDingtalkAccessToken: tea.String(accessToken)},
|
||||||
|
&util.RuntimeOptions{},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dingtalk
|
package dingtalk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
|
||||||
errorcode "ai_scheduler/internal/data/error"
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
"ai_scheduler/utils"
|
"ai_scheduler/utils"
|
||||||
"context"
|
"context"
|
||||||
|
|
@ -15,15 +14,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Oauth2Client struct {
|
type Oauth2Client struct {
|
||||||
config *config.Config
|
|
||||||
cli *oauth2.Client
|
cli *oauth2.Client
|
||||||
redisCli *redis.Client
|
redisCli *redis.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOauth2Client(config *config.Config, rds *utils.Rdb) (*Oauth2Client, error) {
|
func NewOauth2Client(rds *utils.Rdb) (*Oauth2Client, error) {
|
||||||
cfg := &openapi.Config{
|
cfg := &openapi.Config{
|
||||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
|
||||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
|
||||||
Protocol: tea.String("https"),
|
Protocol: tea.String("https"),
|
||||||
RegionId: tea.String("central"),
|
RegionId: tea.String("central"),
|
||||||
}
|
}
|
||||||
|
|
@ -31,16 +27,22 @@ func NewOauth2Client(config *config.Config, rds *utils.Rdb) (*Oauth2Client, erro
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &Oauth2Client{config: config, cli: c, redisCli: rds.Rdb}, nil
|
return &Oauth2Client{cli: c, redisCli: rds.Rdb}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type AppKey struct {
|
type AppKey struct {
|
||||||
AppKey string `json:"appKey"`
|
AppKey string `json:"appKey"`
|
||||||
AppSecret string `json:"appSecret"`
|
AppSecret string `json:"appSecret"`
|
||||||
|
AccessToken string `json:"accessToken"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccessToken 获取access token
|
// GetAccessToken 获取access token
|
||||||
func (c *Oauth2Client) GetAccessToken(req AppKey) (string, error) {
|
func (c *Oauth2Client) GetAccessToken(req AppKey) (string, error) {
|
||||||
|
// 兼容直接传入 access token 场景
|
||||||
|
if req.AccessToken != "" {
|
||||||
|
return req.AccessToken, nil
|
||||||
|
}
|
||||||
|
|
||||||
// 取cache
|
// 取cache
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
accessToken, err := c.redisCli.Get(ctx, fmt.Sprintf("dingtalk:oauth2:%s:access_token", req.AppKey)).Result()
|
accessToken, err := c.redisCli.Get(ctx, fmt.Sprintf("dingtalk:oauth2:%s:access_token", req.AppKey)).Result()
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ func (c *OldClient) GetAccessToken() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateInternalGroupConversation 创建企业内部群聊
|
// CreateInternalGroupConversation 创建企业内部群聊
|
||||||
func (c *OldClient) CreateInternalGroupConversation(ctx context.Context, groupName string, userIds []string) (string, error) {
|
func (c *OldClient) CreateInternalGroupConversation(ctx context.Context, groupName string, userIds []string) (chatId, openConversationId string, err error) {
|
||||||
body := struct {
|
body := struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
|
|
@ -130,9 +130,10 @@ func (c *OldClient) CreateInternalGroupConversation(ctx context.Context, groupNa
|
||||||
UserIds: userIds,
|
UserIds: userIds,
|
||||||
}
|
}
|
||||||
b, _ := json.Marshal(body)
|
b, _ := json.Marshal(body)
|
||||||
res, err := c.do(ctx, http.MethodPost, "/chat/create", b)
|
var res []byte
|
||||||
|
res, err = c.do(ctx, http.MethodPost, "/chat/create", b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return
|
||||||
}
|
}
|
||||||
var resp struct {
|
var resp struct {
|
||||||
Code int `json:"errcode"`
|
Code int `json:"errcode"`
|
||||||
|
|
@ -141,11 +142,37 @@ func (c *OldClient) CreateInternalGroupConversation(ctx context.Context, groupNa
|
||||||
OpenConversationId string `json:"openConversationId"`
|
OpenConversationId string `json:"openConversationId"`
|
||||||
ConversationTag int `json:"conversationTag"`
|
ConversationTag int `json:"conversationTag"`
|
||||||
}
|
}
|
||||||
|
if err = json.Unmarshal(res, &resp); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resp.Code != 0 {
|
||||||
|
return "", "", errors.New(resp.Msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ChatId, resp.OpenConversationId, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取入群二维码链接
|
||||||
|
func (c *OldClient) GetJoinGroupQrcode(ctx context.Context, chatId, userId string) (string, error) {
|
||||||
|
body := struct {
|
||||||
|
ChatId string `json:"chatid"`
|
||||||
|
UserId string `json:"userid"`
|
||||||
|
}{ChatId: chatId, UserId: userId}
|
||||||
|
b, _ := json.Marshal(body)
|
||||||
|
res, err := c.do(ctx, http.MethodPost, "/topapi/chat/qrcode/get", b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var resp struct {
|
||||||
|
Code int `json:"errcode"`
|
||||||
|
Msg string `json:"errmsg"`
|
||||||
|
Result string `json:"result"`
|
||||||
|
}
|
||||||
if err := json.Unmarshal(res, &resp); err != nil {
|
if err := json.Unmarshal(res, &resp); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
if resp.Code != 0 {
|
if resp.Code != 0 {
|
||||||
return "", errors.New(resp.Msg)
|
return "", errors.New(resp.Msg)
|
||||||
}
|
}
|
||||||
return resp.OpenConversationId, nil
|
return resp.Result, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dingtalk
|
package dingtalk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"ai_scheduler/internal/config"
|
|
||||||
errorcode "ai_scheduler/internal/data/error"
|
errorcode "ai_scheduler/internal/data/error"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
|
@ -12,14 +11,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type RobotClient struct {
|
type RobotClient struct {
|
||||||
config *config.Config
|
|
||||||
cli *robot.Client
|
cli *robot.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRobotClient(config *config.Config) (*RobotClient, error) {
|
func NewRobotClient() (*RobotClient, error) {
|
||||||
cfg := &openapi.Config{
|
cfg := &openapi.Config{
|
||||||
AccessKeyId: tea.String(config.Tools.DingTalkBot.APIKey),
|
|
||||||
AccessKeySecret: tea.String(config.Tools.DingTalkBot.APISecret),
|
|
||||||
Protocol: tea.String("https"),
|
Protocol: tea.String("https"),
|
||||||
RegionId: tea.String("central"),
|
RegionId: tea.String("central"),
|
||||||
}
|
}
|
||||||
|
|
@ -27,7 +23,7 @@ func NewRobotClient(config *config.Config) (*RobotClient, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &RobotClient{config: config, cli: c}, nil
|
return &RobotClient{cli: c}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendGroupMessagesReq struct {
|
type SendGroupMessagesReq struct {
|
||||||
|
|
@ -37,17 +33,19 @@ type SendGroupMessagesReq struct {
|
||||||
RobotCode string
|
RobotCode string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RobotClient) SendGroupMessages(accessToken string, req *SendGroupMessagesReq) (string, error) {
|
func (c *RobotClient) SendGroupMessages(appKey AppKey, req *SendGroupMessagesReq) (string, error) {
|
||||||
headers := &robot.OrgGroupSendHeaders{}
|
|
||||||
headers.XAcsDingtalkAccessToken = tea.String(accessToken)
|
|
||||||
msgParamBytes, _ := json.Marshal(req.MsgParam)
|
msgParamBytes, _ := json.Marshal(req.MsgParam)
|
||||||
msgParamJson := string(msgParamBytes)
|
msgParamJson := string(msgParamBytes)
|
||||||
resp, err := c.cli.OrgGroupSendWithOptions(&robot.OrgGroupSendRequest{
|
resp, err := c.cli.OrgGroupSendWithOptions(
|
||||||
|
&robot.OrgGroupSendRequest{
|
||||||
MsgKey: tea.String(req.MsgKey),
|
MsgKey: tea.String(req.MsgKey),
|
||||||
MsgParam: tea.String(msgParamJson),
|
MsgParam: tea.String(msgParamJson),
|
||||||
OpenConversationId: tea.String(req.OpenConversationId),
|
OpenConversationId: tea.String(req.OpenConversationId),
|
||||||
RobotCode: tea.String(req.RobotCode),
|
RobotCode: tea.String(req.RobotCode),
|
||||||
}, headers, &util.RuntimeOptions{})
|
},
|
||||||
|
&robot.OrgGroupSendHeaders{XAcsDingtalkAccessToken: tea.String(appKey.AccessToken)},
|
||||||
|
&util.RuntimeOptions{},
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ var ProviderSetClient = wire.NewSet(
|
||||||
// dingtalk.NewRobotClient,
|
// dingtalk.NewRobotClient,
|
||||||
dingtalk.NewOauth2Client,
|
dingtalk.NewOauth2Client,
|
||||||
dingtalk.NewCardClient,
|
dingtalk.NewCardClient,
|
||||||
|
dingtalk.NewImClient,
|
||||||
|
|
||||||
utils_oss.NewClient,
|
utils_oss.NewClient,
|
||||||
lsxd.NewLogin,
|
lsxd.NewLogin,
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ func (s *CallbackService) handleBugOptimizationSubmitUpdate(ctx context.Context,
|
||||||
|
|
||||||
// 获取创建者uid
|
// 获取创建者uid
|
||||||
accessToken, _ := s.dingtalkOldClient.GetAccessToken()
|
accessToken, _ := s.dingtalkOldClient.GetAccessToken()
|
||||||
creatorId, err := s.dingtalkContactClient.SearchUserOne(accessToken, data.Creator)
|
creatorId, err := s.dingtalkContactClient.SearchUserOne(dingtalk.AppKey{AccessToken: accessToken}, data.Creator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errorcode.ParamErrf("invalid data type: %v", err)
|
return "", errorcode.ParamErrf("invalid data type: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -286,7 +286,7 @@ func (s *CallbackService) handleBugOptimizationSubmitUpdate(ctx context.Context,
|
||||||
unionId := userDetails.UnionID
|
unionId := userDetails.UnionID
|
||||||
|
|
||||||
// 更新记录
|
// 更新记录
|
||||||
ok, err := s.dingtalkNotableClient.UpdateRecord(accessToken, &dingtalk.UpdateRecordReq{
|
ok, err := s.dingtalkNotableClient.UpdateRecord(dingtalk.AppKey{AccessToken: accessToken}, &dingtalk.UpdateRecordReq{
|
||||||
BaseId: data.BaseId,
|
BaseId: data.BaseId,
|
||||||
SheetId: data.SheetId,
|
SheetId: data.SheetId,
|
||||||
RecordId: data.RecordId,
|
RecordId: data.RecordId,
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,16 @@ import (
|
||||||
"ai_scheduler/internal/pkg/dingtalk"
|
"ai_scheduler/internal/pkg/dingtalk"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/card"
|
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/card"
|
||||||
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"
|
"gitea.cdlsxd.cn/self-tools/l-dingtalk-stream-sdk-go/chatbot"
|
||||||
|
"github.com/alibabacloud-go/dingtalk/card_1_0"
|
||||||
|
"github.com/alibabacloud-go/dingtalk/im_1_0"
|
||||||
|
"github.com/alibabacloud-go/tea/tea"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
@ -25,8 +29,10 @@ type DingBotService struct {
|
||||||
dingTalkBotBiz *biz.DingTalkBotBiz
|
dingTalkBotBiz *biz.DingTalkBotBiz
|
||||||
dingTalkOld *dingtalk.OldClient
|
dingTalkOld *dingtalk.OldClient
|
||||||
dingtalkCardClient *dingtalk.CardClient
|
dingtalkCardClient *dingtalk.CardClient
|
||||||
|
dingtalkImClient *dingtalk.ImClient
|
||||||
botGroupConfigImpl *impl.BotGroupConfigImpl
|
botGroupConfigImpl *impl.BotGroupConfigImpl
|
||||||
botGroupImpl *impl.BotGroupImpl
|
botGroupImpl *impl.BotGroupImpl
|
||||||
|
botConfigImpl *impl.BotConfigImpl
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDingBotService(
|
func NewDingBotService(
|
||||||
|
|
@ -34,16 +40,20 @@ func NewDingBotService(
|
||||||
dingTalkBotBiz *biz.DingTalkBotBiz,
|
dingTalkBotBiz *biz.DingTalkBotBiz,
|
||||||
dingTalkOld *dingtalk.OldClient,
|
dingTalkOld *dingtalk.OldClient,
|
||||||
dingtalkCardClient *dingtalk.CardClient,
|
dingtalkCardClient *dingtalk.CardClient,
|
||||||
|
dingtalkImClient *dingtalk.ImClient,
|
||||||
botGroupConfigImpl *impl.BotGroupConfigImpl,
|
botGroupConfigImpl *impl.BotGroupConfigImpl,
|
||||||
botGroupImpl *impl.BotGroupImpl,
|
botGroupImpl *impl.BotGroupImpl,
|
||||||
|
botConfigImpl *impl.BotConfigImpl,
|
||||||
) *DingBotService {
|
) *DingBotService {
|
||||||
return &DingBotService{
|
return &DingBotService{
|
||||||
config: config,
|
config: config,
|
||||||
dingTalkBotBiz: dingTalkBotBiz,
|
dingTalkBotBiz: dingTalkBotBiz,
|
||||||
dingTalkOld: dingTalkOld,
|
dingTalkOld: dingTalkOld,
|
||||||
dingtalkCardClient: dingtalkCardClient,
|
dingtalkCardClient: dingtalkCardClient,
|
||||||
|
dingtalkImClient: dingtalkImClient,
|
||||||
botGroupConfigImpl: botGroupConfigImpl,
|
botGroupConfigImpl: botGroupConfigImpl,
|
||||||
botGroupImpl: botGroupImpl,
|
botGroupImpl: botGroupImpl,
|
||||||
|
botConfigImpl: botConfigImpl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,18 +180,110 @@ func (d *DingBotService) OnCardMessageReceived(ctx context.Context, data *card.C
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 卡片同步回调超时时间为2s,2s内同步返回,2s后异步更新卡片
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
// action 处理 - 这里先只处理第一个匹配的actionId
|
// action 处理 - 这里先只处理第一个匹配的actionId
|
||||||
for _, actionId := range data.CardActionData.CardPrivateData.ActionIdList {
|
for _, actionId := range data.CardActionData.CardPrivateData.ActionIdList {
|
||||||
switch actionId {
|
switch actionId {
|
||||||
case constants.CardActionTypeCreateGroup:
|
case constants.CardActionTypeCreateGroup:
|
||||||
// 解析 OutTrackId 以获取 SpaceId 和 BotId
|
// 解析 OutTrackId 以获取 SpaceId 和 BotId
|
||||||
spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId)
|
spaceId, botId := constants.ParseCardOutTrackId(data.OutTrackId)
|
||||||
// 群id+机器人id确认一个群配置
|
|
||||||
var botGroup *model.AiBotGroup
|
// 获取新群聊人员
|
||||||
botGroup, err = d.botGroupImpl.GetByConversationIdAndRobotCode(spaceId, botId)
|
var userIds []string
|
||||||
|
userIds, err = d.buildNewGroupUserIds(ctx, spaceId, botId, data.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 新群分享链接
|
||||||
|
newGroupShareLink := ""
|
||||||
|
timeOutLimit := 1500 * time.Millisecond
|
||||||
|
|
||||||
|
// 钉钉appKey
|
||||||
|
appKey := dingtalk.AppKey{}
|
||||||
|
|
||||||
|
if data.CardActionData.CardPrivateData.Params["status"] == "confirm" {
|
||||||
|
// 创建群聊 - 这里用的是“统一登录平台”这个应用的接口
|
||||||
|
// 不是很关心成功失败,ws中,后续考虑协程去创建
|
||||||
|
chatId, openConversationId, err := d.dingTalkOld.CreateInternalGroupConversation(ctx, "问题处理群", userIds)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("创建群聊失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = openConversationId
|
||||||
|
// 获取机器人配置
|
||||||
|
var botConfig model.AiBotConfig
|
||||||
|
cond := builder.NewCond().And(builder.Eq{"robot_code": botId})
|
||||||
|
err = d.botConfigImpl.GetOneBySearchToStrut(&cond, &botConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 解出 config
|
||||||
|
var config entitys.DingTalkBot
|
||||||
|
err = json.Unmarshal([]byte(botConfig.BotConfig), &config)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("配置解析失败 %v", err.Error())
|
||||||
|
}
|
||||||
|
appKey = dingtalk.AppKey{
|
||||||
|
AppKey: config.ClientId,
|
||||||
|
AppSecret: config.ClientSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加当前机器人到新群
|
||||||
|
_, err = d.dingtalkImClient.AddRobotToConversation(
|
||||||
|
appKey,
|
||||||
|
&im_1_0.AddRobotToConversationRequest{
|
||||||
|
OpenConversationId: tea.String(openConversationId),
|
||||||
|
RobotCode: tea.String(botId),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("添加机器人到会话失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回新群分享链接,直接进群
|
||||||
|
newGroupShareLink, err = d.dingTalkOld.GetJoinGroupQrcode(ctx, chatId, data.UserId)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("获取入群二维码失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
endTime := time.Now()
|
||||||
|
if endTime.Sub(startTime) > timeOutLimit {
|
||||||
|
// 异步更新卡片
|
||||||
|
d.dingtalkCardClient.UpdateCard(appKey, &card_1_0.UpdateCardRequest{
|
||||||
|
OutTrackId: tea.String(data.OutTrackId),
|
||||||
|
CardData: &card_1_0.UpdateCardRequestCardData{
|
||||||
|
CardParamMap: map[string]*string{
|
||||||
|
"button_display": tea.String("false"),
|
||||||
|
"new_group_share_link": tea.String(newGroupShareLink),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CardUpdateOptions: &card_1_0.UpdateCardRequestCardUpdateOptions{
|
||||||
|
UpdateCardDataByKey: tea.Bool(true),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建关闭创建群组卡片按钮的响应
|
||||||
|
resp = d.buildCreateGroupCardResp(newGroupShareLink)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &card.CardResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildNewGroupUserIds 构建新群聊人员列表
|
||||||
|
func (d *DingBotService) buildNewGroupUserIds(ctx context.Context, spaceId, botId, groupOwner string) ([]string, error) {
|
||||||
|
// 群id+机器人id确认一个群配置
|
||||||
|
botGroup, err := d.botGroupImpl.GetByConversationIdAndRobotCode(spaceId, botId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// 获取群配置
|
// 获取群配置
|
||||||
var groupConfig model.AiBotGroupConfig
|
var groupConfig model.AiBotGroupConfig
|
||||||
cond := builder.NewCond().And(builder.Eq{"config_id": botGroup.ConfigID})
|
cond := builder.NewCond().And(builder.Eq{"config_id": botGroup.ConfigID})
|
||||||
|
|
@ -189,6 +291,7 @@ func (d *DingBotService) OnCardMessageReceived(ctx context.Context, data *card.C
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取处理人列表
|
// 获取处理人列表
|
||||||
issueOwnerJson := groupConfig.IssueOwner
|
issueOwnerJson := groupConfig.IssueOwner
|
||||||
type issueOwnerType struct {
|
type issueOwnerType struct {
|
||||||
|
|
@ -199,35 +302,23 @@ func (d *DingBotService) OnCardMessageReceived(ctx context.Context, data *card.C
|
||||||
if err = json.Unmarshal([]byte(issueOwnerJson), &issueOwner); err != nil {
|
if err = json.Unmarshal([]byte(issueOwnerJson), &issueOwner); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并所有userid
|
// 合并所有userid
|
||||||
userIds := []string{data.UserId} // 当前用户为群主
|
userIds := []string{groupOwner} // 当前用户为群主
|
||||||
for _, owner := range issueOwner {
|
for _, owner := range issueOwner {
|
||||||
userIds = append(userIds, owner.UserId)
|
userIds = append(userIds, owner.UserId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if data.CardActionData.CardPrivateData.Params["status"] == "confirm" {
|
return userIds, nil
|
||||||
// 创建群聊 - 这里用的是“统一登录平台”这个应用的接口
|
|
||||||
// 不是很关心成功失败,ws中,后续考虑协程去创建
|
|
||||||
if _, err = d.dingTalkOld.CreateInternalGroupConversation(ctx, "问题处理群", userIds); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建关闭创建群组卡片按钮的响应
|
// buildCreateGroupCardResp 构建关闭创建群组卡片按钮
|
||||||
resp = d.buildCreateGroupCardResp()
|
func (d *DingBotService) buildCreateGroupCardResp(newGroupShareLink string) *card.CardResponse {
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &card.CardResponse{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭创建群组卡片按钮
|
|
||||||
func (d *DingBotService) buildCreateGroupCardResp() *card.CardResponse {
|
|
||||||
return &card.CardResponse{
|
return &card.CardResponse{
|
||||||
CardData: &card.CardDataDto{
|
CardData: &card.CardDataDto{
|
||||||
CardParamMap: map[string]string{
|
CardParamMap: map[string]string{
|
||||||
"button_display": "false",
|
"button_display": "false",
|
||||||
|
"new_group_share_link": newGroupShareLink,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CardUpdateOptions: &card.CardUpdateOptions{
|
CardUpdateOptions: &card.CardUpdateOptions{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue