feat: 系统资源表
This commit is contained in:
2
admin/package-lock.json
generated
2
admin/package-lock.json
generated
@@ -9416,7 +9416,7 @@
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.4.0.tgz",
|
||||
"integrity": "sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q==",
|
||||
"deprecated": "This was arguably a breaking change. Not in API, but more results can be returned. Upgrade to the next major when you are ready for that",
|
||||
"deprecated": "This was arguably a breaking change. Not in Api, but more results can be returned. Upgrade to the next major when you are ready for that",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.8",
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
export function grantPermission(id: string, menuIds: string[]) {
|
||||
return request.patch('/permission/' + id, { menuIds });
|
||||
return request.put(`/permission/roles/${id}/menus`, { menuIds });
|
||||
}
|
||||
|
||||
export function getRoleMenuIds(id: string) {
|
||||
return request.get('/permission/' + id + '/menu');
|
||||
return request.get(`/permission/roles/${id}/menus`);
|
||||
}
|
||||
|
||||
21
admin/src/api/resource/index.ts
Normal file
21
admin/src/api/resource/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
export function createResource(data) {
|
||||
return request.post('/resource', data);
|
||||
}
|
||||
|
||||
export function getResource() {
|
||||
return request.get('/resource');
|
||||
}
|
||||
|
||||
export function getTreeResource() {
|
||||
return request.get('/resource/tree');
|
||||
}
|
||||
|
||||
export function updateResource(data) {
|
||||
return request.patch('/resource/' + data.id, data);
|
||||
}
|
||||
|
||||
export function deleteResource(id: string) {
|
||||
return request.delete('/resource/' + id);
|
||||
}
|
||||
60
admin/src/pages/resource/constants.tsx
Normal file
60
admin/src/pages/resource/constants.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
TableColumnProps,
|
||||
Button,
|
||||
Popconfirm,
|
||||
Space,
|
||||
} from '@arco-design/web-react';
|
||||
|
||||
export const enum TableOptions {
|
||||
EDIT = 'edit',
|
||||
DELETE = 'delete',
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getColumns(callback: (type: TableOptions, record: any) => Promise<void>): TableColumnProps[] {
|
||||
return [
|
||||
{
|
||||
title: '权限id',
|
||||
dataIndex: 'id',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '权限名称',
|
||||
dataIndex: 'name',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '权限字符',
|
||||
dataIndex: 'permission',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'operations',
|
||||
align: 'center',
|
||||
width: '300px',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button size='small' onClick={() => callback(TableOptions.EDIT, record)} type="text"
|
||||
status="default">更新</Button>
|
||||
<Popconfirm
|
||||
focusLock
|
||||
title="删除数据"
|
||||
content="确认删除该条数据吗?"
|
||||
onOk={() => callback(TableOptions.DELETE, record)}
|
||||
>
|
||||
<Button size='small' type="text" status="danger">删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
]
|
||||
}
|
||||
101
admin/src/pages/resource/form.tsx
Normal file
101
admin/src/pages/resource/form.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Modal, Form, Input, Message, Select, InputNumber} from '@arco-design/web-react';
|
||||
import {createResource, getResource, updateResource} from '@/api/resource';
|
||||
|
||||
interface Props {
|
||||
record: { [key: string]: any } | null,
|
||||
visible: boolean,
|
||||
handleConfirm: () => void,
|
||||
handleCancel: () => void
|
||||
}
|
||||
|
||||
export default function FormComponent(props: Props) {
|
||||
const {visible, record, handleConfirm, handleCancel} = props;
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
|
||||
const onOk = async () => {
|
||||
try {
|
||||
setConfirmLoading(true);
|
||||
const values = await form.validate();
|
||||
if (record?.id) {
|
||||
await updateResource(values);
|
||||
} else {
|
||||
await createResource(values);
|
||||
}
|
||||
Message.success(`${record?.id ? '更新' : '新增'}成功!`);
|
||||
handleConfirm();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setConfirmLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [menuList, setMenuList] = useState([]);
|
||||
|
||||
const fetchMenuList = async () => {
|
||||
const {data} = await getResource();
|
||||
const mergeMenu = [
|
||||
{
|
||||
id: null,
|
||||
name: '无父级'
|
||||
},
|
||||
...data
|
||||
];
|
||||
setMenuList(mergeMenu);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
record && form.setFieldsValue({
|
||||
...record
|
||||
});
|
||||
}, [record]);
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
fetchMenuList();
|
||||
} else {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal
|
||||
title={<span>{`${record?.id ? '更新' : '新增'}数据`}</span>}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.Item hidden field="id">
|
||||
<Input/>
|
||||
</Form.Item>
|
||||
<Form.Item label="父级" field="parentId">
|
||||
<Select
|
||||
placeholder="请选择父级"
|
||||
allowClear
|
||||
>
|
||||
{menuList.map((option) => (
|
||||
<Select.Option disabled={option.id === record?.id} key={option.id} value={option.id}>
|
||||
{option.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="权限名称" field="name" rules={[{required: true}]}>
|
||||
<Input placeholder="请输入权限名称"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="权限字符" field="permission" rules={[{required: true}]}>
|
||||
<Input placeholder="请输入权限字符"/>
|
||||
</Form.Item>
|
||||
<Form.Item label="排序" field="sort">
|
||||
<InputNumber placeholder="请输入排序"/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
100
admin/src/pages/resource/index.tsx
Normal file
100
admin/src/pages/resource/index.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
import React, {useState} from 'react';
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
Typography,
|
||||
TableColumnProps,
|
||||
Button,
|
||||
Space,
|
||||
Message
|
||||
} from '@arco-design/web-react';
|
||||
import {useQuery} from 'react-query';
|
||||
import Form from './form';
|
||||
import {deleteResource, getTreeResource} from '@/api/resource';
|
||||
import {getColumns, TableOptions} from './constants';
|
||||
import {useHistory} from "react-router";
|
||||
|
||||
const {Title} = Typography;
|
||||
|
||||
function ConfigManage() {
|
||||
const reactQueryKey = useHistory().location.pathname;
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [record, setRecord] = useState(null);
|
||||
|
||||
const onDelete = async ({id}) => {
|
||||
await deleteResource(id);
|
||||
Message.success('删除成功!');
|
||||
await refetch();
|
||||
};
|
||||
|
||||
const tableCallback = async (type: TableOptions, record: any) => {
|
||||
switch (type) {
|
||||
case TableOptions.EDIT:
|
||||
showModal(record)
|
||||
break;
|
||||
case TableOptions.DELETE:
|
||||
await onDelete(record);
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const columns: TableColumnProps[] = getColumns(tableCallback)
|
||||
|
||||
const fetchData = async () => {
|
||||
const {data} = await getTreeResource();
|
||||
return data
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetching,
|
||||
refetch
|
||||
} = useQuery(
|
||||
[reactQueryKey], fetchData);
|
||||
|
||||
const showModal = (payload?) => {
|
||||
payload && setRecord(payload);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleConfirm = async () => {
|
||||
setRecord(null);
|
||||
setVisible(false);
|
||||
await refetch();
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setRecord(null);
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Card>
|
||||
<Form
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
visible={visible}
|
||||
record={record}
|
||||
/>
|
||||
<Title heading={6}>数据权限</Title>
|
||||
|
||||
<Space style={{marginBottom: 12}}>
|
||||
<Button type="primary" onClick={() => showModal()}>新建数据</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
loading={isLoading || isFetching}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
data={data}
|
||||
/>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default ConfigManage;
|
||||
@@ -50,6 +50,10 @@ class Request {
|
||||
return this.instance.patch(url, data);
|
||||
}
|
||||
|
||||
put(url: string, data?: object) {
|
||||
return this.instance.put(url, data);
|
||||
}
|
||||
|
||||
delete(url: string) {
|
||||
return this.instance.delete(url);
|
||||
}
|
||||
|
||||
678
admin/yarn.lock
678
admin/yarn.lock
File diff suppressed because it is too large
Load Diff
14
server/prisma/migrations/20250311054029_/migration.sql
Normal file
14
server/prisma/migrations/20250311054029_/migration.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
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");
|
||||
@@ -107,15 +107,14 @@ 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路径
|
||||
name String /// 菜单名称或者接口名称
|
||||
path String? @unique() /// 菜单或api路径
|
||||
permission String? @unique() /// 权限字符
|
||||
parentId String? @map("parent_id")
|
||||
sort Int @default(0)
|
||||
type Int /// 资源类型 0:菜单 1:api
|
||||
sysResourcePermission SysResourcePermission[]
|
||||
|
||||
@@unique([name, type])
|
||||
@@unique([path])
|
||||
@@map("sys_resource")
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ const prisma = new PrismaClient();
|
||||
|
||||
enum PermissionType {
|
||||
Menu,
|
||||
API,
|
||||
Api,
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -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