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; } }