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

    • 动态字段关系管理
    • 配置钉钉同步
    • 配置企业微信同步
    • 配置企业飞书同步
  • 常见问题
版本历史
  • openLDAP
捐赠支持
在线体验 (opens new window)
  • Fantastic-admin 开箱即用的管理系统框架 (opens new window)
GitHub (opens new window)
首页
  • 产品概述
  • 安装入门
  • 参与贡献
  • 最佳实践

    • 动态字段关系管理
    • 配置钉钉同步
    • 配置企业微信同步
    • 配置企业飞书同步
  • 常见问题
版本历史
  • openLDAP
捐赠支持
在线体验 (opens new window)
  • Fantastic-admin 开箱即用的管理系统框架 (opens new window)
GitHub (opens new window)
  • 产品概述

    • 关于go-ldap-admin
    • 项目许可证
    • 平台功能概览
    • 关于修改密码的方法以及说明
    • 付费服务
  • 安装入门

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

    • 问题反馈
  • 最佳实践

    • 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
10

创建用户的动态关系:

{
  "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
18

# 飞书

创建分组的动态关系

{
  "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)
上次更新: 2023/10/14, 16:01:05
ldap用户以及分组的设计思路
配置钉钉同步

← ldap用户以及分组的设计思路 配置钉钉同步→

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