添加企业微信的同步能力 (#30)

This commit is contained in:
二丫讲梵 2022-06-18 17:09:14 +08:00 committed by GitHub
parent b29200aec9
commit 56f2cda6dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 463 additions and 24 deletions

View File

@ -107,6 +107,11 @@ dingtalk:
agent-id: "12121212" # 目前agent-id未使用到可忽略 agent-id: "12121212" # 目前agent-id未使用到可忽略
enable-sync: false # 是否开启定时同步钉钉的任务 enable-sync: false # 是否开启定时同步钉钉的任务
wecom: wecom:
flag: "wecom" # 配置获取详细文档参考http://ldapdoc.eryajf.net/pages/cf1698/
flag: "wecom" # 作为微信在平台的标识
corp-id: "xxxx" # 企业微信企业ID
agent-id: 1000003 # 企业微信中创建的应用ID
corp-secret: "xxxxx" # 企业微信中创建的应用secret
enable-sync: false # 是否开启定时同步企业微信的任务
feishu: feishu:
flag: "feishu" flag: "feishu"

View File

@ -162,6 +162,9 @@ type DingTalkConfig struct {
type WeComConfig struct { type WeComConfig struct {
Flag string `mapstructure:"flag" json:"flag"` Flag string `mapstructure:"flag" json:"flag"`
CorpID string `mapstructure:"corp-id" json:"corpId"`
AgentID int `mapstructure:"agent-id" json:"agentId"`
CorpSecret string `mapstructure:"corp-secret" json:"corpSecret"`
} }
type FeiShuConfig struct { type FeiShuConfig struct {

View File

@ -88,3 +88,11 @@ func (m *GroupController) SyncDingTalkDepts(c *gin.Context) {
return logic.DingTalk.SyncDingTalkDepts(c, req) return logic.DingTalk.SyncDingTalkDepts(c, req)
}) })
} }
//同步企业微信部门信息
func (m *GroupController) SyncWeComDepts(c *gin.Context) {
req := new(request.SyncWeComDeptsReq)
Run(c, req, func() (interface{}, interface{}) {
return logic.WeCom.SyncWeComDepts(c, req)
})
}

View File

@ -72,3 +72,11 @@ func (uc UserController) SyncDingTalkUsers(c *gin.Context) {
return logic.DingTalk.SyncDingTalkUsers(c, req) return logic.DingTalk.SyncDingTalkUsers(c, req)
}) })
} }
// 同步企业微信用户信息
func (uc UserController) SyncWeComUsers(c *gin.Context) {
req := new(request.SyncWeComUserReq)
Run(c, req, func() (interface{}, interface{}) {
return logic.WeCom.SyncWeComUsers(c, req)
})
}

5
go.mod
View File

@ -11,7 +11,7 @@ require (
github.com/go-ldap/ldap/v3 v3.4.2 github.com/go-ldap/ldap/v3 v3.4.2
github.com/go-playground/locales v0.14.0 github.com/go-playground/locales v0.14.0
github.com/go-playground/universal-translator v0.18.0 github.com/go-playground/universal-translator v0.18.0
github.com/go-playground/validator/v10 v10.9.0 github.com/go-playground/validator/v10 v10.10.0
github.com/juju/ratelimit v1.0.1 github.com/juju/ratelimit v1.0.1
github.com/patrickmn/go-cache v2.1.0+incompatible github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/robfig/cron/v3 v3.0.0 github.com/robfig/cron/v3 v3.0.0
@ -25,6 +25,8 @@ require (
gorm.io/gorm v1.20.12 gorm.io/gorm v1.20.12
) )
require github.com/wenerme/go-wecom v0.0.0-20220617125121-2ee950da3e63
require ( require (
github.com/BurntSushi/toml v1.1.0 // indirect github.com/BurntSushi/toml v1.1.0 // indirect
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
@ -33,6 +35,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.0 // indirect github.com/pelletier/go-toml/v2 v2.0.0 // indirect
github.com/rogpeppe/go-internal v1.8.1 // 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.0-20210107192922-496545a6307b // indirect
) )

