diff --git a/config.yml b/config.yml index 401a65e..70a02d3 100644 --- a/config.yml +++ b/config.yml @@ -93,6 +93,9 @@ ldap: ldap-group-name-modify: false ldap-user-name-modify: false dingtalk: + # 使用之前是需要在钉钉开发者后台(https://open-dev.dingtalk.com/#/index) 创建一个小程序或应用.获取appkey和appsecret,agentId + # 目前agent-id尚未使用,先存着后续功能可能会用到 + # 由于获取钉钉第一个部门的id默认为1,故而这边需要配置一下钉钉的第一个部门的名称,不去钉钉获取 ding-talk-app-key: "xxxxxx" ding-talk-app-secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxx-vhIGL" ding-talk-agent-id: "12121212" @@ -101,3 +104,5 @@ dingtalk: #因为分组表不可能成为性能瓶颈,故而不再拆分到新的关系表去维护第三方信息,用户表设计同理 ding-talk-id-source: "dingtalk" ding-talk-user-init-password: "dingding@123" + # 是否开启定时同步钉钉的任务 + ding-talk-enable-sync: false diff --git a/config/config.go b/config/config.go index 3175064..b2a02f9 100644 --- a/config/config.go +++ b/config/config.go @@ -153,4 +153,5 @@ type DingTalkConfig struct { DingTalkRootOuName string `mapstructure:"ding-talk-root-ou-name" json:"dingTalkRootOuName"` DingTalkIdSource string `mapstructure:"ding-talk-id-source" json:"dingTalkIdSource"` DingTalkUserInitPassword string `mapstructure:"ding-talk-user-init-password" json:"dingTalkUserInitPassword"` + DingTalkEnableSync bool `mapstructure:"ding-talk-enable-sync" json:"dingTalkEnableSync"` } diff --git a/go.mod b/go.mod index fbb9a5e..5635814 100644 --- a/go.mod +++ b/go.mod @@ -51,8 +51,7 @@ require ( github.com/magiconair/properties v1.8.4 // indirect github.com/mattn/go-isatty v0.0.12 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/mozillazg/go-pinyin v0.19.0 github.com/pelletier/go-toml v1.8.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/spf13/afero v1.5.1 // indirect diff --git a/go.sum b/go.sum index 0349710..25e6858 100644 --- a/go.sum +++ b/go.sum @@ -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 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 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/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= diff --git a/logic/dingtakl_logic.go b/logic/dingtakl_logic.go index 1d83a15..636a072 100644 --- a/logic/dingtakl_logic.go +++ b/logic/dingtakl_logic.go @@ -10,9 +10,11 @@ import ( "github.com/eryajf/go-ldap-admin/service/isql" "github.com/eryajf/go-ldap-admin/svc/request" "github.com/gin-gonic/gin" + "github.com/mozillazg/go-pinyin" "github.com/zhaoyunxing92/dingtalk/v2" dingreq "github.com/zhaoyunxing92/dingtalk/v2/request" "gorm.io/gorm" + "regexp" "strconv" "strings" ) @@ -57,9 +59,9 @@ func (d *DingTalkLogic) GetSubDepts(client *dingtalk.DingTalk, req *dingreq.Dept fmt.Println("GetSubDepts获取到的钉钉部门列表:", depts) // 遍历并处理当前部门信息 for _, dept := range depts.Depts { - //先判断分组类型 + //先判断分组类型,默认为cn,方便应对钉钉动态调整原本没有成员的部门加入成员后,导致我们无法增加 localDept := request.DingGroupAddReq{ - GroupType: "ou", + GroupType: "cn", ParentId: pgId, GroupName: dept.Name, Remark: dept.Name, @@ -69,17 +71,17 @@ func (d *DingTalkLogic) GetSubDepts(client *dingtalk.DingTalk, req *dingreq.Dept SourceUserNum: 0, } //获取钉钉方,若部门存在人员信息,则设置为cn类型 - reqTemp := &dingreq.DeptUserId{} - reqTemp.DeptId = dept.Id - repTemp, err := client.GetDeptUserIds(reqTemp) - if err != nil { - return errors.New(fmt.Sprintf("GetSubDepts获取部门用户Id列表失败:%s", err.Error())) - } - fmt.Println("钉钉部门人员列表:", repTemp) - if len(repTemp.UserIds) > 0 { - localDept.GroupType = "cn" - localDept.SourceUserNum = len(repTemp.UserIds) - } + //reqTemp := &dingreq.DeptUserId{} + //reqTemp.DeptId = dept.Id + //repTemp, err := client.GetDeptUserIds(reqTemp) + //if err != nil { + // return errors.New(fmt.Sprintf("GetSubDepts获取部门用户Id列表失败:%s", err.Error())) + //} + //fmt.Println("钉钉部门人员列表:", repTemp) + //if len(repTemp.UserIds) > 0 { + // localDept.GroupType = "cn" + // localDept.SourceUserNum = len(repTemp.UserIds) + //} // 处理部门入库 deptTemp, err := d.AddDept(&localDept) if err != nil { @@ -157,11 +159,22 @@ func (d DingTalkLogic) AddDeptUser(client *dingtalk.DingTalk, dept *model.Group, // } // 获取人员信息 fmt.Println("钉钉人员详情:", detail) - userName := detail.Mobile + userName := "" if detail.OrgEmail != "" { emailstr := strings.Split(detail.OrgEmail, "@") 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 sourceDeptIds := []string{} for _, deptId := range detail.DeptIds { @@ -317,6 +330,17 @@ func (d DingTalkLogic) UpdateDept(r *request.DingGroupAddReq) error { // AddUser 添加用户数据 func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rspError error) { + // 兼容处理钉钉异常人员信息,若username,mail,mobile都没有的直接跳过 + if r.Username == "" && r.Mail == "" && r.Mobile == "" { + emptyData := new(model.User) + emptyData.Introduction = fmt.Sprintf("此用户:%s,username,mail,mobile皆为空,跳过入库,请手动置后台添加", r.Nickname) + emptyData.Nickname = r.Nickname + emptyData.SourceUserId = r.SourceUserId + emptyData.Source = r.Source + emptyData.GivenName = r.GivenName + return emptyData, nil + } + isExist := false oldData := new(model.User) 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 } } - if !isExist { - if r.Mail != "" && isql.User.Exist(tools.H{"mail": r.Mail}) { - err := isql.User.Find(tools.H{"mail": r.Mail}, oldData) - if err != nil { - return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户mail获取用户失败:%s", err.Error())) - } - isExist = true - } - } + //if !isExist { + // if r.Mail != "" && isql.User.Exist(tools.H{"mail": r.Mail}) { + // err := isql.User.Find(tools.H{"mail": r.Mail}, oldData) + // if err != nil { + // return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户mail获取用户失败:%s", err.Error())) + // } + // isExist = true + // } + //} if !isExist { if isql.User.Exist(tools.H{"job_number": r.JobNumber}) { 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 } } - 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 isql.User.Exist(tools.H{"mobile": r.Mobile}) { 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 } } + 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 { + r.Username = oldData.Username user, err := d.UpdateUser(r, oldData) if err != nil { return nil, errors.New(fmt.Sprintf("AddUser用户已存在,更新用户失败:%s", err.Error())) diff --git a/main.go b/main.go index 5b62071..fb29523 100644 --- a/main.go +++ b/main.go @@ -65,19 +65,21 @@ func main() { common.Log.Fatalf("listen: %s\n", err) } }() - //启动定时任务 - c := cron.New(cron.WithSeconds()) - c.AddFunc("0 1 0 * * *", func() { - common.Log.Info("每天0点1分0秒执行一次同步钉钉部门和用户信息到ldap") - logic.DingTalk.DsyncDingTalkDepts(nil, nil) - }) - //每天凌晨1点执行一次 - c.AddFunc("0 15 0 * * *", func() { - common.Log.Info("每天凌晨00点15分执行一次同步钉钉部门和用户信息到ldap") - logic.DingTalk.SyncDingTalkUsers(nil, nil) - }) + if config.Conf.DingTalk.DingTalkEnableSync { + //启动定时任务 + c := cron.New(cron.WithSeconds()) + c.AddFunc("0 1 0 * * *", func() { + common.Log.Info("每天0点1分0秒执行一次同步钉钉部门和用户信息到ldap") + logic.DingTalk.DsyncDingTalkDepts(nil, nil) + }) + //每天凌晨1点执行一次 + c.AddFunc("0 15 0 * * *", func() { + 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)) diff --git a/service/isql/user_isql.go b/service/isql/user_isql.go index d45e127..d2076db 100644 --- a/service/isql/user_isql.go +++ b/service/isql/user_isql.go @@ -120,6 +120,11 @@ func (s UserService) Exist(filter map[string]interface{}) bool { 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 获取单个资源 func (s UserService) Find(filter map[string]interface{}, data *model.User) error { return common.DB.Where(filter).Preload("Roles").First(&data).Error diff --git a/svc/request/base_req.go b/svc/request/base_req.go index f5e579e..3780519 100644 --- a/svc/request/base_req.go +++ b/svc/request/base_req.go @@ -2,7 +2,7 @@ package request // BaseChangePwdReq 修改密码结构体 type BaseChangePwdReq struct { - Mail string `json:"mail" validate:"required,min=0,max=20"` + Mail string `json:"mail" validate:"required,min=0,max=100"` } // BaseDashboardReq 系统首页展示数据结构体