From e7bd916caef86db18694485f3d9aafe86a215792 Mon Sep 17 00:00:00 2001
From: xy <10816187@qq.com>
Date: Mon, 10 Mar 2025 23:55:10 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20RBAC=E6=9D=83=E9=99=90=E7=AE=A1?=
=?UTF-8?q?=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
admin/src/pages/menu/constants.tsx | 4 +-
admin/src/pages/menu/form.tsx | 4 +-
admin/src/pages/role/grantPerm.tsx | 7 +-
admin/src/routes.ts | 2 +-
server/README.md | 7 ++
.../migration.sql | 68 +++++++++--------
server/prisma/schema.prisma | 73 ++++++++++---------
server/prisma/seed.ts | 19 +++--
server/src/common/enum/index.ts | 1 +
.../system/menu/dto/create-menu.dto.ts | 6 +-
.../src/modules/system/menu/menu.service.ts | 41 ++++++-----
.../system/permission/permission.service.ts | 10 +--
.../src/modules/system/user/user.service.ts | 10 ++-
13 files changed, 142 insertions(+), 110 deletions(-)
rename server/prisma/migrations/{20250308094457_ => 20250310150829_}/migration.sql (82%)
diff --git a/admin/src/pages/menu/constants.tsx b/admin/src/pages/menu/constants.tsx
index 9b607de..0384080 100644
--- a/admin/src/pages/menu/constants.tsx
+++ b/admin/src/pages/menu/constants.tsx
@@ -27,8 +27,8 @@ export function getColumns(callback: (type: TableOptions, record: any) => Promis
align: 'center',
},
{
- title: '菜单url',
- dataIndex: 'url',
+ title: '菜单path',
+ dataIndex: 'path',
align: 'center',
},
{
diff --git a/admin/src/pages/menu/form.tsx b/admin/src/pages/menu/form.tsx
index 85a6533..624ef2e 100644
--- a/admin/src/pages/menu/form.tsx
+++ b/admin/src/pages/menu/form.tsx
@@ -88,8 +88,8 @@ export default function FormComponent(props: Props) {
-
-
+
+
diff --git a/admin/src/pages/role/grantPerm.tsx b/admin/src/pages/role/grantPerm.tsx
index f301f8d..d2d5721 100644
--- a/admin/src/pages/role/grantPerm.tsx
+++ b/admin/src/pages/role/grantPerm.tsx
@@ -20,7 +20,7 @@ function GrantPermission(props: Props) {
try {
setConfirmLoading(true);
const values = await form.validate();
- await grantPermission(values.id, checkedKeys.map(item=>Number(item)));
+ await grantPermission(values.id, checkedKeys);
Message.success(`操作成功!`);
handleConfirm();
} catch (e) {
@@ -41,13 +41,12 @@ function GrantPermission(props: Props) {
const [handleFinished, setHandleFinished] = useState(false);
const handleMenuAndKeys = (menu, menuIds) => {
- let newMenuIds = menuIds.map((menuId) => menuId.toString());
+ let newMenuIds = menuIds.map((menuId) => menuId);
function handleMenu(_menu) {
_menu.forEach((item) => {
// 组件需要的key必须为string
- item.id = item.id.toString();
- item.parentId = item.id.toString();
+ item.parentId = item.id;
if (item.children?.length > 0) {
if (item.children.some(child => !newMenuIds.includes(child.id))) {
diff --git a/admin/src/routes.ts b/admin/src/routes.ts
index aa4988c..cbf58c2 100644
--- a/admin/src/routes.ts
+++ b/admin/src/routes.ts
@@ -15,7 +15,7 @@ export type IRoute = AuthParams & {
const generatorMenu = (data, parentId = '-') => {
const menu = [];
for (const item of data) {
- item.key = item.url;
+ item.key = item.path;
if ((item.parentId || '-') === parentId) {
const children = generatorMenu(data, item.id);
if (children.length) {
diff --git a/server/README.md b/server/README.md
index 6941be1..20ec267 100644
--- a/server/README.md
+++ b/server/README.md
@@ -2,3 +2,10 @@
# 种子文件
npx prisma db seed
```
+```json
+{
+ "prisma": {
+ "seed": "ts-node prisma/seed.ts"
+ }
+}
+```
diff --git a/server/prisma/migrations/20250308094457_/migration.sql b/server/prisma/migrations/20250310150829_/migration.sql
similarity index 82%
rename from server/prisma/migrations/20250308094457_/migration.sql
rename to server/prisma/migrations/20250310150829_/migration.sql
index bfee5d9..a2085c8 100644
--- a/server/prisma/migrations/20250308094457_/migration.sql
+++ b/server/prisma/migrations/20250310150829_/migration.sql
@@ -57,35 +57,11 @@ CREATE TABLE "sys_permission" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
- "type" INTEGER NOT NULL,
+ "name" TEXT,
CONSTRAINT "sys_permission_pkey" PRIMARY KEY ("id")
);
--- CreateTable
-CREATE TABLE "sys_menu" (
- "id" TEXT NOT NULL,
- "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updated_at" TIMESTAMP(3) NOT NULL,
- "name" TEXT NOT NULL,
- "url" TEXT NOT NULL,
- "parent_id" TEXT,
- "sort" INTEGER NOT NULL DEFAULT 0,
-
- CONSTRAINT "sys_menu_pkey" PRIMARY KEY ("id")
-);
-
--- CreateTable
-CREATE TABLE "sys_menu_permission" (
- "id" TEXT NOT NULL,
- "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
- "updated_at" TIMESTAMP(3) NOT NULL,
- "menu_id" TEXT NOT NULL,
- "permission_id" TEXT NOT NULL,
-
- CONSTRAINT "sys_menu_permission_pkey" PRIMARY KEY ("id")
-);
-
-- CreateTable
CREATE TABLE "sys_role_permission" (
"id" TEXT NOT NULL,
@@ -97,6 +73,31 @@ CREATE TABLE "sys_role_permission" (
CONSTRAINT "sys_role_permission_pkey" PRIMARY KEY ("id")
);
+-- CreateTable
+CREATE TABLE "sys_resource" (
+ "id" TEXT NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+ "name" TEXT NOT NULL,
+ "path" TEXT,
+ "parent_id" TEXT,
+ "sort" INTEGER NOT NULL DEFAULT 0,
+ "type" INTEGER NOT NULL,
+
+ CONSTRAINT "sys_resource_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "sys_resource_permission" (
+ "id" TEXT NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+ "resource_id" TEXT NOT NULL,
+ "permission_id" TEXT NOT NULL,
+
+ CONSTRAINT "sys_resource_permission_pkey" PRIMARY KEY ("id")
+);
+
-- CreateTable
CREATE TABLE "sys_config" (
"id" TEXT NOT NULL,
@@ -125,10 +126,7 @@ CREATE INDEX "sys_user_role_user_id_idx" ON "sys_user_role"("user_id");
CREATE INDEX "sys_user_role_role_id_idx" ON "sys_user_role"("role_id");
-- CreateIndex
-CREATE INDEX "sys_menu_permission_menu_id_idx" ON "sys_menu_permission"("menu_id");
-
--- CreateIndex
-CREATE INDEX "sys_menu_permission_permission_id_idx" ON "sys_menu_permission"("permission_id");
+CREATE UNIQUE INDEX "sys_permission_name_key" ON "sys_permission"("name");
-- CreateIndex
CREATE INDEX "sys_role_permission_role_id_idx" ON "sys_role_permission"("role_id");
@@ -136,5 +134,17 @@ CREATE INDEX "sys_role_permission_role_id_idx" ON "sys_role_permission"("role_id
-- CreateIndex
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");
+
+-- CreateIndex
+CREATE UNIQUE INDEX "sys_resource_path_key" ON "sys_resource"("path");
+
+-- CreateIndex
+CREATE INDEX "sys_resource_permission_resource_id_idx" ON "sys_resource_permission"("resource_id");
+
+-- CreateIndex
+CREATE INDEX "sys_resource_permission_permission_id_idx" ON "sys_resource_permission"("permission_id");
+
-- CreateIndex
CREATE UNIQUE INDEX "sys_config_key_key" ON "sys_config"("key");
diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma
index 3312b2c..b3dcb98 100644
--- a/server/prisma/schema.prisma
+++ b/server/prisma/schema.prisma
@@ -77,45 +77,16 @@ model File {
// 权限表
model SysPermission {
- id String @id @default(cuid()) /// 主键id
- createdAt DateTime @default(now()) @map("created_at") /// 创建时间
- updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
- type Int /// 权限类型
- sysRolePermission SysRolePermission[]
- sysMenuPermission SysMenuPermission[]
+ id String @id @default(cuid()) /// 主键id
+ createdAt DateTime @default(now()) @map("created_at") /// 创建时间
+ updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
+ name String? @unique // 权限字符
+ sysRolePermission SysRolePermission[]
+ sysResourcePermission SysResourcePermission[]
@@map("sys_permission")
}
-// 系统菜单表
-model SysMenu {
- id String @id @default(cuid()) /// 主键id
- createdAt DateTime @default(now()) @map("created_at") /// 创建时间
- updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
- name String
- url String
- parentId String? @map("parent_id")
- sort Int @default(0)
- sysMenuPermission SysMenuPermission[]
-
- @@map("sys_menu")
-}
-
-// 菜单权限关联表
-model SysMenuPermission {
- id String @id @default(cuid()) /// 主键id
- createdAt DateTime @default(now()) @map("created_at") /// 创建时间
- updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
- menuId String @map("menu_id")
- permissionId String @map("permission_id")
- menu SysMenu @relation(fields: [menuId], references: [id], onDelete: Cascade)
- permission SysPermission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
-
- @@index([menuId])
- @@index([permissionId])
- @@map("sys_menu_permission")
-}
-
// 角色权限关联表
model SysRolePermission {
id String @id @default(cuid()) /// 主键id
@@ -131,6 +102,38 @@ model SysRolePermission {
@@map("sys_role_permission")
}
+// 系统资源表
+model SysResource {
+ id String @id @default(cuid()) /// 主键id
+ createdAt DateTime @default(now()) @map("created_at") /// 创建时间
+ updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
+ name String
+ path String? /// 菜单或api路径
+ parentId String? @map("parent_id")
+ sort Int @default(0)
+ type Int /// 资源类型 0:菜单 1:api
+ sysResourcePermission SysResourcePermission[]
+
+ @@unique([name, type])
+ @@unique([path])
+ @@map("sys_resource")
+}
+
+// 系统资源权限表
+model SysResourcePermission {
+ id String @id @default(cuid()) /// 主键id
+ createdAt DateTime @default(now()) @map("created_at") /// 创建时间
+ updatedAt DateTime @updatedAt @map("updated_at") /// 修改时间
+ resourceId String @map("resource_id")
+ resource SysResource @relation(fields: [resourceId], references: [id], onDelete: Cascade)
+ permissionId String @map("permission_id")
+ permission SysPermission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
+
+ @@index([resourceId])
+ @@index([permissionId])
+ @@map("sys_resource_permission")
+}
+
// 系统配置
model SysConfig {
id String @id @default(cuid()) /// 主键id
diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts
index 953e766..f96e000 100644
--- a/server/prisma/seed.ts
+++ b/server/prisma/seed.ts
@@ -3,6 +3,7 @@ const prisma = new PrismaClient();
enum PermissionType {
Menu,
+ API,
}
async function main() {
@@ -18,23 +19,25 @@ async function main() {
},
});
const menus = [
- { name: '用户管理', url: 'user' },
- { name: '角色管理', url: 'role' },
- { name: '菜单管理', url: 'menu' },
- { name: '系统配置', url: 'config' },
+ { name: '用户管理', path: 'user', type: PermissionType.Menu },
+ { name: '角色管理', path: 'role', type: PermissionType.Menu },
+ { name: '菜单管理', path: 'menu', type: PermissionType.Menu },
+ { name: '系统配置', path: 'config', type: PermissionType.Menu },
];
for (const menu of menus) {
// 创建菜单
await prisma.$transaction(async (_prisma) => {
- const { id: menuId } = await _prisma.sysMenu.create({ data: menu });
+ const { id: resourceId } = await _prisma.sysResource.create({
+ data: menu,
+ });
// 创建菜单权限
const { id: permissionId } = await _prisma.sysPermission.create({
- data: { type: PermissionType.Menu },
+ data: {},
});
// 关联权限和菜单
- await _prisma.sysMenuPermission.create({
- data: { menuId, permissionId },
+ await _prisma.sysResourcePermission.create({
+ data: { resourceId, permissionId },
});
});
}
diff --git a/server/src/common/enum/index.ts b/server/src/common/enum/index.ts
index cbac3e5..47f8ef0 100644
--- a/server/src/common/enum/index.ts
+++ b/server/src/common/enum/index.ts
@@ -10,4 +10,5 @@ export enum RedisGroup {
export enum PermissionType {
Menu,
+ API,
}
diff --git a/server/src/modules/system/menu/dto/create-menu.dto.ts b/server/src/modules/system/menu/dto/create-menu.dto.ts
index 0f130aa..e893b2b 100644
--- a/server/src/modules/system/menu/dto/create-menu.dto.ts
+++ b/server/src/modules/system/menu/dto/create-menu.dto.ts
@@ -7,7 +7,7 @@ export class CreateMenuDto {
@IsNotEmpty()
@IsString()
- url: string;
+ path: string;
@IsOptional()
@IsString()
@@ -16,4 +16,8 @@ export class CreateMenuDto {
@IsOptional()
@IsInt()
sort?: number;
+
+ @IsOptional()
+ @IsInt()
+ type: number;
}
diff --git a/server/src/modules/system/menu/menu.service.ts b/server/src/modules/system/menu/menu.service.ts
index b61d98d..ba94779 100644
--- a/server/src/modules/system/menu/menu.service.ts
+++ b/server/src/modules/system/menu/menu.service.ts
@@ -11,8 +11,9 @@ export class MenuService {
findOne(params: { id?: string; excludeId?: string }) {
const { id, excludeId } = params;
- return this.prisma.sysMenu.findFirst({
+ return this.prisma.sysResource.findFirst({
where: {
+ type: PermissionType.Menu,
...(id ? { id } : {}),
...(excludeId ? { NOT: { id: excludeId } } : {}),
},
@@ -20,18 +21,20 @@ export class MenuService {
}
findOneById(id: string) {
- return this.prisma.sysMenu.findUnique({ where: { id } });
+ return this.prisma.sysResource.findUnique({ where: { id } });
}
async create(data: CreateMenuDto) {
await this.prisma.$transaction(async (prisma) => {
- const { id: menuId } = await prisma.sysMenu.create({ data });
+ const { id: resourceId } = await prisma.sysResource.create({ data });
// 创建菜单权限
const { id: permissionId } = await prisma.sysPermission.create({
- data: { type: PermissionType.Menu },
+ data: {},
+ });
+ // // 关联权限和菜单
+ await prisma.sysResourcePermission.create({
+ data: { resourceId, permissionId },
});
- // 关联权限和菜单
- await prisma.sysMenuPermission.create({ data: { menuId, permissionId } });
});
}
@@ -50,23 +53,23 @@ export class MenuService {
}
async getTreeMenu() {
- const query: Prisma.SysMenuFindManyArgs = {
+ const query: Prisma.SysResourceFindManyArgs = {
orderBy: [{ sort: 'desc' }, { createdAt: 'desc' }],
};
- const list = await this.prisma.sysMenu.findMany(query);
+ const list = await this.prisma.sysResource.findMany(query);
return this.generatorMenu(list);
}
findAll() {
- const query: Prisma.SysMenuFindManyArgs = {
+ const query: Prisma.SysResourceFindManyArgs = {
select: {
id: true,
name: true,
- url: true,
+ path: true,
parentId: true,
},
};
- return this.prisma.sysMenu.findMany(query);
+ return this.prisma.sysResource.findMany(query);
}
async update(id: string, data: UpdateMenuDto) {
@@ -82,18 +85,18 @@ export class MenuService {
);
}
- await this.prisma.sysMenu.update({ data, where: { id } });
+ await this.prisma.sysResource.update({ data, where: { id } });
}
- async remove(menuId: string) {
- const doesExists = await this.findOneById(menuId);
+ async remove(resourceId: string) {
+ const doesExists = await this.findOneById(resourceId);
if (!doesExists) {
throw new HttpException('菜单不存在!', HttpStatus.NOT_FOUND);
}
// 判断是否存在子菜单
- const doesExistsChildren = await this.prisma.sysMenu.findFirst({
- where: { parentId: menuId },
+ const doesExistsChildren = await this.prisma.sysResource.findFirst({
+ where: { parentId: resourceId },
});
if (doesExistsChildren) {
@@ -108,14 +111,14 @@ export class MenuService {
// 因为schema文件中设置了onDelete: Cascade 删除菜单会删除关联表数据 则无法找出对应权限
await prisma.sysPermission.deleteMany({
where: {
- sysMenuPermission: {
- some: { menuId },
+ sysResourcePermission: {
+ some: { resourceId },
},
},
});
//step2 删除菜单数据
- await prisma.sysMenu.delete({ where: { id: menuId } });
+ await prisma.sysResourcePermission.delete({ where: { id: resourceId } });
});
}
}
diff --git a/server/src/modules/system/permission/permission.service.ts b/server/src/modules/system/permission/permission.service.ts
index 00d0871..65e09c3 100644
--- a/server/src/modules/system/permission/permission.service.ts
+++ b/server/src/modules/system/permission/permission.service.ts
@@ -7,9 +7,9 @@ export class PermissionService {
constructor(private readonly prisma: PrismaService) {}
async getMenu(id: string) {
- const menuIds = await this.prisma.sysMenuPermission.findMany({
+ const menuIds = await this.prisma.sysResourcePermission.findMany({
select: {
- menuId: true,
+ resourceId: true,
},
where: {
permission: {
@@ -20,7 +20,7 @@ export class PermissionService {
},
});
- return menuIds.map((item) => item.menuId);
+ return menuIds.map((item) => item.resourceId);
}
async grantMenu(id: string, { menuIds }: GrantMenuDto) {
@@ -30,9 +30,9 @@ export class PermissionService {
where: { roleId: id },
});
// 通过菜单id 找出所有权限id
- const rolePermIds = await prisma.sysMenuPermission.findMany({
+ const rolePermIds = await prisma.sysResourcePermission.findMany({
select: { permissionId: true },
- where: { menuId: { in: menuIds } },
+ where: { resourceId: { in: menuIds } },
});
// 根据权限id 关联菜单和权限
if (rolePermIds.length > 0) {
diff --git a/server/src/modules/system/user/user.service.ts b/server/src/modules/system/user/user.service.ts
index f5abbd7..d36c16e 100644
--- a/server/src/modules/system/user/user.service.ts
+++ b/server/src/modules/system/user/user.service.ts
@@ -13,7 +13,7 @@ import { QueryListDto } from './dto/query-list.dto';
import * as bcrypt from 'bcryptjs';
import { UpdatePasswordDto } from './dto/update-password.dto';
import { SkipAuth } from '@/common/decorators/skip-auth.decorator';
-import { RedisGroup } from '@/common/enum';
+import { PermissionType, RedisGroup } from '@/common/enum';
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
@Injectable()
@@ -126,11 +126,12 @@ export class UserService {
},
});
- const query: Prisma.SysMenuFindManyArgs = { where: {} };
+ const query: Prisma.SysResourceFindManyArgs = { where: {} };
if (!isAdmin) {
query.where = {
- sysMenuPermission: {
+ type: PermissionType.Menu,
+ sysResourcePermission: {
some: {
permission: {
sysRolePermission: {
@@ -142,7 +143,8 @@ export class UserService {
};
}
- const menus = await this.prisma.sysMenu.findMany(query);
+ const menus = await this.prisma.sysResource.findMany();
+
return {
...user,
menus,