feat: 系统资源表

This commit is contained in:
2025-03-11 14:26:38 +08:00
parent e7bd916cae
commit ffb4c7691b
24 changed files with 761 additions and 532 deletions

View File

@@ -1,4 +1,5 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { PermissionType } from '@/common/enum';
export class CreateMenuDto {
@IsNotEmpty()
@@ -19,5 +20,5 @@ export class CreateMenuDto {
@IsOptional()
@IsInt()
type: number;
type = PermissionType.Menu;
}

View File

@@ -9,29 +9,21 @@ import { PermissionType } from '@/common/enum';
export class MenuService {
constructor(private readonly prisma: PrismaService) {}
findOne(params: { id?: string; excludeId?: string }) {
const { id, excludeId } = params;
return this.prisma.sysResource.findFirst({
where: {
type: PermissionType.Menu,
...(id ? { id } : {}),
...(excludeId ? { NOT: { id: excludeId } } : {}),
},
});
}
findOneById(id: string) {
return this.prisma.sysResource.findUnique({ where: { id } });
}
async create(data: CreateMenuDto) {
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.path },
});
if (doesExists)
throw new HttpException('菜单路径不可重复!', HttpStatus.NOT_FOUND);
await this.prisma.$transaction(async (prisma) => {
const { id: resourceId } = await prisma.sysResource.create({ data });
// 创建菜单权限
const { id: permissionId } = await prisma.sysPermission.create({
data: {},
});
// // 关联权限和菜单
// 关联权限和菜单
await prisma.sysResourcePermission.create({
data: { resourceId, permissionId },
});
@@ -54,7 +46,10 @@ export class MenuService {
async getTreeMenu() {
const query: Prisma.SysResourceFindManyArgs = {
orderBy: [{ sort: 'desc' }, { createdAt: 'desc' }],
orderBy: [{ sort: 'desc' }, { createdAt: 'asc' }],
where: {
type: PermissionType.Menu,
},
};
const list = await this.prisma.sysResource.findMany(query);
return this.generatorMenu(list);
@@ -62,20 +57,23 @@ export class MenuService {
findAll() {
const query: Prisma.SysResourceFindManyArgs = {
select: {
id: true,
name: true,
path: true,
parentId: true,
where: {
type: PermissionType.Menu,
},
orderBy: [{ sort: 'desc' }, { createdAt: 'asc' }],
};
return this.prisma.sysResource.findMany(query);
}
async update(id: string, data: UpdateMenuDto) {
const doesExists = await this.findOne({ id });
if (!doesExists) {
throw new HttpException('菜单不存在!', HttpStatus.NOT_FOUND);
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.path },
});
console.log(doesExists);
if (doesExists) {
throw new HttpException('菜单路径不可重复!', HttpStatus.NOT_FOUND);
}
if (data.parentId === id) {
@@ -88,15 +86,10 @@ export class MenuService {
await this.prisma.sysResource.update({ data, where: { id } });
}
async remove(resourceId: string) {
const doesExists = await this.findOneById(resourceId);
if (!doesExists) {
throw new HttpException('菜单不存在!', HttpStatus.NOT_FOUND);
}
async remove(id: string) {
// 判断是否存在子菜单
const doesExistsChildren = await this.prisma.sysResource.findFirst({
where: { parentId: resourceId },
where: { parentId: id, NOT: { id } },
});
if (doesExistsChildren) {
@@ -112,13 +105,13 @@ export class MenuService {
await prisma.sysPermission.deleteMany({
where: {
sysResourcePermission: {
some: { resourceId },
some: { resourceId: id },
},
},
});
//step2 删除菜单数据
await prisma.sysResourcePermission.delete({ where: { id: resourceId } });
await prisma.sysResourcePermission.delete({ where: { id } });
});
}
}

View File

@@ -1,19 +1,19 @@
import { Controller, Body, Patch, Param, Get } from '@nestjs/common';
import { Controller, Body, Put, Param, Get } from '@nestjs/common';
import { PermissionService } from './permission.service';
import { GrantMenuDto } from '@/modules/system/permission/dto/grant-menu.dto';
import { GrantMenuDto } from './dto/grant-menu.dto';
@Controller()
export class PermissionController {
constructor(private readonly permissionService: PermissionService) {}
@Get(':id/menu')
getMenu(@Param('id') id: string) {
return this.permissionService.getMenu(id);
@Get('/roles/:roleId/menus')
getRoleMenus(@Param('roleId') roleId: string) {
return this.permissionService.getRoleMenus(roleId);
}
@Patch(':id')
grantMenu(@Param('id') id: string, @Body() dto: GrantMenuDto) {
return this.permissionService.grantMenu(id, dto);
@Put('/roles/:roleId/menus')
assignMenus(@Param('roleId') roleId: string, @Body() dto: GrantMenuDto) {
return this.permissionService.assignMenus(roleId, dto);
}
}

View File

@@ -6,7 +6,7 @@ import { PrismaService } from '@/prisma/prisma.service';
export class PermissionService {
constructor(private readonly prisma: PrismaService) {}
async getMenu(id: string) {
async getRoleMenus(roleId: string) {
const menuIds = await this.prisma.sysResourcePermission.findMany({
select: {
resourceId: true,
@@ -14,7 +14,7 @@ export class PermissionService {
where: {
permission: {
sysRolePermission: {
some: { roleId: id },
some: { roleId },
},
},
},
@@ -23,11 +23,11 @@ export class PermissionService {
return menuIds.map((item) => item.resourceId);
}
async grantMenu(id: string, { menuIds }: GrantMenuDto) {
async assignMenus(roleId: string, { menuIds }: GrantMenuDto) {
await this.prisma.$transaction(async (prisma) => {
// 删除角色对应的权限
await prisma.sysRolePermission.deleteMany({
where: { roleId: id },
where: { roleId },
});
// 通过菜单id 找出所有权限id
const rolePermIds = await prisma.sysResourcePermission.findMany({
@@ -37,7 +37,7 @@ export class PermissionService {
// 根据权限id 关联菜单和权限
if (rolePermIds.length > 0) {
await prisma.sysRolePermission.createMany({
data: rolePermIds.map((item) => ({ ...item, roleId: id })),
data: rolePermIds.map((item) => ({ ...item, roleId })),
});
}
});

View File

@@ -0,0 +1,24 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { PermissionType } from '@/common/enum';
export class CreateResourceDto {
@IsNotEmpty()
@IsString()
name: string;
@IsNotEmpty()
@IsString()
permission: string;
@IsOptional()
@IsString()
parentId?: string;
@IsOptional()
@IsInt()
sort?: number;
@IsOptional()
@IsInt()
type = PermissionType.Api;
}

View File

@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateResourceDto } from './create-resource.dto';
export class UpdateResourceDto extends PartialType(CreateResourceDto) {}

View File

@@ -0,0 +1,42 @@
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { ResourceService } from './resource.service';
import { CreateResourceDto } from './dto/create-resource.dto';
import { UpdateResourceDto } from './dto/update-resource.dto';
@Controller()
export class ResourceController {
constructor(private readonly resourceService: ResourceService) {}
@Post()
create(@Body() data: CreateResourceDto) {
return this.resourceService.create(data);
}
@Get('tree')
getTreeData() {
return this.resourceService.getTreeData();
}
@Get()
findAll() {
return this.resourceService.findAll();
}
@Patch(':id')
update(@Param('id') id: string, @Body() data: UpdateResourceDto) {
return this.resourceService.update(id, data);
}
@Delete(':id')
remove(@Param('id') id: string) {
return this.resourceService.remove(id);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { ResourceService } from './resource.service';
import { ResourceController } from './resource.controller';
@Module({
controllers: [ResourceController],
providers: [ResourceService],
})
export class ResourceModule {}

View File

@@ -0,0 +1,113 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { CreateResourceDto } from './dto/create-resource.dto';
import { UpdateResourceDto } from './dto/update-resource.dto';
import { PrismaService } from '@/prisma/prisma.service';
import { PermissionType } from '@/common/enum';
import { Prisma } from '@prisma/client';
@Injectable()
export class ResourceService {
constructor(private readonly prisma: PrismaService) {}
async create(data: CreateResourceDto) {
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.permission },
});
if (doesExists)
throw new HttpException('权限字符不可重复!', HttpStatus.NOT_FOUND);
await this.prisma.$transaction(async (prisma) => {
const { id: resourceId } = await prisma.sysResource.create({ data });
// 创建资源权限
const { id: permissionId } = await prisma.sysPermission.create({
data: {},
});
//关联权限和api
await prisma.sysResourcePermission.create({
data: { resourceId, permissionId },
});
});
}
generatorTreeData(data, parentId = '-') {
const list = [];
for (const item of data) {
if ((item.parentId || '-') === parentId) {
const children = this.generatorTreeData(data, item.id);
if (children.length) {
item.children = children;
}
list.push(item);
}
}
return list;
}
async getTreeData() {
const query: Prisma.SysResourceFindManyArgs = {
orderBy: [{ sort: 'desc' }, { createdAt: 'desc' }],
where: {
type: PermissionType.Api,
},
};
const list = await this.prisma.sysResource.findMany(query);
return this.generatorTreeData(list);
}
findAll() {
const query: Prisma.SysResourceFindManyArgs = {
where: {
type: PermissionType.Api,
},
};
return this.prisma.sysResource.findMany(query);
}
async update(id: string, data: UpdateResourceDto) {
const doesExists = await this.prisma.sysResource.findUnique({
where: { path: data.permission, NOT: { id } },
});
if (doesExists)
throw new HttpException('权限字符不可重复!', HttpStatus.NOT_FOUND);
if (data.parentId === id) {
throw new HttpException(
'父级id不可与自身id相同',
HttpStatus.BAD_REQUEST,
);
}
await this.prisma.sysResource.update({ data, where: { id } });
}
async remove(id: string) {
// 判断是否存在子资源
const doesExistsChildren = await this.prisma.sysResource.findFirst({
where: { parentId: id },
});
if (doesExistsChildren) {
throw new HttpException(
'删除失败:请确保当前资源下不存在子资源!',
HttpStatus.FORBIDDEN,
);
}
await this.prisma.$transaction(async (prisma) => {
// step1 通过关联表删除权限表数据 这一步必须在前
// 因为schema文件中设置了onDelete: Cascade 删除资源会删除关联表数据 则无法找出对应权限
await prisma.sysPermission.deleteMany({
where: {
sysResourcePermission: {
some: { resourceId: id },
},
},
});
//step2 删除资源数据
await prisma.sysResourcePermission.delete({ where: { id } });
});
}
}

View File

@@ -8,6 +8,7 @@ import { MenuModule } from './menu/menu.module';
import { ConfigModule } from './config/config.module';
import { PermissionModule } from './permission/permission.module';
import { TestModule } from './test/test.module';
import { ResourceModule } from './resource/resource.module';
@Module({
imports: [
@@ -44,6 +45,10 @@ import { TestModule } from './test/test.module';
path: 'permission',
module: PermissionModule,
},
{
path: 'resource',
module: ResourceModule,
},
{
path: 'test',
module: TestModule,
@@ -59,6 +64,7 @@ import { TestModule } from './test/test.module';
ConfigModule,
PermissionModule,
TestModule,
ResourceModule,
],
})
export class SystemModule {}

View File

@@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
@Injectable()
export class TestService {
findAll() {
return `This action returns all test`;
throw new HttpException('用户不存在!', HttpStatus.NOT_FOUND);
}
}

View File

@@ -141,9 +141,13 @@ export class UserService {
},
},
};
} else {
query.where = {
type: PermissionType.Menu,
};
}
const menus = await this.prisma.sysResource.findMany();
const menus = await this.prisma.sysResource.findMany(query);
return {
...user,