EOS 菜单树 NodeID 与 ObjectID 说明

本文档说明 eosrole 页面「配置 EOS 菜单权限」树节点中 NodeID(前端 zTree 的 id)与 ObjectID(前端 objectId)的含义、数据库来源、后端组装与前端使用方式。

关联文档:EOS 菜单与角色关联关系说明

2026-07-31 更新:权限树接口额外返回 menuType / sysMenuId;保存 body 含 MES 按钮与 ERP 操作扩展字段。

结论摘要

前端字段 含义 数据库来源 主要用途
zTree id 菜单节点 ID messys.Tree.NodeID 树结构、父子关系(pId);前端勾选态缓存键
zTree objectId EOS 权限对象 ID messys.Tree.ObjectID = messys.ObjectRegister.ObjectID 角色菜单授权(STPermission.ObjectID,「执行」)
zTree menuType 菜单类型 EosMenuTypeHelper.resolveMenuType mes / erp / folder,决定右侧详情面板
zTree sysMenuId MES 菜单 ID 优先 sys_menu_eos_node,否则 NodeDesc=MES_MENU: MES 菜单/按钮写入 sys_role_menu

NodeID 与 ObjectID 是两套独立编号体系,不可混用。角色菜单「执行」授权必须使用 ObjectID;NodeID 用于树层级与 detail 查询参数。


1. NodeID 是什么

NodeID 是 EOS 菜单树 messys.dbo.Tree 的主键,标识一个菜单/目录节点在树中的位置。

在 MES 前端 zTree 中,NodeID 映射为节点的 **id**(idKey: "id"),ParentID 映射为 **pId**,用于渲染树形结构与展开/折叠。前端勾选态 permissionCheckedNodeIds 也按 NodeID 缓存。

NodeID 不写入 STPermission。权限表引用的是 ObjectID。detail 接口用 nodeId 定位节点后再解析业务 ObjectID。


2. NodeID 从哪来

2.1 数据库表

表 库 字段 说明
Tree messys NodeID 菜单节点主键
Tree messys ParentID 父节点 NodeID(根为 0)
UniqueNO messys No 下一个可用 NodeID(NName='tree')

2.2 MES 新建节点时的分配逻辑

MES 通过 TreeServiceImpl.getNextNodeIdForTree() 从 UniqueNO 表取号:

  1. 悲观锁查询 UniqueNO 中 NName='tree' 的记录(PESSIMISTIC_WRITE)
  2. 取当前 No 作为新 NodeID
  3. 将 No + 1 写回,占用该编号
1
2
3
4
5
6
// TreeServiceImpl.getNextNodeIdForTree()
UniqueNOEntity row = uniqueNODao.findByNnameForUpdate(UNIQUE_NO_NAME_TREE);
int currentNo = row.getNo();
row.setNo(currentNo + 1);
uniqueNODao.save(row);
return Long.valueOf(currentNo);

2.3 触发 NodeID 分配的操作

操作 方法 说明
新增菜单节点 TreeServiceImpl.addNode 先 getNextNodeIdForTree(),再写 Tree
sys_menu 同步 TreeServiceImpl.syncFromSysMenu 每个同步节点分配新 NodeID
复制菜单 TreeServiceImpl.copyMenu 源节点及子树每个节点各分配新 NodeID
交接班菜单批量创建 createShiftLogDispatchMenus → addNode 同上

2.4 EOS 原生 / 历史数据

在 MES 介入之前,NodeID 可能由 EOS 客户端或其他工具写入 Tree,同样依赖 UniqueNO 序列或历史遗留编号。MES 只保证新增/同步/复制时走 getNextNodeIdForTree() 逻辑。

2.5 NodeID 在 Functions 中的用途

Tree.Functions 字段保存菜单跳转 URL,其中 menuId 参数值为 NodeID(不是 ObjectID)。例如同步 sys_menu 时:

1
{baseUrl}/{url}?token=%Token%&menuId={NodeID}

复制菜单时,TreeServiceImpl 会将 Functions 里的 menuId=旧NodeID 替换为 menuId=新NodeID。

2.6 NodeID 生命周期

  • 查询:treeDao.findAll(...)、queryObject(nodeid) 等
  • 删除:delete / deleteBatch 按 NodeID 删除 Tree,并先 ObjectRegisterDao.deleteByOwnerId(NodeID) 清理关联对象
flowchart LR
  A[UniqueNO<br/>NName=tree] -->|取 No 作为 NodeID| B[Tree.NodeID]
  B --> C[listTreeForPermission<br/>返回 id=NodeID]
  C --> D[zTree id / pId<br/>树结构展示]

3. ObjectID 是什么

ObjectID 是 EOS 对象注册表 messys.dbo.ObjectRegister 的主键。每个可授权对象在 ObjectRegister 中注册一条记录;菜单/目录节点对应 TypeID = 7,并通过 OwnerID = Tree.NodeID 与菜单树关联。

MES 维护菜单时会将 ObjectID 冗余写入 Tree.ObjectID,便于权限树接口一次查询带出。

在角色菜单授权场景下,前端勾选菜单后提交的是 objectId 列表,写入 mesdata.dbo.STPermission.ObjectID(配合 UserID=角色 OID、Oprate=「执行」、Permission=1)。


4. ObjectID 从哪来

4.1 数据库表

层级 库/表 字段 含义
权威主键 messys ObjectRegister ObjectID 全局对象 ID(自增:MAX+1)
关联 messys ObjectRegister OwnerID = Tree.NodeID
类型 messys ObjectRegister TypeID MES 新建菜单固定为 7(菜单/目录)
冗余 messys Tree ObjectID 与 ObjectRegister.ObjectID 一致
授权 mesdata STPermission ObjectID 引用 ObjectRegister.ObjectID

关系:Tree.NodeID ← ObjectRegister.OwnerID;Tree.ObjectID = ObjectRegister.ObjectID。

4.2 MES 新建节点时的分配逻辑

TreeServiceImpl.saveObjectRegisterForTree():

  1. ObjectRegisterDao.findMaxObjectId() 取 MAX(ObjectID) + 1
  2. 插入 ObjectRegister(TypeID=7,OwnerID=NodeID,Object=菜单名)
  3. 将返回的 ObjectID 写入 Tree.ObjectID

