From 07a63e9ad3b9f482ad9b7f69d9587f093ebe6392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E4=B8=AB=E8=AE=B2=E6=A2=B5?= Date: Wed, 22 Feb 2023 22:16:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0sql=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=A1=A5=E5=81=BF=E8=83=BD=E5=8A=9B=20(#153)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller/group_controller.go | 8 ++ controller/user_controller.go | 8 ++ logic/a_logic.go | 12 ++ logic/sqlToLdap_login.go | 184 +++++++++++++++++++++++++++++++ model/group.go | 3 +- model/request/group_req.go | 6 + model/request/user_req.go | 4 + model/user.go | 1 + public/common/init_mysql_data.go | 14 +++ routes/group_routes.go | 9 +- routes/user_routes.go | 9 +- service/ildap/group_ildap.go | 43 +++++++- service/ildap/user_ildap.go | 31 ++++++ service/isql/group_isql.go | 35 +----- service/isql/user_isql.go | 10 +- 15 files changed, 333 insertions(+), 44 deletions(-) create mode 100644 logic/sqlToLdap_login.go diff --git a/controller/group_controller.go b/controller/group_controller.go index d152e0b..d156172 100644 --- a/controller/group_controller.go +++ b/controller/group_controller.go @@ -112,3 +112,11 @@ func (m *GroupController) SyncOpenLdapDepts(c *gin.Context) { return logic.OpenLdap.SyncOpenLdapDepts(c, req) }) } + +//同步Sql中的分组信息到ldap +func (m *GroupController) SyncSqlGroups(c *gin.Context) { + req := new(request.SyncSqlGrooupsReq) + Run(c, req, func() (interface{}, interface{}) { + return logic.Sql.SyncSqlGroups(c, req) + }) +} diff --git a/controller/user_controller.go b/controller/user_controller.go index b38f430..24b6f5d 100644 --- a/controller/user_controller.go +++ b/controller/user_controller.go @@ -96,3 +96,11 @@ func (uc UserController) SyncOpenLdapUsers(c *gin.Context) { return logic.OpenLdap.SyncOpenLdapUsers(c, req) }) } + +// 同步sql用户信息到ldap +func (uc UserController) SyncSqlUsers(c *gin.Context) { + req := new(request.SyncSqlUserReq) + Run(c, req, func() (interface{}, interface{}) { + return logic.Sql.SyncSqlUsers(c, req) + }) +} diff --git a/logic/a_logic.go b/logic/a_logic.go index c222a99..853b781 100644 --- a/logic/a_logic.go +++ b/logic/a_logic.go @@ -27,6 +27,7 @@ var ( WeCom = &WeComLogic{} FeiShu = &FeiShuLogic{} OpenLdap = &OpenLdapLogic{} + Sql = &SqlLogic{} Base = &BaseLogic{} FieldRelation = &FieldRelationLogic{} @@ -364,6 +365,17 @@ func InitCron() { common.Log.Errorf("启动同步用户的定时任务失败: %v", err) } } + + // 自动检索未同步数据 + _, err := c.AddFunc("0 */2 * * * *", func() { + // 开发调试时调整为10秒执行一次 + // _, err := c.AddFunc("*/10 * * * * *", func() { + _ = SearchGroupDiff() + _ = SearchUserDiff() + }) + if err != nil { + common.Log.Errorf("启动同步任务状态检查任务失败: %v", err) + } c.Start() } diff --git a/logic/sqlToLdap_login.go b/logic/sqlToLdap_login.go new file mode 100644 index 0000000..1a2d0a6 --- /dev/null +++ b/logic/sqlToLdap_login.go @@ -0,0 +1,184 @@ +package logic + +import ( + "fmt" + + "github.com/eryajf/go-ldap-admin/config" + "github.com/eryajf/go-ldap-admin/model" + "github.com/eryajf/go-ldap-admin/model/request" + "github.com/eryajf/go-ldap-admin/public/tools" + "github.com/eryajf/go-ldap-admin/service/ildap" + "github.com/eryajf/go-ldap-admin/service/isql" + "github.com/gin-gonic/gin" +) + +type SqlLogic struct{} + +// 同步sql的用户信息到ldap +func (d *SqlLogic) SyncSqlUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { + r, ok := req.(*request.SyncSqlUserReq) + if !ok { + return nil, ReqAssertErr + } + _ = c + // 1.获取所有用户 + for _, id := range r.UserIds { + filter := tools.H{"id": int(id)} + if !isql.User.Exist(filter) { + return nil, tools.NewMySqlError(fmt.Errorf("有用户不存在")) + } + } + users, err := isql.User.GetUserByIds(r.UserIds) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("获取用户信息失败: " + err.Error())) + } + // 2.再将用户添加到ldap + for _, user := range users { + err = ildap.User.Add(&user) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("SyncUser向LDAP同步用户失败:" + err.Error())) + } + // 获取用户将要添加的分组 + groups, err := isql.Group.GetGroupByIds(tools.StringToSlice(user.DepartmentId, ",")) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("根据部门ID获取部门信息失败" + err.Error())) + } + for _, group := range groups { + //根据选择的部门,添加到部门内 + err = ildap.Group.AddUserToGroup(group.GroupDN, user.UserDN) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("向Ldap添加用户到分组关系失败:" + err.Error())) + } + } + user.SyncState = 1 + err = isql.User.Update(&user) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("用户同步完毕之后更新状态失败:" + err.Error())) + } + } + + return nil, nil +} + +// 同步sql中的分组信息到ldap +func (d *SqlLogic) SyncSqlGroups(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { + r, ok := req.(*request.SyncSqlGrooupsReq) + if !ok { + return nil, ReqAssertErr + } + _ = c + // 1.获取所有分组 + for _, id := range r.GroupIds { + filter := tools.H{"id": int(id)} + if !isql.Group.Exist(filter) { + return nil, tools.NewMySqlError(fmt.Errorf("有分组不存在")) + } + } + groups, err := isql.Group.GetGroupByIds(r.GroupIds) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("获取分组信息失败: " + err.Error())) + } + // 2.再将分组添加到ldap + for _, group := range groups { + err = ildap.Group.Add(group) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("SyncUser向LDAP同步分组失败:" + err.Error())) + } + if len(group.Users) > 0 { + for _, user := range group.Users { + if user.UserDN == config.Conf.Ldap.AdminDN { + continue + } + err = ildap.Group.AddUserToGroup(group.GroupDN, user.UserDN) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("同步分组之后处理分组内的用户失败:" + err.Error())) + } + } + } + group.SyncState = 1 + err = isql.Group.Update(group) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("分组同步完毕之后更新状态失败:" + err.Error())) + } + } + + return nil, nil +} + +// 检索未同步到ldap中的分组 +func SearchGroupDiff() (err error) { + // 获取sql中的数据 + var sqlGroupList []*model.Group + sqlGroupList, err = isql.Group.ListAll() + if err != nil { + return err + } + // 获取ldap中的数据 + var ldapGroupList []*model.Group + ldapGroupList, err = ildap.Group.ListGroupDN() + if err != nil { + return err + } + // 比对两个系统中的数据 + groups := diffGroup(sqlGroupList, ldapGroupList) + for _, group := range groups { + if group.GroupDN == config.Conf.Ldap.BaseDN { + continue + } + group.SyncState = 2 + err = isql.Group.Update(group) + } + return +} + +// 检索未同步到ldap中的用户 +func SearchUserDiff() (err error) { + // 获取sql中的数据 + var sqlUserList []*model.User + sqlUserList, err = isql.User.ListAll() + if err != nil { + return err + } + // 获取ldap中的数据 + var ldapUserList []*model.User + ldapUserList, err = ildap.User.ListUserDN() + if err != nil { + return err + } + // 比对两个系统中的数据 + users := diffUser(sqlUserList, ldapUserList) + for _, user := range users { + user.SyncState = 2 + err = isql.User.Update(user) + } + return +} + +func diffGroup(a, b []*model.Group) (rst []*model.Group) { + var tmp = make(map[string]struct{}, 0) + + for _, v := range b { + tmp[v.GroupDN] = struct{}{} + } + + for _, v := range a { + if _, ok := tmp[v.GroupDN]; !ok { + rst = append(rst, v) + } + } + return +} +func diffUser(a, b []*model.User) (rst []*model.User) { + var tmp = make(map[string]struct{}, len(a)) + + for _, v := range b { + tmp[v.UserDN] = struct{}{} + } + + for _, v := range a { + if _, ok := tmp[v.UserDN]; !ok { + rst = append(rst, v) + } + } + return +} diff --git a/model/group.go b/model/group.go index d269134..6e7b85f 100644 --- a/model/group.go +++ b/model/group.go @@ -17,7 +17,8 @@ type Group struct { SourceDeptParentId string `gorm:"type:varchar(100);comment:'父部门编号'" json:"sourceDeptParentId"` SourceUserNum int `gorm:"default:0;comment:'部门下的用户数量,从第三方获取的数据'" json:"source_user_num"` Children []*Group `gorm:"-" json:"children"` - GroupDN string `gorm:"type:varchar(255);not null;comment:'分组dn'" json:"groupDn"` // 分组在ldap的dn + GroupDN string `gorm:"type:varchar(255);not null;comment:'分组dn'" json:"groupDn"` // 分组在ldap的dn + SyncState uint `gorm:"type:tinyint(1);default:1;comment:'同步状态:1已同步, 2未同步'" json:"syncState"` // 数据到ldap的同步状态 } func (g *Group) SetGroupName(groupName string) { diff --git a/model/request/group_req.go b/model/request/group_req.go index b18a759..ba845c3 100644 --- a/model/request/group_req.go +++ b/model/request/group_req.go @@ -6,6 +6,7 @@ type GroupListReq struct { Remark string `json:"remark" form:"remark"` PageNum int `json:"pageNum" form:"pageNum"` PageSize int `json:"pageSize" form:"pageSize"` + SyncState uint `json:"syncState" form:"syncState" ` } // GroupListAllReq 获取资源列表结构体,不分页 @@ -110,3 +111,8 @@ type SyncFeiShuDeptsReq struct { // SyncOpenLdapDeptsReq 同步原ldap部门信息 type SyncOpenLdapDeptsReq struct { } + +// SyncOpenLdapDeptsReq 同步原ldap部门信息 +type SyncSqlGrooupsReq struct { + GroupIds []uint `json:"groupIds" validate:"required"` +} diff --git a/model/request/user_req.go b/model/request/user_req.go index 2f1582e..cdaf204 100644 --- a/model/request/user_req.go +++ b/model/request/user_req.go @@ -119,6 +119,9 @@ type SyncFeiShuUserReq struct { // SyncOpenLdapUserReq 同步ldap用户信息 type SyncOpenLdapUserReq struct { } +type SyncSqlUserReq struct { + UserIds []uint `json:"userIds" validate:"required"` +} // UserListReq 获取用户列表结构体 type UserListReq struct { @@ -128,6 +131,7 @@ type UserListReq struct { GivenName string `json:"givenName" form:"givenName"` DepartmentId []uint `json:"departmentId" form:"departmentId"` Status uint `json:"status" form:"status" ` + SyncState uint `json:"syncState" form:"syncState" ` PageNum int `json:"pageNum" form:"pageNum"` PageSize int `json:"pageSize" form:"pageSize"` } diff --git a/model/user.go b/model/user.go index c87d43f..891d9a2 100644 --- a/model/user.go +++ b/model/user.go @@ -24,6 +24,7 @@ type User struct { SourceUserId string `gorm:"type:varchar(100);not null;comment:'第三方用户id'" json:"sourceUserId"` // 第三方用户id SourceUnionId string `gorm:"type:varchar(100);not null;comment:'第三方唯一unionId'" json:"sourceUnionId"` // 第三方唯一unionId UserDN string `gorm:"type:varchar(255);not null;comment:'用户dn'" json:"userDn"` // 用户在ldap的dn + SyncState uint `gorm:"type:tinyint(1);default:1;comment:'同步状态:1已同步, 2未同步'" json:"syncState"` // 数据到ldap的同步状态 } func (u *User) SetUserName(userName string) { diff --git a/public/common/init_mysql_data.go b/public/common/init_mysql_data.go index 150344d..d4cff25 100644 --- a/public/common/init_mysql_data.go +++ b/public/common/init_mysql_data.go @@ -368,6 +368,13 @@ func InitData() { Remark: "从openldap拉取用户信息", Creator: "系统", }, + { + Method: "POST", + Path: "/user/syncSqlUsers", + Category: "user", + Remark: "将数据库中的用户同步到Ldap", + Creator: "系统", + }, { Method: "GET", Path: "/group/list", @@ -459,6 +466,13 @@ func InitData() { Remark: "从openldap拉取部门信息", Creator: "系统", }, + { + Method: "POST", + Path: "/group/syncSqlGroups", + Category: "group", + Remark: "将数据库中的分组同步到Ldap", + Creator: "系统", + }, { Method: "GET", Path: "/role/list", diff --git a/routes/group_routes.go b/routes/group_routes.go index 438b833..4712d22 100644 --- a/routes/group_routes.go +++ b/routes/group_routes.go @@ -26,10 +26,11 @@ func InitGroupRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) g group.GET("/useringroup", controller.Group.UserInGroup) group.GET("/usernoingroup", controller.Group.UserNoInGroup) - group.POST("/syncDingTalkDepts", controller.Group.SyncDingTalkDepts) // 同步部门 - group.POST("/syncWeComDepts", controller.Group.SyncWeComDepts) // 同步部门 - group.POST("/syncFeiShuDepts", controller.Group.SyncFeiShuDepts) // 同步部门 - group.POST("/syncOpenLdapDepts", controller.Group.SyncOpenLdapDepts) // 同步部门 + group.POST("/syncDingTalkDepts", controller.Group.SyncDingTalkDepts) // 同步钉钉部门到平台 + group.POST("/syncWeComDepts", controller.Group.SyncWeComDepts) // 同步企业微信部门到平台 + group.POST("/syncFeiShuDepts", controller.Group.SyncFeiShuDepts) // 同步飞书部门到平台 + group.POST("/syncOpenLdapDepts", controller.Group.SyncOpenLdapDepts) // 同步ldap的分组到平台InitGroupRoutes + group.POST("/syncSqlGroups", controller.Group.SyncSqlGroups) // 同步Sql分组到Ldap } return r diff --git a/routes/user_routes.go b/routes/user_routes.go index f359903..558baa6 100644 --- a/routes/user_routes.go +++ b/routes/user_routes.go @@ -24,10 +24,11 @@ func InitUserRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) gi user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码 user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态 - user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 同步用户 - user.POST("/syncWeComUsers", controller.User.SyncWeComUsers) // 同步用户 - user.POST("/syncFeiShuUsers", controller.User.SyncFeiShuUsers) // 同步用户 - user.POST("/syncOpenLdapUsers", controller.User.SyncOpenLdapUsers) // 同步用户 + user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 同步钉钉用户到平台 + user.POST("/syncWeComUsers", controller.User.SyncWeComUsers) // 同步企业微信用户到平台 + user.POST("/syncFeiShuUsers", controller.User.SyncFeiShuUsers) // 同步飞书用户到平台 + user.POST("/syncOpenLdapUsers", controller.User.SyncOpenLdapUsers) // 同步Ldap用户到平台 + user.POST("/syncSqlUsers", controller.User.SyncSqlUsers) // 同步Sql用户到Ldap } return r } diff --git a/service/ildap/group_ildap.go b/service/ildap/group_ildap.go index 2433b03..5847736 100644 --- a/service/ildap/group_ildap.go +++ b/service/ildap/group_ildap.go @@ -40,8 +40,8 @@ func (x GroupService) Add(g *model.Group) error { //organizationalUnit // UpdateGroup 更新一个分组 func (x GroupService) Update(oldGroup, newGroup *model.Group) error { - modify := ldap.NewModifyRequest(oldGroup.GroupDN, nil) - modify.Replace("description", []string{newGroup.Remark}) + modify1 := ldap.NewModifyRequest(oldGroup.GroupDN, nil) + modify1.Replace("description", []string{newGroup.Remark}) // 获取 LDAP 连接 conn, err := common.GetLDAPConn() @@ -50,14 +50,14 @@ func (x GroupService) Update(oldGroup, newGroup *model.Group) error { return err } - err = conn.Modify(modify) + err = conn.Modify(modify1) if err != nil { return err } // 如果配置文件允许修改分组名称,且分组名称发生了变化,那么执行修改分组名称 if config.Conf.Ldap.GroupNameModify && newGroup.GroupName != oldGroup.GroupName { - modify := ldap.NewModifyDNRequest(oldGroup.GroupDN, newGroup.GroupDN, true, "") - err := conn.ModifyDN(modify) + modify2 := ldap.NewModifyDNRequest(oldGroup.GroupDN, newGroup.GroupDN, true, "") + err := conn.ModifyDN(modify2) if err != nil { return err } @@ -112,3 +112,36 @@ func (x GroupService) RemoveUserFromGroup(gdn, udn string) error { return conn.Modify(newmr) } + +// DelUserFromGroup 将用户从分组删除 +func (x GroupService) ListGroupDN() (groups []*model.Group, err error) { + // Construct query request + searchRequest := ldap.NewSearchRequest( + config.Conf.Ldap.BaseDN, // This is basedn, we will start searching from this node. + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly + "(|(objectClass=organizationalUnit)(objectClass=groupOfUniqueNames))", // This is Filter for LDAP query + []string{"DN"}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned + nil, + ) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return groups, err + } + var sr *ldap.SearchResult + // Search through ldap built-in search + sr, err = conn.Search(searchRequest) + if err != nil { + return nil, err + } + if len(sr.Entries) > 0 { + for _, v := range sr.Entries { + groups = append(groups, &model.Group{ + GroupDN: v.DN, + }) + } + } + return +} diff --git a/service/ildap/user_ildap.go b/service/ildap/user_ildap.go index ac49e72..d55e23e 100644 --- a/service/ildap/user_ildap.go +++ b/service/ildap/user_ildap.go @@ -125,3 +125,34 @@ func (x UserService) NewPwd(username string) (string, error) { } return newpass.GeneratedPassword, nil } +func (x UserService) ListUserDN() (users []*model.User, err error) { + // Construct query request + searchRequest := ldap.NewSearchRequest( + config.Conf.Ldap.BaseDN, // This is basedn, we will start searching from this node. + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, // Here several parameters are respectively scope, derefAliases, sizeLimit, timeLimit, typesOnly + "(|(objectClass=inetOrgPerson)(objectClass=simpleSecurityObject))", // This is Filter for LDAP query + []string{"DN"}, // Here are the attributes returned by the query, provided as an array. If empty, all attributes are returned + nil, + ) + + // 获取 LDAP 连接 + conn, err := common.GetLDAPConn() + defer common.PutLADPConn(conn) + if err != nil { + return users, err + } + var sr *ldap.SearchResult + // Search through ldap built-in search + sr, err = conn.Search(searchRequest) + if err != nil { + return nil, err + } + if len(sr.Entries) > 0 { + for _, v := range sr.Entries { + users = append(users, &model.User{ + UserDN: v.DN, + }) + } + } + return +} diff --git a/service/isql/group_isql.go b/service/isql/group_isql.go index c2301c3..d3f530d 100644 --- a/service/isql/group_isql.go +++ b/service/isql/group_isql.go @@ -28,6 +28,10 @@ func (s GroupService) List(req *request.GroupListReq) ([]*model.Group, error) { if groupRemark != "" { db = db.Where("remark LIKE ?", fmt.Sprintf("%%%s%%", groupRemark)) } + syncState := req.SyncState + if syncState != 0 { + db = db.Where("sync_state = ?", syncState) + } pageReq := tools.NewPageOption(req.PageNum, req.PageSize) err := db.Offset(pageReq.PageNum).Limit(pageReq.PageSize).Preload("Users").Find(&list).Error @@ -54,36 +58,9 @@ func (s GroupService) ListTree(req *request.GroupListReq) ([]*model.Group, error } // List 获取数据列表 -func (s GroupService) ListAll(req *request.GroupListAllReq) ([]*model.Group, error) { - var list []*model.Group - db := common.DB.Model(&model.Group{}).Order("created_at DESC") - - groupName := strings.TrimSpace(req.GroupName) - if groupName != "" { - db = db.Where("group_name LIKE ?", fmt.Sprintf("%%%s%%", groupName)) - } - groupRemark := strings.TrimSpace(req.Remark) - if groupRemark != "" { - db = db.Where("remark LIKE ?", fmt.Sprintf("%%%s%%", groupRemark)) - } - groupType := strings.TrimSpace(req.GroupType) - if groupType != "" { - db = db.Where("group_type = ?", groupType) - } - source := strings.TrimSpace(req.Source) - if source != "" { - db = db.Where("source = ?", source) - } - sourceDeptId := strings.TrimSpace(req.SourceDeptId) - if sourceDeptId != "" { - db = db.Where("source_dept_id = ?", sourceDeptId) - } - sourceDeptParentId := strings.TrimSpace(req.SourceDeptParentId) - if sourceDeptParentId != "" { - db = db.Where("source_dept_parent_id = ?", sourceDeptParentId) - } +func (s GroupService) ListAll() (list []*model.Group, err error) { + err = common.DB.Model(&model.Group{}).Order("created_at DESC").Find(&list).Error - err := db.Find(&list).Error return list, err } diff --git a/service/isql/user_isql.go b/service/isql/user_isql.go index 8406e68..1f18648 100644 --- a/service/isql/user_isql.go +++ b/service/isql/user_isql.go @@ -59,9 +59,13 @@ func (s UserService) List(req *request.UserListReq) ([]*model.User, error) { if status != 0 { db = db.Where("status = ?", status) } + syncState := req.SyncState + if syncState != 0 { + db = db.Where("sync_state = ?", syncState) + } pageReq := tools.NewPageOption(req.PageNum, req.PageSize) - err := db.Offset(pageReq.PageNum).Limit(pageReq.PageSize).Preload("Roles").Find(&list).Error + err := db.Offset(pageReq.PageNum).Limit(pageReq.PageSize).Preload("Roles").Find(&list).Debug().Error return list, err } @@ -94,6 +98,10 @@ func (s UserService) ListCount(req *request.UserListReq) (int64, error) { if status != 0 { db = db.Where("status = ?", status) } + syncState := req.SyncState + if syncState != 0 { + db = db.Where("sync_state = ?", syncState) + } err := db.Count(&count).Error return count, err