快照段(飞书→SQLite 镜像→离职双确认推进)落地,不碰 LLDAP: - feishu 新增离职确认卡片:自定义机器人 webhook 发送 interactive 卡片, 确认按钮 url 跳程序自己的一次性端点;本地假 webhook 单测验证载荷与错误处理 - config.ConfirmURL/ResignConfirmRoute:确认端点路由与 URL 拼接(server/syncer 共用避免循环) - mapping.FlatDeptInfo:部门树拍平为拼音点分命名并计算 DN(纯函数), 供快照落库持久化 pinyin_name/dn,物化与 WebUI 复用 - SnapshotRunner.RunSnapshot:拉飞书→事务写镜像→离职生命周期 reconcile (机器确认→token+卡片→pending_confirm;节流防刷屏;重新可见撤销并作废 token) - 链路单测:快照落库、离职机器确认、发卡、状态机转移、节流、重新入职全部通过
79 lines
2.5 KiB
Go
79 lines
2.5 KiB
Go
package mapping
|
|
|
|
// 部门组的拍平命名与 DN 构造。
|
|
//
|
|
// LLDAP 组不能嵌套(组里只能有用户,没有父/子组概念),因此部门树必须拍平:
|
|
// 组名 = 父链拼音点分拼接(如 cn=yanfa.pingtai)。拼音为空时回落到部门 ID 保证
|
|
// 非空稳定。组名天然唯一(部门树路径唯一),重名时追加部门 ID 消歧。
|
|
//
|
|
// 这些 DN 在快照落库阶段一次性算好并持久化到 mirror_departments,供物化阶段
|
|
// 直接复用,也让 WebUI 能展示每个部门的预期 DN。
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"idsync/internal/feishu"
|
|
)
|
|
|
|
// FlatDept 是一个部门的拍平命名结果。
|
|
type FlatDept struct {
|
|
DeptID string
|
|
// Chain 是拍平后的拼音链,如 "yanfa.pingtai"。
|
|
Chain string
|
|
// DN 是完整组 DN,如 "cn=yanfa.pingtai,ou=groups,<base>"。
|
|
DN string
|
|
// Level 是部门在树中的层级,根为 0。
|
|
Level int
|
|
}
|
|
|
|
// GroupRootDN 返回部门组根 OU 的完整 DN。LLDAP 分组固定挂在 ou=groups 下。
|
|
func GroupRootDN(baseDN string) string {
|
|
return "ou=groups," + strings.TrimSpace(baseDN)
|
|
}
|
|
|
|
// FlatDeptInfo 把部门树一次性拍平为拼音点分命名并计算每股的 DN。
|
|
//
|
|
// 输入是快照里的全量部门;返回 dept_id → FlatDept。父先于子顺序处理,
|
|
// 因此计算子部门时可复用父部门的 Chain。纯函数,不触碰 IO。
|
|
func FlatDeptInfo(depts []*feishu.Department, baseDN string) map[string]*FlatDept {
|
|
byID := make(map[string]*feishu.Department, len(depts))
|
|
for _, d := range depts {
|
|
byID[d.ID] = d
|
|
}
|
|
ordered := orderByDepth(depts, byID)
|
|
|
|
root := GroupRootDN(baseDN)
|
|
out := make(map[string]*FlatDept, len(depts))
|
|
used := make(map[string]string, len(depts)) // chain -> 占用它的 dept_id
|
|
|
|
for _, d := range ordered {
|
|
prefix, parentLevel := "", -1
|
|
if p, ok := out[d.ParentID]; ok {
|
|
prefix = p.Chain
|
|
parentLevel = p.Level
|
|
}
|
|
level := parentLevel + 1
|
|
|
|
// 单个部门的拼音段;拼音为空时回落部门 ID(cnName 保证非空)。
|
|
seg := cnName(d.Name, d.ID)
|
|
chain := seg
|
|
if prefix != "" {
|
|
chain = prefix + "." + seg
|
|
}
|
|
|
|
// 重名消歧:同一层出现相同拼音(如两个「开发组」)会让 Chain 撞车,
|
|
// 组名必须唯一,因此在链尾追加部门 ID 片段。
|
|
if other, taken := used[chain]; taken && other != d.ID {
|
|
chain += "." + d.ID
|
|
}
|
|
used[chain] = d.ID
|
|
|
|
out[d.ID] = &FlatDept{
|
|
DeptID: d.ID,
|
|
Chain: chain,
|
|
DN: "cn=" + chain + "," + root,
|
|
Level: level,
|
|
}
|
|
}
|
|
return out
|
|
} |