背景: 最近我们接了一个项目,该项目有四个租户,分别是集团总部、公司A、公司B、公司C四个租户,我的工作是同步四个租户的用户到我们的系统。其中涉及到跨租户人员调整,跨租户部门调整,以及多租户登录问题,还是有点难度的。
名词解释
- 跨租户人员调整:例如公司A中的某个人被借调到集团总部
- 跨租户部门调整:例如公司A中的某个部门整体需要调整到集团总部,包括该部门和部门成员以及所有子部门和所有子部门用户全部调整到集团总部
- 多租户登录:例如同一个用户存在在公司A、公司B等多个租户的情况下登录
问题所在
- 部门同步同步过来的部门是没有顺序的,有可能子部门先过来,父部门再过来
- 如何处理部门跨租户调整时,子部门先过来的情况,此时并没有父部门
- 登录需要oauth单点登录
问题解决
部门同步
- 构建部门映射表:通过遍历原始列表,将部门ID作为键,部门对象作为值存入deptMap,实现O(1)时间复杂度的父部门查找。
- 计算部门层级:通过getDeptLevel函数递归(实际为循环)计算每个部门的层级:
根部门:层级为0(无父部门)。
子部门:层级为父部门层级+1。
终止条件:父部门不存在时停止计算。 - 排序逻辑
排序规则:
1.层级优先:层级越低的部门(更靠近根)排在前面。
2.父部门ID次之:同一层级内,父部门ID较小的部门优先。
3.部门ID最后:父部门相同的情况下,按部门ID升序排列。
数据结构:使用SortableDept结构体临时存储部门及其层级、父ID信息,便于排序。 - 生成结果
提取排序后的SortableDept列表中的部门对象,返回最终结果。
type DeptIDANDParentID struct {
DeptID string `json:"deptId"`
ParentDeptID string `json:"parentDeptId"`
}
type SortableDept struct {
Dept DeptIDANDParentID
Level int // 部门层级,根部门为0
ParentID string // 父部门ID,用于同层级排序
}
// sortDeptsByParentID 按父部门ID排序,确保相同父ID的部门相邻
func (s *service) sortDeptsByParentID(depts []DeptIDANDParentID) []DeptIDANDParentID {
// 1. 构建部门ID到部门的映射
deptMap := make(map[string]DeptIDANDParentID)
for _, dept := range depts {
deptMap[dept.DeptID] = dept
}
// 2. 计算每个部门的层级并准备排序信息
var sortableDepts []SortableDept
for _, dept := range depts {
level := s.getDeptLevel(dept, deptMap)
sortableDepts = append(sortableDepts, SortableDept{
Dept: dept,
Level: level,
ParentID: dept.ParentDeptID,
})
}
// 3. 排序:先按层级,再按父部门ID,最后按部门ID
sort.Slice(sortableDepts, func(i, j int) bool {
// 先比较层级
if sortableDepts[i].Level != sortableDepts[j].Level {
return sortableDepts[i].Level < sortableDepts[j].Level
}
// 层级相同则比较父部门ID
if sortableDepts[i].ParentID != sortableDepts[j].ParentID {
return sortableDepts[i].ParentID < sortableDepts[j].ParentID
}
// 父ID也相同则比较部门ID
return sortableDepts[i].Dept.DeptID < sortableDepts[j].Dept.DeptID
})
// 4. 提取排序后的部门列表
var result []DeptIDANDParentID
for _, sd := range sortableDepts {
result = append(result, sd.Dept)
}
return result
}
// getDeptLevel 计算部门的层级(根部门为0)
func (s *service) getDeptLevel(dept DeptIDANDParentID, deptMap map[string]DeptIDANDParentID) int {
level := 0
current := dept
for current.ParentDeptID != "" {
parent, exists := deptMap[current.ParentDeptID]
if !exists {
break // 父部门不存在,停止计算
}
level++
current = parent
}
return level
}
跨租户部门调整
处理部门在旧租户下的逻辑
- 将当前部门ID记到父部门全路径上
- 记录租户变更部门的父部门全路径[array]
- 记录租户变更部门ID[array]
- 查询当前部门全部用户,然后对查询到用户先放到map里面map[deptId]staffInfo,然后做离职处理
- 然后停用当前部门
处理部门在新租户的逻辑
- 遍历父部门全路径数组
- 将父部门全路径分开成单个部门ID
- 遍历部门ID,判断当前部门ID有没有在租户变更部门ID列表里
- 如果存在,先在新租户下创建新部门
- 然后在新部门中创建用户
- 如果不在直接跳过循环
- 结束
跨租户用户调整
- 对当前用户做离职处理
- 然后在新租户下创建新的用户
多租户用户登录
- 在oauth_back回调接口时,判断是不是多租户
- 如果不是多租户直接登录
- 如果是多租户,将租户信息存入redis,然后将key添加在我们系统登录页面的url上
- 跳转到我们系统的登录页面
- 前端根据key请求接口获取租户信息,显示在页面上
- 用户选择需要登录的租户进行登录