与 NodeID 分配在同一事务内完成(如 addNode 先取 NodeID,再创建 ObjectRegister)。

4.3 触发 ObjectID 分配的操作

与 NodeID 分配的操作相同(新增、同步、复制)。复制菜单时 NodeID 与 ObjectID 均重新分配,不会沿用源节点的 ObjectID。

场景 ObjectID 来源
MES 新增 EOS 菜单节点 saveObjectRegisterForTree 新分配
从 sys_menu 同步 每个同步节点一条新 ObjectRegister
复制菜单 新 NodeID + 新 ObjectRegister + 新 ObjectID
EOS 原生/历史数据 可能由 EOS 客户端写入;Tree.ObjectID 可能已存在
失败兜底 object == null 时 Tree.ObjectID = 0(无有效授权对象)
flowchart LR
  A[分配 NodeID] --> B[插入 ObjectRegister<br/>ObjectID=MAX+1]
  B --> C[Tree.ObjectID = ObjectID]
  C --> D[listTreeForPermission<br/>返回 objectId]
  D --> E[前端勾选提交 objectIds]
  E --> F[STPermission 写入]

5. NodeID 与 ObjectID 对照

1
2
3
4
5
6
Tree.NodeID       ──►  zTree.id         (树结构、ParentID 父子关系)
Tree.ObjectID ──► zTree.objectId (STPermission「执行」授权键)

ObjectRegister.OwnerID = Tree.NodeID
ObjectRegister.ObjectID = Tree.ObjectID
ObjectRegister.TypeID = 7(菜单/目录)
对比项 NodeID ObjectID
所属表 messys.Tree 主键 messys.ObjectRegister 主键,冗余在 Tree.ObjectID
分配方式 UniqueNO(NName=’tree’)递增 MAX(ObjectID)+1
前端 zTree 字段 id objectId
是否用于 STPermission「执行」 否 是
复制菜单 重新分配 重新分配

5.1 业务 ObjectID 与 NodeID 的非 OwnerID 关联

菜单授权与 ERP 按钮授权使用不同的 ObjectID,且业务对象的 OwnerID 不一定等于 Tree.NodeID。

场景 ObjectID 示例 TypeID OwnerID STPermission 用途
菜单对象 468 7 430 Oprate=执行, Permission=1
业务对象 965 1 null Oprate=EX61:..., Permission=0

解析路径(EosMenuObjectResolver.resolveBusinessObjects):

  1. ObjectRegister.OwnerID = NodeID 且 TypeID != 7
  2. Tree.Functions 括号内单据名 + 菜单对象名 → DjDy.DjCName → ObjectRegister.Object = DjCName 且 TypeID != 7
  3. 回退:Tree.ObjectID 对应 ObjectRegister(可能含 TypeID=7)

禁止仅用 OwnerID=NodeID 或 Tree.ObjectID 推断按钮授权 ObjectID。复合页可能返回多个业务 ObjectID;同一 ObjectID 上可有多个 EXxx: 按钮 Oprate。

listAvailableOperations:标准操作 + ExFunction EXxx: + 库中已有非「执行」Oprate。

示例(NodeID=430):菜单树 objectId=468 用于勾选「执行」;按钮 EX61:组织架构分发 写在 ObjectID=965。


6. 后端组装与 API

6.1 权限菜单树

  • 接口:GET /eosrole/menu/tree(或 @RequestMapping)
  • 实现:EosRoleController.menuTree() → TreeService.listTreeForPermission()
  • 返回字段:
字段 来源 说明
id Tree.NodeID zTree 节点 ID
pId Tree.ParentID(0 → -1) 父节点
name Tree.NodeCaption 显示名
objectId Tree.ObjectID STPermission「执行」键
menuType EosMenuTypeHelper.resolveMenuType mes / erp / folder
sysMenuId sys_menu_eos_node,缺失时 NodeDesc fallback 仅 MES 节点有值
1
2
3
4
5
6
7
// TreeServiceImpl.listTreeForPermission()
m.put("id", e.getNodeid());
m.put("pId", e.getParentid() == null || e.getParentid() == 0 ? -1 : e.getParentid());
m.put("name", e.getNodecaption() != null ? e.getNodecaption().trim() : "");
m.put("objectId", e.getObjectid());
m.put("menuType", EosMenuTypeHelper.resolveMenuType(e));
m.put("sysMenuId", sysMenuIdLookup.get(e.getNodeid()));

与 listTreeForSelect()(eostree 选目录)的区别:后者不含 objectId / menuType / sysMenuId,仅 id / pId / name。

6.2 权限查询与保存

接口 作用
GET /eosrole/permission/{roleId} 返回已授权 objectId 列表(「执行」+ Permission=1)
GET /eosrole/permission/detail/{roleId}?nodeId= 按 NodeID 加载对象/按钮详情(MES 或 ERP)
GET /eosrole/permission/mes-menu-ids/{roleId} 返回 MES sys_role_menu 全部 menuId
POST /eosrole/permission/save 菜单执行 + ERP 操作 + MES 按钮

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

save body(EosRolePermissionSaveRequest):

字段 说明
roleIds EOS 角色 OID
objectIds 勾选菜单的 Tree.ObjectID → STPermission「执行」
mode append / replace
mesMenuIds MES 目录/菜单 menu_id → sys_role_menu
mesButtonMenuIds MES 按钮 menu_id → sys_role_menu
operationPermissions ERP {objectId, oprate, granted}
operationReplaceObjectIds replace 时清理非「执行」行的业务 ObjectID

保存委托:先 EosRolePermissionServiceImpl 写「执行」,再 EosRoleObjectPermissionServiceImpl.saveMesAndObjectPermissions 写 ERP/MES。


7. 前端使用(eosrole.js)

7.1 zTree 配置

1
2
3
4
5
6
7
8
9
10
11
var permissionTreeSetting = {
data: {
simpleData: {
enable: true,
idKey: "id", // = NodeID
pIdKey: "pId", // = ParentID
rootPId: -1
}
},
check: { enable: true, nocheckInherit: true }
};

