1. RBAC
今天我们来说说MES的权限模块,首先我们要了解什么叫做RBAC
RBAC(Role-Based Access Control)是一种通过角色作为权限分配中介的访问控制模型,其核心逻辑为:
用户 → 分配角色 → 角色拥有权限 → 权限控制资源访问
关键特征: - 角色抽象:将权限与具体用户解耦,通过角色实现权限的批量管理(如"仓库角色"自动继承所有仓库相关权限) - 最小特权原则:用户仅获得完成工作所需的最小权限集 - 职责分离(SoD):关键操作需多角色共同授权(如审计员与操作员角色互斥)
graph TD
User -->|1:N| Role
Role -->|M:N| Permission
Permission -->|操作+资源| Resource
Session -->|动态绑定| Context
2. 数据库设计
表ER图如下:
说明 用户和组织一对一 用户和角色多对多 角色和菜单多对多
2.1 系统用户表
| 字段名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| id | BIGINT | NOT NULL | 主键ID |
| org_id | BIGINT | 部门ID | |
| account | VARCHAR(128) | NOT NULL | 账号 |
| password | VARCHAR(256) | NOT NULL | 密码 |
| name | VARCHAR(128) | NOT NULL | 姓名 |
| gender | VARCHAR(16) | 性别 | |
| VARCHAR(128) | 邮件 | ||
| phone | VARCHAR(32) | 电话 | |
| user_pic | VARCHAR(128) | 用户照片 | |
| status | INT | NOT NULL | 状态 |
| create_by | BIGINT | 创建人 | |
| create_time | DATETIME | 创建时间 | |
| update_by | BIGINT | 更新人 | |
| update_time | DATETIME | 更新时间 |
2.2 组织架构
| 字段名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| id | BIGINT | NOT NULL | |
| parent_id | BIGINT | 父节点ID | |
| name | VARCHAR(128) | NOT NULL | 名称 |
| description | VARCHAR(128) | 描述 | |
| status | INT | 状态 | |
| create_by | BIGINT | 创建人 | |
| create_time | DATETIME | ||
| update_by | BIGINT | ||
| update_time | DATETIME |
2.3 系统角色
| 字段名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| id | BIGINT | NOT NULL | 主键ID |
| code | VARCHAR(128) | NOT NULL | 角色编码 |
| name | VARCHAR(128) | NOT NULL | 角色名称 |
| description | VARCHAR(1024) | 描述 | |
| status | INT | 状态 | |
| create_by | BIGINT | 创建人 | |
| create_time | DATETIME | 创建时间 | |
| update_by | BIGINT | 更新人 | |
| update_time | DATETIME |
2.4 菜单信息
| 字段名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| id | BIGINT | NOT NULL | 主键ID |
| parent_id | BIGINT | 父ID | |
| label | VARCHAR(128) | 标题 | |
| name | VARCHAR(128) | 名称 | |
| status | VARCHAR(8) | NOT NULL | 状态 |
| sn | INT | 排序 | |
| type | INT | 类型 | |
| icon | VARCHAR(128) | 图标 | |
| component | VARCHAR(256) | 路由组件 | |
| route | VARCHAR(256) | 路由地址 | |
| permission | VARCHAR(256) | 权限标识 | |
| hidden | CHAR(1) | 隐藏 | |
| new_feature | BIT(1) | 新特性 | |
| frame_src | VARCHAR(256) | 内嵌页面 | |
| create_by | BIGINT | ||
| create_time | DATETIME | ||
| update_by | BIGINT | ||
| update_time | DATETIME |
说明
- 菜单类型分为: 目录、菜单、按钮
- permission字段可以给对应的目录、菜单、按钮设置权限码,系统通过当前用户是否有对应的权限码进行相应的隐藏和现实
2.5 用户角色关联
| 字段名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| id | BIGINT | NOT NULL | 主键ID |
| role_id | BIGINT | 角色ID | |
| user_id | BIGINT | 用户ID | |
| create_by | BIGINT | ||
| create_time | DATETIME | ||
| update_by | BIGINT | ||
| update_time | DATETIME |
2.6 角色菜单关联
| 字段名 | 类型 | 是否必填 | 说明 |
|---|---|---|---|
| id | BIGINT | NOT NULL | 主键 |
| role_id | BIGINT | NOT NULL | 角色ID |
| menu_id | BIGINT | NOT NULL | 菜单ID |
| create_by | BIGINT | ||
| create_time | DATETIME | ||
| update_by | BIGINT | ||
| update_time | DATETIME |
3 关键代码
3.1 菜单数据树形结构处理
//代码路径:src/main/java/com/hgyc/mom/system/service/impl/SysMenuServiceImpl.java
private List<MenuVO> convertToTree(List<SysMenu> menus) {
Map<Long, List<MenuVO>> parentChildrenMap = new HashMap<>();
for (SysMenu menu : menus) {
Long parentId = menu.getParentId();
MenuVO menuVO = new MenuVO();
BeanUtils.copyProperties(menu, menuVO);
// 将当前菜单项添加到父菜单项的子菜单项列表中
List<MenuVO> children = parentChildrenMap.getOrDefault(parentId, new ArrayList<>());
children.add(menuVO);
parentChildrenMap.put(parentId, children);
}
// 将根节点的子菜单项列表转换为树形结构
List<MenuVO> rootChildren = parentChildrenMap.getOrDefault(0L, new ArrayList<>());
for (MenuVO menu : rootChildren) {
convertToTree(menu, parentChildrenMap);
}
return rootChildren;
}
private void convertToTree(MenuVO parent, Map<Long, List<MenuVO>> parentChildrenMap) {
Long parentId = parent.getId();
List<MenuVO> children = parentChildrenMap.getOrDefault(parentId, new ArrayList<>());
parent.setChildren(children);
for (MenuVO child : children) {
convertToTree(child, parentChildrenMap);
}
}
前端菜单树的数据结构为树形parent->children, 我们后端查询出来的是表格的数据结构,所以我们要把列表型的数据结构转换成树形的数据结构。
同理,前端左侧菜单是有当前用户所拥有的菜单权限展示的,我们也需要把菜单权限转换成antdesign-menu的数据结构和react-router路由所需的数据结构; 菜单数据渲染
//代码路径
hgyc-mom-web/src/layouts/dashboard/nav/nav-vertical.tsx
//处理菜单权限数据结构
const permissionRoutes = usePermissionRoutes();
...
const menuList = useMemo(() => {
const menuRoutes = menuFilter(permissionRoutes);
return routeToMenuFn(menuRoutes);
}, [routeToMenuFn, permissionRoutes]);
...
<Scrollbar>
<Menu
mode="inline"
inlineIndent={16}
items={menuList}
theme={sidebarTheme}
selectedKeys={selectedKeys}
openKeys={openKeys}
onOpenChange={handleOpenChange}
className="!border-none [&.ant-menu-inline-collapsed_.ant-menu-item]:!px-2 [&.ant-menu-inline-collapsed_.ant-menu-submenu-title]:!px-2 [&_.ant-menu-item]:!text-[14px] [&_.ant-menu-submenu-title]:!text-[14px]"
onClick={onClick}
/>
</Scrollbar>
数据转换主要是: menuFilter 和 routeToMenuFn 这两个方法。
如需详细代码,请下载源码查看。
3.2 按钮权限
前端自定义hook组件
import useAuth from "@/hooks/system/useAuth";
const { hasPermission } = useAuth();
// 使用示例
<div>
{hasPermission(['mes:md:mditem:add']) && (
<Button type="primary" onClick={handleAdd}>新增</Button>
)}
</div>
4. 功能介绍
4.1 组织管理
以树形结构展示部门的上下级关系。
4.2 用户管理
维护用户的基础信息,点击编辑,用户基本信息以抽屉的方式在右侧弹出
4.3 角色管理
角色基本信息维护
角色权限配置,点击列表右侧操作栏中的【权限】按钮,会弹出已有的权限菜单,以树形展示,如果已有菜单权限,勾选框会自动勾选
分配角色用户,点击列表右侧操作栏中的【分配用户】按钮,抽屉方式展示该角色已分配的用户信息,点击添加按钮可以分配角色用户。
4.4 菜单管理
菜单类型分为三种: 目录、菜单、按钮。默认新增的是目录菜单。目录菜单设置的路由为该目录下所有菜单的根地址,路由跳转会自动带上 根路由+菜单路由
说明:
- 路由:访问菜单浏览器显示的地址
- 组件路径:访问该页面代码的页面的路径
- 权限标识:配置权限字符,在按钮或接口访问,如果没有权限字符会提示:权限不足
开源项目地址:
欢迎在评论区分享你的技术选型经验,或对本文方案的改进建议!