diff --git a/admin/src/api/permission/index.ts b/admin/src/api/permission/index.ts index 47664d0..30d8e8d 100644 --- a/admin/src/api/permission/index.ts +++ b/admin/src/api/permission/index.ts @@ -1,17 +1,17 @@ import request from '@/utils/request'; -export function assignMenus(id: string, menuIds: string[]) { - return request.put(`/permission/roles/${id}/menus`, { menuIds }); +export function assignMenus(id: string, ids: string[]) { + return request.put(`/permission/roles/${id}/menus`, { ids }); } export function getMenus(id: string) { return request.get(`/permission/roles/${id}/menus`); } -export function assignResources(id: string, resourceIds: string[]) { - return request.put(`/permission/roles/${id}/menus`, { resourceIds }); +export function assignResources(id: string, ids: string[]) { + return request.put(`/permission/roles/${id}/resources`, { ids }); } export function getResources(id: string) { - return request.get(`/permission/roles/${id}/menus`); + return request.get(`/permission/roles/${id}/resources`); } diff --git a/admin/src/pages/role/assignMenus.tsx b/admin/src/pages/role/assignMenus.tsx index 4c58e06..6172816 100644 --- a/admin/src/pages/role/assignMenus.tsx +++ b/admin/src/pages/role/assignMenus.tsx @@ -3,9 +3,7 @@ import { Modal, Form, Input, Message, Tree } from '@arco-design/web-react'; import { getTreeMenu } from '@/api/menu'; import { getMenus, - getResources, assignMenus, - assignResources, } from '@/api/permission'; interface Props { diff --git a/admin/src/pages/role/assignResources.tsx b/admin/src/pages/role/assignResources.tsx new file mode 100644 index 0000000..a24e9fd --- /dev/null +++ b/admin/src/pages/role/assignResources.tsx @@ -0,0 +1,126 @@ +import React, { useEffect, useState } from 'react'; +import { Modal, Form, Input, Message, Tree } from '@arco-design/web-react'; +import { getTreeResource } from '@/api/resource'; +import { + getResources, + assignResources, +} from '@/api/permission'; + +interface Props { + record: { [key: string]: any } | null; + visible: boolean; + handleConfirm: () => void; + handleCancel: () => void; +} + +function GrantPermission(props: Props) { + const { visible, record, handleConfirm, handleCancel } = props; + const [confirmLoading, setConfirmLoading] = useState(false); + const [checkedKeys, setCheckedKeys] = useState([]); + const [treeData, setTreeData] = useState([]); + + const onOk = async () => { + try { + setConfirmLoading(true); + const values = await form.validate(); + await assignResources(values.id, checkedKeys); + Message.success(`操作成功!`); + handleConfirm(); + } catch (e) { + console.log(e); + } finally { + setConfirmLoading(false); + } + }; + + const [form] = Form.useForm(); + + useEffect(() => { + record && + form.setFieldsValue({ + ...record, + }); + }, [record]); + + const [handleFinished, setHandleFinished] = useState(false); + const handleMenuAndKeys = (menu, menuIds) => { + let newMenuIds = menuIds.map((menuId) => menuId); + + function handleMenu(_menu) { + _menu.forEach((item) => { + // 组件需要的key必须为string + item.parentId = item.id; + + if (item.children?.length > 0) { + if (item.children.some((child) => !newMenuIds.includes(child.id))) { + newMenuIds = newMenuIds.filter((id) => id !== item.id); + } + handleMenu(item.children); + } + }); + } + + handleMenu(menu); + setTreeData(menu); + setCheckedKeys(newMenuIds); + }; + + useEffect(() => { + if (treeData.length) { + setHandleFinished(true); + } + }, [treeData]); + + useEffect(() => { + if (visible) { + const fetchTreeMenu = async () => { + const { data } = await getTreeResource(); + const { data: menuIds } = await getResources(record.id); + handleMenuAndKeys(data, menuIds); + }; + fetchTreeMenu(); + } else { + form.resetFields(); + setTreeData([]); + setCheckedKeys([]); + setHandleFinished(false); + } + }, [visible]); + + return ( +
+ 分配权限} + visible={visible} + onOk={onOk} + onCancel={handleCancel} + confirmLoading={confirmLoading} + > +
+ + + {handleFinished && ( + { + setCheckedKeys(value); + }} + treeData={treeData} + fieldNames={{ + key: 'id', + title: 'name', + }} + > + )} + +
+
+
+ ); +} + +export default GrantPermission; diff --git a/admin/src/pages/role/index.tsx b/admin/src/pages/role/index.tsx index 38686ec..9e4a567 100644 --- a/admin/src/pages/role/index.tsx +++ b/admin/src/pages/role/index.tsx @@ -14,10 +14,10 @@ import Form from './form'; import {IconPlus} from '@arco-design/web-react/icon'; import {getColumns, TableOptions} from './constants'; import {getRole,deleteRole} from "@/api/role"; -import AssignMenusForm from "./grantPerm"; +import AssignMenusForm from "./assignMenus"; +import AssignResources from "./assignResources"; import SearchForm from "./searchForm"; - const {Title} = Typography; function UserManage() { @@ -25,6 +25,7 @@ function UserManage() { const [visible, setVisible] = useState(false); const [visibleAssignMenus, setVisibleAssignMenus] = useState(false); + const [visibleAssignResources, setVisibleAssignResources] = useState(false); const [record, setRecord] = useState(null); const [pagination, setPagination] = useState({ sizeCanChange: true, @@ -45,8 +46,9 @@ function UserManage() { setVisibleAssignMenus(true); }; - const showAssignResourcesModal = () => { - + const showAssignResourcesModal = (payload) => { + setRecord(payload); + setVisibleAssignResources(true); } const showModal = (payload?) => { @@ -116,6 +118,7 @@ function UserManage() { setRecord(null); setVisible(false); setVisibleAssignMenus(false); + setVisibleAssignResources(false); await refetch(); }; @@ -123,6 +126,7 @@ function UserManage() { setRecord(null); setVisible(false); setVisibleAssignMenus(false); + setVisibleAssignResources(false); }; useEffect(() => { @@ -148,6 +152,14 @@ function UserManage() { visible={visibleAssignMenus} record={record} /> + + + 角色管理 diff --git a/server/src/common/guard/permission.guard.ts b/server/src/common/guard/permission.guard.ts index 8101127..492a7ca 100644 --- a/server/src/common/guard/permission.guard.ts +++ b/server/src/common/guard/permission.guard.ts @@ -14,6 +14,7 @@ import { SKIP_AUTH } from '@/common/decorators/skip-auth.decorator'; import { PERMISSIONS_KEY } from '@/common/decorators/permissions.decorator'; import { PrismaService } from '@/prisma/prisma.service'; import { PermissionType, Role } from '@/common/enum'; +import { isArrayFullyContained } from '@/utils'; @Injectable() export class PermissionGuard implements CanActivate { @@ -44,6 +45,9 @@ export class PermissionGuard implements CanActivate { sysUserRole: { some: { userId: request.user.sub, + role: { + status: 1, + }, }, }, }, @@ -59,18 +63,34 @@ export class PermissionGuard implements CanActivate { return true; } - // // 已知 权限字符 通过权限字符找出权限id 再根据权限id 找出拥有权限的角色code - // const hasPermission = await this.prisma.sysRolePermission.findMany({ - // where: { - // roleId: { - // in: roleIds, - // }, - // }, - // }); - // // TODO - // - // console.log(hasPermission); + // 已知 权限字符 通过权限字符判断是否有符合条件的结果 + const rolePermissions = await this.prisma.sysResource.findMany({ + select: { + permissionKey: true, + }, + where: { + type: PermissionType.Api, + sysResourcePermission: { + some: { + permission: { + sysRolePermission: { + some: { + roleId: { + in: roleIds, + }, + }, + }, + }, + }, + }, + }, + }); + const hasPermission = isArrayFullyContained( + rolePermissions.map((item) => item.permissionKey), + requiredPermissions, + ); + if (hasPermission) return true; // 判断当前接口是否存在权限 throw new ForbiddenException('无权访问!'); } diff --git a/server/src/modules/system/permission/permission.controller.ts b/server/src/modules/system/permission/permission.controller.ts index 4ab24fe..5ad9403 100644 --- a/server/src/modules/system/permission/permission.controller.ts +++ b/server/src/modules/system/permission/permission.controller.ts @@ -24,14 +24,14 @@ export class PermissionController { @Get('/roles/:roleId/resources') getRolResources(@Param('roleId') roleId: string) { - return this.permissionService.getRoleResources(roleId, PermissionType.Menu); + return this.permissionService.getRoleResources(roleId, PermissionType.Api); } @Put('/roles/:roleId/resources') assignResources(@Param('roleId') roleId: string, @Body() data: GrantMenuDto) { return this.permissionService.assignResources( roleId, - PermissionType.Menu, + PermissionType.Api, data, ); } diff --git a/server/src/modules/system/permission/permission.service.ts b/server/src/modules/system/permission/permission.service.ts index 96edb66..6463816 100644 --- a/server/src/modules/system/permission/permission.service.ts +++ b/server/src/modules/system/permission/permission.service.ts @@ -13,6 +13,9 @@ export class PermissionService { resourceId: true, }, where: { + resource: { + type, + }, permission: { sysRolePermission: { some: { roleId }, diff --git a/server/src/modules/system/test/test.controller.ts b/server/src/modules/system/test/test.controller.ts index 51b7376..5b0c2ae 100644 --- a/server/src/modules/system/test/test.controller.ts +++ b/server/src/modules/system/test/test.controller.ts @@ -1,11 +1,9 @@ import { Controller, Get } from '@nestjs/common'; import { TestService } from './test.service'; -import { SkipAuth } from '@/common/decorators/skip-auth.decorator'; import { Permissions } from '@/common/decorators/permissions.decorator'; @Controller() -// @SkipAuth() @Permissions('test', 'test2') export class TestController { constructor(private readonly testService: TestService) {} diff --git a/server/src/utils/index.ts b/server/src/utils/index.ts index e69de29..d4bd419 100644 --- a/server/src/utils/index.ts +++ b/server/src/utils/index.ts @@ -0,0 +1,5 @@ +import { difference, isEmpty } from 'lodash'; + +export const isArrayFullyContained = (arr1: any[], arr2: any[]) => { + return isEmpty(difference(arr2, arr1)); +};