This commit is contained in:
eryajf 2022-06-04 09:48:52 +08:00
commit 429e841801
8 changed files with 100 additions and 47 deletions

View File

@ -93,6 +93,9 @@ ldap:
ldap-group-name-modify: false ldap-group-name-modify: false
ldap-user-name-modify: false ldap-user-name-modify: false
dingtalk: dingtalk:
# 使用之前是需要在钉钉开发者后台(https://open-dev.dingtalk.com/#/index) 创建一个小程序或应用.获取appkey和appsecretagentId
# 目前agent-id尚未使用先存着后续功能可能会用到
# 由于获取钉钉第一个部门的id默认为1故而这边需要配置一下钉钉的第一个部门的名称不去钉钉获取
ding-talk-app-key: "xxxxxx" ding-talk-app-key: "xxxxxx"
ding-talk-app-secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxx-vhIGL" ding-talk-app-secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxx-vhIGL"
ding-talk-agent-id: "12121212" ding-talk-agent-id: "12121212"
@ -101,3 +104,5 @@ dingtalk:
#因为分组表不可能成为性能瓶颈,故而不再拆分到新的关系表去维护第三方信息,用户表设计同理 #因为分组表不可能成为性能瓶颈,故而不再拆分到新的关系表去维护第三方信息,用户表设计同理
ding-talk-id-source: "dingtalk" ding-talk-id-source: "dingtalk"
ding-talk-user-init-password: "dingding@123" ding-talk-user-init-password: "dingding@123"
# 是否开启定时同步钉钉的任务
ding-talk-enable-sync: false

View File

@ -153,4 +153,5 @@ type DingTalkConfig struct {
DingTalkRootOuName string `mapstructure:"ding-talk-root-ou-name" json:"dingTalkRootOuName"` DingTalkRootOuName string `mapstructure:"ding-talk-root-ou-name" json:"dingTalkRootOuName"`
DingTalkIdSource string `mapstructure:"ding-talk-id-source" json:"dingTalkIdSource"` DingTalkIdSource string `mapstructure:"ding-talk-id-source" json:"dingTalkIdSource"`
DingTalkUserInitPassword string `mapstructure:"ding-talk-user-init-password" json:"dingTalkUserInitPassword"` DingTalkUserInitPassword string `mapstructure:"ding-talk-user-init-password" json:"dingTalkUserInitPassword"`
DingTalkEnableSync bool `mapstructure:"ding-talk-enable-sync" json:"dingTalkEnableSync"`
} }

3
go.mod
View File

