feat(ram): 安装时 RAM 角色授权(GrantRoles)+ ServiceOnline 离线生命周期(下线标记/上报恢复)
This commit is contained in:
1 parent
fda236eb0c
commit
588452aa92
4 files changed
+89
-1
No files matched your search
@@ -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离线)'),
|
||||
),
|
||||
]
|
||||
@@ -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:
|
||||
|
||||
@@ -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}})
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user