开发机器人的过程中,需要将用户添加的机器人存储在数据库中,对于企业内部机器人,官方只提供一个token,其他的机器人信息都没有提供。 对于自定义webhook机器人,还多了一个secret,其实,一个机器人应该有如下的信息
type DingRobot struct {
RobotId string `gorm:"primaryKey;foreignKey:RobotId" json:"robot_id"` //机器人的token
Deleted gorm.DeletedAt `json:"deleted"` //软删除字段
Type string `json:"type"` //机器人类型,1为企业内部机器人,2为自定义webhook机器人
TypeDetail string `json:"type_detail"` //具体机器人类型
ChatBotUserId string `json:"chat_bot_user_id"` //加密的机器人id,该字段无用
Secret string `json:"secret"` //如果是自定义成机器人, 则存在此字段
DingUsers []DingUser `json:"ding_users" gorm:"many2many:user_robot"` //机器人@多个人,一个人可以被多个机器人@
ChatId string `json:"chat_id"` //机器人所在的群聊chatId
OpenConversationID string `json:"open_conversation_id"`//机器人所在的群聊openConversationID
Tasks []Task `gorm:"foreignKey:RobotId;references:RobotId"` //机器人拥有多个任务
Name string `json:"name"` //机器人的名称
DingToken `json:"ding_token" gorm:"-"`
}
其中,DingToken字段中存储的是token,也就是访问钉钉接口的凭证,Task字段是钉钉机器人拥有的定时任务,是我自己封装的,还有一个字段是DingUsers ,也就是说一个钉钉里面存了好多个用户,这些用户就是群成员,存这些用户的目的是为了能够让机器人@群成员。
其中,RobotId就是token,是唯一的,可以当做主键。 而 ChatBotUserId DingUsers ChatId OpenConversationID Name 如何获取获取呢?
上面的问题只针对于企业内部机器人,如果是自定义机器人,则不需存这些字段。
解决方案:
首先,我们知道RobotId,也就是机器人的token,此token是调用机器人的关键信息,我们先把RobotId存储起来
然后我们使用钉钉的调试器
https://open-dev.dingtalk.com/apiExplorer?spm=ding_open_doc.document.0.0.20bf4063FEGqWg#/jsapi?api=biz.chat.chooseConversationByCorpId
输入cropId之后,我们可以在手机上面选择群聊信息,然后可以获取到chatId(chatId必须通过手机扫描二维码授权)和Title(群聊名称,可以作为机器人的名称),然后我们拿着chatId可以获取到openconversationId,然后通过openConverstaionId可以到该群的所有群成员信息(用来实现机器人@群成员)。
上面是大体的思路,经过上面一番操作后,就可以把原来只有一个robotId机企业内部机器人给绑定上其所在的群成员信息和群聊基本信息。
但是,如果想要让用户使用,首先用户肯定不能手动打开钉钉调试器,其次是用户也不知道企业的cropId,所以我们需要使用程序,让该功能简单化,理想的情况是,用户输入机器人RobotId,然后加载二维码,然后用户使用手机选择机器人所在的群聊,之后机器人就和群聊信息已经该群的群成员绑定在一起了。
难点一:
如何让用户扫描二维码?此二维码实时更新,而且被隐藏在一个canvas中,无法获取到。 解决办法是,我们使用chromedp来模拟浏览器操作,直接把cropId放在程序,自动输入即可,至于二维码,直接使用chromedp进行截图,然后存储在数据库中,然后渲染给前端,等待用户扫描即可
难点二:
如何获取所有群成员,钉钉开放的有接口,我们通过二维码扫码获取chatId,然后获取openconversationId,然后在钉钉开放的小程序上面添加一个酷应用,然后把酷应用添加到群聊中,才可以获取到群成员信息,不然,就会出现系统错误(该问题是我请教钉钉技术支持解决的)
部分源代码展示
//获取二维码buf,chatId, title
func (u *DingUser) GetQRCode(c *gin.Context) (buf []byte, chatId, title string, err error) {
d := data{}
opts := append(
chromedp.DefaultExecAllocatorOptions[:],
chromedp.NoDefaultBrowserCheck, //不检查默认浏览器
chromedp.Flag("headless", false),
chromedp.Flag("blink-settings", "imagesEnabled=true"), //开启图像界面,重点是开启这个
chromedp.Flag("ignore-certificate-errors", true), //忽略错误
chromedp.Flag("disable-web-security", true), //禁用网络安全标志
chromedp.Flag("disable-extensions", true), //开启插件支持
chromedp.Flag("disable-default-apps", true),
chromedp.NoFirstRun, //设置网站不是首次运行
chromedp.WindowSize(1921, 1024),
chromedp.UserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36"), //设置UserAgent
)
allocCtx, cancel := chromedp.NewExecAllocator(context.Background(), opts...)
//defer cancel()
print(cancel)
// 创建上下文实例
ctx, cancel := chromedp.NewContext(
allocCtx,
chromedp.WithLogf(log.Printf),
)
//defer cancel()
// 创建超时上下文
ctx, cancel = context.WithTimeout(ctx, 10*time.Minute)
//defer cancel()
// navigate to a page, wait for an element, click
// capture screenshot of an element
// capture entire browser viewport, returning png with quality=90
var html string
if err := chromedp.Run(ctx,
//打开网页
chromedp.Navigate(`https://open-dev.dingtalk.com/apiExplorer?spm=ding_open_doc.document.0.0.20bf4063FEGqWg#/jsapi?api=biz.chat.chooseConversationByCorpId`),
//定位登录按钮
chromedp.Click(`document.querySelector(".ant-btn.ant-btn-primary")`, chromedp.ByJSPath),
//等二维码出现
chromedp.WaitVisible(`document.querySelector(".ant-modal")`, chromedp.ByJSPath),
//截图
chromedp.ActionFunc(func(ctx context.Context) error {
// get layout metrics
_, _, _, _, _, contentSize, err := page.GetLayoutMetrics().Do(ctx)
if err != nil {
return err
}
width, height := int64(math.Ceil(contentSize.Width)), int64(math.Ceil(contentSize.Height))
// force viewport emulation
err = emulation.SetDeviceMetricsOverride(width, height, 1, false).
WithScreenOrientation(&emulation.ScreenOrientation{
Type: emulation.OrientationTypePortraitPrimary,
Angle: 0,
}).
Do(ctx)
if err != nil {
return err
}
// capture screenshot
buf, err = page.CaptureScreenshot().
WithQuality(90).
WithClip(&page.Viewport{
X: contentSize.X,
Y: contentSize.Y,
Width: contentSize.Width,
Height: contentSize.Height,
Scale: 1,
}).Do(ctx)
username, _ := c.Get(global.CtxUserNameKey)
err = ioutil.WriteFile(fmt.Sprintf("./Screenshot_%s.png", username), buf, 0644)
if err != nil {
zap.L().Error("二维码写入失败", zap.Error(err))
}
return nil
}),
//等待用户扫码连接成功
chromedp.WaitVisible(`document.querySelector(".connect-info")`, chromedp.ByJSPath),
//chromedp.SendKeys(`document.querySelector("#corpId")`, "caonima",chromedp.ByJSPath),
//设置输入框中的值为空
chromedp.SetValue(`document.querySelector("#corpId")`, "", chromedp.ByJSPath),
//chromedp.Click(`document.querySelector(".ant-btn.ant-btn-primary")`, chromedp.ByJSPath),
//chromedp.Clear(`#corpId`,chromedp.ByID),
//输入正确的值
chromedp.SendKeys(`document.querySelector("#corpId")`, "输入自己企业的cropId", chromedp.ByJSPath),
//点击发起调用按钮
chromedp.Click(`document.querySelector(".ant-btn.ant-btn-primary")`, chromedp.ByJSPath),
chromedp.WaitVisible(`document.querySelector("#dingapp > div > div > div.api-explorer-wrap > div.api-info > div > div.ant-tabs-content.ant-tabs-content-animated.ant-tabs-top-content > div.ant-tabs-tabpane.ant-tabs-tabpane-active > div.debug-result > div.code-mirror > div.code-content > div > div > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div:nth-child(2) > pre > span > span.cm-tab")`, chromedp.ByJSPath),
//自定义函数进行爬虫
chromedp.ActionFunc(func(ctx context.Context) error {
//b := chromedp.WaitEnabled(`document.querySelector("#dingapp > div > div > div.api-explorer-wrap > div.api-info > div > div.ant-tabs-content.ant-tabs-content-animated.ant-tabs-top-content > div.ant-tabs-tabpane.ant-tabs-tabpane-active > div.debug-result > div.code-mirror > div.code-content > div > div > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre")`, chromedp.ByJSPath)
//b.Do(ctx)
a := chromedp.OuterHTML(`document.querySelector("body")`, &html, chromedp.ByJSPath)
a.Do(ctx)
dom, err := goquery.NewDocumentFromReader(strings.NewReader(html))
if err != nil {
fmt.Println("123", err.Error())
return err
}
var data string
dom.Find("#dingapp > div > div > div.api-explorer-wrap > div.api-info > div > div.ant-tabs-content.ant-tabs-content-animated.ant-tabs-top-content > div.ant-tabs-tabpane.ant-tabs-tabpane-active > div.debug-result > div.code-mirror > div.code-content > div > div > div.CodeMirror-scroll > div.CodeMirror-sizer > div > div > div > div.CodeMirror-code > div > pre").Each(func(i int, selection *goquery.Selection) {
data = data + selection.Text()
selection.Next()
})
data = strings.ReplaceAll(data, " ", "")
data = strings.ReplaceAll(data, "\n", "")
reader := strings.NewReader(data)
bytearr, err := ioutil.ReadAll(reader)
err1 := json.Unmarshal(bytearr, &d)
if err1 != nil {
}
return nil
}),
); err != nil {
zap.L().Error("使用chromedp失败")
return nil, "", "", err
}
if &d == nil {
return nil, "", "", err
}
return buf, d.Result.ChatId, d.Result.Title, err
}
把图片返回前端代码
User.GET("showQRCode", func(c *gin.Context) {
username, _ := c.Get(global.CtxUserNameKey)
c.File(fmt.Sprintf("Screenshot_%s.png", username))
})
获取openConversationId文章来源:https://www.toymoban.com/news/detail-564121.html
type DingGroup struct {
OpenConversationID string
ChatID string
Name string
Token DingToken
}
func (g *DingGroup) GetOpenConversationID() string {
client, _err := createClient()
if _err != nil {
return g.OpenConversationID
}
chatIdToOpenConversationIdHeaders := &dingtalkim_1_0.ChatIdToOpenConversationIdHeaders{}
chatIdToOpenConversationIdHeaders.XAcsDingtalkAccessToken = tea.String(g.Token.Token)
tryErr := func() (_e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
result, _err := client.ChatIdToOpenConversationIdWithOptions(tea.String(g.ChatID), chatIdToOpenConversationIdHeaders, &util.RuntimeOptions{})
if _err != nil {
return _err
}
g.OpenConversationID = *(result.Body.OpenConversationId)
return nil
}()
if tryErr != nil {
var err = &tea.SDKError{}
if _t, ok := tryErr.(*tea.SDKError); ok {
err = _t
} else {
err.Message = tea.String(tryErr.Error())
}
if !tea.BoolValue(util.Empty(err.Code)) && !tea.BoolValue(util.Empty(err.Message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
}
return g.OpenConversationID
}
获取机器人所在的群成员文章来源地址https://www.toymoban.com/news/detail-564121.html
type DingRobot struct {
RobotId string `gorm:"primaryKey;foreignKey:RobotId" json:"robot_id"` //机器人的token
Deleted gorm.DeletedAt `json:"deleted"` //软删除字段
Type string `json:"type"` //机器人类型,1为企业内部机器人,2为自定义webhook机器人
TypeDetail string `json:"type_detail"` //具体机器人类型
ChatBotUserId string `json:"chat_bot_user_id"` //加密的机器人id,该字段无用
Secret string `json:"secret"` //如果是自定义成机器人, 则存在此字段
DingUserID string `json:"ding_user_id"` // 机器人所属用户id
UserName string `json:"user_name"` //机器人所属用户名
DingUsers []DingUser `json:"ding_users" gorm:"many2many:user_robot"` //机器人@多个人,一个人可以被多个机器人@
ChatId string `json:"chat_id"` //机器人所在的群聊chatId
OpenConversationID string `json:"open_conversation_id"` //机器人所在的群聊openConversationID
Tasks []Task `gorm:"foreignKey:RobotId;references:RobotId"` //机器人拥有多个任务
Name string `json:"name"` //机器人的名称
DingToken `json:"ding_token" gorm:"-"`
}
//获取机器人所在的群聊的userIdList ,前提是获取到OpenConversationId,获取到OpenConverstaionId的前提是获取到二维码
func (r *DingRobot) GetGroupUserIds() (userIds []string, _err error) {
//所需参数access_token, OpenConversationId string
olduserIds := []*string{}
client, _err := createClient()
if _err != nil {
return
}
batchQueryGroupMemberHeaders := &dingtalkim_1_0.BatchQueryGroupMemberHeaders{}
batchQueryGroupMemberHeaders.XAcsDingtalkAccessToken = tea.String(r.DingToken.Token)
batchQueryGroupMemberRequest := &dingtalkim_1_0.BatchQueryGroupMemberRequest{
OpenConversationId: tea.String(r.OpenConversationID),
CoolAppCode: tea.String("小程序下面的酷应用编码"),
MaxResults: tea.Int64(300),
NextToken: tea.String("XXXXX"),
}
tryErr := func() (_e error) {
defer func() {
if r := tea.Recover(recover()); r != nil {
_e = r
}
}()
result, _err := client.BatchQueryGroupMemberWithOptions(batchQueryGroupMemberRequest, batchQueryGroupMemberHeaders, &util.RuntimeOptions{})
if _err != nil {
return _err
}
olduserIds = result.Body.MemberUserIds
return
}()
if tryErr != nil {
var err = &tea.SDKError{}
if _t, ok := tryErr.(*tea.SDKError); ok {
err = _t
} else {
err.Message = tea.String(tryErr.Error())
}
if !tea.BoolValue(util.Empty(err.Code)) && !tea.BoolValue(util.Empty(err.Message)) {
// err 中含有 code 和 message 属性,可帮助开发定位问题
}
}
userIds = make([]string, len(olduserIds))
for i, id := range olduserIds {
userIds[i] = *id
}
return
}
到了这里,关于钉钉企业内部机器人开发——绑定群聊信息到机器人的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!