14
go.sum
View File

@ -142,6 +142,10 @@ github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8= github.com/go-asn1-ber/asn1-ber v1.5.1 h1:pDbRAunXzIUXfx4CB2QJFv5IuPiuoW+sWvr/Us009o8=
github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-asn1-ber/asn1-ber v1.5.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8=
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -160,8 +164,9 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
@ -466,6 +471,7 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA= github.com/sagikazarmark/crypt v0.5.0/go.mod h1:l+nzl7KWh51rpzp2h7t4MZWyiEWdhNpOAnclKvg+mdA=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sbzhu/weworkapi_golang v0.0.0-20210525081115-1799804a7c8d/go.mod h1:gLXVYg36wlOl44Uh8Uw0aDiNMcZNnV+tzZq1FBj+f6A=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY=
@ -512,6 +518,10 @@ github.com/ugorji/go v1.2.3/go.mod h1:5l8GZ8hZvmL4uMdy+mhCO1LjswGRYco9Q3HfuisB21
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.3 h1:/mVYEV+Jo3IZKeA5gBngN0AvNnQltEDkR+eQikkWQu0= github.com/ugorji/go/codec v1.2.3 h1:/mVYEV+Jo3IZKeA5gBngN0AvNnQltEDkR+eQikkWQu0=
github.com/ugorji/go/codec v1.2.3/go.mod h1:5FxzDJIgeiWJZslYHPj+LS1dq1ZBQVelZFnjsFGI/Uc= github.com/ugorji/go/codec v1.2.3/go.mod h1:5FxzDJIgeiWJZslYHPj+LS1dq1ZBQVelZFnjsFGI/Uc=
github.com/wenerme/go-req v0.0.0-20210907160348-d822e81276bb h1:4/6Qqg+E8z98SCi21dFnhL6goSWOYMunJkMc+YanrEw=
github.com/wenerme/go-req v0.0.0-20210907160348-d822e81276bb/go.mod h1:aQUkMiMp1qZkuSsdu2Vy2ZQK33cPNVmyWFzXatfP+Y4=
github.com/wenerme/go-wecom v0.0.0-20220617125121-2ee950da3e63 h1:wRIOQxBR5XbUZVMKziAjCnlnDhdAjVjBmLsUSn/j/+M=
github.com/wenerme/go-wecom v0.0.0-20220617125121-2ee950da3e63/go.mod h1:Jz7prkOPdCbWbT5bOQOFkZUQp7pqEc8yiWaCgOVWPH0=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -564,6 +574,7 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -747,6 +758,7 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@ -16,5 +16,6 @@ var (
Menu = &MenuLogic{} Menu = &MenuLogic{}
OperationLog = &OperationLogLogic{} OperationLog = &OperationLogLogic{}
DingTalk = &DingTalkLogic{} DingTalk = &DingTalkLogic{}
WeCom = &WeComLogic{}
Base = &BaseLogic{} Base = &BaseLogic{}
) )

View File

