feat: RBAC
This commit is contained in:
@@ -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`);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
126
admin/src/pages/role/assignResources.tsx
Normal file
126
admin/src/pages/role/assignResources.tsx
Normal file
@@ -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 (
|
||||
<div>
|
||||
<Modal
|
||||
title={<span>分配权限</span>}
|
||||
visible={visible}
|
||||
onOk={onOk}
|
||||
onCancel={handleCancel}
|
||||
confirmLoading={confirmLoading}
|
||||
>
|
||||
<Form form={form}>
|
||||
<Form.Item hidden field="id">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="菜单权限">
|
||||
{handleFinished && (
|
||||
<Tree
|
||||
checkable
|
||||
checkedKeys={checkedKeys}
|
||||
autoExpandParent={true}
|
||||
onCheck={(value) => {
|
||||
setCheckedKeys(value);
|
||||
}}
|
||||
treeData={treeData}
|
||||
fieldNames={{
|
||||
key: 'id',
|
||||
title: 'name',
|
||||
}}
|
||||
></Tree>
|
||||
)}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GrantPermission;
|
||||
@@ -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<PaginationProps>({
|
||||
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}
|
||||
/>
|
||||
|
||||
<AssignResources
|
||||
handleConfirm={handleConfirm}
|
||||
handleCancel={handleCancel}
|
||||
visible={visibleAssignResources}
|
||||
record={record}
|
||||
/>
|
||||
|
||||
<Title heading={6}>角色管理</Title>
|
||||
|
||||
<SearchForm onSearch={handleSearch}/>
|
||||
|
||||
@@ -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('无权访问!');
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ export class PermissionService {
|
||||
resourceId: true,
|
||||
},
|
||||
where: {
|
||||
resource: {
|
||||
type,
|
||||
},
|
||||
permission: {
|
||||
sysRolePermission: {
|
||||
some: { roleId },
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { difference, isEmpty } from 'lodash';
|
||||
|
||||
export const isArrayFullyContained = (arr1: any[], arr2: any[]) => {
|
||||
return isEmpty(difference(arr2, arr1));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user