diff --git a/config.yml b/config.yml index cd017f4..401a65e 100644 --- a/config.yml +++ b/config.yml @@ -92,3 +92,12 @@ ldap: ldap-group-dn: "ou=group,dc=eryajf,dc=net" ldap-group-name-modify: false ldap-user-name-modify: false +dingtalk: + ding-talk-app-key: "xxxxxx" + ding-talk-app-secret: "xxxxxxxxxxxxxxxxxxxxxxxxxxx-vhIGL" + ding-talk-agent-id: "12121212" + ding-talk-root-ou-name: "钉钉首个部门的名称" + #为了方便数据库存储,防止第三方id重复,故而增加一个前缀(用于用户表和分组表中第三方id存储,加上此处配置的source字段进行区分来源,判断唯一)。长度不超过10. + #因为分组表不可能成为性能瓶颈,故而不再拆分到新的关系表去维护第三方信息,用户表设计同理 + ding-talk-id-source: "dingtalk" + ding-talk-user-init-password: "dingding@123" diff --git a/config/config.go b/config/config.go index bdb3882..3175064 100644 --- a/config/config.go +++ b/config/config.go @@ -24,6 +24,7 @@ type config struct { RateLimit *RateLimitConfig `mapstructure:"rate-limit" json:"rateLimit"` Ldap *LdapConfig `mapstructure:"ldap" json:"ldap"` Email *EmailConfig `mapstructure:"email" json:"email"` + DingTalk *DingTalkConfig `mapstructure:"dingtalk" json:"dingTalk"` } // 设置读取配置信息 @@ -144,3 +145,12 @@ type EmailConfig struct { Pass string `mapstructure:"pass" json:"pass"` From string `mapstructure:"from" json:"from"` } + +type DingTalkConfig struct { + DingTalkAppKey string `mapstructure:"ding-talk-app-key" json:"dingTalkAppKey"` + DingTalkAppSecret string `mapstructure:"ding-talk-app-secret" json:"dingTalkAppSecret"` + DingTalkAgentId string `mapstructure:"ding-talk-agent-id" json:"dingTalkAgentId"` + 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"` +} diff --git a/controller/group_controller.go b/controller/group_controller.go index 7e1ca6e..017ed56 100644 --- a/controller/group_controller.go +++ b/controller/group_controller.go @@ -80,3 +80,11 @@ func (m *GroupController) RemoveUser(c *gin.Context) { return logic.Group.RemoveUser(c, req) }) } + +//同步钉钉部门信息 +func (m *GroupController) SyncDingTalkDepts(c *gin.Context) { + req := new(request.SyncDingTalkDeptsReq) + Run(c, req, func() (interface{}, interface{}) { + return logic.DingTalk.DsyncDingTalkDepts(c, req) + }) +} diff --git a/controller/user_controller.go b/controller/user_controller.go index 9315f47..6f5923c 100644 --- a/controller/user_controller.go +++ b/controller/user_controller.go @@ -64,3 +64,11 @@ func (uc UserController) GetUserInfo(c *gin.Context) { return logic.User.GetUserInfo(c, req) }) } + +// 同步钉钉用户信息 +func (uc UserController) SyncDingTalkUsers(c *gin.Context) { + req := new(request.SyncDingUserReq) + Run(c, req, func() (interface{}, interface{}) { + return logic.DingTalk.SyncDingTalkUsers(c, req) + }) +} diff --git a/go.mod b/go.mod index a55eb81..05b6915 100644 --- a/go.mod +++ b/go.mod @@ -14,8 +14,10 @@ require ( github.com/go-playground/validator/v10 v10.9.0 github.com/juju/ratelimit v1.0.1 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/robfig/cron/v3 v3.0.0 github.com/spf13/viper v1.7.1 github.com/thoas/go-funk v0.7.0 + github.com/zhaoyunxing92/dingtalk/v2 v2.0.7-0.20220601083444-173c10c3f835 go.uber.org/zap v1.19.1 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/natefinch/lumberjack.v2 v2.0.0 diff --git a/go.sum b/go.sum index f7f0989..6e76dbd 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,9 @@ github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNC github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -314,6 +315,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/robfig/cron/v3 v3.0.0 h1:kQ6Cb7aHOHTSzNVNEhmp8EcWKLb4CbiMW9h9VyIhO4E= +github.com/robfig/cron/v3 v3.0.0/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -381,6 +384,8 @@ github.com/ugorji/go/codec v1.2.3/go.mod h1:5FxzDJIgeiWJZslYHPj+LS1dq1ZBQVelZFnj github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/zhaoyunxing92/dingtalk/v2 v2.0.7-0.20220601083444-173c10c3f835 h1:T6/rI54b4nVpQlIDv0iB0hTff4hzlXe63QcBcZ3u73s= +github.com/zhaoyunxing92/dingtalk/v2 v2.0.7-0.20220601083444-173c10c3f835/go.mod h1:MSvHUbYR94ffuWbJKFb8yHYyHg3qC/kQ3Hqpr6lK5ko= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -582,9 +587,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/logic/a_logic.go b/logic/a_logic.go index 24802eb..4470333 100644 --- a/logic/a_logic.go +++ b/logic/a_logic.go @@ -15,5 +15,6 @@ var ( Role = &RoleLogic{} Menu = &MenuLogic{} OperationLog = &OperationLogLogic{} + DingTalk = &DingTalkLogic{} Base = &BaseLogic{} ) diff --git a/logic/dingtakl_logic.go b/logic/dingtakl_logic.go new file mode 100644 index 0000000..1d83a15 --- /dev/null +++ b/logic/dingtakl_logic.go @@ -0,0 +1,535 @@ +package logic + +import ( + "errors" + "fmt" + "github.com/eryajf/go-ldap-admin/config" + "github.com/eryajf/go-ldap-admin/model" + "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/eryajf/go-ldap-admin/svc/request" + "github.com/gin-gonic/gin" + "github.com/zhaoyunxing92/dingtalk/v2" + dingreq "github.com/zhaoyunxing92/dingtalk/v2/request" + "gorm.io/gorm" + "strconv" + "strings" +) + +type DingTalkLogic struct { +} + +//通过钉钉获取部门信息 +func (d *DingTalkLogic) DsyncDingTalkDepts(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { + client, err := dingtalk.NewClient(config.Conf.DingTalk.DingTalkAppKey, config.Conf.DingTalk.DingTalkAppSecret) + // 先存根部门信息到数据库和ldap,钉钉根部门id为1,ldap根部门名称为:config.Conf.DingTalk.DingTalkRootOu + r := request.DingGroupAddReq{} + r.GroupName = config.Conf.DingTalk.DingTalkRootOuName + r.GroupType = "ou" + r.ParentId = 0 + r.Remark = "钉钉根部门" + r.Source = config.Conf.DingTalk.DingTalkIdSource + r.SourceDeptId = fmt.Sprintf("%s_%s", config.Conf.DingTalk.DingTalkIdSource, "1") + r.SourceDeptParentId = fmt.Sprintf("%s_%s", config.Conf.DingTalk.DingTalkIdSource, "0") + group, err := d.AddDept(&r) + if err != nil { + return nil, fmt.Sprintf("新增部门失败:部门名称为:%s,钉钉部门id为:%d,错误信息:%s", r.GroupName, r.SourceDeptId, err.Error()) + } + // 获取根部门下的部门信息,进行处理 + reqDept := &dingreq.DeptList{} + reqDept.DeptId = 1 + reqDept.Language = "zh_CN" + err = d.GetSubDepts(client, reqDept, group.ID, r.Source) + if err != nil { + return nil, fmt.Sprintf("DsyncDingTalkDepts同步部门出错:%s", err.Error()) + } + return nil, nil +} + +// 通过钉钉获取部门信息,并存入数据库 +func (d *DingTalkLogic) GetSubDepts(client *dingtalk.DingTalk, req *dingreq.DeptList, pgId uint, source string) error { + // 获取子部门列表 + depts, err := client.GetDeptList(req) + if err != nil { + return errors.New(fmt.Sprintf("GetSubDepts获取部门列表失败:%s", err.Error())) + } + fmt.Println("GetSubDepts获取到的钉钉部门列表:", depts) + // 遍历并处理当前部门信息 + for _, dept := range depts.Depts { + //先判断分组类型 + localDept := request.DingGroupAddReq{ + GroupType: "ou", + ParentId: pgId, + GroupName: dept.Name, + Remark: dept.Name, + Source: config.Conf.DingTalk.DingTalkIdSource, + SourceDeptParentId: fmt.Sprintf("%s_%d", source, dept.ParentId), + SourceDeptId: fmt.Sprintf("%s_%d", source, dept.Id), + 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) + } + // 处理部门入库 + deptTemp, err := d.AddDept(&localDept) + if err != nil { + return errors.New(fmt.Sprintf("GetSubDepts添加部门入库失败:%s", err.Error())) + } + // 递归调用 + sub := &dingreq.DeptList{} + sub.DeptId = dept.Id + sub.Language = "zh_CN" + d.GetSubDepts(client, sub, deptTemp.ID, deptTemp.Source) + } + return nil +} + +//根据现有数据库同步到的部门信息,开启用户同步 +func (d DingTalkLogic) SyncDingTalkUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { + client, err := dingtalk.NewClient(config.Conf.DingTalk.DingTalkAppKey, config.Conf.DingTalk.DingTalkAppSecret) + //获取数据库里面的钉钉同步过来的部门信息 + r := request.GroupListAllReq{} + r.GroupType = "cn" + r.Source = config.Conf.DingTalk.DingTalkIdSource + depts, err := isql.Group.ListAll(&r) + if err != nil { + return nil, fmt.Sprintf("SyncDingTalkUsers查询本地部门列表失败:", err.Error()) + } + //遍历处理部门,获取钉钉对应的用户信息 + for index, dept := range depts { + fmt.Println(fmt.Sprintf("当前进行的步调为:%d,部门名称为:%s", index, dept.GroupName)) + err = d.AddDeptUser(client, dept, 0) + if err != nil { + return nil, fmt.Sprintf("SyncDingTalkUsers添加部门下用户失败:", err.Error()) + } + } + return nil, nil +} + +//获取并处理钉钉部门下的用户信息入库 +func (d DingTalkLogic) AddDeptUser(client *dingtalk.DingTalk, dept *model.Group, cursor int) error { + // 处理部门下的人员信息 + deptId := strings.Split(dept.SourceDeptId, "_") + tempId, err := strconv.Atoi(deptId[1]) + if err != nil { + return err + } + //方式一:获取部门下用户信息,一次100个,遍历后插入数据库,经过验证,第三方依赖包有问题 + r := dingreq.DeptDetailUserInfo{} + r.DeptId = tempId + r.Language = "zh_CN" + r.Cursor = cursor + r.Size = 100 + //获取钉钉部门人员信息 + rep, err := client.GetDeptDetailUserInfo(&r) + fmt.Println(fmt.Sprintf("当前获取的部门名称为:%s,总用户量为:%d", dept.GroupName, len(rep.DeptDetailUsers))) + if err != nil { + return errors.New(fmt.Sprintf("AddDeptUser获取钉钉部门人员信息失败:%s", err.Error())) + } + //方式二:临时处理方案:获取部门用户id列表,遍历,挨个从钉钉获取用户信息 + //dingr := dingreq.DeptUserId{} + //dingr.DeptId = tempId + //deptUserIds, err := client.GetDeptUserIds(&dingr) + //if err != nil { + // return errors.New(fmt.Sprintf("AddDeptUser通过用户部门id从钉钉获取用户id列表失败:%s", err.Error())) + //} + // 遍历并处理当前部门下的人员信息 + for _, detail := range rep.DeptDetailUsers { + //for index, userId := range deptUserIds.UserIds { + // fmt.Println(fmt.Sprintf("获取到的部门用户数为:%d,正在处理的用户序号为:%d,总Ids为:", len(deptUserIds.UserIds), index)) + // fmt.Println(deptUserIds.UserIds) + // userReq := dingreq.UserDetail{} + // userReq.UserId = userId + // userReq.Language = "zh_CN" + // detail, err := client.GetUserDetail(&userReq) + // if err != nil { + // return errors.New(fmt.Sprintf("AddDeptUser通过用户id从钉钉获取用户详情失败:%s", err.Error())) + // } + // 获取人员信息 + fmt.Println("钉钉人员详情:", detail) + userName := detail.Mobile + if detail.OrgEmail != "" { + emailstr := strings.Split(detail.OrgEmail, "@") + userName = emailstr[0] + } + //钉钉部门ids,转换为内部部门id + sourceDeptIds := []string{} + for _, deptId := range detail.DeptIds { + sourceDeptIds = append(sourceDeptIds, fmt.Sprintf("%s_%d", config.Conf.DingTalk.DingTalkIdSource, deptId)) + } + groupIds, err := isql.Group.DingTalkDeptIdsToGroupIds(sourceDeptIds) + if err != nil { + return errors.New(fmt.Sprintf("AddDeptUser转换钉钉部门id到本地分组id出错:%s", err.Error())) + } + user := request.DingUserAddReq{ + Username: userName, + Password: config.Conf.DingTalk.DingTalkUserInitPassword, + Nickname: detail.Name, + GivenName: detail.Name, + Mail: detail.OrgEmail, + JobNumber: detail.JobNumber, + Mobile: detail.Mobile, + Avatar: detail.Avatar, + PostalAddress: detail.WorkPlace, + Departments: dept.GroupName, + Position: detail.Title, + Introduction: detail.Remark, + Status: 1, + DepartmentId: groupIds, + Source: config.Conf.DingTalk.DingTalkIdSource, + SourceUserId: fmt.Sprintf("%s_%s", config.Conf.DingTalk.DingTalkIdSource, detail.UserId), + SourceUnionId: fmt.Sprintf("%s_%s", config.Conf.DingTalk.DingTalkIdSource, detail.UnionId), + } + // 入库 + repUser, err := d.AddUser(&user) + if err != nil { + return errors.New(fmt.Sprintf("AddDeptUser添加用户失败:%s", err.Error())) + } + fmt.Println("入库成功,用户信息为:") + fmt.Println(repUser) + } + if rep.HasMore { + err = d.AddDeptUser(client, dept, rep.NextCursor) + if err != nil { + return errors.New(fmt.Sprintf("AddDeptUser添加用户失败:%s", err.Error())) + } + } + return nil +} + +// AddGroup 添加部门数据 +func (d DingTalkLogic) AddDept(r *request.DingGroupAddReq) (data *model.Group, rspError error) { + // 判断部门名称是否存在 + filter := tools.H{"source_dept_id": r.SourceDeptId} + dept := new(model.Group) + err := isql.Group.Find(filter, dept) + flag := errors.Is(err, gorm.ErrRecordNotFound) + fmt.Println("部门是否存在:", filter, flag) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errors.New(fmt.Sprintf("AddDept添加部门失败:%s", err.Error())) + } + //分组不存在,直接创建(此处通过部门名称和第三方id来共同判定唯一,理论上不会出现重复) + if errors.Is(err, gorm.ErrRecordNotFound) { + group := model.Group{ + GroupType: r.GroupType, + ParentId: r.ParentId, + GroupName: r.GroupName, + Remark: r.Remark, + Creator: "system", + Source: r.Source, + SourceDeptParentId: r.SourceDeptParentId, + SourceDeptId: r.SourceDeptId, + SourceUserNum: r.SourceUserNum, + } + pdn := "" + if group.ParentId > 0 { + pdn, err = isql.Group.GetGroupDn(r.ParentId, "") + if err != nil { + return nil, errors.New(fmt.Sprintf("AddDept获取父级部门dn失败:%s", err.Error())) + } + } + err = ildap.Group.Add(&group, pdn) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("AddDept向LDAP创建分组失败" + err.Error())) + } + // 创建 + err = isql.Group.Add(&group) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("AddDept向MySQL创建分组失败:" + err.Error())) + } + // 默认创建分组之后,需要将admin添加到分组中 + adminInfo := new(model.User) + err = isql.User.Find(tools.H{"id": 1}, adminInfo) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddDept获取admin用户失败:%s", tools.NewMySqlError(err).Error())) + } + + err = isql.Group.AddUserToGroup(&group, []model.User{*adminInfo}) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("AddDept添加用户到分组失败: %s", err.Error())) + } + return &group, nil + } else { //分组存在 + //判断是否名字/备注/钉钉部门ID有修改 + if r.GroupName != dept.GroupName || r.Remark != dept.Remark || r.SourceDeptParentId != dept.SourceDeptParentId || r.SourceUserNum != dept.SourceUserNum { + err = d.UpdateDept(r) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddDept更新部门失败:%s", err.Error())) + } + } + //处理父级部门变化 + if r.SourceDeptParentId != dept.SourceDeptParentId { + // TODO 待处理父级部门变化情况 + } + return dept, nil + } +} + +// UpdateDept 更新部门数据 +func (d DingTalkLogic) UpdateDept(r *request.DingGroupAddReq) error { + oldData := new(model.Group) + filter := tools.H{"source_dept_id": r.SourceDeptId} + err := isql.Group.Find(filter, oldData) + if err != nil { + return errors.New(fmt.Sprintf("UpdateDept获取旧的部门信息失败:%s", tools.NewMySqlError(err).Error())) + } + dept := model.Group{ + Model: oldData.Model, + GroupName: r.GroupName, + Remark: r.Remark, + Creator: "system", + GroupType: oldData.GroupType, + SourceDeptId: r.SourceDeptId, + SourceDeptParentId: r.SourceDeptParentId, + SourceUserNum: r.SourceUserNum, + } + + oldGroupName := oldData.GroupName + oldRemark := oldData.Remark + dn, err := isql.Group.GetGroupDn(oldData.ID, "") + if err != nil { + return errors.New(fmt.Sprintf("UpdateDept不去部门dn失败:%s", tools.NewMySqlError(err).Error())) + } + err = ildap.Group.Update(&dept, dn, oldGroupName, oldRemark) + if err != nil { + return tools.NewLdapError(fmt.Errorf("UpdateDept向LDAP更新分组失败:" + err.Error())) + } + //若配置了不允许修改分组名称,则不更新分组名称 + if !config.Conf.Ldap.LdapGroupNameModify { + dept.GroupName = oldGroupName + } + err = isql.Group.Update(&dept) + if err != nil { + return tools.NewLdapError(fmt.Errorf("UpdateDept向MySQL更新分组失败:" + err.Error())) + } + return nil +} + +// AddUser 添加用户数据 +func (d DingTalkLogic) AddUser(r *request.DingUserAddReq) (data *model.User, rspError error) { + isExist := false + oldData := new(model.User) + if isql.User.Exist(tools.H{"source_user_id": r.SourceUserId}) { + err := isql.User.Find(tools.H{"source_user_id": r.SourceUserId}, oldData) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户id获取用户失败:%s", err.Error())) + } + isExist = true + } + if !isExist { + if isql.User.Exist(tools.H{"source_union_id": r.SourceUnionId}) { + err := isql.User.Find(tools.H{"source_union_id": r.SourceUnionId}, oldData) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户unionid获取用户失败:%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) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户job_number获取用户失败:%s", err.Error())) + } + 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) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser根据钉钉用户mobile获取用户失败:%s", err.Error())) + } + isExist = true + } + } + if isExist { + user, err := d.UpdateUser(r, oldData) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser用户已存在,更新用户失败:%s", err.Error())) + } + return user, nil + } + // 根据角色id获取角色 + r.RoleIds = []uint{2} // 默认添加为普通用户角色 + roles, err := isql.Role.GetRolesByIds(r.RoleIds) + if err != nil { + return nil, tools.NewValidatorError(fmt.Errorf("AddUser根据角色ID获取角色信息失败:%s", err.Error())) + } + + var reqRoleSorts []int + for _, role := range roles { + reqRoleSorts = append(reqRoleSorts, int(role.Sort)) + } + deptIds := tools.SliceToString(r.DepartmentId, ",") + user := model.User{ + Username: r.Username, + Password: r.Password, + Nickname: r.Nickname, + GivenName: r.GivenName, + Mail: r.Mail, + JobNumber: r.JobNumber, + Mobile: r.Mobile, + Avatar: r.Avatar, + PostalAddress: r.PostalAddress, + Departments: r.Departments, + Position: r.Position, + Introduction: r.Introduction, + Status: r.Status, + Creator: "system", + DepartmentId: deptIds, + Roles: roles, + Source: r.Source, + SourceUserId: r.SourceUserId, + SourceUnionId: r.SourceUnionId, + } + if user.Introduction == "" { + user.Introduction = r.Nickname + } + if user.JobNumber == "" { + user.JobNumber = r.Mobile + } + //先识别用户选择的部门是否是OU开头 + gdns := make(map[uint]string) + for _, deptId := range r.DepartmentId { + dn, err := isql.Group.GetGroupDn(deptId, "") + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser根据用户dn信息失败:%s", err.Error())) + } + gdn := fmt.Sprintf("%s,%s", dn, config.Conf.Ldap.LdapBaseDN) + if gdn[:3] == "ou=" { + return nil, errors.New(fmt.Sprintf("AddUser不能添加用户到OU组织单元:%s", gdn)) + } + gdns[deptId] = gdn + } + //先创建用户到默认分组 + err = ildap.User.Add(&user) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("AddUser向LDAP创建用户失败:" + err.Error())) + } + isExistUser := false + for deptId, gdn := range gdns { + //根据选择的部门,添加到部门内 + err = ildap.Group.AddUserToGroup(gdn, fmt.Sprintf("uid=%s,%s", user.Username, config.Conf.Ldap.LdapUserDN)) + if err != nil { + return nil, errors.New(fmt.Sprintf("AddUser向部门添加用户失败:%s", err.Error())) + } + if !isExistUser { + err = isql.User.Add(&user) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("向MySQL创建用户失败:" + err.Error())) + } + isExistUser = true + } + //根据部门分配,将用户和部门信息维护到部门关系表里面 + users := []model.User{} + users = append(users, user) + depart := new(model.Group) + filter := tools.H{"id": deptId} + err = isql.Group.Find(filter, depart) + if err != nil { + return nil, tools.NewMySqlError(err) + } + err = isql.Group.AddUserToGroup(depart, users) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("AddUser向MySQL添加用户到分组关系失败:" + err.Error())) + } + } + return &user, nil +} + +// Update 更新数据 +func (d DingTalkLogic) UpdateUser(r *request.DingUserAddReq, oldData *model.User) (data *model.User, rspError error) { + deptIds := tools.SliceToString(r.DepartmentId, ",") + user := model.User{ + Model: oldData.Model, + Username: r.Username, + Nickname: r.Nickname, + GivenName: r.GivenName, + Mail: r.Mail, + JobNumber: r.JobNumber, + Mobile: r.Mobile, + Avatar: r.Avatar, + PostalAddress: r.PostalAddress, + Departments: r.Departments, + Position: r.Position, + Introduction: r.Introduction, + Creator: "system", + DepartmentId: deptIds, + Source: oldData.Source, + Roles: oldData.Roles, + SourceUserId: r.SourceUserId, + SourceUnionId: r.SourceUnionId, + } + if user.Introduction == "" { + user.Introduction = r.Nickname + } + if user.PostalAddress == "" { + user.PostalAddress = "没有填写地址" + } + if user.Position == "" { + user.Position = "技术" + } + if user.JobNumber == "" { + user.JobNumber = r.Mobile + } + err := ildap.User.Update(oldData.Username, &user) + if err != nil { + return nil, tools.NewLdapError(fmt.Errorf("UpdateUser在LDAP更新用户失败:" + err.Error())) + } + + // 更新用户 + if !config.Conf.Ldap.LdapUserNameModify { + user.Username = oldData.Username + } + err = isql.User.Update(&user) + if err != nil { + return nil, tools.NewMySqlError(fmt.Errorf("UpdateUser在MySQL更新用户失败:" + err.Error())) + } + //判断部门信息是否有变化有变化则更新相应的数据库 + oldDeptIds := tools.StringToSlice(oldData.DepartmentId, ",") + addDeptIds, removeDeptIds := tools.ArrUintCmp(oldDeptIds, r.DepartmentId) + for _, deptId := range removeDeptIds { + //从旧组中删除 + err = User.RemoveUserToGroup(deptId, []uint{oldData.ID}) + if err != nil { + return nil, errors.New(fmt.Sprintf("UpdateUser将用户从分组移除失败:%s", err.Error())) + } + } + for _, deptId := range addDeptIds { + //添加到新分组中 + err = User.AddUserToGroup(deptId, []uint{oldData.ID}) + if err != nil { + return nil, errors.New(fmt.Sprintf("UpdateUser将用户添加至分组失败:%s", err.Error())) + } + } + return &user, nil +} diff --git a/logic/group_logic.go b/logic/group_logic.go index b72ede4..bd163e3 100644 --- a/logic/group_logic.go +++ b/logic/group_logic.go @@ -37,11 +37,14 @@ func (l GroupLogic) Add(c *gin.Context, req interface{}) (data interface{}, rspE } group := model.Group{ - GroupType: r.GroupType, - ParentId: r.ParentId, - GroupName: r.GroupName, - Remark: r.Remark, - Creator: ctxUser.Username, + GroupType: r.GroupType, + ParentId: r.ParentId, + GroupName: r.GroupName, + Remark: r.Remark, + Creator: ctxUser.Username, + Source: "platform", //默认是平台添加 + SourceDeptId: "platform_0", + SourceDeptParentId: "platform_0", } pdn := "" if group.ParentId > 0 { @@ -114,7 +117,7 @@ func (l GroupLogic) GetTree(c *gin.Context, req interface{}) (data interface{}, _ = c var groups []*model.Group - groups, err := isql.Group.List(r) + groups, err := isql.Group.ListTree(r) if err != nil { return nil, tools.NewMySqlError(fmt.Errorf("获取资源列表失败: " + err.Error())) } @@ -219,6 +222,7 @@ func (l GroupLogic) Delete(c *gin.Context, req interface{}) (data interface{}, r return nil, tools.NewMySqlError(fmt.Errorf("删除接口失败: %s", err.Error())) } // TODO: 删除用户组关系 + return nil, nil } diff --git a/main.go b/main.go index 8e2e9c7..5b62071 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,8 @@ package main import ( "context" "fmt" + "github.com/eryajf/go-ldap-admin/logic" + "github.com/robfig/cron/v3" "net/http" "os" "os/signal" @@ -63,6 +65,19 @@ 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) + }) + + c.Start() common.Log.Info(fmt.Sprintf("Server is running at %s:%d/%s", host, port, config.Conf.System.UrlPathPrefix)) diff --git a/model/group.go b/model/group.go index 473e5e6..e7f9c49 100644 --- a/model/group.go +++ b/model/group.go @@ -4,11 +4,15 @@ import "gorm.io/gorm" type Group struct { gorm.Model - GroupName string `gorm:"type:varchar(20);comment:'分组名称'" json:"groupName"` - Remark string `gorm:"type:varchar(100);comment:'分组中文说明'" json:"remark"` - Creator string `gorm:"type:varchar(20);comment:'创建人'" json:"creator"` - GroupType string `gorm:"type:varchar(20);comment:'分组类型:cn、ou'" json:"groupType"` - Users []*User `gorm:"many2many:group_users" json:"users"` - ParentId uint `gorm:"default:0;comment:'父组编号(编号为0时表示根组)'" json:"parentId"` - Children []*Group `gorm:"-" json:"children"` + GroupName string `gorm:"type:varchar(20);comment:'分组名称'" json:"groupName"` + Remark string `gorm:"type:varchar(100);comment:'分组中文说明'" json:"remark"` + Creator string `gorm:"type:varchar(20);comment:'创建人'" json:"creator"` + GroupType string `gorm:"type:varchar(20);comment:'分组类型:cn、ou'" json:"groupType"` + Users []*User `gorm:"many2many:group_users" json:"users"` + ParentId uint `gorm:"default:0;comment:'父组编号(编号为0时表示根组)'" json:"parentId"` + SourceDeptId string `gorm:"type:varchar(100);comment:'部门编号'" json:"sourceDeptId"` + Source string `gorm:"type:varchar(20);comment:'来源:dingTalk、weCom、ldap、platform'" json:"source"` + SourceDeptParentId string `gorm:"type:varchar(100);comment:'父部门编号'" json:"sourceDeptParentId"` + SourceUserNum int `gorm:"default:0;comment:'部门下的用户数量,从第三方获取的数据'" json:"source_user_num"` + Children []*Group `gorm:"-" json:"children"` } diff --git a/model/user.go b/model/user.go index d9b2263..f2f10ae 100644 --- a/model/user.go +++ b/model/user.go @@ -21,4 +21,6 @@ type User struct { Source string `gorm:"type:varchar(50);comment:'用户来源:dingTalk、weCom、ldap、platform'" json:"source"` // 来源 DepartmentId string `gorm:"type:varchar(100);not null;comment:'部门id'" json:"departmentId"` // 部门id Roles []*Role `gorm:"many2many:user_roles" json:"roles"` // 角色 + 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 } diff --git a/public/common/init_mysql_data.go b/public/common/init_mysql_data.go index 1cd6468..0c8634e 100644 --- a/public/common/init_mysql_data.go +++ b/public/common/init_mysql_data.go @@ -324,6 +324,13 @@ func InitData() { Remark: "更改用户在职状态", Creator: "系统", }, + { + Method: "POST", + Path: "/user/syncDingTalkUsers", + Category: "user", + Remark: "从钉钉拉取用户信息", + Creator: "系统", + }, { Method: "GET", Path: "/group/list", @@ -387,6 +394,13 @@ func InitData() { Remark: "获取不在分组内的用户列表", Creator: "系统", }, + { + Method: "POST", + Path: "/group/syncDingTalkDepts", + Category: "group", + Remark: "从钉钉拉取部门信息", + Creator: "系统", + }, { Method: "GET", Path: "/role/list", @@ -555,10 +569,12 @@ func InitData() { "/user/info", "/user/list", "/user/changePwd", + "/user/syncDingTalkUsers", "/group/list", "/group/tree", "/group/useringroup", "/group/usernoingroup", + "/group/syncDingTalkDepts", "/role/list", "/role/getmenulist", "/role/getapilist", diff --git a/routes/group_routes.go b/routes/group_routes.go index fa8613c..93bba90 100644 --- a/routes/group_routes.go +++ b/routes/group_routes.go @@ -25,6 +25,7 @@ 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) } return r diff --git a/routes/user_routes.go b/routes/user_routes.go index 18ead95..bd0905f 100644 --- a/routes/user_routes.go +++ b/routes/user_routes.go @@ -16,13 +16,14 @@ func InitUserRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) gi // 开启casbin鉴权中间件 user.Use(middleware.CasbinMiddleware()) { - user.GET("/info", controller.User.GetUserInfo) // 暂时未完成 - user.GET("/list", controller.User.List) // 用户列表 - user.POST("/add", controller.User.Add) // 添加用户 - user.POST("/update", controller.User.Update) // 更新用户 - user.POST("/delete", controller.User.Delete) // 删除用户 - user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码 - user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态 + user.GET("/info", controller.User.GetUserInfo) // 暂时未完成 + user.GET("/list", controller.User.List) // 用户列表 + user.POST("/add", controller.User.Add) // 添加用户 + user.POST("/update", controller.User.Update) // 更新用户 + user.POST("/delete", controller.User.Delete) // 删除用户 + user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码 + user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态 + user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 同步用户 } return r } diff --git a/service/ildap/user_ildap.go b/service/ildap/user_ildap.go index 9fcf742..33c4ccd 100644 --- a/service/ildap/user_ildap.go +++ b/service/ildap/user_ildap.go @@ -31,8 +31,8 @@ func (x UserService) Add(user *model.User) error { } add := ldap.NewAddRequest(fmt.Sprintf("uid=%s,%s", user.Username, config.Conf.Ldap.LdapUserDN), nil) add.Attribute("objectClass", []string{"inetOrgPerson"}) - add.Attribute("cn", []string{user.Nickname}) - add.Attribute("sn", []string{user.Username}) + add.Attribute("cn", []string{user.Username}) + add.Attribute("sn", []string{user.Nickname}) add.Attribute("businessCategory", []string{user.Departments}) add.Attribute("departmentNumber", []string{user.Position}) add.Attribute("description", []string{user.Introduction}) diff --git a/service/isql/group_isql.go b/service/isql/group_isql.go index d9d1223..d1bdda7 100644 --- a/service/isql/group_isql.go +++ b/service/isql/group_isql.go @@ -34,6 +34,59 @@ func (s GroupService) List(req *request.GroupListReq) ([]*model.Group, error) { return list, err } +// List 获取数据列表 +func (s GroupService) ListTree(req *request.GroupListReq) ([]*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)) + } + + pageReq := tools.NewPageOption(req.PageNum, req.PageSize) + err := db.Offset(pageReq.PageNum).Limit(pageReq.PageSize).Find(&list).Error + return list, err +} + +// 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) + } + + err := db.Find(&list).Error + return list, err +} + // 拼装dn信息 func (s GroupService) GetGroupDn(groupId uint, oldDn string) (dn string, e error) { depart := new(model.Group) @@ -121,3 +174,17 @@ func (s GroupService) AddUserToGroup(group *model.Group, users []model.User) err func (s GroupService) RemoveUserFromGroup(group *model.Group, users []model.User) error { return common.DB.Model(&group).Association("Users").Delete(users) } + +// DingTalkDeptIdsToGroupIds 将钉钉部门id转换为分组id +func (s GroupService) DingTalkDeptIdsToGroupIds(dingTalkIds []string) (groupIds []uint, err error) { + tempGroups := []model.Group{} + err = common.DB.Model(&model.Group{}).Where("source_dept_id IN (?)", dingTalkIds).Find(&tempGroups).Error + if err != nil { + return nil, err + } + tempGroupIds := []uint{} + for _, g := range tempGroups { + tempGroupIds = append(tempGroupIds, g.ID) + } + return tempGroupIds, nil +} diff --git a/service/isql/user_isql.go b/service/isql/user_isql.go index 79b992d..d45e127 100644 --- a/service/isql/user_isql.go +++ b/service/isql/user_isql.go @@ -232,7 +232,7 @@ func (s UserService) ChangePwd(username string, hashNewPasswd string) error { } else { // 没有缓存就获取用户信息缓存 var user model.User - common.DB.Where("username = ?", username).First(&user) + common.DB.Where("username = ?", username).Preload("Roles").First(&user) userInfoCache.Set(username, user, cache.DefaultExpiration) } } diff --git a/svc/request/group_req.go b/svc/request/group_req.go index 7a3c519..31154f0 100644 --- a/svc/request/group_req.go +++ b/svc/request/group_req.go @@ -8,6 +8,16 @@ type GroupListReq struct { PageSize int `json:"pageSize" form:"pageSize"` } +// GroupListAllReq 获取资源列表结构体,不分页 +type GroupListAllReq struct { + GroupName string `json:"groupName" form:"groupName"` + GroupType string `json:"groupType" form:"groupType"` + Remark string `json:"remark" form:"remark"` + Source string `json:"source" form:"source"` + SourceDeptId string `json:"sourceDeptId"` + SourceDeptParentId string `json:"SourceDeptParentId"` +} + // GroupAddReq 添加资源结构体 type GroupAddReq struct { GroupType string `json:"groupType" validate:"required,min=1,max=20"` @@ -17,6 +27,19 @@ type GroupAddReq struct { Remark string `json:"remark" validate:"min=0,max=100"` // 分组的中文描述 } +// DingTalkGroupAddReq 添加钉钉资源结构体 +type DingGroupAddReq struct { + GroupType string `json:"groupType" validate:"required,min=1,max=20"` + GroupName string `json:"groupName" validate:"required,min=1,max=20"` + //父级Id 大于等于0 必填 + ParentId uint `json:"parentId" validate:"omitempty,min=0"` + Remark string `json:"remark" validate:"min=0,max=100"` // 分组的中文描述 + SourceDeptId string `json:"sourceDeptId"` + Source string `json:"source"` + SourceDeptParentId string `json:"SourceDeptParentId"` + SourceUserNum int `json:"sourceUserNum"` +} + // GroupUpdateReq 更新资源结构体 type GroupUpdateReq struct { ID uint `json:"id" form:"id" validate:"required"` @@ -58,3 +81,7 @@ type UserNoInGroupReq struct { GroupID uint `json:"groupId" form:"groupId" validate:"required"` Nickname string `json:"nickname" form:"nickname"` } + +// SyncDingTalkDeptsReq 同步钉钉部门信息 +type SyncDingTalkDeptsReq struct { +} diff --git a/svc/request/user_req.go b/svc/request/user_req.go index 8fe65d9..5f94603 100644 --- a/svc/request/user_req.go +++ b/svc/request/user_req.go @@ -6,7 +6,7 @@ type UserAddReq struct { Password string `json:"password"` Nickname string `json:"nickname" validate:"required,min=0,max=20"` GivenName string `json:"givenName" validate:"min=0,max=20"` - Mail string `json:"mail" validate:"required,min=0,max=20"` + Mail string `json:"mail" validate:"required,min=0,max=100"` JobNumber string `json:"jobNumber" validate:"required,min=0,max=20"` PostalAddress string `json:"postalAddress" validate:"min=0,max=255"` Departments string `json:"departments" validate:"min=0,max=255"` @@ -20,13 +20,35 @@ type UserAddReq struct { RoleIds []uint `json:"roleIds" validate:"required"` } +// DingUserAddReq 钉钉用户创建资源结构体 +type DingUserAddReq struct { + Username string `json:"username" validate:"required,min=2,max=20"` + Password string `json:"password"` + Nickname string `json:"nickname" validate:"required,min=0,max=20"` + GivenName string `json:"givenName" validate:"min=0,max=20"` + Mail string `json:"mail" validate:"required,min=0,max=100"` + JobNumber string `json:"jobNumber" validate:"required,min=0,max=20"` + PostalAddress string `json:"postalAddress" validate:"min=0,max=255"` + Departments string `json:"departments" validate:"min=0,max=255"` + Position string `json:"position" validate:"min=0,max=255"` + Mobile string `json:"mobile" validate:"required,checkMobile"` + Avatar string `json:"avatar"` + Introduction string `json:"introduction" validate:"min=0,max=255"` + Status uint `json:"status" validate:"oneof=1 2"` + DepartmentId []uint `json:"departmentId" validate:"required"` + Source string `json:"source" validate:"min=0,max=20"` + RoleIds []uint `json:"roleIds" validate:"required"` + SourceUserId string `json:"sourceUserId"` // 第三方用户id + SourceUnionId string `json:"sourceUnionId"` // 第三方唯一unionId +} + // UserUpdateReq 更新资源结构体 type UserUpdateReq struct { ID uint `json:"id" validate:"required"` Username string `json:"username" validate:"required,min=2,max=20"` Nickname string `json:"nickname" validate:"min=0,max=20"` GivenName string `json:"givenName" validate:"min=0,max=20"` - Mail string `json:"mail" validate:"min=0,max=20"` + Mail string `json:"mail" validate:"min=0,max=100"` JobNumber string `json:"jobNumber" validate:"min=0,max=20"` PostalAddress string `json:"postalAddress" validate:"min=0,max=255"` Departments string `json:"departments" validate:"min=0,max=255"` @@ -60,6 +82,10 @@ type UserChangeUserStatusReq struct { type UserGetUserInfoReq struct { } +// 同步钉钉用户信息 +type SyncDingUserReq struct { +} + // UserListReq 获取用户列表结构体 type UserListReq struct { Username string `json:"username" form:"username"`