《NestJS 实战:RBAC 系统管理模块开发 (三)》:角色权限分配与数据一致性

前言

大家好,我是 elk!在上篇文章中,我们实现了菜单管理和动态路由权限控制。今天,我们将深入 RBAC 权限模型的核心环节------角色管理。角色作为连接用户与权限的桥梁,其管理功能直接决定了系统的权限分配灵活性。

🔑 核心功能亮点

  • 角色与菜单权限的绑定机制
  • 事务处理保证数据一致性
  • 批量操作用户角色关系

模块创建与结构优化

标准化模块生成

bash 复制代码
nest g res role --no-spec  # 生成角色管理基础结构

DTO 定义(数据处理对象)

typescript 复制代码
// create-role.dto.ts
export class CreateRoleDto {
  @ApiProperty({ description: '角色名称' })
  @IsNotEmpty()
  roleName: string;

  @ApiProperty({ description: '菜单权限ID集合' })
  @IsNumber({}, { each: true }) 
  menuIds: number[];  // 更清晰的字段命名

  // 移除非必要字段(如delFlag)
}

实体映射

typescript 复制代码
// role.entity.ts
import { sys_role as Role } from '@prisma/client';
export class RoleEntity implements Role {
  role_id: number;
  role_name: string;
  role_label: string;
  status: string;
  remark: string;
  order_num: number;
  del_flag: string;
  created_by: string;
  created_at: Date;
  updated_by: string;
  updated_at: Date;
}

🔐 角色管理核心实现

接口设计全景

功能 方法 路径 参数 技术点
创建角色 POST /system/role CreateRoleDto 事务处理、批量权限关联
角色列表 GET /system/role/list pageNum, pageSize 驼峰转换(humps)、分页参数处理
角色详情 GET /system/role/:id id 关联查询(include)
更新角色 PUT /system/role UpdateRoleDto 事务删除-重建权限
删除角色 DELETE /system/role/:id id 级联删除

关键业务逻辑优化

1. 角色创建(事务处理)

typescript 复制代码
async create(createRoleDto: CreateRoleDto) {
    // 角色权限-接收处理
    const roleKeys = createRoleDto.roleKey;
    // 新增角色
    const role = this.prisma.sys_role.create({
      data: {
        role_name: createRoleDto.roleName,
        role_label: createRoleDto.roleLabel,
        status: createRoleDto.status,
        remark: createRoleDto.remark,
        order_num: createRoleDto.orderNum,
        del_flag: createRoleDto.delFlag,
      },
    });
    // 角色与菜单进行绑定
    const roleMenu = this.prisma.sys_role_menu.createMany({
      data: roleKeys.map((roleKey) => {
        return {
          role_id: createRoleDto.roleId,
          menu_id: roleKey,
        };
      }),
    });
    // 事务-保证角色和角色菜单表数据一致性
    const transaction = await this.prisma.$transaction([role, roleMenu]);
    if (!transaction) {
      return '新增失败';
    } else {
      return '新增成功';
    }
  }

2. 权限更新(原子操作)

typescript 复制代码
async update(dto: UpdateRoleDto) {
  return this.prisma.$transaction([
    // 删除原有权限
    this.prisma.sys_role_menu.deleteMany({
      where: { role_id: dto.roleId }
    }),
    // 添加新权限
    this.prisma.sys_role_menu.createMany({
      data: dto.menuIds.map(menuId => ({
        role_id: dto.roleId,
        menu_id: menuId
      }))
    }),
    // 更新角色信息
    this.prisma.sys_role.update({
      where: { role_id: dto.roleId },
      data: { 
        role_name: dto.roleName,
        updated_at: new Date() 
      }
    })
  ]);
}

3. 角色详情查询(关联数据优化)

typescript 复制代码
  async findOne(id: number): Promise<RoleEntity> {
    const role = await this.prisma.sys_role.findUnique({
      where: {
        role_id: id,
      },
      include: {
        menus: {
          select: {
            menu_id: true
          }
        }
      }
    });
    // 判断是否存在无数据的情况
    if (!role) {
      throw new NotFoundException(`角色ID ${id} 不存在`);
    }
    // 空值以排除,安然取值
    return {
      ...camelizeKeys(role),
      roleKey: role.menus.map(menu => menu.menu_id)
    };
  }

4. 批量删除支持

typescript 复制代码
@Delete('batch')
async batchRemove(@Body() ids: number[]) {
  return this.prisma.$transaction([
    // 删除关联权限
    this.prisma.sys_role_menu.deleteMany({
      where: { role_id: { in: ids } }
    }),
    // 删除角色本体
    this.prisma.sys_role.deleteMany({
      where: { role_id: { in: ids } }
    })
  ]);
}

💡 深度实践技巧

1. 事务处理最佳实践

typescript 复制代码
// 错误处理增强
try {
  await this.prisma.$transaction([...operations], {
    maxWait: 5000, // 最大等待时间
    timeout: 10000 // 超时时间
  });
} catch (e) {
  this.logger.error('角色更新失败', e.stack);
  throw new BadRequestException('数据更新失败');
}

2. 数据权限扩展实现

typescript 复制代码
// 在角色实体中添加数据权限字段
@ApiProperty({ description: '数据权限范围' })
dataScope: DataScopeEnum; // ENUM: ALL, DEPT, CUSTOM

// 创建时存储部门权限
if (dto.dataScope === DataScopeEnum.CUSTOM) {
  await prisma.sys_role_dept.createMany({
    data: dto.deptIds.map(deptId => ({
      role_id: role.role_id,
      dept_id: deptId
    }))
  });
}

3. 缓存优化策略

typescript 复制代码
// 角色权限缓存
@Cacheable({ key: 'role:${roleId}:menus' })
async getRoleMenus(roleId: number) {
  return this.prisma.sys_role_menu.findMany({
    where: { role_id: roleId }
  });
}

// 更新时清除缓存
@CacheEvict({ key: 'role:${dto.roleId}:menus' })
async update(dto: UpdateRoleDto) { ... }

🔜 下篇预告:用户管理模块

在下一篇文章中,我们将深入:

1️⃣ 用户-角色绑定关系管理

2️⃣ 部门树形结构实现

3️⃣ 用户信息加密存储方案

4️⃣ 批量导入导出实战

💡 思考题:如何实现用户离职时的权限自动回收?欢迎在评论区分享你的方案!

相关推荐
某人的小眼睛4 分钟前
vue3 element-plus 大文件切片上传
前端·vue.js
东坡白菜6 分钟前
最快实现的前端灰度方案
前端
curdcv_po10 分钟前
🔴 你老说拿下 react,真的 拿下 了吗
前端
魔都吴所谓11 分钟前
[前端]HTML模拟实现一个基于摄像头的手势识别交互页面
前端·html·交互
来自星星的猫教授13 分钟前
Vue 3.6前瞻:响应式性能革命与Vapor模式展望
前端·javascript·vue.js
菜鸟谢15 分钟前
windows xp 下载 sp0 sp1 sp2 sp3 sp4
后端
涵信16 分钟前
第九节 高频代码题-实现Sleep函数(异步控制)
前端·javascript·typescript
AirMan17 分钟前
你真的懂 MySQL 的一致性读和当前读的区别吗?
后端·面试
David爱编程23 分钟前
容器性能优化实战指南——防止“吃爆”服务器就靠这些招!
后端·docker·容器
Android洋芋26 分钟前
GitHub项目部署的终极指南:从零到一掌握Docker实战
后端