@ -51,8 +51,7 @@ require (
github.com/magiconair/properties v1.8.4 // indirect github.com/magiconair/properties v1.8.4 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/mozillazg/go-pinyin v0.19.0
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect github.com/pelletier/go-toml v1.8.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/afero v1.5.1 // indirect github.com/spf13/afero v1.5.1 // indirect

2
go.sum
View File

@ -288,6 +288,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mozillazg/go-pinyin v0.19.0 h1:p+J8/kjJ558KPvVGYLvqBhxf8jbZA2exSLCs2uUVN8c=
github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=

View File

@ -10,9 +10,11 @@ import (
"github.com/eryajf/go-ldap-admin/service/isql" "github.com/eryajf/go-ldap-admin/service/isql"
"github.com/eryajf/go-ldap-admin/svc/request" "github.com/eryajf/go-ldap-admin/svc/request"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/mozillazg/go-pinyin"
"github.com/zhaoyunxing92/dingtalk/v2" "github.com/zhaoyunxing92/dingtalk/v2"
dingreq "github.com/zhaoyunxing92/dingtalk/v2/request" dingreq "github.com/zhaoyunxing92/dingtalk/v2/request"
"gorm.io/gorm" "gorm.io/gorm"
"regexp"
"strconv" "strconv"
"strings" "strings"
) )
@ -57,9 +59,9 @@ func (d *DingTalkLogic) GetSubDepts(client *dingtalk.DingTalk, req *dingreq.Dept
fmt.Println("GetSubDepts获取到的钉钉部门列表", depts) fmt.Println("GetSubDepts获取到的钉钉部门列表", depts)
// 遍历并处理当前部门信息 // 遍历并处理当前部门信息
for _, dept := range depts.Depts { for _, dept := range depts.Depts {
//先判断分组类型 //先判断分组类型,默认为cn方便应对钉钉动态调整原本没有成员的部门加入成员后导致我们无法增加
localDept := request.DingGroupAddReq{ localDept := request.DingGroupAddReq{
GroupType: "ou", GroupType: "cn",
ParentId: pgId, ParentId: pgId,
GroupName: dept.Name, GroupName: dept.Name,
Remark: dept.Name, Remark: dept.Name,
@ -69,17 +71,17 @@ func (d *DingTalkLogic) GetSubDepts(client *dingtalk.DingTalk, req *dingreq.Dept
SourceUserNum: 0, SourceUserNum: 0,
} }
//获取钉钉方若部门存在人员信息则设置为cn类型 //获取钉钉方若部门存在人员信息则设置为cn类型
reqTemp := &dingreq.DeptUserId{} //reqTemp := &dingreq.DeptUserId{}
reqTemp.DeptId = dept.Id //reqTemp.DeptId = dept.Id
repTemp, err := client.GetDeptUserIds(reqTemp) //repTemp, err := client.GetDeptUserIds(reqTemp)
if err != nil { //if err != nil {
return errors.New(fmt.Sprintf("GetSubDepts获取部门用户Id列表失败%s", err.Error())) // return errors.New(fmt.Sprintf("GetSubDepts获取部门用户Id列表失败%s", err.Error()))
} //}
fmt.Println("钉钉部门人员列表:", repTemp) //fmt.Println("钉钉部门人员列表:", repTemp)
if len(repTemp.UserIds) > 0 { //if len(repTemp.UserIds) > 0 {
localDept.GroupType = "cn" // localDept.GroupType = "cn"
localDept.SourceUserNum = len(repTemp.UserIds) // localDept.SourceUserNum = len(repTemp.UserIds)
} //}
// 处理部门入库 // 处理部门入库
deptTemp, err := d.AddDept(&localDept) deptTemp, err := d.AddDept(&localDept)
if err != nil { if err != nil {
@ -157,11 +159,22 @@ func (d DingTalkLogic) AddDeptUser(client *dingtalk.DingTalk, dept *model.Group,
// } // }
// 获取人员信息 // 获取人员信息
fmt.Println("钉钉人员详情:", detail) fmt.Println("钉钉人员详情:", detail)
userName := detail.Mobile userName := ""
if detail.OrgEmail != "" { if detail.OrgEmail != "" {
emailstr := strings.Split(detail.OrgEmail, "@") emailstr := strings.Split(detail.OrgEmail, "@")
userName = emailstr[0] userName = emailstr[0]
} }
if userName == "" && detail.Name != "" {
name := pinyin.LazyConvert(detail.Name, nil)
userName = strings.Join(name, "")
}
if userName == "" && detail.Mobile != "" {
userName = detail.Mobile
}
if detail.JobNumber == "" {
detail.JobNumber = userName
}
//钉钉部门ids,转换为内部部门id //钉钉部门ids,转换为内部部门id
sourceDeptIds := []string{} sourceDeptIds := []string{}
for _, deptId := range detail.DeptIds { for _, deptId := range detail.DeptIds {
@ -317,6 +330,17 @@ func (d DingTalkLogic) UpdateDept(r *request.DingGroupAddReq) error {
// AddUser 添加用户数据 // AddUser 添加用户数据
func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rspError error) { func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rspError error) {
// 兼容处理钉钉异常人员信息若usernamemailmobile都没有的直接跳过
if r.Username == "" && r.Mail == "" && r.Mobile == "" {
emptyData := new(model.User)
emptyData.Introduction = fmt.Sprintf("此用户:%susernamemailmobile皆为空跳过入库请手动置后台添加", r.Nickname)
emptyData.Nickname = r.Nickname
emptyData.SourceUserId = r.SourceUserId
emptyData.Source = r.Source
emptyData.GivenName = r.GivenName
return emptyData, nil
}
isExist := false isExist := false
oldData := new(model.User) oldData := new(model.User)
if isql.User.Exist(tools.H{"source_user_id": r.SourceUserId}) { if isql.User.Exist(tools.H{"source_user_id": r.SourceUserId}) {
@ -335,15 +359,15 @@ func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rsp
isExist = true isExist = true
} }
} }
if !isExist { //if !isExist {
if r.Mail != "" && isql.User.Exist(tools.H{"mail": r.Mail}) { // if r.Mail != "" && isql.User.Exist(tools.H{"mail": r.Mail}) {
err := isql.User.Find(tools.H{"mail": r.Mail}, oldData) // err := isql.User.Find(tools.H{"mail": r.Mail}, oldData)
if err != nil { // if err != nil {
return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户mail获取用户失败%s", err.Error())) // return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户mail获取用户失败%s", err.Error()))
} // }
isExist = true // isExist = true
} // }
} //}
if !isExist { if !isExist {
if isql.User.Exist(tools.H{"job_number": r.JobNumber}) { if isql.User.Exist(tools.H{"job_number": r.JobNumber}) {
err := isql.User.Find(tools.H{"job_number": r.JobNumber}, oldData) err := isql.User.Find(tools.H{"job_number": r.JobNumber}, oldData)
@ -353,15 +377,6 @@ func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rsp
isExist = true isExist = true
} }
} }
if !isExist {
if isql.User.Exist(tools.H{"username": r.Username}) {
err := isql.User.Find(tools.H{"username": r.Username}, oldData)
if err != nil {
return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户username获取用户失败%s", err.Error()))
}
isExist = true
}
}
if !isExist { if !isExist {
if isql.User.Exist(tools.H{"mobile": r.Mobile}) { if isql.User.Exist(tools.H{"mobile": r.Mobile}) {
err := isql.User.Find(tools.H{"mobile": r.Mobile}, oldData) err := isql.User.Find(tools.H{"mobile": r.Mobile}, oldData)
@ -371,7 +386,31 @@ func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rsp
isExist = true isExist = true
} }
} }
if !isExist {
//组装用户名
//先根据钉钉唯一id获取查看数据库中是否存在
//不存在,则根据用户名 like 用户名获取尾号最大的账号
//重新设定用户名
userData := new(model.User)
err := isql.User.FindTheSameUserName(r.Username, userData)
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
} else {
// 找到重名用户,
re := regexp.MustCompile("[0-9]+")
num := re.FindString(userData.Username)
n := 1
if num != "" {
m, err := strconv.Atoi(num)
if err != nil {
return
}
n = m + 1
}
r.Username = fmt.Sprintf("%s%d", r.Username, n)
}
}
if isExist { if isExist {
r.Username = oldData.Username
user, err := d.UpdateUser(r, oldData) user, err := d.UpdateUser(r, oldData)
if err != nil { if err != nil {
return nil, errors.New(fmt.Sprintf("AddUser用户已存在更新用户失败%s", err.Error())) return nil, errors.New(fmt.Sprintf("AddUser用户已存在更新用户失败%s", err.Error()))

26
main.go
View File

@ -65,19 +65,21 @@ func main() {
common.Log.Fatalf("listen: %s\n", err) common.Log.Fatalf("listen: %s\n", err)
} }
}() }()
//启动定时任务 if config.Conf.DingTalk.DingTalkEnableSync {
c := cron.New(cron.WithSeconds()) //启动定时任务
c.AddFunc("0 1 0 * * *", func() { c := cron.New(cron.WithSeconds())
common.Log.Info("每天0点1分0秒执行一次同步钉钉部门和用户信息到ldap") c.AddFunc("0 1 0 * * *", func() {
logic.DingTalk.DsyncDingTalkDepts(nil, nil) common.Log.Info("每天0点1分0秒执行一次同步钉钉部门和用户信息到ldap")
}) logic.DingTalk.DsyncDingTalkDepts(nil, nil)
//每天凌晨1点执行一次 })
c.AddFunc("0 15 0 * * *", func() { //每天凌晨1点执行一次
common.Log.Info("每天凌晨00点15分执行一次同步钉钉部门和用户信息到ldap") c.AddFunc("0 15 0 * * *", func() {
logic.DingTalk.SyncDingTalkUsers(nil, nil) common.Log.Info("每天凌晨00点15分执行一次同步钉钉部门和用户信息到ldap")
}) logic.DingTalk.SyncDingTalkUsers(nil, nil)
})
c.Start() c.Start()
}
common.Log.Info(fmt.Sprintf("Server is running at %s:%d/%s", host, port, config.Conf.System.UrlPathPrefix)) common.Log.Info(fmt.Sprintf("Server is running at %s:%d/%s", host, port, config.Conf.System.UrlPathPrefix))

View File

@ -120,6 +120,11 @@ func (s UserService) Exist(filter map[string]interface{}) bool {
return !errors.Is(err, gorm.ErrRecordNotFound) return !errors.Is(err, gorm.ErrRecordNotFound)
} }
// Find 获取同名用户已入库的序号最大的用户信息
func (s UserService) FindTheSameUserName(username string, data *model.User) error {
return common.DB.Where("username REGEXP ? ", fmt.Sprintf("^%s[0-9]{0,3}$", username)).Order("username desc").First(&data).Error
}
// Find 获取单个资源 // Find 获取单个资源
func (s UserService) Find(filter map[string]interface{}, data *model.User) error { func (s UserService) Find(filter map[string]interface{}, data *model.User) error {
return common.DB.Where(filter).Preload("Roles").First(&data).Error return common.DB.Where(filter).Preload("Roles").First(&data).Error

View File

@ -2,7 +2,7 @@ package request
// BaseChangePwdReq 修改密码结构体 // BaseChangePwdReq 修改密码结构体
type BaseChangePwdReq struct { type BaseChangePwdReq struct {
Mail string `json:"mail" validate:"required,min=0,max=20"` Mail string `json:"mail" validate:"required,min=0,max=100"`
} }
// BaseDashboardReq 系统首页展示数据结构体 // BaseDashboardReq 系统首页展示数据结构体