@ -23,7 +23,7 @@ type DingTalkLogic struct {
//通过钉钉获取部门信息 //通过钉钉获取部门信息
func (d *DingTalkLogic) SyncDingTalkDepts(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { func (d *DingTalkLogic) SyncDingTalkDepts(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取所有部门 // 1.获取所有部门
depts, err := dingtalk.GetDingTalkAllDepts(1) depts, err := dingtalk.GetAllDepts(1)
if err != nil { if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("获取钉钉部门列表失败:%s", err.Error())) return nil, tools.NewOperationError(fmt.Errorf("获取钉钉部门列表失败:%s", err.Error()))
} }
@ -100,7 +100,7 @@ func (d DingTalkLogic) AddDepts(r *request.DingGroupAddReq) error {
//根据现有数据库同步到的部门信息,开启用户同步 //根据现有数据库同步到的部门信息,开启用户同步
func (d DingTalkLogic) SyncDingTalkUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) { func (d DingTalkLogic) SyncDingTalkUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取钉钉用户列表 // 1.获取钉钉用户列表
users, err := dingtalk.GetDingTalkAllUsers() users, err := dingtalk.GetAllUsers()
if err != nil { if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncDingTalkUsers获取钉钉用户列表失败%s", err.Error())) return nil, tools.NewOperationError(fmt.Errorf("SyncDingTalkUsers获取钉钉用户列表失败%s", err.Error()))
} }
@ -135,7 +135,7 @@ func (d DingTalkLogic) SyncDingTalkUsers(c *gin.Context, req interface{}) (data
for _, deptId := range detail.DeptIds { for _, deptId := range detail.DeptIds {
sourceDeptIds = append(sourceDeptIds, fmt.Sprintf("%s_%d", config.Conf.DingTalk.Flag, deptId)) sourceDeptIds = append(sourceDeptIds, fmt.Sprintf("%s_%d", config.Conf.DingTalk.Flag, deptId))
} }
groupIds, err := isql.Group.DingTalkDeptIdsToGroupIds(sourceDeptIds) groupIds, err := isql.Group.DeptIdsToGroupIds(sourceDeptIds)
if err != nil { if err != nil {
return nil, tools.NewMySqlError(fmt.Errorf("SyncDingTalkUsers获取钉钉部门ids转换为内部部门id失败%s", err.Error())) return nil, tools.NewMySqlError(fmt.Errorf("SyncDingTalkUsers获取钉钉部门ids转换为内部部门id失败%s", err.Error()))
} }
@ -168,7 +168,7 @@ func (d DingTalkLogic) SyncDingTalkUsers(c *gin.Context, req interface{}) (data
} }
// 3.获取钉钉已离职用户id列表 // 3.获取钉钉已离职用户id列表
userIds, err := dingtalk.GetDingTalkLeaveUserIds() userIds, err := dingtalk.GetLeaveUserIds()
if err != nil { if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncDingTalkUsers获取钉钉离职用户列表失败%s", err.Error())) return nil, tools.NewOperationError(fmt.Errorf("SyncDingTalkUsers获取钉钉离职用户列表失败%s", err.Error()))
} }

281
logic/wecom_logic.go Normal file
View File

@ -0,0 +1,281 @@
package logic
import (
"fmt"
"strings"
"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/wechat"
"github.com/mozillazg/go-pinyin"
"github.com/wenerme/go-wecom/wecom"
"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 WeComLogic struct {
}
//通过企业微信获取部门信息
func (d *WeComLogic) SyncWeComDepts(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取所有部门
depts, err := wechat.GetAllDepts()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("获取企业微信部门列表失败:%s", err.Error()))
}
// 2.将部门这个数组进行拆分一组是父ID为1的一组是父ID不为1的
var firstDepts []wecom.ListDepartmentResponseItem // 父ID为1的部门
var otherDepts []wecom.ListDepartmentResponseItem // 父ID不为1的部门
for _, dept := range depts {
if dept.ID == 1 { // 跳过ID为1的根部门由系统配置的根部门进行占位
continue
}
if dept.ParentID == 1 {
firstDepts = append(firstDepts, dept)
} else {
otherDepts = append(otherDepts, dept)
}
}
// 3.先写父ID为1的再写父ID不为1的
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_%d", config.Conf.WeCom.Flag, dept.ID),
Source: config.Conf.WeCom.Flag,
SourceDeptParentId: fmt.Sprintf("%s_%d", config.Conf.WeCom.Flag, 1),
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncWeComDepts添加根部门失败%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_%d", config.Conf.WeCom.Flag, dept.ID),
Source: config.Conf.WeCom.Flag,
SourceDeptParentId: fmt.Sprintf("%s_%d", config.Conf.WeCom.Flag, dept.ParentID),
})
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncWeComDepts添加根部门失败%s", err.Error()))
}
}
return nil, nil
}
// AddGroup 添加部门数据
func (d WeComLogic) 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 WeComLogic) SyncWeComUsers(c *gin.Context, req interface{}) (data interface{}, rspError interface{}) {
// 1.获取企业微信用户列表
users, err := wechat.GetAllUsers()
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncWeComUsers获取企业微信用户列表失败%s", err.Error()))
}
// 2.将用户拆分成两组一组状态为1的在职正常用户一组状态为2的禁用账户
// 此问题在企业微信这里没有统一的定论
// 有的公司是员工离职,直接在企业微信后台删除(企业微信又没有对应接口拿到已经被删除的用户)
// 也有公司是在后台禁用账号,这两者不同,处理方式也不一样
// 目前先以第二种为准,判断用户状态字段来判断用户是否离职
var liveUsers []wecom.ListUserResponseItem
var leaveUsers []wecom.ListUserResponseItem
for _, user := range users {
if user.Status == 1 {
liveUsers = append(liveUsers, user)
}
if user.Status == 2 {
leaveUsers = append(leaveUsers, user)
}
}
// 2.遍历用户,开始写入
for _, detail := range liveUsers {
// 用户名的几种情况
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.BizMail == "" {
detail.BizMail = detail.Email
}
// 如果企业内没有工号,则工号用名字占位
// if detail.JobNumber == "" {
// detail.JobNumber = detail.Mobile
// }
//企业微信部门ids,转换为内部部门id
var sourceDeptIds []string
for _, deptId := range detail.Department {
sourceDeptIds = append(sourceDeptIds, fmt.Sprintf("%s_%d", config.Conf.WeCom.Flag, deptId))
}
groupIds, err := isql.Group.DeptIdsToGroupIds(sourceDeptIds)
if err != nil {
return nil, tools.NewMySqlError(fmt.Errorf("SyncWeComUsers获取企业微信部门ids转换为内部部门id失败%s", err.Error()))
}
// 写入用户
user := request.WeComUserAddReq{
Username: userName,
Password: config.Conf.Ldap.UserInitPassword,
Nickname: detail.Name,
GivenName: detail.Name,
Mail: detail.BizMail,
JobNumber: detail.Name, // 工号暂用名字替代
Mobile: detail.Mobile,
Avatar: detail.Avatar,
PostalAddress: detail.Address,
// Departments: dept.GroupName,
Position: detail.Position,
Introduction: detail.Name,
Status: 1,
DepartmentId: groupIds,
Source: config.Conf.WeCom.Flag,
SourceUserId: fmt.Sprintf("%s_%s", config.Conf.WeCom.Flag, detail.UserID),
SourceUnionId: fmt.Sprintf("%s_%s", config.Conf.WeCom.Flag, detail.UserID),
}
// 入库
err = d.AddUsers(&user)
if err != nil {
return nil, tools.NewOperationError(fmt.Errorf("SyncWeComUsers写入用户失败%s", err.Error()))
}
}
// 3.获取企业微信已离职用户id列表
// 4.遍历id开始处理
for _, userTmp := range leaveUsers {
user := new(model.User)
err = isql.User.Find(tools.H{"source_user_id": fmt.Sprintf("%s_%s", config.Conf.WeCom.Flag, userTmp.UserID)}, 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 WeComLogic) 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
}

