feat: 系统资源表
This commit is contained in:
@@ -10,5 +10,5 @@ export enum RedisGroup {
|
||||
|
||||
export enum PermissionType {
|
||||
Menu,
|
||||
API,
|
||||
Api,
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 } });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 })),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { PartialType } from '@nestjs/mapped-types';
|
||||
import { CreateResourceDto } from './create-resource.dto';
|
||||
|
||||
export class UpdateResourceDto extends PartialType(CreateResourceDto) {}
|
||||
42
server/src/modules/system/resource/resource.controller.ts
Normal file
42
server/src/modules/system/resource/resource.controller.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
9
server/src/modules/system/resource/resource.module.ts
Normal file
9
server/src/modules/system/resource/resource.module.ts
Normal 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 {}
|
||||
113
server/src/modules/system/resource/resource.service.ts
Normal file
113
server/src/modules/system/resource/resource.service.ts
Normal 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 } });
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user