7.2 加载与回显

  1. loadPermissionMenuTree → 请求 /eosrole/menu/tree,用 treeList 初始化 zTree(含 menuType / sysMenuId)
  2. 单角色 + replace 模式:
    • /eosrole/permission/{roleId} → 按 objectId 勾选菜单树(不是 id)
    • /eosrole/permission/mes-menu-ids/{roleId} → 拆分 MES 菜单 ID / 按钮 ID 到 permissionMesMenuIds 等
  3. 点击节点 → /eosrole/permission/detail/{roleId}?nodeId=:
    • menuType=mes:展示 MES 按钮勾选
    • menuType=erp:展示 ERP 操作「禁止」勾选
    • menuType=folder:目录摘要

勾选 MES 节点时把 sysMenuId 加入 permissionMesMenuIds;取消勾选时清理对应按钮态。

7.3 保存

savePermission 收集并提交:

收集方法 字段
collectPermissionObjectIds() objectIds(勾选节点的 objectId,不是 id)
collectMesMenuIdsForSave() mesMenuIds
collectMesButtonMenuIdsForSave() mesButtonMenuIds
collectOperationPermissionsForSave() operationPermissions
collectOperationReplaceObjectIdsForSave() operationReplaceObjectIds

连同 roleIds、mode 一并 POST 到 /eosrole/permission/save。


8. 验证 SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- NodeID 与 ObjectID 对照
SELECT
t.NodeID,
RTRIM(t.NodeCaption) AS MenuCaption,
t.ParentID,
t.ObjectID AS TreeObjectID,
o.ObjectID AS RegObjectID,
o.TypeID,
o.OwnerID
FROM messys.dbo.Tree t
LEFT JOIN messys.dbo.ObjectRegister o ON o.OwnerID = t.NodeID
ORDER BY t.NodeID;

-- UniqueNO 当前序列
SELECT NName, No FROM messys.dbo.UniqueNO WHERE NName = 'tree';

9. 相关代码路径

环节 路径
NodeID 分配 TreeServiceImpl.getNextNodeIdForTree()
ObjectID 分配 TreeServiceImpl.saveObjectRegisterForTree()
新增/同步/复制 TreeServiceImpl.addNode / syncFromSysMenu / copyMenu
权限树 API TreeServiceImpl.listTreeForPermission()、EosRoleController
菜单类型 / sysMenuId EosMenuTypeHelper、SysMenuEosNodeService
菜单「执行」读写 EosRolePermissionServiceImpl
ERP/MES 扩展保存 EosRoleObjectPermissionServiceImpl
业务 ObjectID 解析 EosMenuObjectResolver
实体 TreeEntity、ObjectRegisterEntity、StPermissionEntity
前端 static/js/modules/security/eosrole.js

10. 修订记录

日期 说明
2026-06-16 初版:梳理 Tree NodeID 与 ObjectID 从数据库到前端权限树的完整链路
2026-06-16 补充 NodeID 在 Tree.Functions URL 中作为 menuId 参数的说明
2026-07-01 补充业务 ObjectID 与 NodeID 非 OwnerID 关联及 eosrole 解析路径
2026-07-31 按最新实现修订:权限树补 menuType/sysMenuId;save/前端补 MES 与 ERP 扩展字段;代码路径补 ObjectPermission / Resolver

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 STUsers(uGroup=0/1) 角色 CRUD(eosrole)
用户 ↔ 角色 mesdata STUserGroup 已映射,由 EosRoleUserServiceImpl 管理(双写 sys_user_role)
角色 ↔ 菜单 mesdata STPermission 已映射,由 EosRolePermissionServiceImpl / EosRoleObjectPermissionServiceImpl 读写
EOS 菜单树 messys Tree + ObjectRegister eostree 维护/同步
EOS 角色 → MES 角色 mesdata sys_role(eos_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 角色

相关代码:StusersEntity、StusersServiceImpl.saveRole / updateRole、EosRoleController(路径 /eosrole)。

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

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

1.2 用户-角色:STUserGroup(mesdata)

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

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

  1. 清空该角色在 STUserGroup 中的旧关联,再批量插入新用户
  2. 按 STUsers.UserID ↔ sys_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.NodeID,TypeID = 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.ExFunction(Note1 like 'button%')
授权 ObjectID 每个单据(DjDy)一个 ObjectRegister.Object = DjDy.DjCName 且 TypeID != 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) 菜单 → STPermission(Oprate=执行, Permission=1);按钮 → STPermission(EXxx:... 等, Permission=0=禁用)
MES Web Functions 含「函数打开网页」(menuType=mes),sys_menu_eos_node 存 menu_id ↔ eos_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.ObjectID → STPermission「执行」
mode append / replace
mesMenuIds MES 目录/菜单 menu_id → sys_role_menu
mesButtonMenuIds MES 按钮 menu_id → sys_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 行,再插入勾选的「执行」;mesMenuIds 与 mesButtonMenuIds 都为空时不改动 sys_role_menu。

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

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

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

