From bbc9c5d6bf4ab76600ad253af180c4c4f1607fe4 Mon Sep 17 00:00:00 2001 From: liuyi Date: Wed, 25 Jun 2025 17:44:48 +0800 Subject: [PATCH] add rbac module --- .../rbac/entities/permission.entity.ts | 66 ++++++++++++++++ src/modules/rbac/entities/role.entity.ts | 79 +++++++++++++++++++ .../repositories/permission.repository.ts | 21 +++++ .../rbac/repositories/role.repository.ts | 19 +++++ .../rbac/subscribers/permission.subscriber.ts | 15 ++++ .../rbac/subscribers/role.subscriber.ts | 15 ++++ 6 files changed, 215 insertions(+) create mode 100644 src/modules/rbac/entities/permission.entity.ts create mode 100644 src/modules/rbac/entities/role.entity.ts create mode 100644 src/modules/rbac/repositories/permission.repository.ts create mode 100644 src/modules/rbac/repositories/role.repository.ts create mode 100644 src/modules/rbac/subscribers/permission.subscriber.ts create mode 100644 src/modules/rbac/subscribers/role.subscriber.ts diff --git a/src/modules/rbac/entities/permission.entity.ts b/src/modules/rbac/entities/permission.entity.ts new file mode 100644 index 0000000..52d76bb --- /dev/null +++ b/src/modules/rbac/entities/permission.entity.ts @@ -0,0 +1,66 @@ +import { AbilityTuple, MongoQuery, RawRuleFrom } from '@casl/ability'; +import { Exclude, Expose } from 'class-transformer'; +import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn, Relation } from 'typeorm'; + +import { UserEntity } from '@/modules/user/entities'; + +import { RoleEntity } from './role.entity'; + +/** + * 权限实体 + */ +@Exclude() +@Entity('rbac_permission') +export class PermissionEntity< + P extends AbilityTuple = AbilityTuple, + T extends MongoQuery = MongoQuery, +> { + /** + * 权限ID + */ + @Expose() + @PrimaryGeneratedColumn('uuid') + id: string; + + /** + * 权限名称 + */ + @Expose() + @Column({ comment: '权限名称' }) + name: string; + + /** + * 权限显示名 + */ + @Expose() + @Column({ comment: '权限显示名', nullable: true }) + label?: string; + + /** + * 权限描述 + */ + @Expose() + @Column({ comment: '权限描述', nullable: true, type: 'text' }) + description?: string; + + /** + * 权限规则 + */ + @Column({ type: 'simple-json', comment: '权限规则' }) + rule: Omit, 'conditions'>; + + /** + * 权限角色 + */ + @Expose({ groups: ['permission-list', 'permission-detail'] }) + @ManyToMany(() => RoleEntity, (role) => role.permissions, { cascade: true }) + @JoinTable() + roles: Relation[]; + + /** + * 权限用户 + */ + @ManyToMany(() => UserEntity, (user) => user.permissions) + @JoinTable() + users: Relation[]; +} diff --git a/src/modules/rbac/entities/role.entity.ts b/src/modules/rbac/entities/role.entity.ts new file mode 100644 index 0000000..d824f02 --- /dev/null +++ b/src/modules/rbac/entities/role.entity.ts @@ -0,0 +1,79 @@ +import { Exclude, Expose, Type } from 'class-transformer'; +import { + BaseEntity, + Column, + DeleteDateColumn, + Entity, + JoinTable, + ManyToMany, + PrimaryGeneratedColumn, + Relation, +} from 'typeorm'; + +import { UserEntity } from '@/modules/user/entities'; + +import { PermissionEntity } from './permission.entity'; + +/** + * 角色信息 + */ +@Exclude() +@Entity('rbac_role') +export class RoleEntity extends BaseEntity { + /** + * 角色ID + */ + @Expose() + @PrimaryGeneratedColumn('uuid') + id: string; + + /** + * 角色名称 + */ + @Column({ comment: '角色名称' }) + name: string; + + /** + * 显示名称 + */ + @Column({ comment: '显示名称', nullable: true }) + label?: string; + + /** + * 角色描述 + */ + @Column({ comment: '角色描述', nullable: true, type: 'text' }) + description?: string; + + /** + * 是否为不可更改的系统权限 + */ + @Column({ comment: '是否为不可更改的系统权限', default: false }) + systemed?: boolean; + + /** + * 删除时间 + */ + @Expose({ groups: ['role-detail', 'role-list'] }) + @Type(() => Date) + @DeleteDateColumn({ comment: '删除时间' }) + deletedAt: Date; + + /** + * 角色权限 + */ + @Expose({ groups: ['role-detail'] }) + @Type(() => PermissionEntity) + @ManyToMany(() => PermissionEntity, (permission) => permission.roles, { + cascade: true, + eager: true, + }) + permissions: Relation[]; + + /** + * 角色关联用户 + */ + @ManyToMany(() => UserEntity, (user) => user.roles, { deferrable: 'INITIALLY IMMEDIATE' }) + @JoinTable() + users: Relation[]; +} diff --git a/src/modules/rbac/repositories/permission.repository.ts b/src/modules/rbac/repositories/permission.repository.ts new file mode 100644 index 0000000..925a45a --- /dev/null +++ b/src/modules/rbac/repositories/permission.repository.ts @@ -0,0 +1,21 @@ +import { AbilityTuple, MongoQuery } from '@casl/ability'; + +import { SelectQueryBuilder } from 'typeorm'; + +import { BaseRepository } from '@/modules/database/base/repository'; + +import { CustomRepository } from '@/modules/database/decorators/repository.decorator'; + +import { PermissionEntity } from '../entities/permission.entity'; + +@CustomRepository(PermissionEntity) +export class PermissionRepository extends BaseRepository { + protected _qbName: string = 'permission'; + + buildBaseQB(): SelectQueryBuilder> { + return this.createQueryBuilder(this.qbName).leftJoinAndSelect( + `${this.qbName}.roles`, + 'roles', + ); + } +} diff --git a/src/modules/rbac/repositories/role.repository.ts b/src/modules/rbac/repositories/role.repository.ts new file mode 100644 index 0000000..82d0020 --- /dev/null +++ b/src/modules/rbac/repositories/role.repository.ts @@ -0,0 +1,19 @@ +import { SelectQueryBuilder } from 'typeorm'; + +import { BaseRepository } from '@/modules/database/base/repository'; + +import { CustomRepository } from '@/modules/database/decorators/repository.decorator'; + +import { RoleEntity } from '../entities/role.entity'; + +@CustomRepository(RoleEntity) +export class RoleRepository extends BaseRepository { + protected _qbName: string = 'role'; + + buildBaseQB(): SelectQueryBuilder { + return this.createQueryBuilder(this.qbName).leftJoinAndSelect( + `${this.qbName}.permissions`, + 'permissions', + ); + } +} diff --git a/src/modules/rbac/subscribers/permission.subscriber.ts b/src/modules/rbac/subscribers/permission.subscriber.ts new file mode 100644 index 0000000..261c6e7 --- /dev/null +++ b/src/modules/rbac/subscribers/permission.subscriber.ts @@ -0,0 +1,15 @@ +import { isNil } from 'lodash'; + +import { BaseSubscriber } from '@/modules/database/base/subscriber'; + +import { PermissionEntity } from '../entities/permission.entity'; + +export class PermissionSubscriber extends BaseSubscriber { + protected entity = PermissionEntity; + + async afterLoad(entity: PermissionEntity): Promise { + if (isNil(entity.label)) { + entity.label = entity.name; + } + } +} diff --git a/src/modules/rbac/subscribers/role.subscriber.ts b/src/modules/rbac/subscribers/role.subscriber.ts new file mode 100644 index 0000000..60e0d77 --- /dev/null +++ b/src/modules/rbac/subscribers/role.subscriber.ts @@ -0,0 +1,15 @@ +import { isNil } from 'lodash'; + +import { BaseSubscriber } from '@/modules/database/base/subscriber'; + +import { RoleEntity } from '../entities/role.entity'; + +export class RoleSubscriber extends BaseSubscriber { + protected entity = RoleEntity; + + async afterLoad(entity: RoleEntity): Promise { + if (isNil(entity.label)) { + entity.label = entity.name; + } + } +}