EOS 菜单与角色关联关系说明

本文档说明 MES 项目中 EOS 原生权限体系与 MES Web RBAC 的关系,以及角色、菜单数据分别存储在哪里、如何保存关联。

2026-07-31 更新:按当前代码实现修订。eosrole 已映射并读写 **STPermission**(菜单「执行」+ ERP 对象操作)与 **STUserGroup**(用户-角色);MES Web 按钮权限写入 **sys_role_menu**。核心授权表仍为 **mesdata.dbo.STPermission**。

关联文档:EOS 菜单树 NodeID 与 ObjectID 说明

结论摘要

**在本项目(mes-parent)中,EOS 角色 ↔ 菜单授权可由 MES eosrole「配置菜单权限」写入 STPermission**;也可在 EOS 原生安全管理器中配置。用户 ↔ 角色由 eosrole「配置用户权限」双写 STUserGroup + sys_user_role

能力 MES 是否实现
EOS 用户/角色 mesdata STUsersuGroup=0/1 角色 CRUD(eosrole)
用户 ↔ 角色 mesdata STUserGroup 已映射,由 EosRoleUserServiceImpl 管理(双写 sys_user_role
角色 ↔ 菜单 mesdata STPermission 已映射,由 EosRolePermissionServiceImpl / EosRoleObjectPermissionServiceImpl 读写
EOS 菜单树 messys Tree + ObjectRegister eostree 维护/同步
EOS 角色 → MES 角色 mesdata sys_roleeos_role_id 同步元数据;权限保存时也可自动 syncRole
MES 角色 → MES 菜单/按钮 mesdata sys_role_menu 角色管理页面;eosrole 保存 MES 按钮时亦写入
EOS NodeID → MES menu_id mesdata sys_menu_eos_node sync/backfill/copy 写入

EOS 原生权限生效链路:

1
2
3
4
5
6
STUsers(用户,uGroup=0)
→ STUserGroup(UserID → GroupID)
→ STUsers(角色,uGroup=1)
→ STPermission(UserID=角色OID, ObjectID)
→ messys.ObjectRegister(TypeID=7)
→ messys.Tree(OwnerID=NodeID)

1. EOS 侧数据模型

1.1 用户与角色:STUsers(mesdata)

角色与用户共用 mesdata.dbo.STUsers,通过 uGroup 区分:

uGroup 含义
0 用户
1 角色

相关代码:StusersEntityStusersServiceImpl.saveRole / updateRoleEosRoleController(路径 /eosrole)。

eosrole 页面「保存角色」只写角色基本信息(编码、名称、描述、分组 Categories、启用状态),不涉及菜单勾选。菜单与对象操作权限在「配置菜单权限」弹窗中单独保存(见 §2.3);用户分配在「配置用户权限」中保存(见 §2.5)。

EmpowermentRightRelateUserH 等字段在 MES 新增角色时仅填默认值,不用于菜单授权;菜单授权走 STPermission

1.2 用户-角色:STUserGroup(mesdata)

字段 含义
UserID 用户 OID(STUsers.OIDuGroup=0
GroupID 角色 OID(STUsers.OIDuGroup=1
Back 备注

MES 已映射:StUserGroupEntity + EosRoleUserServiceImpl。eosrole「配置用户权限」保存时:

  1. 清空该角色在 STUserGroup 中的旧关联,再批量插入新用户
  2. STUsers.UserIDsys_user.username 匹配 MES 用户;有 MES 用户时先 ensureMesRole,再全量改写 sys_user_role

也可在 EOS 原生安全管理中分配角色(同样写入 STUserGroup)。

1.3 菜单:Tree + ObjectRegister(messys)

作用
Tree messys EOS Web 菜单树(NodeID、ParentID、NodeCaption、Functions 等)
ObjectRegister messys 对象注册;OwnerID = Tree.NodeIDTypeID = 7 表示菜单/目录
UniqueNO messys 分配 Tree 的 NodeID(NName='tree'

新增菜单节点时(TreeServiceImpl.addNode)会:

  1. UniqueNO 分配新 NodeID
  2. 写入 Tree
  3. 写入 ObjectRegister,并将返回的 ObjectID 回填到 Tree.ObjectID

从 MES 同步菜单时(TreeServiceImpl.syncFromSysMenu),同样会为每个节点创建 ObjectRegister 记录。

数据库 schema 配置见 mes-web/src/main/resources/schema.properties

1
eossys.schema=messys.dbo

1.4 角色-菜单关联:STPermission(mesdata)

核心授权表。EOS 原生安全管理器与 MES eosrole「配置菜单权限」保存时均写入此表。

字段 类型 含义
UserID int STUsers.OID;绝大多数为角色(uGroup=1),少量为用户直授(uGroup=0
ObjectID int messys.dbo.ObjectRegister.ObjectID
Oprate nvarchar 操作类型(见下表)
Permission int 权限值;含义随 Oprate 类型不同(见下表)

ERP 模式下 Permission 双语义(本环境 WebPermission 未启用,按钮授权均走 STPermission):

Oprate 类型 示例 Permission 含义
菜单 执行 1 允许可见(白名单;无记录=不可见)
按钮/操作 增加EX61:组织架构分发 0 禁用(黑名单;无记录=允许)

常见 Oprate 取值:执行(菜单);标准操作(增加、修改、删除、审核…);自定义 EX{ExFunction.ID}:{SDesc}(如 EX61:组织架构分发)。

菜单授权路径:

1
2
3
STPermission(ObjectID, Oprate='执行', Permission=1)
→ ObjectRegister (TypeID=7, OwnerID=Tree.NodeID)
→ Tree.NodeCaption

MES 读写实现:

服务 职责
EosRolePermissionServiceImpl 查询/保存菜单「执行」(objectIds + mode
EosRoleObjectPermissionServiceImpl ERP 对象操作(operationPermissions)+ MES 按钮(sys_role_menu

queryMenuObjectIds 过滤条件:Oprate=执行Permission=1当前未按 ObjectRegister.TypeID=7 过滤)。

1.5 ERP 按钮权限(ExFunction + DjDy)

层级 粒度 存储
按钮定义 每个 ExFunction 一条 messys.ExFunctionNote1 like 'button%'
授权 ObjectID 每个单据(DjDy)一个 ObjectRegister.Object = DjDy.DjCNameTypeID != 7(OwnerID 可为 null)
授权 Oprate 每个按钮一个 EX{ID}:{SDesc};同一 ObjectID 多行 = 同单据多按钮

链路:

1
2
3
4
5
Tree.Functions(如 单据(组织架构))
→ DjDy(DjCName=组织架构, DjDm=303)
→ ExFunction(SType=DJ, SID=DjDm)→ Oprate=EX61:组织架构分发
→ ObjectRegister(Object=组织架构, ObjectID=965, TypeID=1)
→ STPermission(UserID, ObjectID=965, Oprate, Permission=0 表示禁用)

复合单据示例(Tree NodeID=430)

用途 ObjectID TypeID 说明
菜单「执行」 468 7 OwnerID=430,名「组织机构关系表」
按钮 EX61 965 1 OwnerID=null,名「组织架构」,与菜单 ObjectID 不同

同一 DjDm 下多个 ExFunction(如 DjDm=212 的 EX13/EX14/EX16/EX17)共用同一业务 ObjectID,以不同 Oprate 区分。

业务 ObjectID 解析见 EOS 菜单树 NodeID 与 ObjectID 说明 §5.1(EosMenuObjectResolver)。

说明WebPermission 本环境未启用;SType=CX 且安全管理器不可见的 STPermission 记录(如 EX10:查看JSON)视为历史垃圾数据。

当前环境数据量(仅供参考)

记录数 说明
STPermission ~1625 主路径(角色 ~1595,用户直授 ~30)
WebPermission 0 Web 对象权限(RoleID + ObjID),本环境未使用
STDomainPermission 0 域-菜单节点(DomainID + MenuNodeID),本环境未使用

1.6 跨库关系图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──────────────────────── mesdata ────────────────────────┐
│ STUsers (uGroup=0 用户 / uGroup=1 角色) │
│ ↑ UserID ↑ GroupID │
│ STUserGroup ──────────────┘ │
│ ↑ UserID (角色 OID) │
│ STPermission (ObjectID, Oprate, Permission) │
│ sys_role / sys_role_menu / sys_menu_eos_node │
└──────────────────────────│ ObjectID ──────────────────────┘

┌──────────────────────── messys ─────────────────────────┐
│ ObjectRegister (ObjectID, TypeID=7, OwnerID=NodeID) │
│ ↑ OwnerID │
│ Tree (NodeID, NodeCaption, ParentID, ObjectID) │
└─────────────────────────────────────────────────────────┘

2. MES 侧数据模型

2.1 角色-菜单:sys_role_menu

MES Web「角色管理」(role.html + role.js)通过 zTree 勾选菜单,提交 menuIdList/sys/role/save/sys/role/update

保存逻辑(SysRoleMenuServiceImpl.saveOrUpdate):

  1. role_id 删除原有关联
  2. 批量插入新的 (role_id, menu_id) 记录

权限生效链路:

1
sys_user → sys_user_role → sys_role → sys_role_menu → sys_menu

eosrole 保存 MES 菜单/按钮时也会写入同一张表(见 §2.3)。

2.2 EOS 角色同步到 MES

EosRoleSyncServiceImpl / 定时任务 EosTask.sycnEosRole() 调用:

  • sysRoleService.saveWithoutMenu() — 新建 MES 角色
  • sysRoleService.updateWithoutMenu() — 更新 MES 角色

同步字段:角色名、描述、分组、eos_role_id定时/手动同步本身不会同步 EOS STPermission 或 MES sys_role_menu

若在 eosrole「配置菜单权限」中提交了 mesMenuIds / mesButtonMenuIds,且尚无对应 MES 角色,会自动 syncRole 后再写 sys_role_menu

2.3 eosrole「配置菜单权限」

eosrole 弹窗支持统一配置:

菜单类型 识别 保存位置
ERP 原生 Functions 不含「函数打开网页」(menuType=erp 菜单 → STPermissionOprate=执行, Permission=1);按钮 → STPermissionEXxx:... 等, Permission=0=禁用)
MES Web Functions 含「函数打开网页」(menuType=mes),sys_menu_eos_nodemenu_ideos_node_id 菜单执行 → STPermission;按钮 → sys_role_menu
目录 menuType=folder 通常仅作树结构;勾选子节点时汇总子菜单 objectId

相关 API(EosRoleController,权限 eosrole:permission):

方法 路径 作用
* /eosrole/menu/tree 权限菜单树(含 objectId / menuType / sysMenuId
GET /eosrole/permission/{roleId} 已授权菜单 objectId(执行+Permission=1)
GET /eosrole/permission/detail/{roleId}?nodeId= 节点对象/按钮权限详情
GET /eosrole/permission/mes-menu-ids/{roleId} 回显 MES 角色全部 sys_role_menu menuId
POST /eosrole/permission/save 保存菜单执行 + ERP 操作 + MES 按钮
* /eosrole/menu/backfill-mes-menu-id 历史 MES 节点回填 sys_menu_eos_node

POST /eosrole/permission/save body(EosRolePermissionSaveRequest):

字段 说明
roleIds EOS 角色 OID 列表
objectIds 勾选菜单的 Tree.ObjectIDSTPermission「执行」
mode append / replace
mesMenuIds MES 目录/菜单 menu_idsys_role_menu
mesButtonMenuIds MES 按钮 menu_idsys_role_menu
operationPermissions {objectId, oprate, granted};ERP 禁止列表(granted=false 写 Permission=0)
operationReplaceObjectIds replace 时需清理非「执行」权限的业务 ObjectID

保存流程:

  1. EosRolePermissionServiceImpl.savePermissions(roleIds, objectIds, mode) — 写菜单「执行」
  2. EosRoleObjectPermissionServiceImpl.saveMesAndObjectPermissions(request) — ERP 操作 + MES 按钮

mode=replace 时:先按该角色已有「执行」的 ObjectID 删除这些 ObjectID 下全部 Oprate 行,再插入勾选的「执行」;mesMenuIdsmesButtonMenuIds 都为空时不改动 sys_role_menu

说明Tree.Functions 里 URL 的 menuId=EOS NodeID(页面跳转用);sys_menu.menu_id 的权威映射在 **mesdata.sys_menu_eos_node**(eos_node_idmenu_id),由 syncFromSysMenu 写入或 backfillSysMenuEosNode 按 URL/NodeDesc 回填。过渡期仍双写 NodeDesc=MES_MENU:{sysMenuId} 作兼容 fallback。

建表与历史迁移脚本:sys_menu_eos_node.sqlsys_menu_eos_node.mysql.sql(位于 mes-security-eos 模块 doc/)。

2.4 MES 菜单管理「设置到 EOS」

MES 菜单管理页(sys/menu)勾选目录/菜单后点击「设置到 EOS」,调用 POST /tree/syncFromSysMenumenu.js#setToEos)。

步骤 行为
1 用户选择 EOS 目标目录 parentNodeId
2 TreeServiceImpl.syncFromSysMenu 为每个 type=0/1 的 sys_menu 新建 EOS Tree 节点
3 applySysMenuMapping 写入 sys_menu_eos_node,并双写 NodeDesc=MES_MENU:{sysMenuId}(目录与菜单均写)
4 type=1 且有 url 时,Functions 生成 MES Web 跳转 URL,其中 menuId= 为新分配的 EOS NodeID

历史已同步但未写映射的节点,可任选以下方式处理(当前 sync 始终新建节点,不更新旧节点):

方式 说明
定时任务 EosTask.backfillSysMenuEosNode,默认每日 3:00(t_qrtz_schedule_job;升级见 V2__Update_backfill_sys_menu_eos_node_job.sql
手动接口 /eosrole/menu/backfill-mes-menu-id
重新同步 菜单管理页「设置到 EOS」(会新建节点)
SQL 迁移 执行 sys_menu_eos_node.sql 从 NodeDesc 导入历史映射

定时任务与手动接口共用 TreeService.backfillSysMenuEosNode()局限:仅对尚无映射记录的节点回填;URL 匹配仅适用于 Functions 含「函数打开网页」的 MES Web 菜单;type=0 目录(Functions 为空)若 NodeDesc 也无映射,需重新「设置到 EOS」。

2.5 eosrole「配置用户权限」

方法 路径 权限 作用
GET /eosrole/users/assigned/{roleId} eosrole:user 已授权用户(合并 EOS + MES)
GET /eosrole/users/unassigned/{roleId} eosrole:user 待授权用户
POST /eosrole/users/save eosrole:user 双写 STUserGroup + sys_user_role

3. 两套体系对照

维度 EOS 原生 MES Web
用户/角色表 mesdata.STUsers sys_user / sys_role
用户-角色 mesdata.STUserGroup sys_user_role
菜单表 messys.Tree + ObjectRegister sys_menu
角色-菜单关联 mesdata.STPermission sys_role_menu
MES 页面维护 eosrole(菜单+ERP 对象操作+MES 按钮+用户)/ eostree 角色管理(含菜单授权)
跨系统同步 EOS → MES 同步角色元数据;eosrole 保存时可统一写两侧 可与 eosrole 统一配置

4. 查询 SQL 示例

以下 SQL 在 SQL Server 上验证可用;跨库查询需当前连接对 mesdatamessys 均有读权限。

4.1 查看某角色的菜单权限(含菜单名)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- @RoleOID: STUsers.OID,且 uGroup = 1
DECLARE @RoleOID INT = 3;

SELECT
r.OID AS RoleOID,
r.UserName AS RoleName,
p.ObjectID,
o.[Object] AS ObjectName,
o.TypeID,
t.NodeID,
RTRIM(t.NodeCaption) AS MenuCaption,
p.Oprate,
p.Permission
FROM mesdata.dbo.STUsers r
JOIN mesdata.dbo.STPermission p ON p.UserID = r.OID
JOIN messys.dbo.ObjectRegister o ON p.ObjectID = o.ObjectID
LEFT JOIN messys.dbo.Tree t ON o.OwnerID = t.NodeID
WHERE r.OID = @RoleOID
AND r.uGroup = 1
AND o.TypeID = 7 -- 菜单/目录
ORDER BY t.NodeID, p.Oprate;

4.2 查看某用户通过角色继承的菜单(完整链路)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- @UserOID: STUsers.OID,且 uGroup = 0
DECLARE @UserOID INT = 11;

SELECT
u.UserName AS UserName,
r.UserName AS RoleName,
RTRIM(t.NodeCaption) AS MenuCaption,
p.Oprate,
p.Permission
FROM mesdata.dbo.STUsers u
JOIN mesdata.dbo.STUserGroup ug ON ug.UserID = u.OID
JOIN mesdata.dbo.STUsers r ON ug.GroupID = r.OID AND r.uGroup = 1
JOIN mesdata.dbo.STPermission p ON p.UserID = r.OID
JOIN messys.dbo.ObjectRegister o ON p.ObjectID = o.ObjectID AND o.TypeID = 7
JOIN messys.dbo.Tree t ON o.OwnerID = t.NodeID
WHERE u.OID = @UserOID
AND u.uGroup = 0
AND p.Oprate = N'执行' -- 菜单可见通常对应「执行」
ORDER BY r.UserName, t.NodeID;

4.3 统计各角色的菜单授权数量

1
2
3
4
5
6
7
8
9
10
11
SELECT
r.OID,
r.UserName AS RoleName,
COUNT(DISTINCT p.ObjectID) AS MenuObjectCount,
COUNT(*) AS PermissionRowCount
FROM mesdata.dbo.STUsers r
JOIN mesdata.dbo.STPermission p ON p.UserID = r.OID
JOIN messys.dbo.ObjectRegister o ON p.ObjectID = o.ObjectID AND o.TypeID = 7
WHERE r.uGroup = 1
GROUP BY r.OID, r.UserName
ORDER BY MenuObjectCount DESC;

4.4 查找拥有某菜单权限的所有角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- @MenuNodeID: messys.dbo.Tree.NodeID
DECLARE @MenuNodeID INT = 17;

SELECT DISTINCT
r.OID,
r.UserName AS RoleName,
RTRIM(t.NodeCaption) AS MenuCaption,
p.Oprate
FROM messys.dbo.Tree t
JOIN messys.dbo.ObjectRegister o ON o.OwnerID = t.NodeID AND o.TypeID = 7
JOIN mesdata.dbo.STPermission p ON p.ObjectID = o.ObjectID
JOIN mesdata.dbo.STUsers r ON p.UserID = r.OID AND r.uGroup = 1
WHERE t.NodeID = @MenuNodeID
ORDER BY r.UserName, p.Oprate;

4.5 对比 EOS 角色与 MES 角色(不含菜单)

1
2
3
4
5
6
7
8
9
10
11
SELECT
sr.role_id,
sr.role_name AS MesRoleName,
sr.eos_role_id,
er.UserName AS EosRoleName,
er.OID AS EosRoleOID
FROM mesdata.dbo.sys_role sr
LEFT JOIN mesdata.dbo.STUsers er
ON sr.eos_role_id = er.OID AND er.uGroup = 1
WHERE sr.eos_role_id IS NOT NULL
ORDER BY sr.role_id;

4.6 检查菜单节点是否已在 ObjectRegister 注册

1
2
3
4
5
6
7
8
9
SELECT
t.NodeID,
RTRIM(t.NodeCaption) AS MenuCaption,
t.ObjectID,
o.ObjectID AS RegObjectID,
o.TypeID
FROM messys.dbo.Tree t
LEFT JOIN messys.dbo.ObjectRegister o ON o.OwnerID = t.NodeID
ORDER BY t.NodeID;

5. 相关代码路径

模块 路径
EOS 角色实体 mes-security-eos/.../entity/StusersEntity.java
EOS 角色服务 mes-security-eos/.../service/impl/StusersServiceImpl.java
EOS 角色同步 mes-security-eos/.../service/impl/EosRoleSyncServiceImpl.java
EOS 菜单树 mes-security-eos/.../service/impl/TreeServiceImpl.java
菜单「执行」权限 mes-security-eos/.../service/impl/EosRolePermissionServiceImpl.java
ERP/MES 对象与按钮 mes-security-eos/.../service/impl/EosRoleObjectPermissionServiceImpl.java
用户-角色 mes-security-eos/.../service/impl/EosRoleUserServiceImpl.java
业务 ObjectID 解析 mes-security-eos/.../support/EosMenuObjectResolver.java
实体 StPermissionEntityStUserGroupEntityTreeEntityObjectRegisterEntity
前端 static/js/modules/security/eosrole.js
MES 角色-菜单 mes-core/.../service/impl/SysRoleMenuServiceImpl.java
NodeID ↔ menu_id sys_menu_eos_node + SysMenuEosNodeService
sys_menu → Tree SQL doc/sql/sys_menu_to_eos_tree.sql

本环境未使用的 EOS 权限表(可扩展)

说明
WebPermission Web 对象细粒度权限(RoleID + ObjID + Operate)
STDomainPermission 域 ↔ Tree.NodeID

6. 运维与开发建议

  1. EOS 客户端菜单权限:可在 EOS 原生安全管理中配置,也可在 MES eosrole「配置菜单权限」中配置;数据均写入 mesdata.dbo.STPermission。运维排查可用上文 SQL。
  2. MES 页面权限:可在「系统管理 → 角色管理」勾选 sys_menu;也可在 eosrole 中为 MES Web 菜单勾选按钮(写入同一 sys_role_menu)。菜单「执行」可见性仍依赖 STPermission
  3. 两套菜单数据源
    • Tree / ObjectRegister:EOS 客户端 / Web 壳菜单
    • sys_menu:MES 页面与 Shiro 权限
    • eosrole 可在同一弹窗统一配置两侧,但没有STPermission 自动全量同步到 sys_role_menu(或反向)的后台任务;MES 按钮仅在 eosrole 保存时写入。
  4. 从 EOS 同步角色后:若需在 MES Web 中使用该角色的菜单/按钮,可在角色管理中分配,或在 eosrole 配置菜单权限时一并提交 mesMenuIds / mesButtonMenuIds
  5. 跨库注意STUsersSTPermissionSTUserGroupsys_*mesdataTreeObjectRegistermessys,关联字段为 STPermission.ObjectIDObjectRegister.ObjectID

7. 修订记录

日期 说明
2026-06-15 初版:基于 mes-parent 代码梳理 EOS / MES 角色菜单关系
2026-06-16 eosrole 扩展:ERP 对象操作权限 + MES 按钮权限统一配置
2026-07-01 补充 ERP 按钮权限:STPermission 双语义、ExFunction/DjDy 链路、965/468 复合单据示例;WebPermission 未启用
2026-07-31 按最新实现修订:STPermission/STUserGroup 已映射;统一结论/§5/§6 口径;补 save body(含 operationReplaceObjectIds)、用户权限 API、相关代码路径