docs(design): MSYNC 远端App注入 + RAM 资源访问管理方案设计
This commit is contained in:
1 parent
b1dd91dde0
commit
feaa783885
1 file changed
+266
@@ -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)——建议本期不接管,预留
|
||||
Reference in new issue
Block a user