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

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

相关推荐
GetcharZp2 小时前
玩转 Linux 机器视觉:手把手带你搞定 Ubuntu 下海康工业相机 C++ SDK
后端
橙子家3 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
星星在线5 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒6 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x6 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者7 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重8 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户8356290780518 小时前
使用 Python 操作 Word 内容控件
后端·python
像我这样帅的人丶你还8 小时前
啥? 前端也要会干Java?🛵🛵🛵
后端
Hommy888 小时前
【剪映小助手】添加贴纸接口(Add Sticker)
后端·github·剪映小助手·视频剪辑自动化·剪映api