feat: RBAC
This commit is contained in:
@@ -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");
|
||||
@@ -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");
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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('无权访问!');
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -2,5 +2,5 @@ import { IsString } from 'class-validator';
|
||||
|
||||
export class GrantMenuDto {
|
||||
@IsString({ each: true })
|
||||
menuIds: string[];
|
||||
ids: string[];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -8,7 +8,7 @@ export class CreateResourceDto {
|
||||
|
||||
@IsNotEmpty()
|
||||
@IsString()
|
||||
permission: string;
|
||||
permissionKey: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user