View File

@ -40,6 +40,19 @@ type DingGroupAddReq struct {
SourceUserNum int `json:"sourceUserNum"` SourceUserNum int `json:"sourceUserNum"`
} }
// WeComGroupAddReq 添加企业微信资源结构体
type WeComGroupAddReq 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 更新资源结构体 // GroupUpdateReq 更新资源结构体
type GroupUpdateReq struct { type GroupUpdateReq struct {
ID uint `json:"id" form:"id" validate:"required"` ID uint `json:"id" form:"id" validate:"required"`
@ -85,3 +98,7 @@ type UserNoInGroupReq struct {
// SyncDingTalkDeptsReq 同步钉钉部门信息 // SyncDingTalkDeptsReq 同步钉钉部门信息
type SyncDingTalkDeptsReq struct { type SyncDingTalkDeptsReq struct {
} }
// SyncWeComDeptsReq 同步企业微信部门信息
type SyncWeComDeptsReq struct {
}

View File

@ -42,6 +42,28 @@ type DingUserAddReq struct {
SourceUnionId string `json:"sourceUnionId"` // 第三方唯一unionId SourceUnionId string `json:"sourceUnionId"` // 第三方唯一unionId
} }
// WeComUserAddReq 企业微信用户创建资源结构体
type WeComUserAddReq 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 更新资源结构体 // UserUpdateReq 更新资源结构体
type UserUpdateReq struct { type UserUpdateReq struct {
ID uint `json:"id" validate:"required"` ID uint `json:"id" validate:"required"`
@ -82,10 +104,14 @@ type UserChangeUserStatusReq struct {
type UserGetUserInfoReq struct { type UserGetUserInfoReq struct {
} }
// 同步钉钉用户信息 // SyncDingUserReq 同步钉钉用户信息
type SyncDingUserReq struct { type SyncDingUserReq struct {
} }
// SyncWeComUserReq 同步企业微信用户信息
type SyncWeComUserReq struct {
}
// UserListReq 获取用户列表结构体 // UserListReq 获取用户列表结构体
type UserListReq struct { type UserListReq struct {
Username string `json:"username" form:"username"` Username string `json:"username" form:"username"`

View File

@ -7,7 +7,7 @@ import (
"github.com/zhaoyunxing92/dingtalk/v2/request" "github.com/zhaoyunxing92/dingtalk/v2/request"
) )
func GetDingTalkAllDepts(deptId int) (result []*DingTalkDept, err error) { func GetAllDepts(deptId int) (result []*DingTalkDept, err error) {
depts, err := InitDingTalkClient().FetchDeptList(deptId, true, "zh_CN") depts, err := InitDingTalkClient().FetchDeptList(deptId, true, "zh_CN")
if err != nil { if err != nil {
return result, err return result, err
@ -24,8 +24,8 @@ func GetDingTalkAllDepts(deptId int) (result []*DingTalkDept, err error) {
return return
} }
func GetDingTalkAllUsers() (result []*DingTalkUser, err error) { func GetAllUsers() (result []*DingTalkUser, err error) {
depts, err := GetDingTalkAllDepts(1) depts, err := GetAllDepts(1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -82,7 +82,7 @@ func GetDingTalkAllUsers() (result []*DingTalkUser, err error) {
return return
} }
func GetDingTalkLeaveUserIds() ([]string, error) { func GetLeaveUserIds() ([]string, error) {
var ids []string var ids []string
ReqParm := struct { ReqParm := struct {
Cursor int `json:"cursor"` Cursor int `json:"cursor"`

View File

@ -0,0 +1,15 @@
package wechat
import (
"github.com/eryajf/go-ldap-admin/config"
"github.com/wenerme/go-wecom/wecom"
)
func InitWeComClient() *wecom.Client {
client := wecom.NewClient(wecom.Conf{
CorpID: config.Conf.WeCom.CorpID,
AgentID: config.Conf.WeCom.AgentID,
CorpSecret: config.Conf.WeCom.CorpSecret,
})
return client
}

View File

@ -0,0 +1,40 @@
package wechat
import (
"strconv"
"github.com/wenerme/go-wecom/wecom"
)
// GetAllDepts 获取所有部门
func GetAllDepts() ([]wecom.ListDepartmentResponseItem, error) {
depts, err := InitWeComClient().ListDepartment(
&wecom.ListDepartmentRequest{},
)
if err != nil {
return nil, err
}
return depts.Department, nil
}
// GetAllDepts 获取所有部门
func GetAllUsers() ([]wecom.ListUserResponseItem, error) {
depts, err := GetAllDepts()
if err != nil {
return nil, err
}
var us []wecom.ListUserResponseItem
for _, dept := range depts {
users, err := InitWeComClient().ListUser(
&wecom.ListUserRequest{
DepartmentID: strconv.Itoa(dept.ID),
FetchChild: "1",
},
)
if err != nil {
return nil, err
}
us = append(us, users.UserList...)
}
return us, nil
}

View File

@ -333,6 +333,13 @@ func InitData() {
Remark: "从钉钉拉取用户信息", Remark: "从钉钉拉取用户信息",
Creator: "系统", Creator: "系统",
}, },
{
Method: "POST",
Path: "/user/syncWeComUsers",
Category: "user",
Remark: "从企业微信拉取用户信息",
Creator: "系统",
},
{ {
Method: "GET", Method: "GET",
Path: "/group/list", Path: "/group/list",
@ -403,6 +410,13 @@ func InitData() {
Remark: "从钉钉拉取部门信息", Remark: "从钉钉拉取部门信息",
Creator: "系统", Creator: "系统",
}, },
{
Method: "POST",
Path: "/group/syncWeComDepts",
Category: "group",
Remark: "从企业微信拉取部门信息",
Creator: "系统",
},
{ {
Method: "GET", Method: "GET",
Path: "/role/list", Path: "/role/list",
@ -572,11 +586,13 @@ func InitData() {
"/user/list", "/user/list",
"/user/changePwd", "/user/changePwd",
"/user/syncDingTalkUsers", "/user/syncDingTalkUsers",
"/user/syncWeComUsers",
"/group/list", "/group/list",
"/group/tree", "/group/tree",
"/group/useringroup", "/group/useringroup",
"/group/usernoingroup", "/group/usernoingroup",
"/group/syncDingTalkDepts", "/group/syncDingTalkDepts",
"/group/syncWeComkDepts",
"/role/list", "/role/list",
"/role/getmenulist", "/role/getmenulist",
"/role/getapilist", "/role/getapilist",

View File

@ -25,7 +25,9 @@ func InitGroupRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) g
group.GET("/useringroup", controller.Group.UserInGroup) group.GET("/useringroup", controller.Group.UserInGroup)
group.GET("/usernoingroup", controller.Group.UserNoInGroup) group.GET("/usernoingroup", controller.Group.UserNoInGroup)
group.POST("/syncDingTalkDepts", controller.Group.SyncDingTalkDepts)
group.POST("/syncDingTalkDepts", controller.Group.SyncDingTalkDepts) // 同步部门
group.POST("/syncWeComDepts", controller.Group.SyncWeComDepts) // 同步部门
} }
return r return r

View File

@ -23,7 +23,9 @@ func InitUserRoutes(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) gi
user.POST("/delete", controller.User.Delete) // 删除用户 user.POST("/delete", controller.User.Delete) // 删除用户
user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码 user.POST("/changePwd", controller.User.ChangePwd) // 修改用户密码
user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态 user.POST("/changeUserStatus", controller.User.ChangeUserStatus) // 修改用户状态
user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 同步用户 user.POST("/syncDingTalkUsers", controller.User.SyncDingTalkUsers) // 同步用户
user.POST("/syncWeComUsers", controller.User.SyncWeComUsers) // 同步用户
} }
return r return r
} }

View File

@ -151,10 +151,10 @@ func (s GroupService) RemoveUserFromGroup(group *model.Group, users []model.User
return common.DB.Model(&group).Association("Users").Delete(users) return common.DB.Model(&group).Association("Users").Delete(users)
} }
// DingTalkDeptIdsToGroupIds 将钉钉部门id转换为分组id // DeptIdsToGroupIds 将企业IM部门id转换为MySQL分组id
func (s GroupService) DingTalkDeptIdsToGroupIds(dingTalkIds []string) (groupIds []uint, err error) { func (s GroupService) DeptIdsToGroupIds(ids []string) (groupIds []uint, err error) {
var tempGroups []model.Group var tempGroups []model.Group
err = common.DB.Model(&model.Group{}).Where("source_dept_id IN (?)", dingTalkIds).Find(&tempGroups).Error err = common.DB.Model(&model.Group{}).Where("source_dept_id IN (?)", ids).Find(&tempGroups).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }