转出状态2
ci-userbase-service / build-and-push (push) Successful in 46s

This commit is contained in:
chenxing
2026-08-26 12:38:55 +08:00
parent 5627252eab
commit 6675ccc8d5
7 changed files with 210 additions and 9 deletions
@@ -7,6 +7,7 @@ import com.yunhai.userbase.entity.SysGroup;
import com.yunhai.platform.utils.enums.IEnum;
import com.yunhai.userbase.enums.StatusEnum;
import com.yunhai.userbase.enums.SysRoleEnum;
import com.yunhai.userbase.enums.UserGroupRelStatusEnum;
import com.yunhai.userbase.enums.UserTypeEnum;
import com.yunhai.userbase.entity.SysUser;
import com.yunhai.userbase.entity.SysUserAppRoleRel;
@@ -338,6 +339,93 @@ public class SysUserService implements ISysUserService {
sysUserGroupRelRepository.save(rel);
}
@Override
@Transactional
public void changeUserGroupState(Integer userId, Integer groupId, Integer schoolId, Integer state) {
if (state == null || (state != 0 && state != 1)) {
throw new DException("state 仅支持 0=恢复正常、1=转出");
}
// 开放接口边界:groupId 必须属于 schoolId 子树且不能等于 schoolId(禁止跨校/学校根组)
assertGroupWithinSchool(groupId, schoolId);
if (!sysUserRepository.existsById(userId)) {
throw new DException("系统用户不存在");
}
// 状态按组维度存储,必须落到具体的 (userId, groupId) 关联行;同值重复调用幂等
SysUserGroupRel rel = sysUserGroupRelRepository.findByUserIdAndGroupId(userId, groupId)
.orElseThrow(() -> new DException("该用户不在此分组中"));
rel.setStatus(state == 1 ? UserGroupRelStatusEnum.TRANSFERRED_OUT : UserGroupRelStatusEnum.NORMAL);
sysUserGroupRelRepository.save(rel);
}
@Override
public Integer getUserGroupState(Integer userId, Integer groupId, Integer schoolId) {
assertGroupWithinSchool(groupId, schoolId);
SysUserGroupRel rel = sysUserGroupRelRepository.findByUserIdAndGroupId(userId, groupId)
.orElseThrow(() -> new DException("该用户不在此分组中"));
// 存量数据 status 为 NULL,按正常返回
return rel.getStatus() == null ? UserGroupRelStatusEnum.NORMAL.getKey() : rel.getStatus().getKey();
}
@Override
@Transactional
public void transferUserToSchool(Integer userId, Integer fromSchoolId, Integer toSchoolId, Integer groupId) {
if (userId == null) {
throw new DException("用户ID不能为空");
}
if (fromSchoolId == null) {
throw new DException("原学校ID不能为空");
}
if (toSchoolId == null) {
throw new DException("目标学校ID不能为空");
}
if (groupId == null) {
throw new DException("目标组ID不能为空");
}
// 源学校必须真实存在且为学校组(目标学校在 assertGroupWithinSchool 内一并校验)
sysGroupService.assertSchoolId(fromSchoolId);
if (fromSchoolId.equals(toSchoolId)) {
throw new DException("原学校与目标学校不能相同");
}
if (!sysUserRepository.existsById(userId)) {
throw new DException("系统用户不存在");
}
// 目标组必须属于目标学校子树且不等于学校根组(复用移动/删除的边界校验)
assertGroupWithinSchool(groupId, toSchoolId);
List<Integer> fromScope = sysGroupService.listSelfAndDescendantIds(fromSchoolId);
List<Integer> toScope = sysGroupService.listSelfAndDescendantIds(toSchoolId);
List<SysUserGroupRel> rels = sysUserGroupRelRepository.findByUserId(userId);
// ① 前置:用户在源学校范围必须存在至少一条「转出」状态的组关系
boolean hasTransferOut = rels.stream().anyMatch(r ->
r.getStatus() == UserGroupRelStatusEnum.TRANSFERRED_OUT && fromScope.contains(r.getGroupId()));
if (!hasTransferOut) {
throw new DException("该用户在原学校不存在转出状态的组关系,禁止转入");
}
// ② 目标学校范围不得已有组关系(转入语义为进入全新学校,避免覆盖歧义)
boolean existsInTarget = rels.stream().anyMatch(r -> toScope.contains(r.getGroupId()));
if (existsInTarget) {
throw new DException("用户已在该学校存在组关系,禁止重复转入");
}
// ③ 删除源学校范围内全部关系行(仅源学校,其他学校保留)
List<SysUserGroupRel> toDelete = rels.stream()
.filter(r -> fromScope.contains(r.getGroupId()))
.collect(Collectors.toList());
if (!toDelete.isEmpty()) {
sysUserGroupRelRepository.deleteAll(toDelete);
}
// ④ 建立目标学校关系:目标组设为主归属,状态恢复正常(转出流程结束)
SysUserGroupRel rel = new SysUserGroupRel();
rel.setUserId(userId);
rel.setGroupId(groupId);
rel.setIsMain(IS_MAIN);
rel.setStatus(UserGroupRelStatusEnum.NORMAL);
rel.setCreateTime(LocalDateTime.now());
sysUserGroupRelRepository.save(rel);
// ⑤ 推送编辑用户订阅
publishUserEvent(PushInterfaceType.UPDATE_USER, userId);
}
@Override
public Page<SysUserVo> list(SysUserQuery query, Pageable pageable) {
// 组上下文(选中具体组):走原生 SQL 按「组内最小 sort_no」排序的专用分页,
@@ -348,7 +436,7 @@ public class SysUserService implements ISysUserService {
// 根节点 / 未选组:用 Specification 过滤,按主键 id 升序(聚簇索引顺序,无 filesort)。
Specification<SysUser> spec = buildSpecification(query);
Page<SysUser> page = sysUserRepository.findAll(spec, pageable);
return page.map(u -> convertToVo(u, null));
return page.map(u -> convertToVo(u, null, null));
}
/**
@@ -372,15 +460,19 @@ public class SysUserService implements ISysUserService {
return new PageImpl<>(Collections.emptyList(), pageable, 0);
}
// 保持 DB 返回的排序顺序,并记录每个用户的「组范围内最小 sort_no」
// 保持 DB 返回的排序顺序,并记录每个用户的「组范围内最小 sort_no」与「组内状态(任一转出即转出)」
List<Integer> orderedIds = new ArrayList<>(rows.size());
Map<Integer, Integer> sortNoMap = new LinkedHashMap<>();
Map<Integer, Integer> groupStatusMap = new LinkedHashMap<>();
for (Object[] row : rows) {
Integer userId = ((Number) row[0]).intValue();
Object minObj = row[1]; // MIN(sort_no) 可能为空(该用户在组范围内均未设置排序号)
Integer sortNo = minObj == null ? null : ((Number) minObj).intValue();
Object statusObj = row[2]; // MAX(COALESCE(status,0)):组范围内任一条转出即视为转出
Integer groupStatus = statusObj == null ? null : ((Number) statusObj).intValue();
orderedIds.add(userId);
sortNoMap.put(userId, sortNo);
groupStatusMap.put(userId, groupStatus);
}
// 按有序 ID 取实体(findAllById 返回顺序不可靠,需按 orderedIds 重排)
@@ -392,7 +484,7 @@ public class SysUserService implements ISysUserService {
for (Integer id : orderedIds) {
SysUser u = userMap.get(id);
if (u != null) {
voList.add(convertToVo(u, sortNoMap.get(id)));
voList.add(convertToVo(u, sortNoMap.get(id), groupStatusMap.get(id)));
}
}
return new PageImpl<>(voList, pageable, idPage.getTotalElements());
@@ -426,14 +518,29 @@ public class SysUserService implements ISysUserService {
LocalDateTime now = LocalDateTime.now();
for (Integer userId : userIds) {
// 覆盖式重建前先读旧关系,用于继承组内状态(转出标记):
// 行级继承——同组照抄旧状态(管理端重设组不误改);用户级继承——新增组在用户处于转出流程中(存在任一转出行)时继承「转出」
List<SysUserGroupRel> oldRels = sysUserGroupRelRepository.findByUserId(userId);
Map<Integer, UserGroupRelStatusEnum> oldStatusByGroup = new HashMap<>();
boolean inTransferFlow = false;
for (SysUserGroupRel oldRel : oldRels) {
oldStatusByGroup.put(oldRel.getGroupId(), oldRel.getStatus());
if (oldRel.getStatus() == UserGroupRelStatusEnum.TRANSFERRED_OUT) {
inTransferFlow = true;
}
}
sysUserGroupRelRepository.deleteByUserId(userId);
List<SysUserGroupRel> rels = new ArrayList<>(groupIds.size());
for (int i = 0; i < groupIds.size(); i++) {
Integer groupId = groupIds.get(i);
SysUserGroupRel rel = new SysUserGroupRel();
rel.setUserId(userId);
rel.setGroupId(groupIds.get(i));
rel.setGroupId(groupId);
rel.setIsMain(i == 0 ? IS_MAIN : 0);
rel.setCreateTime(now);
rel.setStatus(oldStatusByGroup.containsKey(groupId)
? oldStatusByGroup.get(groupId)
: (inTransferFlow ? UserGroupRelStatusEnum.TRANSFERRED_OUT : UserGroupRelStatusEnum.NORMAL));
rels.add(rel);
}
sysUserGroupRelRepository.saveAll(rels);
@@ -885,18 +992,19 @@ public class SysUserService implements ISysUserService {
}
private SysUserVo convertToVo(SysUser entity) {
return convertToVo(entity, null);
return convertToVo(entity, null, null);
}
private SysUserVo convertToVo(SysUser entity, Integer sortNo) {
private SysUserVo convertToVo(SysUser entity, Integer sortNo, Integer groupStatus) {
SysUserVo vo = new SysUserVo();
BeanUtils.copyProperties(entity, vo);
// disabled 为枚举,需要单独映射为 key(与 GroupTypeEnum 的 type 处理一致)
vo.setDisabled(entity.getDisabled() == null ? null : entity.getDisabled().getKey());
// type 为枚举,同样映射为 key
vo.setType(entity.getType() == null ? null : entity.getType().getKey());
// 当前组维度的排序号;根节点 / 未选组时为 null
// 当前组维度的排序号与组内状态;根节点 / 未选组时为 null
vo.setSortNo(sortNo);
vo.setGroupStatus(groupStatus);
return vo;
}