Go-Ldap-Admin Go-Ldap-Admin
首页
  • 产品概述
  • 安装入门
  • 参与贡献
  • 最佳实践

    • 动态字段关系管理
    • 配置钉钉同步
    • 配置企业微信同步
    • 配置企业飞书同步
  • 常见问题
  • openLDAP
捐赠支持
在线体验 (opens new window)
GitHub (opens new window)
首页
  • 产品概述
  • 安装入门
  • 参与贡献
  • 最佳实践

    • 动态字段关系管理
    • 配置钉钉同步
    • 配置企业微信同步
    • 配置企业飞书同步
  • 常见问题
  • openLDAP
捐赠支持
在线体验 (opens new window)
GitHub (opens new window)
  • 产品概述

    • 关于go-ldap-admin
    • 平台功能概览
    • 关于修改密码的方法以及说明
  • 安装入门

    • docker-compose在本地快速拉起测试环境
    • 生产环境原生部署流程
    • 本地开发指南
    • 快速开始
    • centos安装 Go-Ldap-Admin
  • 参与贡献

    • 问题反馈
  • 最佳实践

    • ldap用户以及分组的设计与管理
    • 动态字段关系管理
      • 前言
      • 实现原理
        • 动态字段
        • 构建数据
      • 预留字段
        • 三方字段
        • 本地字段
      • 实践操练
        • 钉钉
        • 飞书
        • 企业微信
    • 配置钉钉同步
    • 配置企业微信同步
    • 配置飞书同步
    • 三方IM数据同步调试
  • 常见问题

    • 问题汇总摘要
    • 如何将ldap中的basedn更改为自己定义的
    • 如何自定义秘钥对儿
  • 项目相关
  • 最佳实践
二丫讲梵
2022-07-03
目录

动态字段关系管理

# 前言

在平台前期设计中,三方IM的组织架构以及用户信息往平台同步时,都是将两侧的字段做成硬编码映射的,在后来群里一些小伙伴沟通中,慢慢才发现,三方IM中的一些字段属性很灵活,每家公司用的也各不一样,于是,动态字段关联就很自然地提上了日程。

举个例子直白点说明这个问题:

比如钉钉的用户属性中,关于邮箱的有两个字段,参考获取部门用户详情 (opens new window)。

  • email:员工邮箱
  • org_email:员工的企业邮箱

而事实上,不同的公司可能会选择email,也可能选择org_email作为员工的邮箱字段,这个时候,对于平台要把用户信息同步到平台上来说,转化在代码中,就不知道该怎么处理了(本地平台以及ldap中只有一个email邮箱字段来标识用户的邮箱)。

动态字段关联旨在给用户提供创建这种连接关系的自由,用户配置email字段,则同步的时候就映射email字段,用户配置org_email字段,则同步的时候就会将org_email字段对应的值同步到本地。示意图如下:

image_20220703_164546

# 实现原理

事实上一开始我并没有特别具体的思路来实现这块儿的功能,只大概想了一个方向,毕竟后端的经验还不够,这时候人脉资源就很重要了,请教了之前的后端同学,给我指导了具体的思路实现,在此感谢周同学。

核心设计在于:用户基于类似连线的方式添加一份三方IM字段与本地字段的映射关系,通过map存放到MySQL,当获取到远程数据之后,通过遍历映射关系,将远程数据挂载到本地结构体中。

# 动态字段

动态字段结构体定义如下:

package model

import (
	"gorm.io/datatypes"
	"gorm.io/gorm"
)

type FieldRelation struct {
	gorm.Model
	Flag       string         `gorm:"type:varchar(20);comment:'数据标志'" json:"flag"`
	Attributes datatypes.JSON `gorm:"comment:'字段关系'" json:"attributes"`
}
1
2
3
4
5
6
7
8
9
10
11
12

这里引入了 https://github.com/go-gorm/datatypes (opens new window) ,以基于gorm官方封装的能力,进行JSON字段的管理。

最开始我想着把字段横着展开,只是字段将要存储分组与用户两种关系,两种关系的基础字段不一致,因此展开之后并不美观,因此最后选择了将数据直接以JSON格式存入MySQL。

datatypes.JSON已经内置了Value()和Scan(value interface{})两个方法,使得我们在与MySQL交互的时候,可以像普通字段一样对待JSON数据,而不必再进行其他封装。

# 构建数据

这里就只拿Group进行举例,在Group结构体下定义分组中需要灵活映射的字段方法:

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"`
	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"`
	GroupDN            string   `gorm:"type:varchar(255);not null;comment:'分组dn'" json:"groupDn"` // 分组在ldap的dn
}

func (g *Group) SetGroupName(groupName string) {
	g.GroupName = groupName
}

func (g *Group) SetRemark(remark string) {
	g.Remark = remark
}

func (g *Group) SetSourceDeptId(sourceDeptId string) {
	g.SourceDeptId = sourceDeptId
}

func (g *Group) SetSourceDeptParentId(sourceDeptParentId string) {
	g.SourceDeptParentId = sourceDeptParentId
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

然后通过BuildGroupData方法,将远程数据进行挂载:

// BuildGroupData 根据数据与动态字段组装成分组数据
func BuildGroupData(flag string, remoteData map[string]interface{}) (*model.Group, error) {
	output, err := sonic.Marshal(&remoteData)
	if err != nil {
		return nil, err
	}

	oldData := new(model.FieldRelation)
	err = isql.FieldRelation.Find(tools.H{"flag": flag + "_group"}, oldData)
	if err != nil {
		return nil, tools.NewMySqlError(err)
	}
	frs, err := tools.JsonToMap(string(oldData.Attributes))
	if err != nil {
		return nil, tools.NewOperationError(err)
	}

	g := &model.Group{}
	for system, remote := range frs {
		switch system {
		case "groupName":
			g.SetGroupName(gjson.Get(string(output), remote).String())
		case "remark":
			g.SetRemark(gjson.Get(string(output), remote).String())
		case "sourceDeptId":
			g.SetSourceDeptId(fmt.Sprintf("%s_%s", flag, gjson.Get(string(output), remote).String()))
		case "sourceDeptParentId":
			g.SetSourceDeptParentId(fmt.Sprintf("%s_%s", flag, gjson.Get(string(output), remote).String()))
		}
	}
	return g, nil
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

当我们拿到远程数据之后,就可以调用此方法将远程数据,根据字段转化成本地Group的结构体字段了。

# 预留字段

其实说是动态字段,也不能完全没有任何约束的动态化,三方IM与本地平台的字段都不能超出已有给定的字段之外。

这里先陈列出三方IM对应的字段属性,你只能在如下陈列的属性中进行关系映射的选择,如果还有重要的字段没有出现在如下列表,请提交issue。

注意:有些场景中,三方IM提供的字段未必直接适合本地使用,因此后台也提供了一些自定义的字段,以供用户选择,本平台自定义字段,将以 custom_ 前缀作为自定义字段的标识,请注意区分。

# 三方字段

# 钉钉字段

  • Group:字段详情参考获取部门列表 (opens new window)

    • 官方字段

      • id:部门ID
      • name:部门名称
      • parentid:父部门ID
    • 自定义字段

      • custom_name_pinyin:部门名称拼音
  • User:字段详情参考获取部门用户详情 (opens new window)

    • 官方字段

      • userid:用户的userId
      • unionid:用户在当前开发者企业帐号范围内的唯一标识
      • name:用户姓名
      • avatar:头像
      • mobile:手机号码
      • job_number:工号
      • title:职位
      • work_place:工位
      • remark:备注
      • leader:是否是部门的主管
      • org_email:员工的企业邮箱
      • email:员工邮箱
      • department_ids:所属部门id列表
    • 自定义字段

      • custom_name_pinyin:用户姓名拼音,可能会在多音字方面出现问题。
      • custom_nickname_org_email:企业邮箱前缀,如邮箱为:liql@eryajf.net,则该字段值为:liql。
      • custom_nickname_email:员工邮箱前缀,如邮箱为:liql@eryajf.net,则该字段值为:liql。

      如上三个字段都是为了提取出本地用户登录时使用的名字,各个公司情况不一,这里就尽可能把情况都兼容了。

# 企业微信字段

  • Group:字段详情参考获取部门列表 (opens new window)

    • 官方字段
      • id:部门ID
      • name:部门名称
      • name_en:部门英文名称
      • parentid:父部门ID
    • 自定义字段
      • custom_name_pinyin:部门名称拼音
  • User:字段详情参考获取部门用户详情 (opens new window)

    • 官方字段

      • name:用户姓名
      • userid:用户的userid
      • mobile:手机号码
      • position:职位
      • gender:性别
      • email:邮箱
      • biz_email:企业邮箱
      • avatar:头像
      • telephone:座机
      • alias:别名
      • external_position:对外职务
      • address:地址
      • open_userid:用户的openid
      • main_department:主部门
      • english_name:英文名
      • department_ids:所属部门id列表
    • 自定义字段

      • custom_name_pinyin:用户姓名拼音,可能会在多音字方面出现问题。
      • custom_nickname_biz_email:企业邮箱前缀,如邮箱为:liql@eryajf.net,则该字段值为:liql。
      • custom_nickname_email:员工邮箱前缀,如邮箱为:liql@eryajf.net,则该字段值为:liql。

      如上三个字段都是为了提取出本地用户登录时使用的名字,各个公司情况不一,这里就尽可能把情况都兼容了。

# 飞书字段

  • Group:字段详情参考获取部门列表 (opens new window)

    • 官方字段
      • name:部门名称
      • parent_department_id:父部门ID
      • department_id:部门ID
      • open_department_id:部门的open_id
      • leader_user_id:部门的主管ID
      • unit_ids:部门单位的自定义ID列表
    • 自定义字段
      • custom_name_pinyin:部门名称拼音
  • User:字段详情参考获取部门用户详情 (opens new window)

    • 官方字段

      • name:用户姓名
      • union_id:用户的union_id
      • user_id:用户的user_id
      • open_id:用户的open_id
      • en_name:英文名
      • nickname:别名
      • email:邮箱
      • mobile:手机号码
      • gender:性别
      • avatar:头像
      • city:城市
      • country:国家
      • work_station:工位
      • join_time:入职时间
      • employee_no:工号
      • enterprise_email:企业邮箱
      • job_title:职位
      • department_ids:所属部门ID列表
    • 自定义字段

      • custom_name_pinyin:用户姓名拼音,可能会在多音字方面出现问题。
      • custom_nickname_enterprise_email:企业邮箱前缀,如邮箱为:liql@eryajf.net,则该字段值为:liql。
      • custom_nickname_email:员工邮箱前缀,如邮箱为:liql@eryajf.net,则该字段值为:liql。

      如上三个字段都是为了提取出本地用户登录时使用的名字,各个公司情况不一,这里就尽可能把情况都兼容了。

# 本地字段

平台自身的数据属性字段,是可以完全确定的,这里陈列说明如下:

  • Group
    • groupName:分组名称,建议用 name_pinyin 字段映射。
    • remark:分组说明,建议用 name 字段映射。
    • sourceDeptId:部门ID,建议用 id 字段映射。
    • sourceDeptParentId:父部门ID,建议用 parentid 字段映射。
  • User
    • username:用户名,建议用 name_pinyin 或者 custom_nickname_org_email 字段映射。
    • nickname:中文名字,建议用 name 字段映射。
    • givenName:花名,建议用 name或者 nickname字段映射。
    • mail:邮箱,根据实际情况映射用户邮箱。
    • jobNumber:工号,根据对应字段映射。
    • mobile:手机号码,根据对应字段映射。
    • avatar:头像,根据对应字段映射。
    • postalAddress:地址,根据对应字段映射。
    • position:职位,根据对应字段映射。
    • introduction:备注,根据对应字段映射。
    • sourceUserId:用户user_id,根据对应字段映射。
    • sourceUnionId:用户union_id,根据对应字段映射。

# 实践操练

下边是我在本地测试时添加的字段映射关系,仅做参考,各位根据自己的实际情况进行调整。

# 钉钉

创建分组的动态关系:

{
    "flag": "dingtalk_group",  // 字段标识
    "attributes": {  // 字段属性
        "groupName":"custom_name_pinyin",  // 分组名称(通常为分组名的拼音)
        "remark":"name",  // 分组描述
        "sourceDeptId":"id",  // 部门ID
        "sourceDeptParentId":"parentid" // 父部门ID
    }
}
1
2
3
4
5
6
7
8
9

创建用户的动态关系:

{
    "flag": "dingtalk_user", // 字段标识
    "attributes": { // 字段属性
        "username":"custom_name_pinyin", // 用户名(通常为用户名拼音)
        "nickname":"name",  // 中文名字
        "givenName":"name", // 花名
        "mail":"email",  // 邮箱
        "jobNumber":"job_number", // 工号
        "mobile":"mobile", // 手机号
        "avatar":"avatar", // 头像
        "postalAddress":"work_place", // 地址
        "position":"title", // 职位
        "introduction":"remark", // 说明
        "sourceUserId":"userid", // 源用户ID
        "sourceUnionId":"unionid" // 源用户唯一ID
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 飞书

创建分组的动态关系

{
    "flag": "feishu_group",
    "attributes": {
        "groupName":"department_id",
        "remark":"name",
        "sourceDeptId":"open_department_id",
        "sourceDeptParentId":"parent_department_id"
    }
}
1
2
3
4
5
6
7
8
9

创建用户的动态关系:

{
    "flag": "feishu_user",
    "attributes": {
        "username":"custom_name_pinyin",
        "nickname":"name",
        "givenName":"name",
        "mail":"email",
        "jobNumber":"employee_no",
        "mobile":"mobile",
        "avatar":"avatar",
        "postalAddress":"work_station",
        "position":"job_title",
        "introduction":"name",
        "sourceUserId":"user_id",
        "sourceUnionId":"union_id"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 企业微信

创建分组的动态关系

{
    "flag": "wecom_group",
    "attributes": {
        "groupName":"custom_name_pinyin",
        "remark":"name",
        "sourceDeptId":"id",
        "sourceDeptParentId":"parentid"
    }
}
1
2
3
4
5
6
7
8
9

创建用户的动态关系:

{
    "flag": "wecom_user",
    "attributes": {
        "username":"custom_name_pinyin",
        "nickname":"name",
        "givenName":"alias",
        "mail":"email",
        "jobNumber":"mobile",
        "mobile":"mobile",
        "avatar":"avatar",
        "postalAddress":"address",
        "position":"external_position",
        "introduction":"name",
        "sourceUserId":"userid",
        "sourceUnionId":"userid"
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

你可以直接在平台上对这块儿内容就进行维护:

image_20220712_170646

通过页面对字段关系进行维护。

帮助我们改善此页面 (opens new window)
上次更新: 2022/07/12, 18:15:48
ldap用户以及分组的设计与管理
配置钉钉同步

← ldap用户以及分组的设计与管理 配置钉钉同步→

Theme by Vdoing | Copyright © 2022-2022 Eryajf | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式