Skip to content

Commit

Permalink
添加飞书的同步能力 (opsre#34)
Browse files Browse the repository at this point in the history
  • Loading branch information
eryajf authored Jun 21, 2022
1 parent 249e5ee commit 4c65753
Show file tree
Hide file tree
Showing 16 changed files with 415 additions and 10 deletions.
4 changes: 3 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,5 +168,7 @@ type WeComConfig struct {
}

type FeiShuConfig struct {
Flag string `mapstructure:"flag" json:"flag"`
Flag string `mapstructure:"flag" json:"flag"`
AppID string `mapstructure:"app-id" json:"appId"`
AppSecret string `mapstructure:"app-secret" json:"appSecret"`
}
8 changes: 8 additions & 0 deletions controller/group_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,11 @@ func (m *GroupController) SyncWeComDepts(c *gin.Context) {
return logic.WeCom.SyncWeComDepts(c, req)
})
}

//同步飞书部门信息
func (m *GroupController) SyncFeiShuDepts(c *gin.Context) {
req := new(request.SyncFeiShuDeptsReq)
Run(c, req, func() (interface{}, interface{}) {
return logic.FeiShu.SyncFeiShuDepts(c, req)
})
}
8 changes: 8 additions & 0 deletions controller/user_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,11 @@ func (uc UserController) SyncWeComUsers(c *gin.Context) {
return logic.WeCom.SyncWeComUsers(c, req)
})
}

// 同步飞书用户信息
func (uc UserController) SyncFeiShuUsers(c *gin.Context) {
req := new(request.SyncFeiShuUserReq)
Run(c, req, func() (interface{}, interface{}) {
return logic.FeiShu.SyncFeiShuUsers(c, req)
})
}
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ require (
gorm.io/gorm v1.20.12
)

require github.com/wenerme/go-wecom v0.0.0-20220617125121-2ee950da3e63
require (
github.com/chyroc/lark v0.0.96
github.com/wenerme/go-wecom v0.0.0-20220617125121-2ee950da3e63
)

require (
github.com/BurntSushi/toml v1.1.0 // indirect
Expand All @@ -36,7 +39,7 @@ require (
github.com/pelletier/go-toml/v2 v2.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/wenerme/go-req v0.0.0-20210907160348-d822e81276bb // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

require (
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ github.com/casbin/gorm-adapter/v3 v3.1.0/go.mod h1:kaMBsBHluoYwudSbVnism8LhJeVyu
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chyroc/go-ptr v1.6.0 h1:4GwCNrNfk4806eQKbHO2A/N/YOLW6jHIrBPWKfMe6F0=
github.com/chyroc/go-ptr v1.6.0/go.mod h1:FKNjNg3sCLx7VhQGwuml6sITX1mvhKS0Je9uN9tt65Q=
github.com/chyroc/lark v0.0.96 h1:3G977xmpktiIoposLjAEO8VOfrIfqWtzBhX9/Z/JSHc=
github.com/chyroc/lark v0.0.96/go.mod h1:ihmf5fJFY8yneFOORvtJhdYWoNqp1OI3DSrCOlvK9Lw=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
Expand Down Expand Up @@ -493,14 +497,16 @@ github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUs
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=
Expand Down Expand Up @@ -1032,8 +1038,9 @@ 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=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.0.1/go.mod h1:KtqSthtg55lFp3S5kUXqlGaelnWpKitn4k1xZTnoiPw=
gorm.io/driver/mysql v1.0.4 h1:TATTzt+kR+IV0+h3iUB3dHUe8omCvQ0rOkmfCsUBohk=
gorm.io/driver/mysql v1.0.4/go.mod h1:MEgp8tk2n60cSBCq5iTcPDw3ns8Gs+zOva9EUhkknTs=
Expand Down
1 change: 1 addition & 0 deletions logic/a_logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ var (
OperationLog = &OperationLogLogic{}
DingTalk = &DingTalkLogic{}
WeCom = &WeComLogic{}
FeiShu = &FeiShuLogic{}
Base = &BaseLogic{}
)
263 changes: 263 additions & 0 deletions logic/feishu_logic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package logic

import (
"fmt"
"strings"

"github.com/chyroc/lark"
"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/client/feishu"
"github.com/mozillazg/go-pinyin"

"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 FeiShuLogic struct {
}

//通过飞书获取部门信息
func (d *FeiShuLogic) SyncFeiShuDepts(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取所有部门
depts, err := feishu.GetAllDepts()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("获取飞书部门列表失败:%s", err.Error()))
}
// 2.将部门这个数组进行拆分,一组是父ID为根的,一组是父ID不为根的
var firstDepts []*lark.GetDepartmentListRespItem // 父ID为根的部门
var otherDepts []*lark.GetDepartmentListRespItem // 父ID不为根的部门
for _, dept := range depts {
if dept.ParentDepartmentID == "0" {
firstDepts = append(firstDepts, dept)
} else {
otherDepts = append(otherDepts, dept)
}
}
// 3.先写父ID为根的,再写父ID不为根的
for _, dept := range firstDepts {
err := d.AddDepts(&request.WeComGroupAddReq{
GroupType: "cn",
GroupName: strings.Join(pinyin.LazyConvert(dept.Name, nil), ""),
Remark: dept.Name,
SourceDeptId: fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, dept.OpenDepartmentID),
Source: config.Conf.FeiShu.Flag,
SourceDeptParentId: fmt.Sprintf("%s_%d", config.Conf.FeiShu.Flag, 1),
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncFeiShuDepts添加根部门失败:%s", err.Error()))
}
}

for _, dept := range otherDepts {
err := d.AddDepts(&request.WeComGroupAddReq{
GroupType: "cn",
GroupName: strings.Join(pinyin.LazyConvert(dept.Name, nil), ""),
Remark: dept.Name,
SourceDeptId: fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, dept.OpenDepartmentID),
Source: config.Conf.FeiShu.Flag,
SourceDeptParentId: fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, dept.ParentDepartmentID),
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncFeiShuDepts添加根部门失败:%s", err.Error()))
}
}
return nil, nil
}

// AddGroup 添加部门数据
func (d FeiShuLogic) AddDepts(r *request.WeComGroupAddReq) error {
// 判断部门名称是否存在
parentGroup := new(model.Group)
err := isql.Group.Find(tools.H{"source_dept_id": r.SourceDeptParentId}, parentGroup)
if err != nil {
return tools.NewMySqlError(fmt.Errorf("查询父级部门失败:%s", err.Error()))
}
if !isql.Group.Exist(tools.H{"source_dept_id": r.SourceDeptId}) {
groupTmp := model.Group{
GroupName: r.GroupName,
Remark: r.Remark,
Creator: "system",
GroupType: "cn",
ParentId: parentGroup.ID,
SourceDeptId: r.SourceDeptId,
Source: r.Source,
SourceDeptParentId: r.SourceDeptParentId,
GroupDN: fmt.Sprintf("cn=%s,%s", r.GroupName, parentGroup.GroupDN),
}
err = CommonAddGroup(&groupTmp)
if err != nil {
return tools.NewOperationError(fmt.Errorf("添加部门失败:%s", err.Error()))
}
}
// todo: 分组存在,但是信息有变更的情况,需要考量,但是这种组织架构的调整,通常是比较复杂的情况,这里并不好与之一一对应同步,暂时不做支持
return nil
}

//根据现有数据库同步到的部门信息,开启用户同步
func (d FeiShuLogic) SyncFeiShuUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取飞书用户列表
users, err := feishu.GetAllUsers()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncFeiShuUsers获取飞书用户列表失败:%s", err.Error()))
}
// 2.遍历用户,开始写入
for _, detail := range users {
// 用户名的几种情况
var userName string
if detail.Email != "" {
userName = strings.Split(detail.Email, "@")[0]
}
if userName == "" && detail.Name != "" {
userName = strings.Join(pinyin.LazyConvert(detail.Name, nil), "")
}
if userName == "" && detail.Mobile != "" {
userName = detail.Mobile
}
if userName == "" && detail.Email != "" {
userName = strings.Split(detail.Email, "@")[0]
}

// 如果企业内没有工号,则工号用名字占位
if detail.EmployeeNo == "" {
detail.EmployeeNo = detail.Mobile
}

//飞书部门ids,转换为内部部门id
var sourceDeptIds []string
for _, deptId := range detail.DepartmentIDs {
sourceDeptIds = append(sourceDeptIds, fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, deptId))
}
groupIds, err := isql.Group.DeptIdsToGroupIds(sourceDeptIds)
if err != nil {
return nil, tools.NewMySqlError(fmt.Errorf("SyncFeiShuUsers获取飞书部门ids转换为内部部门id失败:%s", err.Error()))
}

