package com.yunhai.userbase.service.impl;
import com.yunhai.platform.utils.enums.IEnum;
import com.yunhai.platform.utils.model.BoaGroup;
import com.yunhai.platform.utils.model.BoaRoleInfo;
import com.yunhai.platform.utils.model.BoaUser;
import com.yunhai.userbase.entity.SysGroup;
import com.yunhai.userbase.entity.SysUser;
import com.yunhai.userbase.entity.SysUserCurrentRole;
import com.yunhai.userbase.entity.SysUserGroupRel;
import com.yunhai.userbase.entity.SysUserRoleRel;
import com.yunhai.userbase.enums.GroupTypeEnum;
import com.yunhai.userbase.enums.StatusEnum;
import com.yunhai.userbase.enums.SysRoleEnum;
import com.yunhai.userbase.repository.SysGroupRepository;
import com.yunhai.userbase.repository.SysUserGroupRelRepository;
import com.yunhai.userbase.repository.SysUserRepository;
import com.yunhai.userbase.repository.SysUserRoleRelRepository;
import com.yunhai.userbase.repository.SysUserCurrentRoleRepository;
import com.yunhai.userbase.service.impl.IdentityService;
import com.yunhai.userbase.service.resolve.MainGroupResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 用户中心核心服务:把 UserBase 自身的 sys_user / sys_group 组装成基础平台统一的
* {@link BoaUser}(含部门/学校/教育局多级组织)。
* 这是「UserBase 由子应用升级为用户中心」的关键:平台侧 {@code UserDetailsProvider}
* ({@code UserBaseUserDetailsProvider})与开放接口({@code OpenBaseController})都复用本服务,
* 保证 CAS 会话流与签名开放流返回的 BoaUser 结构完全一致。
*/
@Service
public class UserCenterService {
/** 虚拟根节点 parentId(与 SysGroupService 约定一致)。 */
private static final int ROOT_PARENT_ID = 0;
@Autowired
private SysUserRepository sysUserRepository;
@Autowired
private SysUserGroupRelRepository sysUserGroupRelRepository;
@Autowired
private SysGroupRepository sysGroupRepository;
@Autowired
private SysUserRoleRelRepository sysUserRoleRelRepository;
@Autowired
private SysUserCurrentRoleRepository sysUserCurrentRoleRepository;
@Autowired
private MainGroupResolver mainGroupResolver;
@Autowired
private IdentityService identityService;
public BoaUser getUserInfoWithDeptByAccount(String account) {
if (account == null) {
return null;
}
return resolve(sysUserRepository.findByAccount(account.trim()));
}
public BoaUser getUserInfoWithDeptByPhone(String phone) {
if (phone == null) {
return null;
}
return resolve(sysUserRepository.findByPhone(phone.trim()));
}
public BoaUser getUserInfoWithDeptById(Integer id) {
if (id == null) {
return null;
}
return resolve(sysUserRepository.findById(id).orElse(null));
}
private BoaUser resolve(SysUser user) {
if (user == null) {
return null;
}
BoaUser boa = new BoaUser();
boa.setId(user.getId());
boa.setAccount(user.getAccount());
boa.setName(user.getRealName());
boa.setIdcard(user.getIdcard());
boa.setPhone(user.getPhone());
boa.setProfilePicture(user.getProfilePicture());
// 用户表无 email / gender 列,置 null
boa.setEmail(null);
boa.setGender(null);
boa.setType(user.getType() != null ? user.getType().getKey() : null);
// 状态语义对齐:本表 disabled(NORMAL=0 正常 / DISABLED=1 禁用) -> BoaUser(1 正常 / 0 禁用)
boa.setStatus(user.getDisabled() == StatusEnum.NORMAL ? 1 : 0);
// 角色列表:从 sys_user_role_rel(平台身份角色权威源)取正常态 roleId 去重,不再读 sys_user.roles
List roleIds = sysUserRoleRelRepository
.findDistinctRoleIdsByUserIdAndStatus(user.getId(), StatusEnum.NORMAL);
boa.setRoleIds(roleIds);
List groups = buildGroups(user.getId());
boa.setGroups(groups);
// 部门/教育局:仍从组织链推导(与主组/区域组语义绑定)
deriveOrg(groups, boa);
// 学校/当前激活身份/可选学校:从身份关联表推导(school 语义=当前激活学校)
deriveIdentity(user.getId(), boa);
return boa;
}
/**
* 组装用户组织链:取主归属组(isMain=1 取 id 最小,无主兜底 id 最小),沿 parentId 向上回溯到根,
* 顺序为「主组 -> ... -> 根方向」(近 -> 远)。
*/
private List buildGroups(Integer userId) {
List rels = sysUserGroupRelRepository.findByUserId(userId);
if (rels == null || rels.isEmpty()) {
return new ArrayList<>();
}
SysUserGroupRel mainRel = mainGroupResolver.resolve(rels);
Integer groupId = mainRel != null ? mainRel.getGroupId() : null;
if (groupId == null) {
return new ArrayList<>();
}
List chain = new ArrayList<>();
Integer cursor = groupId;
Set seen = new HashSet<>();
while (cursor != null && cursor != ROOT_PARENT_ID && seen.add(cursor)) {
SysGroup g = sysGroupRepository.findById(cursor).orElse(null);
if (g == null) {
break;
}
BoaGroup bg = new BoaGroup();
bg.setId(g.getId());
bg.setName(g.getName());
bg.setParentId(g.getParentId());
bg.setType(g.getType() != null ? g.getType().getKey() : null);
chain.add(bg);
cursor = g.getParentId();
}
return chain;
}
/** 从组织链推导 school / bureau / dept。 */
private void deriveOrg(List groups, BoaUser boa) {
if (groups == null || groups.isEmpty()) {
return;
}
BoaGroup school = null;
BoaGroup bureau = null;
for (BoaGroup g : groups) {
if (school == null && Objects.equals(g.getType(), GroupTypeEnum.SCHOOL_GROUP.getKey())) {
school = g;
}
if (bureau == null && Objects.equals(g.getType(), GroupTypeEnum.AREA_GROUP.getKey())) {
bureau = g;
}
}
boa.setSchool(school);
boa.setBureau(bureau);
// 部门:主组(链的近端第一个);若主组本身为学校,则部门即学校
boa.setDept(groups.get(0));
}
/**
* 从身份关联推导当前激活身份与可选学校。school 字段语义=当前激活学校:
* ① 优先读 {@code sys_user_current_role}(用户主动切换后的状态);
* ② 无切换记录时兜底取主身份整条记录(动态解析:主组 → 主校 → 该校 sortNo/id 最小行,
* 保证是合法存在的身份,避免「主组+主角色」组合出 B 学校老师的非法身份);
* ③ currentRoleId:优先当前激活记录 roleId,兜底主身份行 roleId(本期补码,此前从未填充);
* ④ schools:用户在全部学校下的可选身份去重 schoolId(>0)集合组装 BoaGroup 列表(本期补码)。
* school 类型守卫:仅当身份 schoolId 对应组类型=SCHOOL_GROUP 时回填 boa.school,
* 区域组/根直属用户保持 null——等价旧约定「填 0 不回填」的行为,避免子应用读到 school=区域组 对象。
*/
private void deriveIdentity(Integer userId, BoaUser boa) {
Integer currentRoleId = null;
Integer currentSchoolId = null;
String currentSchoolName = null;
SysUserCurrentRole current = sysUserCurrentRoleRepository.findByUserId(userId).orElse(null);
if (current != null && current.getRoleId() != null) {
currentRoleId = current.getRoleId();
currentSchoolId = current.getSchoolId();
currentSchoolName = current.getSchoolName();
} else {
// 兜底:主身份整条记录(动态解析:主组 → 主校 → 该校 sortNo/id 最小行),
// 而不是「主组+主角色」组合(避免组合出 B 学校老师的非法身份)
SysUserRoleRel mainRel = identityService.resolveMainIdentity(userId);
if (mainRel != null) {
currentRoleId = mainRel.getRoleId();
currentSchoolId = mainRel.getSchoolId();
if (currentSchoolId != null && currentSchoolId > 0) {
SysGroup sch = sysGroupRepository.findById(currentSchoolId).orElse(null);
currentSchoolName = sch != null ? sch.getName() : null;
}
}
}
// 当前激活学校:仅当对应组为学校组时回填(区域组/根直属保持 null)
if (currentSchoolId != null && currentSchoolId > 0 && isSchoolGroup(currentSchoolId)) {
boa.setSchool(toBoaGroup(currentSchoolId, currentSchoolName));
}
// 当前激活身份(优先切换记录,兜底主身份)——组装为 BoaRoleInfo 富对象(childId/childName 本期不做,恒空)
BoaRoleInfo currentRole = new BoaRoleInfo();
currentRole.setRoleId(currentRoleId);
currentRole.setRoleName(resolveRoleName(currentRoleId));
currentRole.setSchId(currentSchoolId);
currentRole.setSchName(currentSchoolName);
boa.setCurrentRole(currentRole);
// 可选学校集合:全部正常态身份行的 schoolId 去重(>0 过滤,恒为真实组 id)
List schoolIds = sysUserRoleRelRepository
.findDistinctSchoolIdsByUserIdAndStatus(userId, StatusEnum.NORMAL);
if (schoolIds != null && !schoolIds.isEmpty()) {
List schools = schoolIds.stream()
.filter(sid -> sid != null && sid > 0)
.map(sid -> toBoaGroup(sid, null))
.collect(Collectors.toList());
boa.setSchools(schools);
}
}
/** 该组是否为学校组(SCHOOL_GROUP);组不存在视为 false。 */
private boolean isSchoolGroup(Integer groupId) {
SysGroup g = sysGroupRepository.findById(groupId).orElse(null);
return g != null && g.getType() == GroupTypeEnum.SCHOOL_GROUP;
}
/** 由角色 id 反查角色名(SysRoleEnum.value);未知角色返回 null。 */
private String resolveRoleName(Integer roleId) {
if (roleId == null) {
return null;
}
SysRoleEnum role = IEnum.getByKey(SysRoleEnum.class, roleId);
return role != null ? role.getValue() : null;
}
/** 组装一个 BoaGroup(仅填充 id 与 name;name 为 null 时回源查 sys_group) */
private BoaGroup toBoaGroup(Integer groupId, String fallbackName) {
BoaGroup bg = new BoaGroup();
bg.setId(groupId);
if (fallbackName != null) {
bg.setName(fallbackName);
} else {
SysGroup g = sysGroupRepository.findById(groupId).orElse(null);
bg.setName(g != null ? g.getName() : null);
}
return bg;
}
}