feat: add backend rule group and permission support
This commit is contained in:
@@ -12,12 +12,14 @@ from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum
|
||||
from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.domian.Dto.rbacAdminDto import (
|
||||
RoleAccessSaveDTO,
|
||||
RoleCreateDTO,
|
||||
RolePermissionsBatchDTO,
|
||||
RoleRoutesUpdateDTO,
|
||||
RoleUpdateDTO,
|
||||
)
|
||||
from fastapi_modules.fastapi_leaudit.domian.vo.rbacAdminVo import (
|
||||
RoleAccessSaveVO,
|
||||
RoleListVO,
|
||||
RolePermissionsVO,
|
||||
RoleRoutesVO,
|
||||
@@ -156,6 +158,11 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
{"permission_key": "doc_type:create:write", "display_name": "创建文档类型", "module": "doc_type", "resource": "create", "action": "write", "api_method": "POST", "api_path": "/api/document-types", "route_path": "/document-types"},
|
||||
{"permission_key": "doc_type:update:write", "display_name": "更新文档类型", "module": "doc_type", "resource": "update", "action": "write", "api_method": "PUT", "api_path": "/api/document-types/{id}", "route_path": "/document-types"},
|
||||
{"permission_key": "doc_type:delete:delete", "display_name": "删除文档类型", "module": "doc_type", "resource": "delete", "action": "delete", "api_method": "DELETE", "api_path": "/api/document-types/{id}", "route_path": "/document-types"},
|
||||
{"permission_key": "evaluation_point:list:read", "display_name": "评查点列表", "module": "evaluation_point", "resource": "list", "action": "read", "api_method": "GET", "api_path": "/api/v3/evaluation-points", "route_path": "/rules"},
|
||||
{"permission_key": "evaluation_point:detail:read", "display_name": "评查点详情", "module": "evaluation_point", "resource": "detail", "action": "read", "api_method": "GET", "api_path": "/api/v3/evaluation-points/{id}", "route_path": "/rules"},
|
||||
{"permission_key": "evaluation_point:create:write", "display_name": "创建评查点", "module": "evaluation_point", "resource": "create", "action": "write", "api_method": "POST", "api_path": "/api/v3/evaluation-points", "route_path": "/rules"},
|
||||
{"permission_key": "evaluation_point:update:write", "display_name": "更新评查点", "module": "evaluation_point", "resource": "update", "action": "write", "api_method": "PUT", "api_path": "/api/v3/evaluation-points/{id}", "route_path": "/rules"},
|
||||
{"permission_key": "evaluation_point:delete:delete", "display_name": "删除评查点", "module": "evaluation_point", "resource": "delete", "action": "delete", "api_method": "DELETE", "api_path": "/api/v3/evaluation-points/{id}", "route_path": "/rules"},
|
||||
{"permission_key": "rbac:roles:read", "display_name": "角色列表", "module": "rbac", "resource": "roles", "action": "read", "api_method": "GET", "api_path": "/api/v3/rbac/roles", "route_path": "/role-permissions"},
|
||||
{"permission_key": "rbac:roles:create", "display_name": "创建角色", "module": "rbac", "resource": "roles", "action": "create", "api_method": "POST", "api_path": "/api/v3/rbac/roles", "route_path": "/role-permissions"},
|
||||
{"permission_key": "rbac:roles:update", "display_name": "更新角色", "module": "rbac", "resource": "roles", "action": "update", "api_method": "PUT", "api_path": "/api/v3/rbac/roles/{role_id}", "route_path": "/role-permissions"},
|
||||
@@ -495,32 +502,9 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
routeIds = sorted(set(Body.route_ids))
|
||||
async with GetAsyncSession() as Session:
|
||||
await self._ensureAdminSeeds(Session)
|
||||
allRouteIds = [row[0] for row in (await Session.execute(text("SELECT id FROM sys_routes WHERE deleted_at IS NULL AND route_path = ANY(:paths)").bindparams(paths=[item["route_path"] for item in self._MANAGEABLE_ROUTE_BLUEPRINTS]))).fetchall()]
|
||||
existingRows = (
|
||||
await Session.execute(text("SELECT route_id, status FROM role_route WHERE role_id = :role_id AND route_id = ANY(:route_ids)").bindparams(route_ids=allRouteIds), {"role_id": RoleId})
|
||||
).fetchall()
|
||||
existingMap = {int(routeId): int(status) for routeId, status in existingRows}
|
||||
insertedCount = 0
|
||||
for routeId in allRouteIds:
|
||||
if routeId in routeIds:
|
||||
if routeId in existingMap:
|
||||
await Session.execute(
|
||||
text("UPDATE role_route SET status = 1, permission = :permission, updated_at = NOW() WHERE role_id = :role_id AND route_id = :route_id"),
|
||||
{"role_id": RoleId, "route_id": routeId, "permission": Body.permission},
|
||||
)
|
||||
else:
|
||||
await Session.execute(
|
||||
text("INSERT INTO role_route (role_id, route_id, permission, status, created_at, updated_at) VALUES (:role_id, :route_id, :permission, 1, NOW(), NOW())"),
|
||||
{"role_id": RoleId, "route_id": routeId, "permission": Body.permission},
|
||||
)
|
||||
insertedCount += 1
|
||||
elif routeId in existingMap and existingMap[routeId] != 0:
|
||||
await Session.execute(
|
||||
text("UPDATE role_route SET status = 0, updated_at = NOW() WHERE role_id = :role_id AND route_id = :route_id"),
|
||||
{"role_id": RoleId, "route_id": routeId},
|
||||
)
|
||||
result = await self._updateRoleRoutesInSession(Session, RoleId, routeIds, Body.permission)
|
||||
await Session.commit()
|
||||
return RoleRouteUpdateResultVO(role_id=RoleId, enabled_count=len(routeIds), disabled_count=max(len(allRouteIds) - len(routeIds), 0), inserted_count=insertedCount, route_ids=routeIds)
|
||||
return result
|
||||
|
||||
async def GetRolePermissions(self, CurrentUserId: int, RoleId: int) -> RolePermissionsVO:
|
||||
"""查询角色权限授权。"""
|
||||
@@ -550,29 +534,29 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
await self._assertPermission(CurrentUserId, "rbac:role_permissions:write")
|
||||
async with GetAsyncSession() as Session:
|
||||
await self._ensureAdminSeeds(Session)
|
||||
permissionIds = [item.permission_id for item in Body.permissions]
|
||||
if Body.replace:
|
||||
await Session.execute(text("DELETE FROM role_permissions WHERE role_id = :role_id"), {"role_id": Body.role_id})
|
||||
for item in Body.permissions:
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO role_permissions (role_id, permission_id, grant_type, data_scope, created_at, updated_at)
|
||||
VALUES (:role_id, :permission_id, :grant_type, :data_scope, NOW(), NOW())
|
||||
ON CONFLICT (role_id, permission_id)
|
||||
DO UPDATE SET grant_type = EXCLUDED.grant_type, data_scope = EXCLUDED.data_scope, updated_at = NOW()
|
||||
"""
|
||||
),
|
||||
{
|
||||
"role_id": Body.role_id,
|
||||
"permission_id": item.permission_id,
|
||||
"grant_type": item.grant_type,
|
||||
"data_scope": item.data_scope,
|
||||
},
|
||||
)
|
||||
await self._saveRolePermissionsInSession(Session, Body.role_id, Body.permissions, Body.replace, Body.replace_scope_permission_ids)
|
||||
await Session.commit()
|
||||
return await self.GetRolePermissions(CurrentUserId, Body.role_id)
|
||||
|
||||
async def SaveRoleAccess(self, CurrentUserId: int, RoleId: int, Body: RoleAccessSaveDTO) -> RoleAccessSaveVO:
|
||||
"""原子保存角色菜单与接口权限。"""
|
||||
await self._assertManagePermission(CurrentUserId)
|
||||
await self._assertPermission(CurrentUserId, "rbac:role_routes:write")
|
||||
await self._assertPermission(CurrentUserId, "rbac:role_permissions:write")
|
||||
routeIds = sorted(set(int(routeId) for routeId in Body.route_ids))
|
||||
permissionIds = sorted(set(int(permissionId) for permissionId in Body.permission_ids))
|
||||
permissionConfigs = [
|
||||
{"permission_id": permissionId, "grant_type": "GRANT", "data_scope": "ALL"}
|
||||
for permissionId in permissionIds
|
||||
]
|
||||
async with GetAsyncSession() as Session:
|
||||
await self._ensureAdminSeeds(Session)
|
||||
routeResult = await self._updateRoleRoutesInSession(Session, RoleId, routeIds, Body.route_permission)
|
||||
await self._saveRolePermissionsInSession(Session, RoleId, permissionConfigs, True, Body.replace_scope_permission_ids)
|
||||
permissionResult = await self._getRolePermissionsInSession(Session, RoleId)
|
||||
await Session.commit()
|
||||
return RoleAccessSaveVO(role_id=RoleId, route_result=routeResult, permission_result=permissionResult)
|
||||
|
||||
async def GetRoutePermissions(self, CurrentUserId: int, RouteId: int) -> RoutePermissionsVO:
|
||||
"""查询路由关联权限定义。"""
|
||||
await self._assertManagePermission(CurrentUserId)
|
||||
@@ -592,6 +576,104 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
permissions=permissionMap.get(RouteId, []),
|
||||
)
|
||||
|
||||
async def _updateRoleRoutesInSession(self, Session, RoleId: int, RouteIds: list[int], Permission: str) -> RoleRouteUpdateResultVO:
|
||||
"""在当前事务中写入角色路由授权。"""
|
||||
allRouteIds = [
|
||||
row[0]
|
||||
for row in (
|
||||
await Session.execute(
|
||||
text("SELECT id FROM sys_routes WHERE deleted_at IS NULL AND route_path = ANY(:paths)").bindparams(
|
||||
paths=[item["route_path"] for item in self._MANAGEABLE_ROUTE_BLUEPRINTS]
|
||||
)
|
||||
)
|
||||
).fetchall()
|
||||
]
|
||||
existingRows = (
|
||||
await Session.execute(
|
||||
text("SELECT route_id, status FROM role_route WHERE role_id = :role_id AND route_id = ANY(:route_ids)").bindparams(route_ids=allRouteIds),
|
||||
{"role_id": RoleId},
|
||||
)
|
||||
).fetchall()
|
||||
existingMap = {int(routeId): int(status) for routeId, status in existingRows}
|
||||
insertedCount = 0
|
||||
for routeId in allRouteIds:
|
||||
if routeId in RouteIds:
|
||||
if routeId in existingMap:
|
||||
await Session.execute(
|
||||
text("UPDATE role_route SET status = 1, permission = :permission, updated_at = NOW() WHERE role_id = :role_id AND route_id = :route_id"),
|
||||
{"role_id": RoleId, "route_id": routeId, "permission": Permission},
|
||||
)
|
||||
else:
|
||||
await Session.execute(
|
||||
text("INSERT INTO role_route (role_id, route_id, permission, status, created_at, updated_at) VALUES (:role_id, :route_id, :permission, 1, NOW(), NOW())"),
|
||||
{"role_id": RoleId, "route_id": routeId, "permission": Permission},
|
||||
)
|
||||
insertedCount += 1
|
||||
elif routeId in existingMap and existingMap[routeId] != 0:
|
||||
await Session.execute(
|
||||
text("UPDATE role_route SET status = 0, updated_at = NOW() WHERE role_id = :role_id AND route_id = :route_id"),
|
||||
{"role_id": RoleId, "route_id": routeId},
|
||||
)
|
||||
return RoleRouteUpdateResultVO(
|
||||
role_id=RoleId,
|
||||
enabled_count=len(RouteIds),
|
||||
disabled_count=max(len(allRouteIds) - len(RouteIds), 0),
|
||||
inserted_count=insertedCount,
|
||||
route_ids=RouteIds,
|
||||
)
|
||||
|
||||
async def _saveRolePermissionsInSession(self, Session, RoleId: int, Permissions: list[Any], Replace: bool, ReplaceScopePermissionIds: list[int]) -> None:
|
||||
"""在当前事务中写入角色接口权限。"""
|
||||
if Replace:
|
||||
scopeIds = sorted({int(permissionId) for permissionId in ReplaceScopePermissionIds if permissionId})
|
||||
if scopeIds:
|
||||
# 仅清理当前页面负责维护的权限范围,避免局部页面保存时误删其他模块权限。
|
||||
await Session.execute(
|
||||
text("DELETE FROM role_permissions WHERE role_id = :role_id AND permission_id = ANY(:permission_ids)").bindparams(permission_ids=scopeIds),
|
||||
{"role_id": RoleId},
|
||||
)
|
||||
else:
|
||||
# 兼容旧调用方:若未传作用域,保留原有全量替换行为。
|
||||
await Session.execute(text("DELETE FROM role_permissions WHERE role_id = :role_id"), {"role_id": RoleId})
|
||||
for item in Permissions:
|
||||
permissionId = int(item.permission_id if hasattr(item, "permission_id") else item["permission_id"])
|
||||
grantType = item.grant_type if hasattr(item, "grant_type") else item.get("grant_type")
|
||||
dataScope = item.data_scope if hasattr(item, "data_scope") else item.get("data_scope")
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO role_permissions (role_id, permission_id, grant_type, data_scope, created_at, updated_at)
|
||||
VALUES (:role_id, :permission_id, :grant_type, :data_scope, NOW(), NOW())
|
||||
ON CONFLICT (role_id, permission_id)
|
||||
DO UPDATE SET grant_type = EXCLUDED.grant_type, data_scope = EXCLUDED.data_scope, updated_at = NOW()
|
||||
"""
|
||||
),
|
||||
{
|
||||
"role_id": RoleId,
|
||||
"permission_id": permissionId,
|
||||
"grant_type": grantType,
|
||||
"data_scope": dataScope,
|
||||
},
|
||||
)
|
||||
|
||||
async def _getRolePermissionsInSession(self, Session, RoleId: int) -> RolePermissionsVO:
|
||||
"""在当前事务中查询角色接口权限。"""
|
||||
rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT rp.id, rp.permission_id, p.permission_key, p.display_name, rp.grant_type, rp.data_scope
|
||||
FROM role_permissions rp
|
||||
JOIN permissions p ON p.id = rp.permission_id
|
||||
WHERE rp.role_id = :role_id
|
||||
ORDER BY p.sort_order ASC, p.id ASC
|
||||
"""
|
||||
),
|
||||
{"role_id": RoleId},
|
||||
)
|
||||
).mappings().all()
|
||||
return RolePermissionsVO(role_id=RoleId, permissions=[self._toRolePermissionVo(row) for row in rows])
|
||||
|
||||
async def _assertManagePermission(self, CurrentUserId: int) -> None:
|
||||
"""校验当前用户是否具备管理能力。"""
|
||||
context = await self._getCurrentUserContext(CurrentUserId)
|
||||
|
||||
Reference in New Issue
Block a user