package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.dto.MenuTreeNodeVo; import com.sinata.appointment.dto.RoleManageCreateRequest; import com.sinata.appointment.dto.RoleManageDetailVo; import com.sinata.appointment.dto.RoleManageListVo; import com.sinata.appointment.dto.RoleManageUpdateRequest; import com.sinata.appointment.dto.RoleUserCountRow; import com.sinata.appointment.entity.SysMenu; import com.sinata.appointment.entity.SysRole; import com.sinata.appointment.entity.SysRoleMenu; import com.sinata.appointment.entity.SysUserRole; import com.sinata.appointment.exception.BizException; import com.sinata.appointment.mapper.SysMenuMapper; import com.sinata.appointment.mapper.SysRoleMapper; import com.sinata.appointment.mapper.SysRoleMenuMapper; import com.sinata.appointment.mapper.SysUserRoleMapper; import com.sinata.appointment.util.UuidAssert; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor public class SysRoleManageApplicationService { private final SysRoleMapper sysRoleMapper; private final SysMenuMapper sysMenuMapper; private final SysRoleMenuMapper sysRoleMenuMapper; private final SysUserRoleMapper sysUserRoleMapper; public List menuTree() { List all = sysMenuMapper.selectList( new LambdaQueryWrapper() .eq(SysMenu::getDelFlag, 0) .orderByAsc(SysMenu::getSortOrder) .orderByAsc(SysMenu::getId)); Map nodes = new LinkedHashMap<>(); for (SysMenu m : all) { MenuTreeNodeVo n = new MenuTreeNodeVo(); n.setId(m.getId()); n.setLabel(m.getMenuName()); n.setMenuType(m.getMenuType()); n.setPermCode(m.getPermCode()); n.setRoutePath(m.getRoutePath()); nodes.put(m.getId(), n); } List roots = new ArrayList<>(); for (SysMenu m : all) { MenuTreeNodeVo n = nodes.get(m.getId()); long pid = m.getPid() == null ? 0L : m.getPid(); if (pid == 0L) { roots.add(n); } else { MenuTreeNodeVo p = nodes.get(pid); if (p != null) { p.getChildren().add(n); } else { roots.add(n); } } } return roots; } public IPage pageRoles(long page, long size, String roleName) { LambdaQueryWrapper q = new LambdaQueryWrapper<>(); q.eq(SysRole::getDelFlag, 0); if (StringUtils.hasText(roleName)) { q.like(SysRole::getRoleName, roleName.trim()); } q.orderByDesc(SysRole::getId); Page p = sysRoleMapper.selectPage(new Page<>(page, size), q); List ids = p.getRecords().stream().map(SysRole::getId).toList(); Map countMap = new HashMap<>(); if (!ids.isEmpty()) { for (RoleUserCountRow row : sysUserRoleMapper.countUsersGroupedByRoleId(ids)) { long c = row.getUserCount() == null ? 0L : row.getUserCount(); countMap.put(row.getRoleId(), c); } } return p.convert( r -> { RoleManageListVo vo = new RoleManageListVo(); vo.setId(r.getId()); vo.setUuid(r.getUuid()); vo.setRoleName(r.getRoleName()); vo.setRemark(r.getRemark()); vo.setCreateTime(r.getCreateTime()); vo.setUserCount(countMap.getOrDefault(r.getId(), 0L)); return vo; }); } public RoleManageDetailVo getDetail(Long id, String uuid) { SysRole r = loadActiveWithUuid(id, uuid); RoleManageDetailVo vo = new RoleManageDetailVo(); vo.setId(r.getId()); vo.setUuid(r.getUuid()); vo.setRoleName(r.getRoleName()); vo.setRemark(r.getRemark()); vo.setCreateTime(r.getCreateTime()); vo.setMenuIds(listMenuIdsForRole(id)); return vo; } @Transactional(rollbackFor = Exception.class) public Map create(RoleManageCreateRequest req) { String roleName = req.getRoleName().trim(); validateRoleNameUnique(roleName, null); String code = generateUniqueRoleCode(); SysRole r = new SysRole(); r.setRoleCode(code); r.setRoleName(roleName); r.setRemark(trimToNull(req.getRemark())); sysRoleMapper.insert(r); SysRole fresh = sysRoleMapper.selectById(r.getId()); if (fresh == null || !StringUtils.hasText(fresh.getUuid())) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "创建后读取角色失败"); } replaceRoleMenus(fresh.getId(), req.getMenuIds()); Map m = new HashMap<>(); m.put("id", fresh.getId()); m.put("uuid", fresh.getUuid()); return m; } @Transactional(rollbackFor = Exception.class) public void update(Long id, RoleManageUpdateRequest req) { SysRole r = loadActiveWithUuid(id, req.getUuid()); String roleName = req.getRoleName().trim(); validateRoleNameUnique(roleName, id); r.setRoleName(roleName); r.setRemark(trimToNull(req.getRemark())); sysRoleMapper.updateById(r); replaceRoleMenus(id, req.getMenuIds()); } @Transactional(rollbackFor = Exception.class) public void delete(Long id, String uuid) { SysRole r = loadActiveWithUuid(id, uuid); long n = sysUserRoleMapper.selectCount( new LambdaQueryWrapper() .eq(SysUserRole::getRoleId, id) .eq(SysUserRole::getDelFlag, 0)); if (n > 0) { throw new BizException(BizErrorCode.ROLE_HAS_BOUND_USERS, "该角色下仍有关联账号,无法删除"); } sysRoleMenuMapper.delete( new LambdaQueryWrapper() .eq(SysRoleMenu::getRoleId, id) .eq(SysRoleMenu::getDelFlag, 0)); int affected = sysRoleMapper.deleteById(r.getId()); if (affected <= 0) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "删除失败,请刷新后重试"); } } private List listMenuIdsForRole(Long roleId) { return sysRoleMenuMapper .selectList( new LambdaQueryWrapper() .eq(SysRoleMenu::getRoleId, roleId) .eq(SysRoleMenu::getDelFlag, 0)) .stream() .map(SysRoleMenu::getMenuId) .toList(); } private void replaceRoleMenus(Long roleId, List menuIds) { validateMenuIds(menuIds); sysRoleMenuMapper.delete( new LambdaQueryWrapper() .eq(SysRoleMenu::getRoleId, roleId) .eq(SysRoleMenu::getDelFlag, 0)); if (menuIds == null || menuIds.isEmpty()) { return; } for (Long mid : menuIds.stream().distinct().toList()) { SysRoleMenu rm = new SysRoleMenu(); rm.setRoleId(roleId); rm.setMenuId(mid); sysRoleMenuMapper.insert(rm); } } private void validateMenuIds(List menuIds) { if (menuIds == null || menuIds.isEmpty()) { return; } List distinct = menuIds.stream().distinct().toList(); Long cnt = sysMenuMapper.selectCount( new LambdaQueryWrapper() .in(SysMenu::getId, distinct) .eq(SysMenu::getDelFlag, 0)); if (!Objects.equals(cnt, (long) distinct.size())) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "存在无效的菜单 id"); } } private SysRole loadActiveWithUuid(Long id, String uuid) { SysRole r = sysRoleMapper.selectById(id); if (r == null || r.getDelFlag() != null && r.getDelFlag() == 1) { throw new BizException(BizErrorCode.RESOURCE_VERIFY_FAILED, "资源不存在或无权访问"); } UuidAssert.match(r, uuid); return r; } private String generateUniqueRoleCode() { for (int i = 0; i < 20; i++) { String code = "C" + UUID.randomUUID() .toString() .replace("-", "") .substring(0, 12) .toUpperCase(); long exists = sysRoleMapper.selectCount( new LambdaQueryWrapper() .eq(SysRole::getRoleCode, code) .eq(SysRole::getDelFlag, 0)); if (exists == 0) { return code; } } throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "无法生成唯一角色编码"); } private void validateRoleNameUnique(String roleName, Long excludeRoleId) { LambdaQueryWrapper query = new LambdaQueryWrapper() .eq(SysRole::getDelFlag, 0) .eq(SysRole::getRoleName, roleName); if (excludeRoleId != null) { query.ne(SysRole::getId, excludeRoleId); } long exists = sysRoleMapper.selectCount(query); if (exists > 0) { throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "角色名称已存在"); } } private static String trimToNull(String s) { if (!StringUtils.hasText(s)) { return null; } String t = s.trim(); return t.isEmpty() ? null : t; } }