feat(ram): 安装时 RAM 角色授权(GrantRoles)+ ServiceOnline 离线生命周期(下线标记/上报恢复)

This commit is contained in:
TermiNexus committed 2026-08-14 00:01:46 +08:00
1 parent fda236eb0c
commit 588452aa92
4 files changed
+89 -1

No files matched your search

+33
View File
@@ -444,10 +444,43 @@ def _SyncRemoteAppRecord(manifest: AppManifest, service_name: str, service_domai
app.Source = 'msync'
app.ServiceName = service_name or ''
app.ServiceDomain = service_domain or ''
app.ServiceOnline = 1 # 上报即在线
app.save()
return app
def MarkServiceAppsOffline(ServiceName: str) -> int:
"""子服务器下线:标记该服务提供的全部远端 App 为离线(ServiceOnline=0)
Args:
ServiceName: 服务注册名(App.ServiceName)
Returns:
受影响 App 数量
"""
if not ServiceName:
return 0
return App.objects.filter(Source='msync', ServiceName=ServiceName, ServiceOnline=1).update(
ServiceOnline=0,
)
def MarkServiceAppsOnline(ServiceName: str) -> int:
"""子服务器重新上线:恢复该服务提供的远端 App 在线状态(ServiceOnline=1)
Args:
ServiceName: 服务注册名(App.ServiceName)
Returns:
受影响 App 数量
"""
if not ServiceName:
return 0
return App.objects.filter(Source='msync', ServiceName=ServiceName, ServiceOnline=0).update(
ServiceOnline=1,
)
def SyncRemoteAppManifests(Manifests: List[dict], ServiceName: str, ServiceDomain: str) -> dict:
"""同步 MSYNC 子服务器上报的应用清单(幂等 upsert)
@@ -0,0 +1,21 @@
"""App 表增加 ServiceOnline 字段——MSYNC 提供方服务在线状态
子服务器下线(heartbeat 超时/offline/注销)时标记 0,重新上报恢复 1;
菜单保留但前端显示"服务离线"提示(或按策略停用)。
"""
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('gerp', '0031_app_remote_fields'),
]
operations = [
migrations.AddField(
model_name='app',
name='ServiceOnline',
field=models.SmallIntegerField(default=1, verbose_name='提供方服务在线(1在线/0离线)'),
),
]
+1
View File
@@ -808,6 +808,7 @@ class App(QModel):
Source = models.CharField(max_length=16, default='local', verbose_name='来源(local/msync)')
ServiceName = models.CharField(max_length=128, default='', blank=True, verbose_name='提供方服务名')
ServiceDomain = models.CharField(max_length=256, default='', blank=True, verbose_name='提供方服务域名')
ServiceOnline = models.SmallIntegerField(default=1, verbose_name='提供方服务在线(1在线/0离线)')
CreateTime = models.DateTimeField(default=datetime.datetime.utcnow, verbose_name='创建时间')
class Meta:
+34 -1
View File
@@ -104,6 +104,30 @@ def _ToBytes(value):
return None
def _GrantRolesToApp(grant_roles, app_uuid, tenant_bytes, company_bytes, operator_bytes):
"""RAM 角色粒度授权:安装时把 App 的 view 权限授予指定角色
Args:
grant_roles: 角色 UUID bytes 列表
app_uuid: App UUID(str/bytes)
tenant_bytes: 租户 UUID bytes(作用域)
company_bytes: 公司 UUID bytes(None=租户级授权)
operator_bytes: 授权人 UUID bytes
"""
if not grant_roles:
return
from gvsdsdk.auth import RAMManager
app_bytes = _ToBytes(app_uuid)
for role in grant_roles:
try:
RAMManager.GrantAccess(
'role', role, 'app', app_bytes, 'view',
tenant_bytes, company_bytes, grant_by=operator_bytes,
)
except Exception as e:
logger.warning('RAM 角色授权失败 role=%s: %s', role, e)
def _ResolveConfigCompany(request, company_uuid_str):
"""解析应用配置目标范围并校验权限(租户默认/公司级)
@@ -216,6 +240,10 @@ class AppInstallView(ELTAuthenticatedAPIView):
if not app_uuid:
return Response({'code': 1, 'msg': '缺少应用 UUID'}, status=400)
# RAM 角色粒度授权(可选):安装时指定角色 → 该角色对安装范围可见该 App
grant_roles = _ParseUUIDList(request.data.get('GrantRoles'))
user_bytes = _GetUserBytes(request)
# 公司维度目标(可选):不传则全租户安装
company_uuids = _ParseUUIDList(request.data.get('CompanyUUIDs'))
@@ -227,6 +255,7 @@ class AppInstallView(ELTAuthenticatedAPIView):
ok, err, flat = AppManager.InstallTenantApp(tenant_bytes, app_uuid)
if not ok:
return Response({'code': 1, 'msg': err}, status=400)
_GrantRolesToApp(grant_roles, app_uuid, tenant_bytes, None, user_bytes)
return Response({'code': 0, 'msg': '安装成功(全租户)', 'data': flat})
# 指定公司安装:校验目标公司都在管理员可管理范围内
@@ -239,10 +268,14 @@ class AppInstallView(ELTAuthenticatedAPIView):
if invalid:
return Response({'code': 403, 'msg': '无权为以下公司安装(不在您的管理范围)'}, status=403)
ok, err, count = AppManager.InstallCompanyApps(
company_uuids, app_uuid, InstallBy=_GetUserBytes(request),
company_uuids, app_uuid, InstallBy=user_bytes,
)
if not ok:
return Response({'code': 1, 'msg': err}, status=400)
if grant_roles:
tenant_bytes = _GetTenantBytes(request)
for company in company_uuids:
_GrantRolesToApp(grant_roles, app_uuid, tenant_bytes, company, user_bytes)
return Response({'code': 0, 'msg': f'安装成功({count} 家公司)', 'data': {'InstalledCount': count}})