MES 菜单管理页(sys/menu)勾选目录/菜单后点击「设置到 EOS」,调用 POST /tree/syncFromSysMenu(menu.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 上验证可用;跨库查询需当前连接对 mesdata、messys 均有读权限。

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
实体 StPermissionEntity、StUserGroupEntity、TreeEntity、ObjectRegisterEntity
前端 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. 跨库注意:STUsers、STPermission、STUserGroup、sys_* 在 mesdata;Tree、ObjectRegister 在 messys,关联字段为 STPermission.ObjectID ↔ ObjectRegister.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、相关代码路径

瘦身密码(五):甜蜜陷阱——肥胖引爆的八大健康地雷

肥胖是健康的「隐形杀手」——影响的不只是外形,而是心血管、代谢、内分泌、呼吸、骨骼、生殖乃至心理的全系统连锁反应。以下梳理书中重点阐述的八大健康地雷及应对思路。


1. 糖尿病:胰岛素遇上甜甜圈

中国 20~79 岁人群糖尿病患病率约 10.6%,患病人数约 1.41 亿。肥胖与糖尿病如影随形:

  • 体重每增加 1 kg,糖尿病风险至少升 5%
  • 约 50% 肥胖者将来可能患 2 型糖尿病
  • 糖尿病患者中约 2/3 超重或肥胖

机制:内脏脂肪像「棉袄」包裹细胞,胰岛素(钥匙)难以与受体(锁)结合,葡萄糖进不了细胞 → 胰岛素抵抗 → 代偿性高胰岛素 → 食欲增加 → 更胖 → 最终血糖失控。

逆转密码:

  • 少吃深加工食品、奶茶炸鸡;多吃蔬菜、粗粮
  • 中低强度有氧运动,餐后 30~60 分钟,每次 ≥30 分钟,约 200~300 千卡
  • 超重人群 35~70 岁应每 3 年筛查血糖
  • 九体医学统计:气虚质、阴虚质、痰湿质最易患糖尿病

2. 冠心病:糖脂风暴下的冠状动脉

冠心病患者超重/肥胖率超 50%。BMI 越高,冠心病发病率越高;肥胖者患病概率约为常人 2~3 倍,超平均体重 40% 者死亡风险翻倍。

机制:肥胖 → 血脂紊乱 → 动脉粥样硬化斑块 → 冠脉狭窄 → 心肌缺血。心脏如同小发动机驱动重型卡车,长期超负荷终将「罢工」。

逆转密码:减重 + 控脂 + 戒烟限酒 + 规律运动;编写细胞层面的「燃脂程序」,减轻心脏负担。


3. 睡眠呼吸暂停:内脏脂肪的午夜叛乱

腹型肥胖者脂肪堆积在呼吸道周围,气道变窄,睡眠时呼吸暂停 → 缺氧 → 睡眠质量差 → 日间嗜睡 → 活动减少 → 更胖,形成死亡循环。

逆转密码:减重是核心;侧睡、避免睡前饮酒;严重者需呼吸机等医学干预。


4. 脂肪肝:内脏的「垃圾处理厂」瘫痪

超重/肥胖者并发症中,脂肪肝最常见之一。肝脏本应将脂肪打包运出,糖尿病与肥胖使这一过程效率降低,脂肪在肝内堆积。

常见认知陷阱(书中强调):

  • 「不胖就不会脂肪肝」——瘦人脂肪肝也不少见
  • 「无症状就不用管」——可进展为肝纤维化
  • 「只吃素就能好」——关键在于总能量与代谢
  • 「护肝片够用了」——病因不除,难以逆转

逆转密码:减重 5%~10% 常可显著改善肝脂肪变;控糖控脂 + 运动。


5. 骨关节炎:关节的载荷安全阈值

体重每增加 1 kg,膝关节承受压力约增加 3~4 kg。肥胖 → 慢性炎症 → 软骨磨损加速。

逆转密码:减重减轻关节负荷;水中运动、肌力训练增强关节稳定性。


6. 多囊卵巢综合征:激素风暴

肥胖与多囊卵巢相互加剧:脂肪组织干扰激素平衡,胰岛素抵抗推高雄激素,影响排卵与生育。

逆转密码:减重 5%~10% 可显著改善月经与排卵;生活方式干预是一线治疗。


7. 乳腺癌:癌变风险的土壤

肥胖改变雌激素等激素水平,脂肪组织释放炎症因子,为癌细胞生长提供「土壤」。腹型肥胖与多种妇科、消化道肿瘤风险相关。

逆转密码:保持健康体重、规律筛查、减少久坐。


8. 心理沼泽:身材焦虑与情绪雪崩

肥胖与心理相互纠缠:

  • 自尊心、自我形象受损
  • 焦虑 → 暴食 → 更胖 → 更焦虑
  • 脂肪组织炎症因子可能影响大脑情绪通路
  • 社会偏见与「体型歧视」加重心理负担
  • 极端节食或暴食交替,饮食成为情绪的「翻译器」

逆转密码:接纳与改变并行;寻求心理咨询;用运动、社交替代情绪性进食;体重管理应是关爱自己,而非自我惩罚。


九体视角下的并发症预防

不同体质易患不同慢病,调理应有的放矢:

体质 相关风险
痰湿质 肥胖、高血压、糖尿病、高血脂
气虚质 感冒、内脏下垂、乏力
阳虚质 畏寒、腹泻、痹证
阴虚质 失眠、咽痛、「三高」
血瘀质 肿瘤、痛证、卒中
气郁质 甲状腺、乳腺疾病、情绪障碍

体质可调——在并发症尚未形成前调体,是最好的「逆转密码」。


系列结语

《瘦身密码》传递的核心信息可以概括为三句话:

  1. 中国肥胖形势严峻,腰围与 BMI 同样值得每日关注
  2. 科学减肥是马拉松,忌节食、代餐、极端断食
  3. 九体医学提供个体化路径——先识体质,再管体重,方能健康瘦身不反弹

2024 年国家「体重管理年」已经启程。愿我们都不再以「换大码裤子」为常态,而是在读懂自己身体密码的基础上,走出属于自己的健康之路。

瘦身密码(四):九大体质科学甩肉指南——管体加管重

核心理念:管体 + 管重

  • 管体:辨识体质、纠正偏颇——体质调好了,赘肉才不容易反弹
  • 管重:吃对食物比盲目少吃更重要;体重是体质的一面镜子,但不是唯一指标

同样体重的人,体脂率、肌肉量不同,健康状况可以天差地别。短期节食难持久,结合体质的长期调理才是正道。


平和质:别把好体质作没了

特征:体形匀称,精力充沛,患病少,适应力强。

要点:

  • 膳食平衡,粗粮细粮搭配,寒温适中,七八分饱
  • 每天有氧运动约 30 分钟,或步行不少于 6000 步
  • 22:30 前入睡,春夏养阳、秋冬避风寒
  • 穴位保健:足三里、三阴交、涌泉

药膳举例:南瓜豆腐羹、芝麻山药粥


阳虚质:扶阳驱寒是关键

特征:怕冷、手足凉、夜尿多、精神不振,脂肪像给身体「穿羽绒服」。

要点:

  • 避寒就温,多晒太阳,睡前 40~45℃ 泡脚 15 分钟
  • 宜羊肉、鸡肉、韭菜、核桃;少生冷、少苦寒苦瓜(可煮透减寒)
  • 运动选慢跑、太极拳、八段锦,微汗为度,忌大汗伤阳
  • 穴位:关元、命门、阳池

药膳举例:暖胃茶(红茶+桂圆+生姜+白术+红枣)、当归生姜羊肉汤


阴虚质:补水降火是核心

特征:口干咽燥、手足心热、盗汗、便干,体型偏瘦但血脂易偏高。

要点:

  • 忌熬夜、桑拿、高温环境;室温 14~26℃ 为宜
  • 宜芝麻、蜂蜜、豆腐、鱼类;少辛辣燥烈、火锅
  • 运动选游泳、瑜伽、散步,避免大汗淋漓
  • 穴位:太溪、三阴交、大敦

药膳举例:三汁饮(黄精、生地、天门冬、茯苓)、冰糖五果羹


气郁质:疏肝解郁才能瘦

特征:多愁善感、胸闷叹息、睡眠差;一焦虑就暴食,压力肥堆腰腹。

要点:

  • 环境宜安静,春季多户外活动舒展阳气
  • 宜萝卜、洋葱、柑橘;玫瑰花、茉莉花代茶饮
  • 少芋艿、甘薯等易胀气食物
  • 推荐:玫瑰花茶 + 敲胆经,疏解肝郁

药膳举例:萝卜丝饼、玫瑰佛手茶


血瘀质:活血化瘀加速瘦身

特征:面色晦暗、唇舌黯、黑眼圈,肉硬腿粗,减肥进展慢如「挤牙膏」。

要点:

  • 少食肥腻、寒凉,宜活血食物:山楂、黑豆、玫瑰花
  • 运动促进血液循环,避免久坐
  • 药浴泡脚 + 山楂茶是经典组合
  • 穴位:血海、膈俞、三阴交

痰湿质:祛湿排浊才是真抽脂

特征:体胖腹大、身重困倦、痰多、鼾声大,减肥易卡平台期。

要点:

  • 饮食清淡:冬瓜、白萝卜、薏米、赤小豆;少甜腻、少暴饮暴食
  • 忌潮湿环境,宜阳光下午后运动
  • 按揉丰隆、足三里、中脘;红豆薏米汤、山药冬瓜汤
  • 古籍云:「肥人多痰湿」——祛湿比单纯节食更关键

湿热质:清火除湿瘦成清爽

特征:面油痤疮、口臭、便黏,脸油肚凸的「油胖子」。

要点:

  • 宜冬瓜、绿豆、苦瓜、芹菜;忌辛辣、油炸、甜腻
  • 运动选中长跑、游泳等耗湿清热项目
  • 冬瓜汤、刮肝经,拒绝油腻感
  • 穴位:阴陵泉、曲池、委中

气虚质:补气提效懒人瘦身

特征:疲乏气短、声低懒言、易感冒,肉松垮像棉花。

要点:

  • 宜山药、大枣、鸡肉、黄芪;忌过劳、过饱
  • 运动从散步、八段锦入手,循序渐进,忌大汗
  • 黄芪水 + 八段锦,提升运化效率再减重
  • 穴位:气海、脾俞、足三里

特禀质:温和调理避开雷区

特征:易过敏,减肥过激易诱发风团、喷嚏。

要点:

  • 低敏饮食,避开已知过敏原
  • 运动环境清洁,避免花粉、尘螨密集处
  • 穴位按摩宜轻柔;中药调理需严格遵医嘱
  • 减肥方案宜缓不宜猛

通用运动时间表(可参考)

时段 建议 热量参考
6~9 点 慢跑、快走,微汗 200~300 千卡
12~14 点 午休 30~45 分钟,不宜剧烈运动 —
16~17 点 有氧操、骑行 300~400 千卡
20~22 点 瑜伽拉伸、仙人揉腹 50~100 千卡

写在最后

九体医学告诉我们:没有放之四海而皆准的减肥法。痰湿要祛湿,阳虚要温阳,气郁要疏肝,气虚要补气——先调体,再管重,才能瘦得健康、瘦得长久。

下一篇将聚焦肥胖引爆的八大健康地雷:糖尿病、冠心病、脂肪肝……早知早防。

瘦身密码(三):九体医学入门——解锁你的体质密码

世界上没有两片相同的树叶

九体医学以九种体质分类为指导,研究体质特征与疾病的相关性,从而指导预防、治疗与养生康复。王琦院士将人群科学分为九种类型:

体质 形象比喻 典型特征(口诀)
平和质 小太阳 气血匀,面红润,目光炯,元气振
气虚质 小懒猫 常气短,虚汗浸,懒言语,易疲晕
阳虚质 小冰块 手足冰,腰膝冷,舌淡胖,夜尿频
阴虚质 小火苗 颧潮红,五心热,口咽干,便秘困
痰湿质 小水库 体臃肿,大肚腩,鼾声大,腹胀闷
湿热质 小蒸笼 面油腻,痤疮丛,常口臭,便黏滞
血瘀质 小堵车 唇舌黯,黑眼圈,痛如刺,易长斑
气郁质 小气球 眉紧蹙,多善感,常郁闷,结节生
特禀质 小敏感 易过敏,风团起,喷嚏疾,泪涟涕

平和质是健康基准;其余八种为偏颇体质,如同八条通往特定疾病的道路——早发现、早调体,就能掉头往回走。

九体医学与精准医学

2015 年,王琦院士团队提出「九体医学健康计划」,被视为中国版「精准医学」:与奥巴马「精准医学」计划同年亮相,却更强调中医「治未病」「同病异治」思想。

  • 体质辨识已纳入《国家基本公共卫生服务标准》
  • 《中医体质分类与判定》量表已翻译成英、日、韩等多国语言
  • 覆盖全国 31 个省区,应用于健康管理、亚健康调理、慢病防控

古籍早有端倪:「肥人多痰湿」(朱丹溪)、「阴阳匀平……命曰平人」(《素问》)。王琦院士用 40 余年研究,将零散珍珠串成体系。

四大原理

  1. 体质过程论:体质随年龄阶段演变,养生应因时而异
  2. 形神构成论:躯体与心理一体,痰湿质者情绪稳定型居多
  3. 环境制约论:饮食、气候、地理塑造体质——嗜肥甘生痰湿,过逸成痰瘀
  4. 禀赋遗传论:先天禀赋决定体质底色,后天仍可调节

三大核心

  • 体质可分论:个体差异与群体趋同性并存
  • 体病相关论:痰湿质易患肥胖、三高;气虚质易感冒、内脏下垂;血瘀质易患肿瘤、痛证
  • 体质可调论:偏颇体质可通过饮食、起居、运动、情志干预改善

与肥胖的关系

肥胖与体质密切相关:

  • 痰湿质:中心性肥胖、高血脂、高血糖、高血压高发——「祛湿排浊才是真抽脂」
  • 气虚质:肉松垮、动则气短,减肥效率低
  • 阳虚质:怕冷,脂肪像「穿羽绒服」保温
  • 气郁质:压力肥,焦虑时暴食,肉易堆腰腹
  • 湿热质:脸油肚凸,「油胖子」需清火除湿
  • 血瘀质:肉硬、循环差,减肥像挤牙膏

国家 2024 年「体重管理年」方案明确发挥中医药技术优势;王琦院士「九体医学健康中国计划」正是体重个体化管理的理论基础。

如何判定自己的体质?

依据《中医体质分类与判定》标准:

1
2
原始分 = 各条目分值相加
转化分 = [(原始分 − 条目数) / (条目数 × 4)] × 100

平和质:转化分 ≥60,且其余 8 种均 <30(或均 <40 为「基本是」)

偏颇质:转化分 ≥40 为「是」;30~39 为「倾向是」

建议通过正规医疗机构或社区卫生中心的体质辨识服务获取准确结果,勿仅凭网络测试下定论。

体质不是终身标签

体质会随环境、饮食、情绪变化而调整。一方水土养一方人,养生需因人制宜、因时制宜——读懂身体密码,才能把健康主动权握在自己手里。

下一篇将进入实战:九大体质的科学甩肉指南,痰湿、湿热、阳虚……各有什么吃法、练法和穴位?

瘦身密码(二):脂肪工厂的运作密码——科学减肥是场马拉松

你真的需要减肥吗?

BMI:有用,但不万能

1
BMI = 体重(kg) ÷ 身高²(m²)

中国成人标准:

分类 BMI
体重过低 <18.5
正常 18.5~24
超重 24~28
肥胖 ≥28

BMI 对普通成年人实用,但对运动员等肌肉发达者可能误判;对孕妇、儿童、老年人参考价值有限。更关键的是脂肪分布,而非单一数字。

肥胖三级预警

  • 轻度(28~32.5):糖尿病、高血压风险上升,关节负担加重
  • 中度(32.5~37.5):心血管风险加剧,易出现睡眠呼吸暂停
  • 重度(≥37.5):心衰、脑卒中风险极高,代谢综合征与多种癌症风险显著上升

体脂率与腰臀比

健康体脂率:男性约 15%~20%,女性约 20%~25%。腰臀比反映脂肪分布——苹果型(腹型)远比梨型危险。

脂肪从哪来?

中医视角:易胖体质的「培育土壤」

  1. 饮食不节:肥甘厚味、暴饮暴食,脾胃运化不及,聚为膏脂痰浊
  2. 缺乏运动:久坐少动,气血不畅,痰湿内生
  3. 情志失调:肝郁气滞、思虑伤脾,水湿代谢失常
  4. 脏腑失调:脾虚运化失司、肾阳虚水湿不化

西医视角:代谢失衡

遗传、高热量饮食、运动不足、内分泌疾病(甲减、库欣综合征)、肠道菌群失衡等,共同导致能量摄入大于消耗,多余能量以脂肪储存。

快速瘦身的陷阱

过度节食:得不偿失

节食初期体重下降,但身体会降低基础代谢率(BMR)进入「平台期」。恢复正常饮食后极易反弹,并可能带来:

  • 免疫力下降、肝肾功能失调
  • 女性月经紊乱甚至闭经
  • 脱发、抑郁焦虑
  • 反复节食形成恶性循环

BMR 估算公式(Mifflin-St Jeor):

  • 男性:10×体重(kg) + 6.25×身高(cm) − 5×年龄 + 5
  • 女性:10×体重(kg) + 6.25×身高(cm) − 5×年龄 − 161

代餐、排毒、极端断食

  • 代餐:短期降重,但代谢减慢,恢复饮食后易反弹,长期可能影响消化与免疫
  • 排毒:人体有自净能力;情志调节、均衡饮食、规律运动才是正道
  • 轻断食:间歇性禁食有一定研究支持,但长时间不吃饭可能导致低血糖、电解质紊乱,糖尿病患者尤其危险

什么才是科学减肥?

核心原则:减肥是马拉松,不是百米冲刺。

能量平衡

1
2
每日摄入(减重)= TDEE − 500 千卡
TDEE = BMR × 活动因子

活动因子:久坐 1.2,轻度活动 1.375,中度 1.55,高度 1.725,极高 1.9。

三大营养素分配

  • 碳水化合物:45%~65%(优选全谷物、薯类、豆类)
  • 蛋白质:10%~35%(瘦肉、鱼、蛋、奶、豆制品)
  • 脂肪:20%~30%(植物油、坚果,少饱和脂肪与反式脂肪)

饮食习惯

  • 定时进食,细嚼慢咽,七八分饱
  • 进食顺序:清汤 → 蔬菜 → 肉类 → 主食
  • 烹调以蒸、炖、煮为主,少油炸
  • 看懂营养成分表,警惕「低脂」但高热量的食品

运动:每天 6000 步起步

  • 每周至少 150 分钟中等强度有氧运动
  • 结合力量训练,提高基础代谢
  • 碎片化运动也有价值:通勤多走、爬楼梯、工间拉伸

情绪管理

压力肥、深夜暴食常与情绪相关。好心情是最佳「燃脂剂」——学会与焦虑和解,比盲目节食更重要。

懒人减肥武器库速览

维度 要点
饮食 能量赤字 + 营养充足;外卖少点高糖高油;麻辣烫不加麻酱也是多样饮食的好选择
运动 每天 10 分钟碎片化运动也有累积效应;选自己喜欢的项目才能坚持
情绪 识别压力性进食;用运动、社交替代零食快感
中医适宜技术 针灸、埋线、拔罐、穴位按摩等可作为辅助,需在专业医师指导下进行

管体 + 管重 = 不反弹

单纯减重而不调体质,往往陷入「减—反弹—再减」循环。下一篇将介绍王琦院士创立的九体医学——先认清自己是哪种体质,再对症瘦身。

瘦身密码(一):中国式发福图鉴——14亿人的体重失控现场

近三十年,中国人以惊人速度「胖」了起来。据《中国居民营养与慢性病状况报告(2020)》,成年居民超重率 34.3%、肥胖率 16.4%——每 2 个成年人就有 1 人面临超重/肥胖威胁。

更令人警醒的是增速:1989 年中国超重人口约 1.67 亿,2009 年增至 5.29 亿,20 年间每天约增加 4.9 万人,年均增长率 10.8%,超过同期 GDP 增速。《柳叶刀》估算,2021 年全球超重/肥胖成年人 21.1 亿,中国以 4.02 亿居首;预计到 2050 年,全球将有超过一半成年人超重/肥胖。

若以腰围衡量腹型肥胖,2009 年中国腹型肥胖人口约 6.7 亿,超过欧美总和。李新华等团队在《柳叶刀》发表的研究显示,2004~2018 年间,18~69 岁人群标准化平均 BMI 从 22.7 升至 24.4 kg/m²,肥胖患病率(BMI≥30)从 3.1% 升至 8.1%。2018 年肥胖人数约 8500 万,是 2004 年的三倍。

谁更容易发胖?

肥胖增长并非均匀分布,城乡、性别、年龄各有特点:

维度 主要趋势
城乡 2010 年前城市高于农村;此后农村女性 BMI 增速快于城市,2018 年农村女性已超过城市
性别 2004 年男性低于女性,2018 年完全逆转,男性增速更快
年龄 女性随年龄升高至 50 岁;男性 30~49 岁为高峰
儿童 与 1985 年比,2014 年 7~18 岁学生肥胖患病率增长 54.8 倍
老年人 2011~2015 年超重率 29.2%→33.2%,中心性肥胖 58.3%→63.9%

肥胖正在吃掉你的存款

肥胖已从个人问题演变为国家危机。2024 年 6 月,国家卫生健康委联合 16 部门启动「体重管理年」活动,推动慢病防治关口前移。

健康代价:

  • 近 20 年高血压患病率 19%→28%,糖尿病约 3%→13%
  • 2018 年糖尿病及前期患病率合计约 51%,知晓率、治疗率、控制率均偏低
  • 预测 2030 年成人超重/肥胖率或达 70.5%

经济代价:

  • 2030 年归因于超重/肥胖的医疗费用预计约 4180 亿元,约占全国医疗费用 22%
  • 超重/肥胖对全球经济影响占 GDP 2.4%,2035 年预计升至更高
  • 肥胖还带来间接损失:工作效率下降、请假增多、就业机会受影响(「体型歧视」)

研究还发现,肥胖与收入的关系并非线性——中等收入人群肥胖风险反而最高;农村高收入男性、高教育男性肥胖关联更强,而女性高教育与肥胖呈负相关。

腰围比体重更值得警惕

BMI 虽常用,但有盲区:不区分肌肉与脂肪、不反映脂肪分布。腹型肥胖(苹果型)风险远高于梨型:

  • 男性腰围 ≥90 cm、女性 ≥85 cm,即存在腹型肥胖风险
  • 内脏脂肪升高甘油三酯、胆固醇,促动脉粥样硬化
  • 干扰胰岛素作用,显著增加 2 型糖尿病风险
  • 与结直肠癌、子宫内膜癌等多种癌症相关
  • 压迫横膈膜,引发睡眠呼吸暂停;加重关节负担

建议: 除定期测 BMI 外,务必关注腰围和腰臀比(亚洲男性上限 0.9,女性 0.8)。

写在最后

肥胖威胁的不只是外形,更是心血管、代谢、呼吸、骨骼乃至心理的全方位健康。2024 年「体重管理年」与「九体医学健康中国计划」的推进,标志着体重管理正从「一刀切节食」走向个体化、科学化、中西医协同的新阶段。

下一篇,我们将拆解「脂肪工厂」——脂肪从何而来,以及什么才是科学的减肥马拉松。

Cursor Agent CLI 报错 No version directories found 的排查与修复

在 Windows 终端执行 agent 或 cursor-agent 时,可能遇到如下错误:

1
2
3
4
5
6
C:\Users\liuhualiang\AppData\Local\cursor-agent\agent.ps1 : No version directories found in C:\Users\liuhualiang\AppData\Local\cursor-agent
所在位置 行:1 字符: 1
+ agent
+ ~~~~~
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,agent.ps1

本文记录该问题的现象、根因、修复方式与临时绕过方法。

现象

  • agent、cursor-agent 命令均无法启动
  • 报错提示在 %LOCALAPPDATA%\cursor-agent 下找不到版本目录
  • 但手动查看 versions 子目录,实际存在多个版本文件夹,例如:
1
2
3
4
C:\Users\liuhualiang\AppData\Local\cursor-agent\versions\
├── 2026.06.12-19-59-36-f6aba9a
├── 2026.06.15-03-48-54-da23e37
└── 2026.06.16-20-30-07-a07d3ac

根因

agent.ps1 与 cursor-agent.ps1 启动时会扫描 versions 目录,并用正则过滤合法版本名:

1
$name -match '^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$'

该正则只匹配旧格式:

1
YYYY.MM.DD-commit

例如:2026.06.16-a07d3ac

而 Cursor Agent 更新后的目录名变为新格式,中间多了构建时间:

1
YYYY.MM.DD-HH-MM-SS-commit

例如:2026.06.16-20-30-07-a07d3ac

因此所有现有版本目录都被过滤掉,脚本误判为「没有版本目录」。

修复方法

编辑以下两个文件(内容相同,需同步修改):

  • %LOCALAPPDATA%\cursor-agent\agent.ps1
  • %LOCALAPPDATA%\cursor-agent\cursor-agent.ps1

1. 更新版本目录匹配正则

将原来的:

1
$name -match '^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$'

改为同时兼容新旧格式:

1
$name -match '^\d{4}\.\d{1,2}\.\d{1,2}(-\d{1,2}-\d{1,2}-\d{1,2})?-[a-f0-9]+$'

2. 改进版本排序逻辑(可选但推荐)

Parse-VersionString 函数原先只按日期 YYYY.MM.DD 排序。若同一天存在多个构建,建议解析时间戳后再比较:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Parse-VersionString {
param (
[string]$versionString
)

$segments = $versionString.Split('-')
$datePart = $segments[0]
$parts = $datePart.Split('.')

if ($parts.Length -ne 3) {
throw "Invalid version format. Expected format: YYYY.MM.DD[-HH-MM-SS]-commit"
}

$year = $parts[0]
$month = $parts[1].PadLeft(2, '0')
$day = $parts[2].PadLeft(2, '0')
$timeSuffix = ""

if ($segments.Length -ge 5 -and $segments[1] -match '^\d+$' -and $segments[2] -match '^\d+$' -and $segments[3] -match '^\d+$') {
$timeSuffix = $segments[1].PadLeft(2, '0') + $segments[2].PadLeft(2, '0') + $segments[3].PadLeft(2, '0')
}

return [long]($year + $month + $day + $timeSuffix)
}

3. 验证修复

1
2
agent --version
agent status

预期输出示例:

1
2
2026.06.16-20-30-07-a07d3ac
✓ Logged in as your@email.com

临时绕过(不修改脚本)

若暂时不想改启动脚本,可直接调用最新版本目录下的 node.exe 与 index.js:

1
2
3
$v = "C:\Users\liuhualiang\AppData\Local\cursor-agent\versions\2026.06.16-20-30-07-a07d3ac"
& "$v\node.exe" "$v\index.js" status
& "$v\node.exe" "$v\index.js" --help

将 $v 替换为 versions 下实际存在的最新目录名即可。

一键修复工具

仓库内提供了自动修复脚本,下次 agent update 覆盖修复后可直接运行:

1
2
3
4
5
6
7
8
# 首次安装(写入 %USERPROFILE%\bin 并加入 PATH)
powershell -ExecutionPolicy Bypass -File D:\blog\github\scripts\install-fix-cursor-agent.ps1

# 修复
fix-cursor-agent

# 仅检查是否需要修复(退出码 2 表示需要修复)
fix-cursor-agent -CheckOnly

脚本位置:

  • D:\blog\github\scripts\fix-cursor-agent.ps1
  • D:\blog\github\scripts\fix-cursor-agent.cmd
  • D:\blog\github\scripts\install-fix-cursor-agent.ps1

修复前会自动备份为 agent.ps1.bak-YYYYMMDD-HHMMSS,并尝试执行 agent --version 验证。

注意事项

  1. 更新可能覆盖修复:执行 agent update 或 Cursor 自动更新 Agent CLI 后,agent.ps1 可能被还原,问题会复现,需重新应用上述修改。
  2. IDE CLI 与 Agent CLI 是两套工具:
    • cursor:Cursor IDE 自带 CLI(打开文件、diff、tunnel 等),路径通常在 %LOCALAPPDATA%\Programs\cursor\resources\app\bin\
    • agent / cursor-agent:终端 Agent CLI,安装在 %LOCALAPPDATA%\cursor-agent\
  3. cursor agent 子命令:IDE CLI 也注册了 agent 子命令,但日常使用的 agent 命令通常来自 PATH 中的 cursor-agent 包装脚本。

相关环境信息(排查参考)

项目 值
Cursor IDE 版本 3.8.11
Agent CLI 版本 2026.06.16-20-30-07-a07d3ac
操作系统 Windows 10/11 x64
安装路径 %LOCALAPPDATA%\cursor-agent\

总结

No version directories found 并非版本文件缺失,而是 PowerShell 启动脚本的版本名正则未跟上 Cursor Agent 新的目录命名规范。更新正则后即可恢复正常;若再次被更新覆盖,按本文步骤重新修复即可。

0 A.D. 中文游戏攻略上线

0 A.D.(读作 “zero ey dee”)是一款免费开源的古代即时战略(RTS)游戏:采集资源、建设城镇、训练军队,在古典时代文明之间展开对决。游戏强调历史氛围,当前 Release 28 已包含 15 个文明(含新加入的日耳曼)。

我整理了一份 非官方中文攻略,单独托管在子站,方便查阅与后续更新:

👉 https://0ad.eppencn.com/

源码在 GitHub:eppen/0ad-strategy-guide

攻略里有什么

章节 内容
入门指南 安装、界面、首局 15 分钟流程
核心机制 资源、领土、公民兵、胜负条件
经济发展 dropsite 布局、贸易、升阶段
军事战术 兵种克制、阵型、攻城与海战
三阶段进阶 Village → Town → City 节奏
文明详解 15 个文明特色与推荐打法
多人对战 联机、排位、常见套路
进阶技巧 微操、反制、AI 与 Mod

为什么单独建站

  • MkDocs Material 更适合长文档:侧边导航、全文搜索、章节跳转。
  • 主站 eppencn.com 继续用 Hexo 写博客;攻略子站专注游戏内容,互不干扰。
  • 游戏版本会随 Wildfire Games 更新平衡,攻略仓库独立迭代更快。

适合谁

  • 刚下载游戏、不知道先造什么的新手
  • 被 AI 早攻、不知道怎么升 Town 的单机玩家
  • 想查某个文明(如孔雀象兵流、迦太基雇佣兵)怎么打的休闲玩家

快速上手(3 条)

  1. 资源点旁建 仓库 / 农庄,别让平民走太远。
  2. 7 分钟内升 Town,先塔后兵。
  3. 步兵 + 远程 + 攻城器 混合编队,别散开送。

关于 AI

Skirmish 模式可添加电脑对手(如 Petra AI),难度从 Sandbox 到 Very Hard。攻略中的节奏与打法主要针对 Normal AI,也适用于多人入门。


欢迎 Issue / PR 纠错或补充文明打法。游戏本体版权归 Wildfire Games,攻略文本采用 CC BY-SA 4.0。

再次入口:0ad.eppencn.com