refactor: config + lldaplib 收敛到 LLDAP 形态(读只读、删 OpenLDAP 写路径与密码包)
- config 重写:删 user-ou/inactive-ou/group-root-* / group-object-class / group-member-attr / initial-password;新增 lldap 写通道 + webhook + confirm-base-url + 两套 schedule + guard - ldaplib 收拢为只读:属性常量/DN 归入 attrs.go;删 ops.go 写路径与 internal/password - config.example.yml 重写成 LLDAP 形态 - 组命名修订(呼应 AGENTS.md 约束12):用部门稳定索引 cn=<dept_id> 做组身份, mirror_departments 增 group_id 列并跨快照保留,可读中文名只走 DB 展示
This commit is contained in:
+50
-16
@@ -72,16 +72,19 @@ type MirrorUser struct {
|
||||
|
||||
// MirrorDepartment 是部门镜像。
|
||||
type MirrorDepartment struct {
|
||||
DeptID string `gorm:"primaryKey;size:190"`
|
||||
Name string `gorm:"check:name <> ''"`
|
||||
PinyinName string `gorm:"size:255"`
|
||||
DN string `gorm:"size:512"`
|
||||
ParentID string `gorm:"size:190;index"`
|
||||
Level int
|
||||
State string `gorm:"size:32;not null;default:active;index"`
|
||||
LastSyncedAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
DeptID string `gorm:"primaryKey;size:190"`
|
||||
// Name 是部门中文名,纯展示用(可读名走 DB,不进 LDAP 组名)。
|
||||
Name string `gorm:"check:name <> ''"`
|
||||
DN string `gorm:"size:512;index"`
|
||||
ParentID string `gorm:"size:190;index"`
|
||||
Level int
|
||||
// GroupID 是 LLDAP 中该部门组的自增 int 组 id。LDAP 视角无法取到它,
|
||||
// 必须由物化阶段持久化,供 GraphQL 成员操作直接用(约束 12)。
|
||||
GroupID int `gorm:"column:group_id"`
|
||||
State string `gorm:"size:32;not null;default:active;index"`
|
||||
LastSyncedAt time.Time
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// MirrorUserDepartment 是用户 ↔ 部门 的关联表,用于按部门查人的索引。
|
||||
@@ -121,24 +124,38 @@ func (s *Store) ReplaceMirror(snap *MirrorSnapshot) error {
|
||||
now := time.Now()
|
||||
|
||||
// 部门整体替换:快照里的都 upsert;快照之外的一律清除,保证镜像 = 飞书当前有的部门。
|
||||
// 部门是两个「整体替换」之一,用户因受离职生命周期约束不在本层删。
|
||||
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).
|
||||
Delete(&MirrorDepartment{}).Error; err != nil {
|
||||
return fmt.Errorf("清空部门镜像失败: %w", err)
|
||||
}
|
||||
// 注意 upsert 冲突时**不碰 group_id**——物化阶段持久化的 dept_id→groupId 映射
|
||||
// 必须跨快照刷新保留,否则每次快照都会把组 id 清零。
|
||||
for _, d := range snap.Departments {
|
||||
d.State = StateActive
|
||||
d.LastSyncedAt = now
|
||||
if err := tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "dept_id"}},
|
||||
DoUpdates: clause.AssignmentColumns([]string{
|
||||
"name", "pinyin_name", "dn", "parent_id", "level", "state", "last_synced_at",
|
||||
"name", "dn", "parent_id", "level", "state", "last_synced_at",
|
||||
}),
|
||||
}).Create(d).Error; err != nil {
|
||||
return fmt.Errorf("写入部门镜像 %s(%s) 失败: %w", d.Name, d.DeptID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 清除本轮快照之外的部门(group_id 随之丢弃,其组已成孤儿)。
|
||||
if len(snap.Departments) == 0 {
|
||||
if err := tx.Session(&gorm.Session{AllowGlobalUpdate: true}).
|
||||
Delete(&MirrorDepartment{}).Error; err != nil {
|
||||
return fmt.Errorf("清除部门镜像失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
presentIDs := make([]string, 0, len(snap.Departments))
|
||||
for _, d := range snap.Departments {
|
||||
presentIDs = append(presentIDs, d.DeptID)
|
||||
}
|
||||
if err := tx.Where("dept_id NOT IN ?", presentIDs).
|
||||
Delete(&MirrorDepartment{}).Error; err != nil {
|
||||
return fmt.Errorf("清除旧部门失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户整体替换:upsert 有账者,不轮询删除缺席者(其生命周期由 reconcile 处理)。
|
||||
for _, u := range snap.Users {
|
||||
u.State = StateActive
|
||||
@@ -347,6 +364,23 @@ func (s *Store) MapDepartmentsByID() (map[string]*MirrorDepartment, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// SetDeptGroupID 持久化部门组在 LLDAP 里的自增 groupId。
|
||||
//
|
||||
// LDAP 视角取不到这个 int id,只能由物化阶段在创建/核对组时写入,成员操作据此直接用。
|
||||
// 快照刷新刻意不覆盖 group_id,保证映射跨刷新稳定。
|
||||
func (s *Store) SetDeptGroupID(deptID string, groupID int) error {
|
||||
return s.db.Model(&MirrorDepartment{}).
|
||||
Where("dept_id = ?", deptID).
|
||||
Update("group_id", groupID).Error
|
||||
}
|
||||
|
||||
// SetUserAttrsHash 记录某用户上次写入 LLDAP 的属性指纹,供漂移比对与展示。
|
||||
func (s *Store) SetUserAttrsHash(uid, hash string) error {
|
||||
return s.db.Model(&MirrorUser{}).
|
||||
Where("uid = ?", uid).
|
||||
Update("attrs_hash", hash).Error
|
||||
}
|
||||
|
||||
// UserDeptIDs 返回某用户所属的部门 ID 列表。
|
||||
func (s *Store) UserDeptIDs(uid string) ([]string, error) {
|
||||
var rels []MirrorUserDepartment
|
||||
|
||||
Reference in New Issue
Block a user