feat(database): 重构用户角色关联及权限体系

- 新增 BizException 构造函数支持自定义消息
- 优化 SQL 脚本结构,明确表设计最佳实践
- 修改 t_user、t_role 等表字段类型与索引策略
- 引入代理主键 id 到关联表 t_user_role 和 t_role_permission
- 更新 UserRole 实体类适配 MyBatis-Plus 主键策略
- 增强 UserRoleService 接口功能,支持分配和撤销角色
- 实现批量操作和事务控制提升数据一致性
- 添加安全注解 @PreAuthorize 控制接口访问权限
- 修正 Mapper 注解并优化参数命名提高可读性
- 扩展 ErrorCode 常量增强错误描述准确性
This commit is contained in:
chaos
2025-11-20 23:01:30 +08:00
parent e46b820fca
commit 8dd0efa09e
9 changed files with 273 additions and 132 deletions

View File

@@ -6,37 +6,87 @@ import cn.nopj.chaos_api.domain.entity.UserRole;
import cn.nopj.chaos_api.dto.request.SetUserRoleRequest;
import cn.nopj.chaos_api.mapper.UserRoleMapper;
import cn.nopj.chaos_api.service.UserRoleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class UserRoleServiceImpl implements UserRoleService {
@Autowired
private UserRoleMapper userRoleMapper;
// 继承 ServiceImpl<UserRoleMapper, UserRole> 以获得批量操作能力
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements UserRoleService {
/**
* 批量分配角色给用户。
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void assignRolesToUser(SetUserRoleRequest request) {
if (request == null || request.getUserId() == null || request.getUserId() <= 0L){
throw new BizException(ErrorCode.USER_ID_INVALID);
}
Long userId = request.getUserId();
public void setUserRole(Long userId, List<Long> roles_id) {
if (request.getRolesId() == null || request.getRolesId().isEmpty()){
this.baseMapper.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId,userId));
return;
}
for (Long role_id : roles_id) {
if (role_id == null || role_id == 0L) {
throw new BizException(ErrorCode.ROLE_ID_INVALID);
}else {
userRoleMapper.insert(new UserRole(userId, role_id));
List<Long> rolesId = request.getRolesId();
// 1. 数据预校验确保没有无效角色ID (0或null)
if (rolesId.stream().anyMatch(roleId -> roleId == null || roleId <= 0L)) {
throw new BizException(ErrorCode.ROLE_ID_INVALID);
}
// 2. 构建批量插入列表
List<UserRole> userRoleList = rolesId.stream()
.map(roleId -> {
var userRole = new UserRole();
userRole.setUserId(userId);
userRole.setRoleId(roleId);
return userRole;
})
.collect(Collectors.toList());
try {
// 替代:先删除用户所有旧角色,再插入新的角色集合 (常用授权模式)
this.remove(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, userId));
// 保持原有逻辑:只添加新的角色
boolean success = this.saveBatch(userRoleList);
if (!success) {
// saveBatch 返回 false通常是 MyBatis-Plus 内部错误或部分失败
throw new BizException("批量分配角色操作失败。");
}
} catch (DataIntegrityViolationException e) {
// 捕获数据完整性约束失败 (如外键约束失败: role_id 不存在)
log.error("分配角色时外键约束失败: userId={}, rolesId={}", userId, rolesId, e);
// 给出更具体的提示,可能需要检查是哪个 role_id 不存在
throw new BizException("分配角色失败列表中包含一个或多个不存在的角色ID。");
}
}
/**
* 批量解除用户的角色关联。
* 优化点: 使用 MyBatis-Plus 的批量删除,而不是在循环中单次删除。
* @param request 包含用户ID和角色ID列表
* @return 实际解除关联的记录数
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void setUserRole(SetUserRoleRequest request) {
if (request == null || request.getUserId() == null ){
public int revokeRolesFromUser(SetUserRoleRequest request) {
if (request == null || request.getUserId() == null || request.getUserId() <= 0L){
throw new BizException(ErrorCode.USER_ID_INVALID);
}
if (request.getRolesId() == null || request.getRolesId().isEmpty()){
@@ -44,7 +94,22 @@ public class UserRoleServiceImpl implements UserRoleService {
}
Long userId = request.getUserId();
List<Long> roles_id = request.getRolesId();
setUserRole(userId, roles_id);
List<Long> rolesId = request.getRolesId();
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId, userId)
.in(UserRole::getRoleId, rolesId); // 批量删除指定的角色ID
int count = this.baseMapper.delete(wrapper);
if (count == 0 && !rolesId.isEmpty()){
// 如果用户传入了角色ID列表但删除了 0 行,可能是这些角色未分配给该用户
log.warn("用户 {} 解除角色 {} 失败0条记录被删除。", userId, rolesId);
throw new BizException("解除角色失败,用户可能未分配这些角色。");
}
return count;
}
}
}