package com.jtzx.crm.module.sys.service; import cc.uncarbon.framework.core.constant.HelioConstant; import cc.uncarbon.framework.core.context.UserContextHolder; import cc.uncarbon.framework.core.exception.BusinessException; import cc.uncarbon.framework.core.function.StreamFunction; import cc.uncarbon.framework.core.page.PageParam; import cc.uncarbon.framework.core.page.PageResult; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.text.CharSequenceUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.jtzx.crm.module.sys.constant.SysConstant; import com.jtzx.crm.module.sys.entity.SysRoleEntity; import com.jtzx.crm.module.sys.entity.SysUserRoleRelationEntity; import com.jtzx.crm.module.sys.enums.SysErrorEnum; import com.jtzx.crm.module.sys.mapper.SysRoleMapper; import com.jtzx.crm.module.sys.mapper.SysUserRoleRelationMapper; import com.jtzx.crm.module.sys.model.interior.UserRoleContainer; import com.jtzx.crm.module.sys.model.request.AdminBindRoleMenuRelationDTO; import com.jtzx.crm.module.sys.model.request.AdminInsertOrUpdateSysRoleDTO; import com.jtzx.crm.module.sys.model.request.AdminListSysRoleDTO; import com.jtzx.crm.module.sys.model.response.SysRoleBO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.stream.Collectors; /** * 后台角色 */ @RequiredArgsConstructor @Service @Slf4j public class SysRoleService { private final SysRoleMapper sysRoleMapper; private final SysUserRoleRelationMapper sysUserRoleRelationMapper; private final SysRoleMenuRelationService sysRoleMenuRelationService; private final SysMenuService sysMenuService; /** * 后台管理-分页列表 */ public PageResult adminList(PageParam pageParam, AdminListSysRoleDTO dto) { Page entityPage = sysRoleMapper.selectPage( new Page<>(pageParam.getPageNum(), pageParam.getPageSize()), new QueryWrapper() .lambda() // 名称 .like(CharSequenceUtil.isNotBlank(dto.getTitle()), SysRoleEntity::getTitle, CharSequenceUtil.cleanBlank(dto.getTitle())) // 值 .like(CharSequenceUtil.isNotBlank(dto.getValue()), SysRoleEntity::getValue, CharSequenceUtil.cleanBlank(dto.getValue())) // 排序 .orderByDesc(SysRoleEntity::getCreatedAt) ); return this.entityPage2BOPage(entityPage); } /** * 根据 ID 取详情 * * @param id 主键ID * @return null or BO */ public SysRoleBO getOneById(Long id) { return this.getOneById(id, false); } /** * 根据 ID 取详情 * * @param id 主键ID * @param throwIfInvalidId 是否在 ID 无效时抛出异常 * @return null or BO */ public SysRoleBO getOneById(Long id, boolean throwIfInvalidId) throws BusinessException { SysRoleEntity entity = sysRoleMapper.selectById(id); if (throwIfInvalidId) { SysErrorEnum.INVALID_ID.assertNotNull(entity); } return this.entity2BO(entity, true); } /** * 后台管理-新增 */ @Transactional(rollbackFor = Exception.class) public void adminInsert(AdminInsertOrUpdateSysRoleDTO dto) { log.info("[后台管理-新增后台角色] >> 入参={}", dto); preInsertOrUpdateCheck(dto); this.checkExistence(dto); dto.setId(null); SysRoleEntity entity = new SysRoleEntity(); BeanUtil.copyProperties(dto, entity); sysRoleMapper.insert(entity); } /** * 后台管理-编辑 */ @Transactional(rollbackFor = Exception.class) public void adminUpdate(AdminInsertOrUpdateSysRoleDTO dto) { log.info("[后台管理-编辑后台角色] >> 入参={}", dto); preInsertOrUpdateCheck(dto); this.checkExistence(dto); // 暂不检查该角色是否为当前用户关联的角色 SysRoleEntity entity = new SysRoleEntity(); BeanUtil.copyProperties(dto, entity); sysRoleMapper.updateById(entity); } /** * 后台管理-删除 */ @Transactional(rollbackFor = Exception.class) public void adminDelete(Collection ids) { log.info("[后台管理-删除后台角色] >> 入参={}", ids); preDeleteCheck(ids); sysRoleMapper.deleteByIds(ids); } /** * 后台管理-绑定角色与菜单关联关系 * * @return 新菜单ID集合对应的权限名 */ @Transactional(rollbackFor = Exception.class) public Set adminBindMenus(AdminBindRoleMenuRelationDTO dto) { preBindRoleMenuRelationCheck(dto); Set newPermissions = sysMenuService.listPermissionsByMenuIds(dto.getMenuIds()); sysRoleMenuRelationService.cleanAndBind(dto.getRoleId(), dto.getMenuIds()); return newPermissions; } /** * 后台管理-下拉框数据 */ public List adminSelectOptions() { List entityList = sysRoleMapper.selectList( new QueryWrapper() .lambda() // 只取特定字段 .select(SysRoleEntity::getId, SysRoleEntity::getTitle) // 排序 .orderByAsc(SysRoleEntity::getId) ); // 无需填充菜单IDs return entityList2BOs(entityList, false); } /** * 后台管理-删除指定租户的特定角色 * @param tenantIds 租户IDs,非主键ID,不能为空 * @param roleValues 角色值集合,可以为空 */ @Transactional(rollbackFor = Exception.class) public void adminDeleteTenantRoles(Collection tenantIds, Collection roleValues) { if (CollUtil.isEmpty(tenantIds)) { return; } sysRoleMapper.delete( new QueryWrapper() .lambda() // 租户ID .in(SysRoleEntity::getTenantId, tenantIds) // 值相符 .in(CollUtil.isNotEmpty(roleValues), SysRoleEntity::getValue, roleValues) ); } /** * 取用户ID拥有角色对应的 角色ID-角色名 map * * @param userId 用户ID * @return 失败返回空 map */ public Map getRoleMapByUserId(Long userId) { List relationEntityList = sysUserRoleRelationMapper.selectList( new LambdaQueryWrapper<>(SysUserRoleRelationEntity.class) .select( SysUserRoleRelationEntity::getRoleId, SysUserRoleRelationEntity::getRoleValue ) .eq(SysUserRoleRelationEntity::getUserId, userId) ); if (CollUtil.isEmpty(relationEntityList)) { return Collections.emptyMap(); } // 根据角色Ids取 map return relationEntityList .stream() .collect(Collectors.toMap(SysUserRoleRelationEntity::getRoleId, SysUserRoleRelationEntity::getRoleValue, StreamFunction.ignoredThrowingMerger())); } /** * 取当前用户关联角色信息 * 仅内部使用 */ protected UserRoleContainer getCurrentUserRoleContainer() { return getSpecifiedUserRoleContainer(UserContextHolder.getUserId()); } /** * 取指定用户关联角色信息 * 仅内部使用 */ protected UserRoleContainer getSpecifiedUserRoleContainer(Long specifiedUserId) { if (specifiedUserId == null) { throw new IllegalArgumentException("userId不能为空"); } Set userRoleIds = sysUserRoleRelationMapper.selectList( new QueryWrapper() .lambda() .select(SysUserRoleRelationEntity::getRoleId) .eq(SysUserRoleRelationEntity::getUserId, specifiedUserId) ).stream().map(SysUserRoleRelationEntity::getRoleId).collect(Collectors.toSet()); List userRoles = null; if (CollUtil.isNotEmpty(userRoleIds)) { userRoles = sysRoleMapper.selectByIds(userRoleIds); } if (CollUtil.isEmpty(userRoles)) { userRoles = Collections.emptyList(); } return new UserRoleContainer(userRoleIds, userRoles); } /* ---------------------------------------------------------------- 私有方法 private methods ---------------------------------------------------------------- */ /** * 实体转 BO * * @param entity 实体 * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO * @return BO */ private SysRoleBO entity2BO(SysRoleEntity entity, boolean fillMenuIds) { if (entity == null) { return null; } SysRoleBO bo = new SysRoleBO(); BeanUtil.copyProperties(entity, bo); // 可以在此处为BO填充字段 if (fillMenuIds) { bo.setMenuIds(sysRoleMenuRelationService.listMenuIdsByRoleIds(Collections.singleton(bo.getId()))); } return bo; } /** * 实体 List 转 BO List * * @param entityList 实体 List * @param fillMenuIds 是否根据实体ID,查询关联菜单IDs并填充到BO * @return BO List */ private List entityList2BOs(List entityList, boolean fillMenuIds) { // 深拷贝 List ret = new ArrayList<>(entityList.size()); entityList.forEach( entity -> ret.add(this.entity2BO(entity, fillMenuIds)) ); return ret; } /** * 实体分页转 BO 分页 * * @param entityPage 实体分页 * @return BO 分页 */ private PageResult entityPage2BOPage(Page entityPage) { return new PageResult() .setCurrent(entityPage.getCurrent()) .setSize(entityPage.getSize()) .setTotal(entityPage.getTotal()) // 需填充菜单IDs .setRecords(this.entityList2BOs(entityPage.getRecords(), true)); } /** * 检查是否已存在相同数据 * * @param dto DTO */ private void checkExistence(AdminInsertOrUpdateSysRoleDTO dto) { SysRoleEntity existingEntity = sysRoleMapper.selectOne( new QueryWrapper() .lambda() // 仅取主键ID .select(SysRoleEntity::getId) // 名称相同 .eq(SysRoleEntity::getTitle, dto.getTitle()) .last(HelioConstant.CRUD.SQL_LIMIT_1) ); if (existingEntity != null && !existingEntity.getId().equals(dto.getId())) { throw new BusinessException(400, "已存在相同后台角色,请重新输入"); } } /** * 新增/编辑后台角色信息前检查 */ private void preInsertOrUpdateCheck(AdminInsertOrUpdateSysRoleDTO dto) { if (SysConstant.SUPER_ADMIN_ROLE_VALUE.equalsIgnoreCase(dto.getValue())) { // 角色编码不能为SuperAdmin throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, SysConstant.SUPER_ADMIN_ROLE_VALUE); } boolean isUpdating = Objects.nonNull(dto.getId()); if (isUpdating) { SysRoleEntity existingRole = sysRoleMapper.selectById(dto.getId()); SysErrorEnum.INVALID_ID.assertNotNull(existingRole); if (existingRole.isAdmin()) { // 原来角色编码为SuperAdmin或Admin的,不能被改变 throw new BusinessException(SysErrorEnum.ROLE_VALUE_CANNOT_BE, existingRole.getValue()); } } } /** * 删除后台角色前检查 */ private void preDeleteCheck(Collection ids) { if (CollUtil.contains(ids, SysConstant.SUPER_ADMIN_ROLE_ID)) { throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SUPER_ADMIN_ROLE); } List existingEntityList = sysRoleMapper.selectByIds(ids); for (SysRoleEntity item : existingEntityList) { if (item.isAdmin()) { throw new BusinessException(SysErrorEnum.CANNOT_OPERATE_THIS_USER); } } UserRoleContainer currentUser = getCurrentUserRoleContainer(); if (CollUtil.containsAny(currentUser.getRelatedRoleIds(), ids)) { throw new BusinessException(SysErrorEnum.CANNOT_DELETE_SELF_ROLE); } } /** * 绑定后台角色与菜单关联关系前检查 * 防止越权访问漏洞 */ private void preBindRoleMenuRelationCheck(AdminBindRoleMenuRelationDTO dto) { UserRoleContainer currentUser = getCurrentUserRoleContainer(); if (SysConstant.SUPER_ADMIN_ROLE_ID.equals(dto.getRoleId())) { throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_SUPER_ADMIN_ROLE); } if (CollUtil.contains(currentUser.getRelatedRoleIds(), dto.getRoleId())) { // 不能动自身角色 throw new BusinessException(SysErrorEnum.CANNOT_BIND_MENUS_FOR_SELF); } if (CollUtil.isNotEmpty(dto.getMenuIds()) && !currentUser.isAdmin()) { // 超级管理员之外的角色,都需要校验自身菜单范围是否满足输入值 Set visibleMenuIds = sysRoleMenuRelationService.listMenuIdsByRoleIds(currentUser.getRelatedRoleIds()); if (!CollUtil.containsAll(visibleMenuIds, dto.getMenuIds())) { // 可能存在超自身权限赋权 throw new BusinessException(SysErrorEnum.BEYOND_AUTHORITY_BIND_MENUS); } } } }