refactor: optimize DingTalk integration and add new utilities

This commit is contained in:
renzhiyuan 2025-12-12 15:05:50 +08:00
parent 92bff0e4b9
commit 9c3f0300ad
8 changed files with 87 additions and 59 deletions

View File

@ -70,7 +70,7 @@ func (d *DingTalkBotBiz) InitRequire(ctx context.Context, data *chatbot.BotCallb
}
entitys.ResLog(requireData.Ch, "recognize_start", "收到消息,正在处理中,请稍等")
requireData.UserInfo, err = d.dingTalkUser.GetUserInfo(ctx, data.SenderStaffId, dingtalk.WithId(1))
requireData.UserInfo, err = d.dingTalkUser.GetUserInfoFromBot(ctx, data.SenderStaffId, dingtalk.WithId(1))
return
}

View File

@ -33,30 +33,34 @@ func NewAuth(cfg *config.Config, redis *utils.Rdb, botConfigImpl *impl.BotConfig
}
}
func (a *Auth) GetAccessToken(ctx context.Context, clientId string, clientSecret string) (accessToken string, err error) {
func (a *Auth) GetAccessToken(ctx context.Context, clientId string, clientSecret string) (authInfo *AuthInfo, err error) {
if clientId == "" {
return "", errors.New("clientId is empty")
return nil, errors.New("clientId is empty")
}
accessToken = a.redis.Get(ctx, a.getKey(clientId)).Val()
accessToken := a.redis.Get(ctx, a.getKey(clientId)).Val()
if accessToken == "" {
authInfo, _err := a.getNewAccessToken(ctx, clientId, clientSecret)
dingTalkAuthRes, _err := a.getNewAccessToken(ctx, clientId, clientSecret)
if _err != nil {
return "", _err
return nil, _err
}
err = a.redis.SetEx(ctx, a.getKey(clientId), authInfo.AccessToken, time.Duration(authInfo.ExpireIn-3600)*time.Second).Err()
err = a.redis.SetEx(ctx, a.getKey(clientId), dingTalkAuthRes.AccessToken, time.Duration(dingTalkAuthRes.ExpireIn-3600)*time.Second).Err()
if err != nil {
return
}
accessToken = authInfo.AccessToken
accessToken = dingTalkAuthRes.AccessToken
}
return
return &AuthInfo{
ClientId: clientId,
ClientSecret: clientSecret,
AccessToken: accessToken,
}, nil
}
func (a *Auth) getKey(clientId string) string {
return a.cfg.Redis.Key + ":" + constants.DingTalkAuthBaseKeyPrefix + ":" + clientId
}
func (a *Auth) getNewAccessToken(ctx context.Context, clientId string, clientSecret string) (auth AuthInfo, err error) {
func (a *Auth) getNewAccessToken(ctx context.Context, clientId string, clientSecret string) (auth DingTalkAuthIRes, err error) {
if clientId == "" || clientSecret == "" {
err = errors.New("clientId or clientSecret is empty")
return
@ -79,7 +83,7 @@ func (a *Auth) getNewAccessToken(ctx context.Context, clientId string, clientSec
return
}
func (a *Auth) GetTokenFromBotOption(ctx context.Context, botOption ...BotOption) (token string, err error) {
func (a *Auth) GetTokenFromBotOption(ctx context.Context, botOption ...BotOption) (token *AuthInfo, err error) {
botInfo := &Bot{}
for _, option := range botOption {
option(botInfo)
@ -98,7 +102,8 @@ func (a *Auth) GetTokenFromBotOption(ctx context.Context, botOption ...BotOption
return
}
if botConfigDo.BotID == 0 {
return "", errors.New("未找到机器人服务配置")
err = errors.New("未找到机器人服务配置")
return
}
botInfo.botConfig = &botConfigDo
}

View File

@ -27,14 +27,14 @@ func NewDept(dingDeptImpl *impl.BotDeptImpl, auth *Auth) *Dept {
}
}
func (d *Dept) GetDeptInfoByDeptIds(ctx context.Context, deptIds []int) (depts []*entitys.Dept, err error) {
if len(deptIds) == 0 {
func (d *Dept) GetDeptInfoByDeptIds(ctx context.Context, deptIds []int, authInfo *AuthInfo) (depts []*entitys.Dept, err error) {
if len(deptIds) == 0 || authInfo == nil {
return
}
deptsInfo := make([]model.AiBotDept, 0)
var deptsInfo []model.AiBotDept
cond := builder.NewCond()
cond = cond.And(builder.Eq{"dingtalk_dept_id": deptIds})
err = d.dingDeptImpl.GetRangeToMapStruct(&cond, deptsInfo)
err = d.dingDeptImpl.GetRangeToMapStruct(&cond, &deptsInfo)
if err != nil {
return
}
@ -50,9 +50,9 @@ func (d *Dept) GetDeptInfoByDeptIds(ctx context.Context, deptIds []int) (depts [
if len(diff) > 0 {
deptDo := make([]model.AiBotDept, 0)
for _, deptId := range diff {
deptInfo, err := d.GetDeptInfoFromDingTalk(ctx, deptId)
if err != nil {
return nil, err
deptInfo, _err := d.GetDeptInfoFromDingTalk(ctx, deptId, authInfo.AccessToken)
if _err != nil {
return nil, _err
}
depts = append(depts, &entitys.Dept{
DeptId: deptInfo.DeptId,
@ -74,12 +74,8 @@ func (d *Dept) GetDeptInfoByDeptIds(ctx context.Context, deptIds []int) (depts [
return
}
func (d *Dept) GetDeptInfoFromDingTalk(ctx context.Context, deptId int, botOption ...BotOption) (depts DeptResResult, err error) {
if deptId == 0 {
return
}
token, err := d.auth.GetTokenFromBotOption(ctx, botOption...)
if err != nil {
func (d *Dept) GetDeptInfoFromDingTalk(ctx context.Context, deptId int, token string) (depts DeptResResult, err error) {
if deptId == 0 || len(token) == 0 {
return
}

View File

@ -2,7 +2,7 @@ package dingtalk
import "time"
type AuthInfo struct {
type DingTalkAuthIRes struct {
AccessToken string `json:"accessToken"`
ExpireIn int64 `json:"expireIn"`
}
@ -76,3 +76,9 @@ type DeptResResult struct {
DeptId int `json:"dept_id"`
} `json:"union_dept_ext"`
}
type AuthInfo struct {
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
AccessToken string `json:"accessToken"`
}

View File

@ -36,7 +36,7 @@ func NewUser(
}
}
func (u *User) GetUserInfo(ctx context.Context, staffId string, botOption ...BotOption) (userInfo *entitys.DingTalkUserInfo, err error) {
func (u *User) GetUserInfoFromBot(ctx context.Context, staffId string, botOption ...BotOption) (userInfo *entitys.DingTalkUserInfo, err error) {
if len(staffId) == 0 {
return
}
@ -46,9 +46,13 @@ func (u *User) GetUserInfo(ctx context.Context, staffId string, botOption ...Bot
return
}
}
authInfo, err := u.auth.GetTokenFromBotOption(ctx, botOption...)
if err != nil || authInfo == nil {
return
}
//如果没有找到,则新增
if user == nil {
DingUserInfo, _err := u.getUserInfoFromDingTalkWithBot(ctx, staffId, botOption...)
DingUserInfo, _err := u.getUserInfoFromDingTalk(ctx, authInfo.AccessToken, staffId)
if _err != nil {
return nil, _err
}
@ -60,7 +64,7 @@ func (u *User) GetUserInfo(ctx context.Context, staffId string, botOption ...Bot
DeptIDList: strings.Join(pkg.SliceIntToString(DingUserInfo.DeptIdList), ","),
IsBoss: int32(pkg.Ter(DingUserInfo.Boss, constants.IsBossTrue, constants.IsBossFalse)),
IsSenior: int32(pkg.Ter(DingUserInfo.Senior, constants.IsSeniorTrue, constants.IsSeniorFalse)),
HiredDate: time.Unix(DingUserInfo.HiredDate, 0),
HiredDate: time.UnixMilli(DingUserInfo.HiredDate),
}
_, err = u.dingUserImpl.Add(user)
@ -79,7 +83,7 @@ func (u *User) GetUserInfo(ctx context.Context, staffId string, botOption ...Bot
}
if len(user.DeptIDList) > 0 {
deptIdList := pkg.SliceStringToInt(strings.Split(user.DeptIDList, ","))
depts, _err := u.dept.GetDeptInfoByDeptIds(ctx, deptIdList)
depts, _err := u.dept.GetDeptInfoByDeptIds(ctx, deptIdList, authInfo)
if _err != nil {
return nil, err
}
@ -91,14 +95,6 @@ func (u *User) GetUserInfo(ctx context.Context, staffId string, botOption ...Bot
return userInfo, nil
}
func (u *User) getUserInfoFromDingTalkWithBot(ctx context.Context, staffId string, botOption ...BotOption) (userInfo UserInfoResResult, err error) {
token, err := u.auth.GetTokenFromBotOption(ctx, botOption...)
if err != nil {
return
}
return u.getUserInfoFromDingTalk(ctx, token, staffId)
}
func (u *User) getUserInfoFromDingTalk(ctx context.Context, token string, staffId string) (user UserInfoResResult, err error) {
if token == "" && staffId == "" {
err = errors.New("获取钉钉用户信息的必要参数不足")

View File

@ -0,0 +1,26 @@
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
// Code generated by gorm.io/gen. DO NOT EDIT.
package model
import (
"time"
)
const TableNameAiBotDept = "ai_bot_dept"
// AiBotDept mapped from table <ai_bot_dept>
type AiBotDept struct {
DeptID int32 `gorm:"column:dept_id;primaryKey" json:"dept_id"`
DingtalkDeptID int32 `gorm:"column:dingtalk_dept_id;not null;comment:标记部门的唯一id钉钉钉钉侧提供的dept_id" json:"dingtalk_dept_id"` // 标记部门的唯一id钉钉钉钉侧提供的dept_id
Name string `gorm:"column:name;not null;comment:用户名称" json:"name"` // 用户名称
Status int32 `gorm:"column:status;not null" json:"status"`
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
}
// TableName AiBotDept's table name
func (*AiBotDept) TableName() string {
return TableNameAiBotDept
}

View File

@ -12,19 +12,19 @@ const TableNameAiBotUser = "ai_bot_user"
// AiBotUser mapped from table <ai_bot_user>
type AiBotUser struct {
UserID int32 `gorm:"column:user_id;primaryKey" json:"user_id"`
StaffID string `gorm:"column:staff_id;not null;comment:标记用户用的唯一id钉钉钉钉侧提供的user_id" json:"staff_id"` // 标记用户用的唯一id钉钉钉钉侧提供的user_id
Name string `gorm:"column:name;not null;comment:用户名称" json:"name"` // 用户名称
Title string `gorm:"column:title;not null;comment:职位" json:"title"` // 职位
Extension string `gorm:"column:extension;not null;default:1;comment:信息面板" json:"extension"` // 信息面板
RoleList string `gorm:"column:role_list;not null;comment:角色列表。" json:"role_list"` // 角色列表。
DeptIDList string `gorm:"column:dept_id_list;not null;comment:所在部门id列表" json:"dept_id_list"` // 所在部门id列表
IsBoss int32 `gorm:"column:is_boss;not null;comment:是否是老板" json:"is_boss"` // 是否是老板
IsSenior int32 `gorm:"column:is_senior;not null;comment:是否是高管" json:"is_senior"` // 是否是高管
HiredDate time.Time `gorm:"column:hired_date;not null;default:CURRENT_TIMESTAMP;comment:入职时间" json:"hired_date"` // 入职时间
Status int32 `gorm:"column:status;not null" json:"status"`
DeleteAt time.Time `gorm:"column:delete_at" json:"delete_at"`
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
UserID int32 `gorm:"column:user_id;primaryKey" json:"user_id"`
StaffID string `gorm:"column:staff_id;not null;comment:标记用户用的唯一id钉钉钉钉侧提供的user_id" json:"staff_id"` // 标记用户用的唯一id钉钉钉钉侧提供的user_id
Name string `gorm:"column:name;not null;comment:用户名称" json:"name"` // 用户名称
Title string `gorm:"column:title;not null;comment:职位" json:"title"` // 职位
Extension string `gorm:"column:extension;not null;default:1;comment:信息面板" json:"extension"` // 信息面板
RoleList string `gorm:"column:role_list;not null;comment:角色列表。" json:"role_list"` // 角色列表。
DeptIDList string `gorm:"column:dept_id_list;not null;comment:所在部门id列表" json:"dept_id_list"` // 所在部门id列表
IsBoss int32 `gorm:"column:is_boss;not null;comment:是否是老板" json:"is_boss"` // 是否是老板
IsSenior int32 `gorm:"column:is_senior;not null;comment:是否是高管" json:"is_senior"` // 是否是高管
HiredDate time.Time `gorm:"column:hired_date;not null;default:CURRENT_TIMESTAMP;comment:入职时间" json:"hired_date"` // 入职时间
Status int32 `gorm:"column:status;not null" json:"status"`
DeleteAt *time.Time `gorm:"column:delete_at" json:"delete_at"`
CreateAt time.Time `gorm:"column:create_at;default:CURRENT_TIMESTAMP" json:"create_at"`
}
// TableName AiBotUser's table name

View File

@ -114,23 +114,22 @@ func Difference[T comparable](a, b []T) []T {
// SliceStringToInt []string=>[]int
func SliceStringToInt(strSlice []string) []int {
numSlice := make([]int, len(strSlice), 0)
for _, str := range strSlice {
numSlice := make([]int, len(strSlice))
for i, str := range strSlice {
num, err := strconv.Atoi(str)
if err != nil {
return nil
}
numSlice = append(numSlice, num)
numSlice[i] = num
}
return numSlice
}
// SliceIntToString []int=>[]string
func SliceIntToString(slice []int) []string {
numSlice := make([]string, len(slice), 0)
for _, str := range slice {
numSlice = append(numSlice, strconv.Itoa(str))
strSlice := make([]string, len(slice)) // len=cap=len(slice)
for i, num := range slice {
strSlice[i] = strconv.Itoa(num) // 直接赋值,无 append
}
return numSlice
return strSlice
}