基于Nest.js的RBAC权限控制设计

您好, 如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想

前言

对于后台管理系统,权限控制、千人千面是必须的,例如超级管理员可以看到所有的页面;普通用户能看到A、B页面;VIP用户可以看到A、B、C、D页面,诸如此类,这些业务的逻辑背后就是三种概念的设计。

  • 用户,最基本的用户,例如张三、李四、王五;
  • 角色,一个用户可以对应一个或多个角色,例如张三是普通用户+VIP的角色;
  • 权限,一个角色对应多个权限,例如VIP角色有查看、修改、增加的权限;超级管理员有查看、修改、增加、删除的权限;

以图来划分就像是这样的拆分关系:

接下来我们用nest来0~1实现一下一个系统的根基------------权限设计。

创建数据库

首先我们需要创建数据库,这里我们使用 mysql 数据库,我们执行以下语句创建数据库。

javascript 复制代码
CREATE DATABASE `nest-database` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

项目初始化

我们起一个nest项目,执行以下命令:

javascript 复制代码
nest new nest-project

然后安装数据库相关依赖,主要有typeormmysql2

javascript 复制代码
npm install --save @nestjs/typeorm typeorm mysql2

然后进行typeorm的相关配置,在app.module.ts中:

typescript 复制代码
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { TypeOrmModule } from '@nestjs/typeorm'

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'nest-database',
      synchronize: true,
      logging: true,
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      poolSize: 10,
      connectorPackage: 'mysql2'
    })
  ],
  controllers: [AppController],
  providers: [AppService]
})

export class AppModule {}

然后我们进行表设计,一般rbac会有5张表,如下:

  1. 用户表,user,保存用户基本信息,如用户名、密码、邮箱;
  2. 角色表,role,保存角色基本信息,如角色名,角色code;
  3. 权限表,permission,保存权限基本信息,如权限名,权限code;
  4. 用户角色关联表,user_role_relation,用户角色关联表,用于记录用户和角色的关系;
  5. 角色权限关联表,role_permission_relation,角色权限关联表,用于记录角色和权限的关系;

领域模型图是这样的:

然后在nest中新建三张非关联表,并把关联的关系写一下:

user.entity.ts

typescript 复制代码
import {
  Column,
  CreateDateColumn,
  Entity,
  JoinTable,
  ManyToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn
} from 'typeorm'

import { Role } from './role.entity'

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number

  @Column({
    length: 50
  })
  username: string

  @Column({
    length: 50
  })
  password: string

  @CreateDateColumn()
  createTime: Date

  @UpdateDateColumn()
  updateTime: Date

  @ManyToMany(() => Role)
  @JoinTable({
    name: 'user_role_relation', 
    joinColumn: {
      name: 'userId',
      referencedColumnName: 'id', 
    },
    inverseJoinColumn: {
      name: 'roleId',  
      referencedColumnName: 'id', 
    },
  })
  roles: Role[];
}

我们在User表中定义了rule字段,通过连接到user_role_relation表,连接判断关系是user.id === userRoleRelation.userIdrole.id === userRoleRelation.roleId,才会将满足条件的Role记录插入到User中,自动形成关联关系。

role.entity.ts

typescript 复制代码
import {
  Column,
  CreateDateColumn,
  Entity,
  JoinTable,
  ManyToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn
} from 'typeorm'

import { Permission } from './permission.entity'

@Entity()
export class Role {
  @PrimaryGeneratedColumn()
  id: number

  @Column({
    length: 20
  })
  name: string

  @CreateDateColumn()
  createTime: Date

  @UpdateDateColumn()
  updateTime: Date

  @ManyToMany(() => Permission)
  @JoinTable({
    name: 'user_role', 
    joinColumn: {
      name: 'roleId', 
      referencedColumnName: 'id', 
    },
    inverseJoinColumn: {
      name: 'permissionId', 
      referencedColumnName: 'id', 
    },
  })
  permissions: Permission[]
}

这张角色表的permissions字段原理一样,通过连接到role_permission_relation表,连接判断关系是role.id === rolePermissionRelation.roleIdpermission.id === rolePermissionRelation.permissionId,才会将满足条件的Permission记录插入到Role中,自动形成关联关系。

最后是权限表,没有关联关系,直接记录有哪些权限即可。

permission.entity.ts

typescript 复制代码
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'

@Entity()
export class Permission {
  @PrimaryGeneratedColumn()
  id: number

  @Column({
    length: 50
  })
  name: string

  @Column({
    length: 100,
    nullable: true
  })
  desc: string

  @CreateDateColumn()
  createTime: Date

  @UpdateDateColumn()
  updateTime: Date
}

到这里表的设计就完成了,我们初始化一批数据,写一个init服务。

typescript 复制代码
async initData() {
  const user1 = new User()
  user1.username = '张三'
  user1.password = '111111'

  const user2 = new User()
  user2.username = '李四'
  user2.password = '222222'

  const user3 = new User()
  user3.username = '王五'
  user3.password = '333333'

  const role1 = new Role()
  role1.name = '管理员'

  const role2 = new Role()
  role2.name = '普通用户'

  const permission1 = new Permission()
  permission1.name = '新增 aaa'

  const permission2 = new Permission()
  permission2.name = '修改 aaa'

  const permission3 = new Permission()
  permission3.name = '删除 aaa'

  const permission4 = new Permission()
  permission4.name = '查询 aaa'

  const permission5 = new Permission()
  permission5.name = '新增 bbb'

  const permission6 = new Permission()
  permission6.name = '修改 bbb'

  const permission7 = new Permission()
  permission7.name = '删除 bbb'

  const permission8 = new Permission()
  permission8.name = '查询 bbb'

  role1.permissions = [
    permission1,
    permission2,
    permission3,
    permission4,
    permission5,
    permission6,
    permission7,
    permission8
  ]

  role2.permissions = [permission1, permission2, permission3, permission4]

  user1.roles = [role1]

  user2.roles = [role2]

  await this.entityManager.save(Permission, [
    permission1,
    permission2,
    permission3,
    permission4,
    permission5,
    permission6,
    permission7,
    permission8
  ])

  await this.entityManager.save(Role, [role1, role2])

  await this.entityManager.save(User, [user1, user2])
}

去浏览器或者postman调一下initData接口,表里就有数据了。

这样的一批人员权限关系也就设计好了,验证一下数据没有问题。

有了最基本的权限设计接下来就可以去做注册、登录、JWT拦截相关的开发了,这里之前已经讲过,直接跳转到这两篇文章参考即可。

Midway.js零到一实现登录、注册、鉴权功能

伪造请求怎么办?看这篇就够了

如果喜欢我的文章或者想上岸大厂,可以关注公众号「量子前端」,将不定期关注推送前端好文、分享就业资料秘籍,也希望有机会一对一帮助你实现梦想。

相关推荐
uzong14 分钟前
技术故障复盘模版
后端
GetcharZp42 分钟前
基于 Dify + 通义千问的多模态大模型 搭建发票识别 Agent
后端·llm·agent
加班是不可能的,除非双倍日工资1 小时前
css预编译器实现星空背景图
前端·css·vue3
桦说编程1 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
IT毕设实战小研1 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip2 小时前
vite和webpack打包结构控制
前端·javascript
excel2 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼3 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin