《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️⃣ 批量导入导出实战

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

相关推荐
এ慕ོ冬℘゜21 分钟前
前端实战:jQuery 多条件联合搜索(标题模糊查询 + 日历时间段筛选)
前端·javascript·jquery
武子康30 分钟前
Search Console Platform Properties 扩大 SEO 资产边界:从 Page Ranking 到 Topic Coverage(5 类误读边界 + 3 表数据层设计)
前端·人工智能·后端
腾渊信息科技公司1 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
一点一木1 小时前
从60首歌到1个网站:输入你的故事,还你一首歌
前端·github
IT_陈寒2 小时前
Vue这个特性差点让我加班到凌晨,谁懂啊
前端·人工智能·后端
kyriewen2 小时前
我让AI改一个bug——它偷偷动了5个我没让它碰的地方
前端·javascript·ai编程
段一凡-华北理工大学2 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化
遇乐的果园3 小时前
前端学习笔记-vue加载渲染优化
前端·笔记·学习
码事漫谈3 小时前
1999年的电商前夜和2026年的AI前夜 历史押韵但从不重复
后端
Conan在掘金4 小时前
鸿蒙报错速查:arkts-strict-typing Property does not exist on type 'object',object 装函数就炸,根因 + 真解法
后端