在初始认证基础上,新增完整的 RBAC 权限模型(角色、权限、菜单三级管理), 集成审计日志、接口限流、登录失败锁定、Refresh Token 机制、Redis 分布式缓存与锁、 RocketMQ 消息队列,并引入 Flyway 数据库版本管理,同时补充项目文档与使用示例
344 lines
12 KiB
Java
344 lines
12 KiB
Java
package com.aisi.template.service.impl;
|
|
|
|
import com.aisi.template.domain.dto.RoleDto;
|
|
import com.aisi.template.domain.dto.RoleQueryDto;
|
|
import com.aisi.template.domain.entity.SysPermission;
|
|
import com.aisi.template.domain.entity.SysRole;
|
|
import com.aisi.template.domain.dto.PageResult;
|
|
import com.aisi.template.domain.vo.PermissionVo;
|
|
import com.aisi.template.domain.vo.RoleVo;
|
|
import com.aisi.template.exception.BusinessException;
|
|
import com.aisi.template.repository.SysPermissionRepository;
|
|
import com.aisi.template.repository.SysRoleRepository;
|
|
import com.aisi.template.service.SysRoleService;
|
|
import jakarta.persistence.criteria.Predicate;
|
|
import lombok.RequiredArgsConstructor;
|
|
import org.springframework.data.domain.Page;
|
|
import org.springframework.data.domain.PageRequest;
|
|
import org.springframework.data.jpa.domain.Specification;
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
|
|
/**
|
|
* 系统角色服务实现类
|
|
* 提供角色的 CRUD 操作、权限分配、查询等功能
|
|
*
|
|
* 主要功能:
|
|
* 1. 角色基本操作:创建、更新、删除、查询
|
|
* 2. 权限管理:为角色分配权限、获取角色权限
|
|
* 3. 分页查询:支持条件查询和分页
|
|
*
|
|
* @author Claude
|
|
* @since 2024-04-09
|
|
*/
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class SysRoleServiceImpl implements SysRoleService {
|
|
|
|
/**
|
|
* 角色数据访问接口
|
|
* 用于角色的数据库操作
|
|
*/
|
|
private final SysRoleRepository roleRepository;
|
|
|
|
/**
|
|
* 权限数据访问接口
|
|
* 用于权限的数据库操作
|
|
*/
|
|
private final SysPermissionRepository permissionRepository;
|
|
|
|
/**
|
|
* 创建角色
|
|
* 步骤:
|
|
* 1. 校验角色编码是否已存在
|
|
* 2. 构建角色实体对象
|
|
* 3. 保存到数据库
|
|
*
|
|
* @param roleDto 角色数据传输对象
|
|
* @return 创建的角色视图对象
|
|
* @throws BusinessException 当角色编码已存在时抛出异常
|
|
*/
|
|
@Override
|
|
@Transactional(rollbackFor = Exception.class)
|
|
public RoleVo create(RoleDto roleDto) {
|
|
// 1. 检查角色编码是否已存在
|
|
roleRepository.findByRoleCode(roleDto.getRoleCode()).ifPresent(role -> {
|
|
throw new BusinessException("角色编码已存在: " + role.getRoleCode());
|
|
});
|
|
|
|
// 2. 构建角色实体
|
|
SysRole role = new SysRole();
|
|
role.setRoleCode(roleDto.getRoleCode());
|
|
role.setRoleName(roleDto.getRoleName());
|
|
role.setDescription(roleDto.getDescription());
|
|
role.setSortOrder(roleDto.getSortOrder());
|
|
role.setStatus(roleDto.getStatus());
|
|
|
|
// 3. 保存角色到数据库
|
|
SysRole savedRole = roleRepository.save(role);
|
|
return convertToVo(savedRole);
|
|
}
|
|
|
|
/**
|
|
* 更新角色
|
|
* 步骤:
|
|
* 1. 检查角色是否存在
|
|
* 2. 如果修改了角色编码,检查新编码是否已被使用
|
|
* 3. 更新角色信息
|
|
*
|
|
* @param id 角色ID
|
|
* @param roleDto 角色数据传输对象
|
|
* @return 更新后的角色视图对象
|
|
* @throws BusinessException 当角色不存在或编码冲突时抛出异常
|
|
*/
|
|
@Override
|
|
@Transactional(rollbackFor = Exception.class)
|
|
public RoleVo update(Long id, RoleDto roleDto) {
|
|
// 1. 查询角色是否存在
|
|
SysRole role = roleRepository.findById(id)
|
|
.orElseThrow(() -> new BusinessException("角色不存在: " + id));
|
|
|
|
// 2. 如果修改了角色编码,检查新编码是否已被使用
|
|
if (!role.getRoleCode().equals(roleDto.getRoleCode())) {
|
|
roleRepository.findByRoleCode(roleDto.getRoleCode()).ifPresent(r -> {
|
|
throw new BusinessException("角色编码已存在: " + r.getRoleCode());
|
|
});
|
|
}
|
|
|
|
// 3. 更新角色信息
|
|
role.setRoleCode(roleDto.getRoleCode());
|
|
role.setRoleName(roleDto.getRoleName());
|
|
role.setDescription(roleDto.getDescription());
|
|
role.setSortOrder(roleDto.getSortOrder());
|
|
role.setStatus(roleDto.getStatus());
|
|
|
|
// 4. 保存更新
|
|
SysRole savedRole = roleRepository.save(role);
|
|
return convertToVo(savedRole);
|
|
}
|
|
|
|
/**
|
|
* 删除角色
|
|
* 步骤:
|
|
* 1. 检查角色是否存在
|
|
* 2. 删除角色(关联的权限关系会自动级联删除)
|
|
*
|
|
* 注意:如果角色已分配给用户,需要先解除关联
|
|
*
|
|
* @param id 角色ID
|
|
* @throws BusinessException 当角色不存在时抛出异常
|
|
*/
|
|
@Override
|
|
@Transactional(rollbackFor = Exception.class)
|
|
public void delete(Long id) {
|
|
// 1. 检查角色是否存在
|
|
if (!roleRepository.existsById(id)) {
|
|
throw new BusinessException("角色不存在: " + id);
|
|
}
|
|
|
|
// 2. 删除角色
|
|
roleRepository.deleteById(id);
|
|
}
|
|
|
|
/**
|
|
* 根据ID获取角色
|
|
* 步骤:
|
|
* 1. 查询角色基本信息
|
|
* 2. 使用 JOIN FETCH 一次性加载关联的权限(避免 N+1 问题)
|
|
* 3. 转换为视图对象返回
|
|
*
|
|
* @param id 角色ID
|
|
* @return 角色视图对象
|
|
* @throws BusinessException 当角色不存在时抛出异常
|
|
*/
|
|
@Override
|
|
public RoleVo getById(Long id) {
|
|
// 1. 查询角色(使用 JOIN FETCH 避免懒加载问题)
|
|
SysRole role = roleRepository.findByIdWithPermissions(id)
|
|
.orElseThrow(() -> new BusinessException("角色不存在: " + id));
|
|
|
|
// 2. 转换为视图对象
|
|
return convertToVo(role);
|
|
}
|
|
|
|
/**
|
|
* 获取所有角色
|
|
* 步骤:
|
|
* 1. 查询所有角色
|
|
* 2. 使用 JOIN FETCH 预加载权限
|
|
* 3. 转换为视图对象列表
|
|
*
|
|
* @return 角色视图对象列表
|
|
*/
|
|
@Override
|
|
public List<RoleVo> getAllRoles() {
|
|
// 1. 查询所有角色(使用 JOIN FETCH 避免懒加载问题)
|
|
List<SysRole> roles = roleRepository.findAllWithPermissions();
|
|
|
|
// 2. 转换为视图对象列表
|
|
return roles.stream()
|
|
.map(this::convertToVo)
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* 分页查询角色
|
|
* 步骤:
|
|
* 1. 构建动态查询条件(支持角色编码、名称、状态)
|
|
* 2. 执行分页查询
|
|
* 3. 转换结果为视图对象
|
|
*
|
|
* @param queryDto 查询条件对象
|
|
* @param page 页码(从 0 开始)
|
|
* @param size 每页大小
|
|
* @return 分页结果
|
|
*/
|
|
@Override
|
|
public PageResult<RoleVo> queryRoles(RoleQueryDto queryDto, int page, int size) {
|
|
// 1. 构建动态查询条件
|
|
Specification<SysRole> spec = (root, query, cb) -> {
|
|
List<Predicate> predicates = new ArrayList<>();
|
|
|
|
// 1.1 角色编码模糊查询
|
|
if (queryDto.getRoleCode() != null && !queryDto.getRoleCode().isEmpty()) {
|
|
predicates.add(cb.like(root.get("roleCode"), "%" + queryDto.getRoleCode() + "%"));
|
|
}
|
|
|
|
// 1.2 角色名称模糊查询
|
|
if (queryDto.getRoleName() != null && !queryDto.getRoleName().isEmpty()) {
|
|
predicates.add(cb.like(root.get("roleName"), "%" + queryDto.getRoleName() + "%"));
|
|
}
|
|
|
|
// 1.3 状态精确查询
|
|
if (queryDto.getStatus() != null) {
|
|
predicates.add(cb.equal(root.get("status"), queryDto.getStatus()));
|
|
}
|
|
|
|
return cb.and(predicates.toArray(new Predicate[0]));
|
|
};
|
|
|
|
// 2. 执行分页查询
|
|
Page<SysRole> rolePage = roleRepository.findAll(spec, PageRequest.of(page, size));
|
|
|
|
// 3. 转换为分页结果
|
|
return PageResult.of(
|
|
rolePage.getContent().stream().map(this::convertToVo).collect(Collectors.toList()),
|
|
rolePage.getTotalElements(),
|
|
rolePage.getNumber(),
|
|
rolePage.getSize()
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 为角色分配权限
|
|
* 步骤:
|
|
* 1. 查询角色是否存在
|
|
* 2. 根据权限ID列表查询所有权限
|
|
* 3. 清空角色原有的权限
|
|
* 4. 添加新的权限
|
|
* 5. 保存更新
|
|
*
|
|
* @param roleId 角色ID
|
|
* @param permissionIds 权限ID列表
|
|
* @throws BusinessException 当角色或权限不存在时抛出异常
|
|
*/
|
|
@Override
|
|
@Transactional(rollbackFor = Exception.class)
|
|
public void assignPermissions(Long roleId, List<Long> permissionIds) {
|
|
// 1. 检查角色是否存在
|
|
SysRole role = roleRepository.findById(roleId)
|
|
.orElseThrow(() -> new BusinessException("角色不存在: " + roleId));
|
|
|
|
// 2. 查询所有权限(如果权限不存在会抛出异常)
|
|
Set<SysPermission> permissions = permissionIds.stream()
|
|
.map(permissionId -> permissionRepository.findById(permissionId)
|
|
.orElseThrow(() -> new BusinessException("权限不存在: " + permissionId)))
|
|
.collect(Collectors.toSet());
|
|
|
|
// 3. 清空原有权限并添加新权限
|
|
role.getPermissions().clear();
|
|
role.getPermissions().addAll(permissions);
|
|
|
|
// 4. 保存更新
|
|
roleRepository.save(role);
|
|
}
|
|
|
|
/**
|
|
* 获取角色的权限ID列表
|
|
* 步骤:
|
|
* 1. 查询角色(使用 JOIN FETCH 加载权限)
|
|
* 2. 提取所有权限的ID
|
|
*
|
|
* @param roleId 角色ID
|
|
* @return 权限ID列表
|
|
* @throws BusinessException 当角色不存在时抛出异常
|
|
*/
|
|
@Override
|
|
public List<Long> getRolePermissionIds(Long roleId) {
|
|
// 1. 查询角色及权限
|
|
SysRole role = roleRepository.findByIdWithPermissions(roleId)
|
|
.orElseThrow(() -> new BusinessException("角色不存在: " + roleId));
|
|
|
|
// 2. 提取权限ID列表
|
|
return role.getPermissions().stream()
|
|
.map(SysPermission::getId)
|
|
.collect(Collectors.toList());
|
|
}
|
|
|
|
/**
|
|
* 将角色实体转换为视图对象
|
|
* 步骤:
|
|
* 1. 复制基本信息
|
|
* 2. 转换权限列表
|
|
*
|
|
* @param role 角色实体
|
|
* @return 角色视图对象
|
|
*/
|
|
private RoleVo convertToVo(SysRole role) {
|
|
RoleVo vo = new RoleVo();
|
|
vo.setId(role.getId());
|
|
vo.setRoleCode(role.getRoleCode());
|
|
vo.setRoleName(role.getRoleName());
|
|
vo.setDescription(role.getDescription());
|
|
vo.setSortOrder(role.getSortOrder());
|
|
vo.setStatus(role.getStatus());
|
|
vo.setCreatedAt(role.getCreatedAt());
|
|
vo.setUpdatedAt(role.getUpdatedAt());
|
|
|
|
// 1. 如果有权限,转换为视图对象
|
|
if (role.getPermissions() != null && !role.getPermissions().isEmpty()) {
|
|
Set<PermissionVo> permissionVos = role.getPermissions().stream()
|
|
.map(this::convertToPermissionVo)
|
|
.collect(Collectors.toSet());
|
|
vo.setPermissions(permissionVos);
|
|
}
|
|
|
|
return vo;
|
|
}
|
|
|
|
/**
|
|
* 将权限实体转换为视图对象
|
|
*
|
|
* @param permission 权限实体
|
|
* @return 权限视图对象
|
|
*/
|
|
private PermissionVo convertToPermissionVo(SysPermission permission) {
|
|
PermissionVo vo = new PermissionVo();
|
|
vo.setId(permission.getId());
|
|
vo.setPermissionCode(permission.getPermissionCode());
|
|
vo.setPermissionName(permission.getPermissionName());
|
|
vo.setResource(permission.getResource());
|
|
vo.setAction(permission.getAction());
|
|
vo.setDescription(permission.getDescription());
|
|
vo.setStatus(permission.getStatus());
|
|
vo.setCreatedAt(permission.getCreatedAt());
|
|
vo.setUpdatedAt(permission.getUpdatedAt());
|
|
return vo;
|
|
}
|
|
}
|