feat: RBAC

This commit is contained in:
2025-03-12 11:38:34 +08:00
parent e06d00feff
commit fb3686968d
17 changed files with 144 additions and 98 deletions

View File

@@ -1,14 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[permission]` on the table `sys_resource` will be added. If there are existing duplicate values, this will fail.
*/
-- DropIndex
DROP INDEX "sys_resource_name_type_key";
-- AlterTable
ALTER TABLE "sys_resource" ADD COLUMN "permission" TEXT;
-- CreateIndex
CREATE UNIQUE INDEX "sys_resource_permission_key" ON "sys_resource"("permission");

View File

@@ -80,6 +80,7 @@ CREATE TABLE "sys_resource" (
"updated_at" TIMESTAMP(3) NOT NULL,
"name" TEXT NOT NULL,
"path" TEXT,
"permission_key" TEXT,
"parent_id" TEXT,
"sort" INTEGER NOT NULL DEFAULT 0,
"type" INTEGER NOT NULL,
@@ -135,10 +136,10 @@ CREATE INDEX "sys_role_permission_role_id_idx" ON "sys_role_permission"("role_id
CREATE INDEX "sys_role_permission_permission_id_idx" ON "sys_role_permission"("permission_id");
-- CreateIndex
CREATE UNIQUE INDEX "sys_resource_name_type_key" ON "sys_resource"("name", "type");
CREATE UNIQUE INDEX "sys_resource_path_key" ON "sys_resource"("path");
-- CreateIndex
CREATE UNIQUE INDEX "sys_resource_path_key" ON "sys_resource"("path");
CREATE UNIQUE INDEX "sys_resource_permission_key_key" ON "sys_resource"("permission_key");
-- CreateIndex
CREATE INDEX "sys_resource_permission_resource_id_idx" ON "sys_resource_permission"("resource_id");

View File

@@ -109,7 +109,7 @@ model SysResource {
updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
name String /// 菜单名称或者接口名称
path String? @unique() /// 菜单或api路径
permission String? @unique() /// 权限字符
permissionKey String? @unique() @map("permission_key") /// 权限字符
parentId String? @map("parent_id")
sort Int @default(0)
type Int /// 资源类型 0:菜单 1:api

View File

@@ -7,27 +7,30 @@ enum PermissionType {
}
async function main() {
await prisma.sysUser.upsert({
where: { account: 'admin' },
update: {},
create: {
name: '管理员',
account: 'admin',
password: '$2a$10$rWb52cHK1hrWuQQg3LiOJ.crDfIqjoOIoglGM96a.Ao1DL/dQwKCu',
passwordSalt: '$2a$10$rWb52cHK1hrWuQQg3LiOJ.',
status: 1,
},
});
const menus = [
{ name: '用户管理', path: 'user', type: PermissionType.Menu },
{ name: '角色管理', path: 'role', type: PermissionType.Menu },
{ name: '菜单管理', path: 'menu', type: PermissionType.Menu },
{ name: '系统配置', path: 'config', type: PermissionType.Menu },
{ name: '资源管理', path: 'resource', type: PermissionType.Menu },
];
for (const menu of menus) {
// 创建菜单
await prisma.$transaction(async (_prisma) => {
await prisma.$transaction(async (_prisma) => {
const user = await _prisma.sysUser.upsert({
where: { account: 'admin' },
update: {},
create: {
name: '管理员',
account: 'admin',
password:
'$2a$10$rWb52cHK1hrWuQQg3LiOJ.crDfIqjoOIoglGM96a.Ao1DL/dQwKCu',
passwordSalt: '$2a$10$rWb52cHK1hrWuQQg3LiOJ.',
status: 1,
gender: 0,
},
});
for (const menu of menus) {
const { id: resourceId } = await _prisma.sysResource.create({
data: menu,
});
@@ -39,8 +42,24 @@ async function main() {
await _prisma.sysResourcePermission.create({
data: { resourceId, permissionId },
});
}
// 创建角色并关联
const role = await _prisma.sysRole.create({
data: {
name: '管理员',
code: 'admin',
status: 1,
},
});
}
await _prisma.sysUserRole.create({
data: {
userId: user.id,
roleId: role.id,
},
});
});
}
main()

View File

@@ -52,25 +52,24 @@ export class PermissionGuard implements CanActivate {
// 所有用户的code
const roleCodes = userRoles.map((item) => item.code);
const roleIds = userRoles.map((item) => item.id);
// 如果是管理员 直接通过验证不需要进行接口鉴权
if (roleCodes.includes(Role.Admin)) {
request['isAdmin'] = true;
return true;
}
console.log(roleIds);
// 已知 权限字符 通过权限字符找出权限id 再根据权限id 找出拥有权限的角色code
const hasPermission = await this.prisma.sysRolePermission.findMany({
where: {
roleId: {
in: roleIds,
},
},
});
// TODO
console.log(hasPermission);
// // 已知 权限字符 通过权限字符找出权限id 再根据权限id 找出拥有权限的角色code
// const hasPermission = await this.prisma.sysRolePermission.findMany({
// where: {
// roleId: {
// in: roleIds,
// },
// },
// });
// // TODO
//
// console.log(hasPermission);
// 判断当前接口是否存在权限
throw new ForbiddenException('无权访问!');

View File

@@ -67,11 +67,9 @@ export class MenuService {
async update(id: string, data: UpdateMenuDto) {
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.path },
where: { path: data.path, NOT: { id } },
});
console.log(doesExists);
if (doesExists) {
throw new HttpException('菜单路径不可重复!', HttpStatus.NOT_FOUND);
}

View File

@@ -2,5 +2,5 @@ import { IsString } from 'class-validator';
export class GrantMenuDto {
@IsString({ each: true })
menuIds: string[];
ids: string[];
}

View File

@@ -2,18 +2,37 @@ import { Controller, Body, Put, Param, Get } from '@nestjs/common';
import { PermissionService } from './permission.service';
import { GrantMenuDto } from './dto/grant-menu.dto';
import { PermissionType } from '@/common/enum';
@Controller()
export class PermissionController {
constructor(private readonly permissionService: PermissionService) {}
@Get('/roles/:roleId/resources')
@Get('/roles/:roleId/menus')
getRoleMenus(@Param('roleId') roleId: string) {
return this.permissionService.getRoleMenus(roleId);
return this.permissionService.getRoleResources(roleId, PermissionType.Menu);
}
@Put('/roles/:roleId/menus')
assignMenus(@Param('roleId') roleId: string, @Body() data: GrantMenuDto) {
return this.permissionService.assignResources(
roleId,
PermissionType.Menu,
data,
);
}
@Get('/roles/:roleId/resources')
getRolResources(@Param('roleId') roleId: string) {
return this.permissionService.getRoleResources(roleId, PermissionType.Menu);
}
@Put('/roles/:roleId/resources')
assignMenus(@Param('roleId') roleId: string, @Body() dto: GrantMenuDto) {
return this.permissionService.assignMenus(roleId, dto);
assignResources(@Param('roleId') roleId: string, @Body() data: GrantMenuDto) {
return this.permissionService.assignResources(
roleId,
PermissionType.Menu,
data,
);
}
}

View File

@@ -1,13 +1,14 @@
import { Injectable } from '@nestjs/common';
import { GrantMenuDto } from '@/modules/system/permission/dto/grant-menu.dto';
import { PrismaService } from '@/prisma/prisma.service';
import { PermissionType } from '@/common/enum';
@Injectable()
export class PermissionService {
constructor(private readonly prisma: PrismaService) {}
async getRoleMenus(roleId: string) {
const menuIds = await this.prisma.sysResourcePermission.findMany({
async getRoleResources(roleId: string, type: PermissionType) {
const ids = await this.prisma.sysResourcePermission.findMany({
select: {
resourceId: true,
},
@@ -20,19 +21,25 @@ export class PermissionService {
},
});
return menuIds.map((item) => item.resourceId);
return ids.map((item) => item.resourceId);
}
async assignMenus(roleId: string, { menuIds }: GrantMenuDto) {
async assignResources(
roleId: string,
type: PermissionType,
{ ids }: GrantMenuDto,
) {
await this.prisma.$transaction(async (prisma) => {
// 删除角色对应的权限
await prisma.sysRolePermission.deleteMany({
where: { roleId },
where: {
roleId,
},
});
// 通过菜单id 找出所有权限id
// 通过资源id 找出所有权限id
const rolePermIds = await prisma.sysResourcePermission.findMany({
select: { permissionId: true },
where: { resourceId: { in: menuIds } },
where: { resourceId: { in: ids } },
});
// 根据权限id 关联菜单和权限
if (rolePermIds.length > 0) {

View File

@@ -8,7 +8,7 @@ export class CreateResourceDto {
@IsNotEmpty()
@IsString()
permission: string;
permissionKey: string;
@IsOptional()
@IsString()

View File

@@ -11,7 +11,7 @@ export class ResourceService {
async create(data: CreateResourceDto) {
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.permission },
where: { permissionKey: data.permissionKey },
});
if (doesExists)
@@ -66,7 +66,7 @@ export class ResourceService {
async update(id: string, data: UpdateResourceDto) {
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.permission, NOT: { id } },
where: { permissionKey: data.permissionKey, NOT: { id } },
});
if (doesExists)

View File

@@ -33,7 +33,6 @@ export class UserController {
getUserInfo(@Request() req) {
const { sub } = req['user'];
const { isAdmin } = req;
console.log(isAdmin);
return this.userService.getUserInfo(sub, isAdmin);
}