refactor: mapping 落库化(期望状态改从镜像视图构造 + 拼音拍平组名)
- mapping.BuildFromMirror:由镜像 active 用户/部门构造期望 LLDAP 状态(纯函数), 组名/DN 直接复用快照落库时算好的拍平拼音,用户 DN 固定 ou=people、去掉离职 OU - Feat: FlatDeptInfo 已在本阶段完成测试覆盖(拍平拼音父子/空拼音回落/重名消歧/DN 稳定) - 移除已被目标架构取代的旧「嵌套 CN」测试断言 - 旧 Build(OpenLDAP 单阶段)暂留保证过渡期编译,阶段6退役
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
package mapping
|
||||
|
||||
// 镜像 → 期望 LLDAP 状态 的落库化映射。
|
||||
//
|
||||
// 与旧 `Build(feishu.Snapshot)`(OpenLDAP 单阶段时代的嵌套 CN + inactive OU 模型)
|
||||
// 不同,这里是目标架构的映射:输入是 SQLite 镜像的活动用户与活动部门,输出是
|
||||
// 期望写入 LLDAP 的纯函数状态。LLDAP 组不能嵌套,因此组用拍平拼音点分命名;
|
||||
// 用户 DN 固定挂在 ou=people 下,离职不靠 DN 移动而是物理删除(见 syncer 阶段 6)。
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"idsync/internal/ldaplib"
|
||||
"idsync/internal/store"
|
||||
)
|
||||
|
||||
// DesiredGroup 是期望的分组(拍平命名)。
|
||||
type DesiredGroup struct {
|
||||
DeptID string
|
||||
// DN 是完整组 DN(来自 mirror_departments,形式 cn=<父.子>,ou=groups,<base>)。
|
||||
DN string
|
||||
// Chain 是拍平拼音链,仅展示用。
|
||||
Chain string
|
||||
Name string
|
||||
// Level 是部门在树中的层级,用于保证父组先于子组创建。
|
||||
Level int
|
||||
// Members 是期望成员的用户 DN 集合。
|
||||
Members map[string]struct{}
|
||||
}
|
||||
|
||||
// DesiredUser 是期望的用户。
|
||||
//
|
||||
// 不再有 InactiveDN:离职走物理删除,用户 DN 固定 ou=people/<base>。
|
||||
type DesiredUser struct {
|
||||
UID string
|
||||
DN string
|
||||
CN string
|
||||
SN string
|
||||
GivenName string
|
||||
DisplayName string
|
||||
Mail string
|
||||
Mobile string
|
||||
EmployeeNumber string
|
||||
DepartmentNumber string
|
||||
PostalAddress string
|
||||
BusinessCategory string
|
||||
Description string
|
||||
DeptIDs []string
|
||||
}
|
||||
|
||||
// DesiredState 是期望写入 LLDAP 的完整状态。
|
||||
type DesiredState struct {
|
||||
Groups []*DesiredGroup
|
||||
Users []*DesiredUser
|
||||
GroupByDeptID map[string]*DesiredGroup
|
||||
}
|
||||
|
||||
// BuildFromMirror 由镜像的活动用户与活动部门构造期望状态。纯函数,不触碰 IO。
|
||||
//
|
||||
// 组名与 DN 直接复用镜像落库时算好的 PinyinName/DN(见 FlatDeptInfo),保证
|
||||
// 物化与预览看到的是同一份命名,不会出现「写入了但预览里看不到」的分叉。
|
||||
func BuildFromMirror(users []*store.MirrorUser, depts []*store.MirrorDepartment, baseDN string) *DesiredState {
|
||||
st := &DesiredState{
|
||||
Groups: make([]*DesiredGroup, 0, len(depts)),
|
||||
Users: make([]*DesiredUser, 0, len(users)),
|
||||
GroupByDeptID: make(map[string]*DesiredGroup, len(depts)),
|
||||
}
|
||||
peopleOU := "ou=people," + strings.TrimSpace(baseDN)
|
||||
|
||||
for _, d := range depts {
|
||||
g := &DesiredGroup{
|
||||
DeptID: d.DeptID,
|
||||
DN: d.DN,
|
||||
Chain: d.PinyinName,
|
||||
Name: d.Name,
|
||||
Level: d.Level,
|
||||
Members: make(map[string]struct{}),
|
||||
}
|
||||
st.Groups = append(st.Groups, g)
|
||||
st.GroupByDeptID[d.DeptID] = g
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
uid := u.UID
|
||||
du := &DesiredUser{
|
||||
UID: uid,
|
||||
DN: ldaplib.UserDN(uid, peopleOU),
|
||||
CN: uid,
|
||||
SN: u.SN,
|
||||
GivenName: u.GivenName,
|
||||
DisplayName: u.DisplayName,
|
||||
Mail: u.Email,
|
||||
Mobile: u.Mobile,
|
||||
EmployeeNumber: u.EmployeeNumber,
|
||||
DepartmentNumber: u.DepartmentNumber,
|
||||
PostalAddress: u.PostalAddress,
|
||||
BusinessCategory: u.BusinessCategory,
|
||||
Description: u.Description,
|
||||
DeptIDs: u.DepartmentIDs,
|
||||
}
|
||||
st.Users = append(st.Users, du)
|
||||
|
||||
// 维护成员关系:用户只加入其实际所属且在本轮同步范围内的部门组。
|
||||
for _, deptID := range u.DepartmentIDs {
|
||||
if g, ok := st.GroupByDeptID[deptID]; ok {
|
||||
g.Members[du.DN] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 分组按层级排序,父组必须排在子组之前(LLDAP 组虽不嵌套,但物化创建
|
||||
// 顺序仍要保持父先于子,避免依赖隐含顺序造成问题)。
|
||||
sort.SliceStable(st.Groups, func(i, j int) bool {
|
||||
if st.Groups[i].Level != st.Groups[j].Level {
|
||||
return st.Groups[i].Level < st.Groups[j].Level
|
||||
}
|
||||
return st.Groups[i].DeptID < st.Groups[j].DeptID
|
||||
})
|
||||
sort.SliceStable(st.Users, func(i, j int) bool { return st.Users[i].UID < st.Users[j].UID })
|
||||
return st
|
||||
}
|
||||
Reference in New Issue
Block a user