From feaa7838851f79ef6fb5a681da303d4e1ffc8a4a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 13 Aug 2026 23:52:56 +0800 Subject: [PATCH] =?UTF-8?q?docs(design):=20MSYNC=20=E8=BF=9C=E7=AB=AFApp?= =?UTF-8?q?=E6=B3=A8=E5=85=A5=20+=20RAM=20=E8=B5=84=E6=BA=90=E8=AE=BF?= =?UTF-8?q?=E9=97=AE=E7=AE=A1=E7=90=86=E6=96=B9=E6=A1=88=E8=AE=BE=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/msync_remote_app_ram_design.md | 266 ++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 docs/msync_remote_app_ram_design.md diff --git a/docs/msync_remote_app_ram_design.md b/docs/msync_remote_app_ram_design.md new file mode 100644 index 00000000..5242e3f5 --- /dev/null +++ b/docs/msync_remote_app_ram_design.md @@ -0,0 +1,266 @@ +# MSYNC 远端 App 注入 + RAM 资源访问管理 —— 方案设计 + +> 生成日期:2026-08-13 +> 状态:方案草案(待评审) +> 关联:docs/architecture_debt_governance.md(治理进度) + +## 1. 背景与目标 + +**构想 A(MSYNC 联动页面)**:子服务器(如 ATestCServer)通过 MSYNC 连接主服务器后,把自己的定制页面作为"App"上报注册,主服务器将其安装到指定公司并注入员工菜单——即 **GERP App 机制的跨服务器版**,支撑定制化项目。 + +**构想 B(权限精细化 + RAM)**:精细化租户/公司/角色权限系统,引入 **RAM(Resource Access Management,资源访问管理)** 角色机制,实现"指定公司的指定角色/员工可见指定资源"的角色粒度授权。 + +**目标**:两构想结合,形成定制化项目端到端能力:**子服务器页面 → MSYNC 上报 → 主服务器注册 → 公司安装 → RAM 角色授权 → 员工菜单注入 → iframe 加载子服务器页面**。 + +--- + +## 2. 现状盘点 + +### 2.1 App 机制(主服务器,已完整) +- [app_manifests.py](file:///d:/SMANAGE/gvsds/apps/gerp/app_manifests.py):`AppManifest` 清单(AppCode/AppName/Menus/FrontendRoute/ConfigKeys)+ `SyncAppManifests`/`InjectAppMenus`/`RemoveAppMenus`;**已预留 MSYNC 扩展**:`_REMOTE_MANIFESTS`/`RegisterRemoteManifest`/`Source='msync'`(L222-248) +- App 表(gerp/models.py L782):AppType(enterprise/personal/both)、MenuRootUUID、FrontendRoute/FrontendType/LegacySrc +- 安装矩阵:`TenantApp`(租户级)/ `CompanyApp`(公司级)/ `UserApp`(个人级),AppManager 统一管理 +- 访问校验:`AppAccessPermission(AppCode)` + `AppManager.CheckAccess`(租户装 ∨ 公司装 ∨ 个人启用) +- 菜单注入:安装 → `InjectAppMenus` → Menu 表 MenuStatus=1 + +### 2.2 菜单下发(MenuListCompatView) +四级过滤链(views/menu.py L34-219): +1. 公司过滤:公有 + 当前公司私有(CompanyUUID) +2. 祖先链校验:祖先不属于当前公司 → 整分支移除 +3. 管理员特权:admin:super/admin:tenant 跳过权限过滤(公司过滤仍生效) +4. 权限过滤(非管理员):PermissionAttr 权限码 + PermissionLevel +5. 应用过滤:未安装 App 的 Manifest 菜单全部隐藏(严格收紧,含管理员) + +### 2.3 MSYNC 通信(主 ↔ 子,已完整) +- 服务端:[sio.py](file:///d:/SMANAGE/gvsdsdk/msync/sio.py)(Socket.IO /msync 命名空间),事件注册/认证/心跳/发现/OGM/事务;业务事件统一 `_RequireAuth(sid)` 校验 +- 客户端:[sio_client.py](file:///d:/SMANAGE/ATestCServer/msync_client/sio_client.py)(MSyncSocketClient:注册/认证/心跳/Discover/OGM/Tx/CallRemote) +- 服务注册表:`ServiceNode`(msync/models.py L19,含 ServiceDomain/ServiceMetadata/ServiceScope) +- 服务令牌:`ServiceToken` + 远程自省 `/api/msync/auth/introspect/`(子服务器 SubServerServiceAuthentication 用) + +### 2.4 权限体系(现状缺口) +- 数据权限:RoleScope 四级(admin/dept_manager/workgroup_lead/member)+ TableScopeFilter 策略注册表(role_scope.py)——成熟 +- 菜单权限:PermissionAttr 权限码二维(菜单↔权限码),非"角色↔资源"直连 +- **缺口①**:App 可见性 = 安装状态驱动(公司装了→全员可见),**无角色粒度** +- **缺口②**:无统一"主体→资源→操作"授权模型(RAM) + +--- + +## 3. 构想 A:MSYNC 远端 App 注入 + +### 3.1 总体流程 + +``` +子服务器(定制项目) 主服务器(gvsds) +────────────────── ────────────────── +① MSYNC 连接/注册/认证(已有) +② 上报 AppManifest 清单 + ── msync:app:manifest:report ──────→ ③ 校验服务身份(已注册+认证) + (AppCode/AppName/Menus/ ④ 写入远端清单 → 同步 App 表(Source=msync) + FrontendRoute/LegacySrc) ⑤ 注入缓存:App/Menu 元数据落库 + ⑥ 管理员在应用中心安装(复用现有流程) + ⑦ InjectAppMenus 注入菜单 + ⑧ RAM 角色授权(构想 B) +⑨ 员工登录 → MenuList 过滤 → 菜单出现 ──→ ⑩ 员工点击菜单 +⑪ iframe 加载子服务器页面(子服务器托管) +⑫ 页面内调子服务器 API(用户令牌透传,见 3.5) +``` + +### 3.2 清单上报协议(msync:app:manifest:report) + +- **事件**:`msync:app:manifest:report`(sio.py 新增 handler,模式同 `msync:ogm:register`) +- **鉴权**:`_RequireAuth(sid)`(服务必须已完成密钥认证);服务身份 = `_sid_service_map[sid]`,清单归属该服务,防伪造 +- **请求体**(PascalCase): + +```json +{ + "ServiceName": "atest-cserver", + "Manifests": [ + { + "AppCode": "custom-project-x", + "AppName": "定制项目X", + "AppIcon": "Custom", + "AppDesc": "ATestCServer 注入的定制项目页面", + "AppType": "enterprise", + "FrontendRoute": "/remote/custom-project-x", + "FrontendType": "legacy", + "LegacySrc": "http://127.0.0.1:25080/pages/custom-project-x/home.html", + "Menus": [ + { "MenuName": "custom-x", "MenuTitle": "项目X", "MenuIcon": "Custom", "MenuSort": 1 }, + { "MenuName": "custom-x-home", "MenuTitle": "项目X首页", "MenuIcon": "Home", + "MenuLink": "http://127.0.0.1:25080/pages/custom-project-x/home.html", + "ParentMenuName": "custom-x", "MenuSort": 1 } + ], + "ConfigKeys": [] + } + ] +} +``` + +- **响应**:`{code:200, data:{Registered:[AppCode], Failed:[{AppCode, Reason}]}}`(幂等 upsert) +- **幂等**:按 AppCode upsert;AppCode 冲突(本地/其他服务已占用)返回失败,需主服务器管理员仲裁 + +### 3.3 主服务器注册与注入缓存 + +**App 表扩展**(新迁移): +| 字段 | 类型 | 说明 | +|---|---|---| +| Source | CharField(16), default='local' | local / msync(清单来源) | +| ServiceName | CharField(128), null=True | 提供方服务名(msync 服务注册名) | +| ServiceDomain | CharField(256), null=True | 提供方服务域名(页面 base,iframe 用) | +| AppStatus | SmallIntegerField | 1启用/0禁用;远端 App 增加"离线"语义(见 3.6) | + +**注入缓存分层**: +1. **DB 持久化**(权威):App 表(Source/ServiceName/ServiceDomain)+ Menu 表注入行 + ServiceNode 注册表 +2. **进程级缓存**(读快):`_REMOTE_MANIFESTS` 注册中心(现有)+ 远端清单→App 映射 +3. **不缓存**:子服务器页面本体(子服务器托管,主服务器只缓存"入口元数据") + +**注册流程**:`on_manifest_report` → 逐清单 `_SyncRemoteApp(manifest, service)`(写 App 表)+ 加入 `_REMOTE_MANIFESTS` + 回执。复用 `_SyncAppRecord` 模式。 + +### 3.4 角色粒度菜单注入 + +- **安装**:复用 `CompanyApp`(公司级,指定公司安装)→ `InjectAppMenus` 注入菜单 +- **角色粒度**:`InjectAppMenus` 后菜单默认"公司内全员可见";要限定角色 → 走 RAM 授权(构想 B): + - 菜单可见性 = 公司安装 AND RAM(角色→App, view) +- **菜单协议**:注入的远端菜单 `MenuLink` 为子服务器绝对 URL(如 `http://127.0.0.1:25080/pages/...`);前端 legacy iframe 直接加载 + +### 3.5 跨服务器鉴权(iframe + 用户令牌透传) + +现状:`SubServerServiceAuthentication`(Service 令牌远程自省)只覆盖"主→子服务调用";员工 iframe 直接访问子服务器页面时**无用户身份**。 + +**方案(用户令牌透传)**: +1. 主服务器新增事件 `msync:user:token:issue`:主服务器签发的 **user-bound 短期令牌**(JWT,claims: UserUUID/RoleUUID/CompanyUUID/TenantUUID,TTL 短如 10min,可刷新) +2. 前端(GERP)进入远端 App 时:请求主服务器 API → 取用户令牌 → 拼到子服务器页面 URL(`?token=` 或 header 注入) +3. 子服务器新增 `UserTokenAuthentication`:远程自省主服务器 `/api/msync/user/token/introspect/`(模式同 ServiceToken 自省)→ 得到用户上下文 → 业务 API 按用户鉴权 +4. **安全**:令牌 TTL 短 + 可撤销(撤销列表)+ 仅限目标服务域使用(claim `Audience=ServiceName`) + +**备选简化**(若定制项目不需要用户身份,只读公开页):iframe 加载公开页面,无令牌;有写操作的 API 走主服务器代理转发(主→子 Service 令牌)。 + +### 3.6 生命周期管理 + +| 事件 | 处理 | +|---|---| +| 子服务器 heartbeat 超时/`msync:service:offline` | 远端 App 标记 `ServiceOnline=0`;菜单保留但前端显示"服务离线"提示(或按租户配置自动停用 AppStatus=0) | +| 子服务器重新上线 | 重新上报清单 → 恢复 `ServiceOnline=1` | +| 子服务器注销(deregister) | 远端 App 停用(AppStatus=0),菜单 MenuStatus=0(复用 RemoveAppMenus) | +| 清单更新(同 AppCode 新上报) | 幂等 upsert 菜单/App 字段 | + +--- + +## 4. 构想 B:RAM 资源访问管理(Resource Access Management) + +### 4.1 模型设计 + +借鉴 AWS RAM/IAM 资源授权思想,与现有 RBAC 融合: + +| 概念 | 定义 | 现网对应 | +|---|---|---| +| 主体 Principal | 资源访问者 | Role(角色)/ User(用户) | +| 资源类型 ResourceType | 可被授权的资源类别 | App(应用)/ Menu(菜单)/ data_scope(数据权限,预留) | +| 资源 ResourceUUID | 具体资源实例 | AppUUID / MenuUUID | +| 操作 Action | 访问能力 | view(可见)/ use(使用)/ manage(管理)/ grant(授权) | +| 作用域 Scope | 资源归属维度 | TenantUUID(租户)/ CompanyUUID(公司) | + +**核心语义**:`RAMGrant` 声明"**某个主体** 对 **某资源** 拥有 **某操作** 权限",且权限只在 **作用域(租户/公司)内** 生效。 + +### 4.2 表结构(新迁移) + +```python +class RAMGrant(QModel): + GrantUUID = UUIDField(primary_key=True) # 授权UUID + PrincipalType = CharField(16) # role / user + PrincipalUUID = UUIDField() # 角色或用户UUID + ResourceType = CharField(32) # app / menu / data_scope + ResourceUUID = UUIDField() # 资源UUID(AppUUID/MenuUUID) + Action = CharField(16) # view / use / manage / grant + TenantUUID = UUIDField(null=True) # 归属租户(null=跨租户,仅 super 可授) + CompanyUUID = UUIDField(null=True) # 归属公司(null=租户内全部公司) + GrantStatus = SmallIntegerField(default=1) # 1生效/0撤销 + GrantBy = UUIDField(null=True) # 授权人 + GrantTime = DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'RAMGrant' + unique_together = [('PrincipalType', 'PrincipalUUID', 'ResourceType', + 'ResourceUUID', 'Action', 'TenantUUID', 'CompanyUUID')] +``` + +### 4.3 服务层(gvsdsdk/auth/ram.py,RAMManager) + +``` +GrantAccess(principal_type, principal_uuid, resource_type, resource_uuid, + action, tenant_uuid, company_uuid=None, grant_by=None) -> (ok, err) +RevokeAccess(grant_uuid) / RevokeByResource(resource_type, resource_uuid) +CheckAccess(principal_uuid, resource_type, resource_uuid, action, + tenant_uuid, company_uuid=None) -> bool +ListGrants(resource_type=None, resource_uuid=None, principal_uuid=None, ...) +ListResourcesForPrincipal(principal_uuid, resource_type, action, tenant_uuid, + company_uuid=None) -> [resource_uuid] +``` + +**判定优先级**(与现有体系一致):管理员(admin:super/tenant/company)→ True(豁免 RAM 校验);RoleType=1 管理岗默认可看其管理范围;其余按 RAMGrant 命中。 + +### 4.4 与现有权限体系融合 + +| 层 | 现有机制 | RAM 增强 | +|---|---|---| +| App 可见性 | AppManager.CheckAccess(安装状态) | AND RAM(角色→App, view):安装∩角色授权 | +| 菜单可见性 | PermissionAttr 权限码 | AND RAM(角色→Menu, view)(可选,默认不启用防性能损耗) | +| 数据权限 | RoleScope 四级 + TableScopeFilter | 预留 ResourceType=data_scope(本期不接管) | + +**菜单可见性公式(更新 MenuListCompatView)**: +``` +可见 = 公司过滤(现状) + ∩ (管理员特权 ∨ (权限码校验(现状) ∧ 应用安装(现状) + ∧ (App 未配置 RAM ∨ RAM.CheckAccess(角色→App, view)))) +``` + +**性能**:RAM 查询走 DB 索引 + 进程级缓存(角色→授权集合,TTL 短);仅对 Source=msync 或配置了 RAM 的 App 启用 RAM 过滤,本地 App 保持现状路径。 + +### 4.5 角色粒度授权示例 + +**场景**:公司 A 安装"定制项目X"(ATestCServer 上报),仅"财务角色"员工可见。 + +1. 子服务器上报清单 → 主服务器注册 App(Source=msync) +2. 管理员:`CompanyApp` 安装到公司 A → `InjectAppMenus` 注入菜单 +3. 管理员:`RAMManager.GrantAccess('role', 财务角色UUID, 'app', AppX_UUID, 'view', 租户, 公司A)` +4. 员工登录(财务角色,公司 A)→ MenuList 过滤 → 项目X 菜单出现 +5. 其他角色(公司 A)→ 菜单不出现;其他公司 → 公司过滤已拦截 + +--- + +## 5. 两构想结合:定制化项目端到端场景 + +``` +① 子服务器(定制项目)MSYNC 连接 → 上报 AppManifest +② 主服务器注册远端 App(Source=msync)+ 注入缓存 +③ 租户管理员应用中心安装(指定公司) +④ RAM 授权(角色粒度:谁的公司、哪个角色可见) +⑤ 该角色员工登录 → 菜单注入 → 点击 → iframe 加载子服务器页面 +⑥ 页面内调子服务器 API → 用户令牌自省 → 业务执行 → 数据可经 OGM 回写主库 +``` + +**与现有能力的关系**: +- 复用:AppManifest/AppManager/InjectAppMenus/菜单过滤/MSYNC 注册认证/ServiceToken 自省/OGM +- 新增:`msync:app:manifest:report` 事件、App.Source/ServiceName/ServiceDomain 字段、RAM 模型+服务层、用户令牌透传、生命周期离线管理、应用中心"远端来源"标记 + +--- + +## 6. 实施计划(建议分阶段) + +| 阶段 | 内容 | 涉及仓库 | +|---|---|---| +| P1 RAM 基础 | RAMGrant 表 + RAMManager + 菜单过滤集成(默认不启用 RAM 分支,向后兼容) | gvsdsdk + gvsds | +| P2 MSYNC 上报 | `msync:app:manifest:report` 事件 + App.Source/ServiceName 字段 + 注册同步 | gvsdsdk + gvsds + ATestCServer | +| P3 角色粒度注入 | 安装/授权 UI(应用中心选公司+角色)+ 菜单过滤 RAM 启用 + 离线生命周期 | gvsds + GERP | +| P4 用户令牌透传 | 用户令牌签发/自省 + 子服务器 UserTokenAuthentication + 前端进入页 | gvsdsdk + gvsds + ATestCServer + GERP | +| P5 端到端 demo | ATestCServer 上报定制 App → 主服安装 → RAM 授权 → 员工菜单 → iframe 页面 | 全部 | + +--- + +## 7. 待确认决策点 + +1. **App 表 Source/ServiceName 字段**:直接加字段 vs AppCode 命名空间前缀(`{service}.{code}`)?——建议加字段(显式、可查询) +2. **RAM 生效范围**:仅对远端(Source=msync)App 启用 RAM 过滤,还是本地 App 也可配置角色授权?——建议先远端,本地后续 +3. **离线策略**:子服务器下线时菜单自动隐藏(ServiceOnline=0)vs 保留+提示?——建议保留+提示(可配置) +4. **用户令牌 TTL**:10min 可刷新 vs 跟随主登录会话?——建议 10min + 刷新 +5. **P1 阶段 RAM 是否接管数据权限**(data_scope)——建议本期不接管,预留