// 写入用户
user := request.WeComUserAddReq{
Username: userName,
Password: config.Conf.Ldap.UserInitPassword,
Nickname: detail.Name,
GivenName: detail.Name,
Mail: detail.Email,
JobNumber: detail.Name, // 工号暂用名字替代
Mobile: detail.Mobile[3:],
Avatar: detail.Avatar.AvatarOrigin,
PostalAddress: detail.City,
Position: detail.JobTitle,
Introduction: detail.Name,
Status: 1,
DepartmentId: groupIds,
Source: config.Conf.FeiShu.Flag,
SourceUserId: fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, detail.UserID),
SourceUnionId: fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, detail.UnionID),
}
// 入库
err = d.AddUsers(&user)
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncFeiShuUsers写入用户失败:%s", err.Error()))
}
}

// 3.获取飞书已离职用户id列表
userIds, err := feishu.GetLeaveUserIds()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncFeiShuUsers获取飞书离职用户列表失败:%s", err.Error()))
}
// 4.遍历id,开始处理
for _, uid := range userIds {
user := new(model.User)
err = isql.User.Find(tools.H{"source_union_id": fmt.Sprintf("%s_%s", config.Conf.FeiShu.Flag, uid)}, user)
if err != nil {
return nil, tools.NewMySqlError(fmt.Errorf("在MySQL查询用户失败: " + err.Error()))
}
// 先从ldap删除用户
err = ildap.User.Delete(user.UserDN)
if err != nil {
return nil, tools.NewLdapError(fmt.Errorf("在LDAP删除用户失败" + err.Error()))
}
// 然后更新MySQL中用户状态
err = isql.User.ChangeStatus(int(user.ID), 2)
if err != nil {
return nil, tools.NewMySqlError(fmt.Errorf("在MySQL更新用户状态失败: " + err.Error()))
}
}

return nil, nil
}

// AddUser 添加用户数据
func (d FeiShuLogic) AddUsers(r *request.WeComUserAddReq) error {
// 根据 unionid 查询用户,不存在则创建
if !isql.User.Exist(tools.H{"source_union_id": r.SourceUnionId}) {
// 根据角色id获取角色
r.RoleIds = []uint{2} // 默认添加为普通用户角色
roles, err := isql.Role.GetRolesByIds(r.RoleIds)
if err != nil {
return tools.NewValidatorError(fmt.Errorf("根据角色ID获取角色信息失败:%s", err.Error()))
}

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,
UserDN: fmt.Sprintf("uid=%s,%s", r.Username, config.Conf.Ldap.UserDN),
}
err = CommonAddUser(&user, r.DepartmentId)
if err != nil {
return err
}
}
// todo: 用户如果存在,则暂时跳过,目前用户名取自邮箱等内容,因为这个不确定性,可能会造成一些逻辑上的问题,因为默认情况下,用户名是无法在ldap中更改的,所以暂时跳过,如果用户有这里的需求,可以根据自己的情况固定用户名的字段,也就可以打开如下的注释了
// else {
// oldData := new(model.User)
// if err := isql.User.Find(tools.H{"source_union_id": r.SourceUnionId}, oldData); err != nil {
// return err
// }
// if r.Username != oldData.Username || r.Mail != oldData.Mail || r.Mobile != oldData.Mobile {
// 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: oldData.Creator,
// DepartmentId: tools.SliceToString(r.DepartmentId, ","),
// Source: oldData.Source,
// Roles: oldData.Roles,
// UserDN: oldData.UserDN,
// }
// if err := CommonUpdateUser(oldData, &user, r.DepartmentId); err != nil {
// return err
// }
// }
// }
return nil
}
4 changes: 4 additions & 0 deletions model/request/group_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,7 @@ type SyncDingTalkDeptsReq struct {
// SyncWeComDeptsReq 同步企业微信部门信息
type SyncWeComDeptsReq struct {
}

// SyncFeiShuDeptsReq 同步飞书部门信息
type SyncFeiShuDeptsReq struct {
}
4 changes: 4 additions & 0 deletions model/request/user_req.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ type SyncDingUserReq struct {
type SyncWeComUserReq struct {
}

// SyncFeiShuUserReq 同步飞书用户信息
type SyncFeiShuUserReq struct {
}

// UserListReq 获取用户列表结构体
type UserListReq struct {
Username string `json:"username" form:"username"`
Expand Down
1 change: 1 addition & 0 deletions public/client/dingtalk/dingtalk.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func GetAllUsers() (result []*DingTalkUser, err error) {
return
}

// GetLeaveUserIds 获取离职人员ID列表
func GetLeaveUserIds() ([]string, error) {
var ids []string
ReqParm := struct {
Expand Down
Loading

0 comments on commit 4c65753

Please sign in to comment.