feat(rbac): add lazy organization tree endpoint
This commit is contained in:
@@ -19,6 +19,10 @@ from fastapi_modules.fastapi_leaudit.domian.Dto.rbacAdminDto import (
|
||||
RoleUpdateDTO,
|
||||
)
|
||||
from fastapi_modules.fastapi_leaudit.domian.vo.rbacAdminVo import (
|
||||
OrganizationNodeVO,
|
||||
OrganizationPathVO,
|
||||
OrganizationTreeUserVO,
|
||||
OrganizationTreeVO,
|
||||
RoleAccessSaveVO,
|
||||
RoleListVO,
|
||||
RolePermissionsVO,
|
||||
@@ -390,7 +394,7 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
f"""
|
||||
SELECT
|
||||
u.id, u.username, u.nick_name, u.phone_number, u.email, u.area, u.ou_name, u.ou_id, u.status,
|
||||
u.is_leader, u.tenant_name, u.dep_name,
|
||||
u.is_leader, u.tenant_name, u.dep_name, u.dep_short_name,
|
||||
COALESCE(
|
||||
json_agg(
|
||||
DISTINCT jsonb_build_object('role_id', r.id, 'role_key', r.role_key, 'role_name', r.role_name)
|
||||
@@ -411,6 +415,175 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
).mappings().all()
|
||||
return UserListVO(total=total, page=Page, page_size=PageSize, items=[self._toUserVo(row) for row in rows])
|
||||
|
||||
async def GetOrganizationTree(self, CurrentUserId: int, IncludeUsers: bool, RootUuid: str | None) -> OrganizationTreeVO:
|
||||
"""查询组织树。"""
|
||||
await self._assertManagePermission(CurrentUserId)
|
||||
await self._assertPermission(CurrentUserId, "rbac:users:read")
|
||||
currentUser = await self._getCurrentUserContext(CurrentUserId)
|
||||
|
||||
user_filters = ["deleted_at IS NULL", "status = 0"]
|
||||
params: dict[str, object] = {}
|
||||
if not currentUser["is_global"]:
|
||||
user_filters.append("COALESCE(area, '') = :user_area")
|
||||
params["user_area"] = currentUser["area"]
|
||||
where_clause = " AND ".join(user_filters)
|
||||
|
||||
async with GetAsyncSession() as Session:
|
||||
user_rows = (
|
||||
await Session.execute(
|
||||
text(
|
||||
f"""
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
nick_name,
|
||||
area,
|
||||
ou_id,
|
||||
ou_name,
|
||||
is_leader,
|
||||
status,
|
||||
tenant_name,
|
||||
dep_name,
|
||||
dep_short_name,
|
||||
email,
|
||||
phone_number
|
||||
FROM sso_users
|
||||
WHERE {where_clause}
|
||||
ORDER BY COALESCE(tenant_name, '') ASC, COALESCE(dep_name, '') ASC, COALESCE(ou_name, '') ASC, id ASC
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
).mappings().all()
|
||||
|
||||
root_uuid = str(RootUuid or "").strip() or None
|
||||
tenant_nodes: dict[str, OrganizationNodeVO] = {}
|
||||
department_nodes: dict[str, OrganizationNodeVO] = {}
|
||||
organization_nodes: dict[str, OrganizationNodeVO] = {}
|
||||
department_children: dict[str, list[str]] = {}
|
||||
organization_children: dict[str, list[str]] = {}
|
||||
users_by_org: dict[str, list[OrganizationTreeUserVO]] = {}
|
||||
total_users = len(user_rows)
|
||||
|
||||
for row in user_rows:
|
||||
ou_id = str(row.get("ou_id") or "").strip()
|
||||
ou_name = str(row.get("ou_name") or "").strip() or ou_id
|
||||
tenant_name = str(row.get("tenant_name") or "").strip()
|
||||
dep_name = str(row.get("dep_name") or "").strip()
|
||||
dep_short_name = str(row.get("dep_short_name") or "").strip()
|
||||
tenant_label = tenant_name or "未分组租户"
|
||||
department_label = dep_name or dep_short_name or "未分组部门"
|
||||
organization_label = ou_name or "未分组组织"
|
||||
|
||||
tenant_key = f"tenant__{tenant_label}"
|
||||
dep_key = f"dep__{tenant_label}__{department_label}"
|
||||
org_key = ou_id or f"org__{tenant_label}__{department_label}__{organization_label}"
|
||||
|
||||
if tenant_key not in tenant_nodes:
|
||||
tenant_nodes[tenant_key] = OrganizationNodeVO(
|
||||
ou_id=tenant_key,
|
||||
ou_name=tenant_label,
|
||||
parent_ou_id=None,
|
||||
level=1,
|
||||
)
|
||||
if dep_key not in department_nodes:
|
||||
department_nodes[dep_key] = OrganizationNodeVO(
|
||||
ou_id=dep_key,
|
||||
ou_name=department_label,
|
||||
parent_ou_id=tenant_key,
|
||||
level=2,
|
||||
)
|
||||
department_children.setdefault(tenant_key, []).append(dep_key)
|
||||
if org_key not in organization_nodes:
|
||||
organization_nodes[org_key] = OrganizationNodeVO(
|
||||
ou_id=org_key,
|
||||
ou_name=organization_label,
|
||||
parent_ou_id=dep_key,
|
||||
level=3,
|
||||
)
|
||||
organization_children.setdefault(dep_key, []).append(org_key)
|
||||
|
||||
if IncludeUsers and ou_id:
|
||||
users_by_org.setdefault(org_key, []).append(
|
||||
OrganizationTreeUserVO(
|
||||
id=int(row["id"]),
|
||||
username=str(row.get("username") or ""),
|
||||
nick_name=str(row.get("nick_name") or ""),
|
||||
area=row.get("area"),
|
||||
ou_id=ou_id,
|
||||
ou_name=ou_name,
|
||||
is_leader=bool(row.get("is_leader", False)),
|
||||
status=int(row.get("status") or 0),
|
||||
tenant_name=row.get("tenant_name"),
|
||||
dep_name=row.get("dep_name"),
|
||||
dep_short_name=row.get("dep_short_name"),
|
||||
email=row.get("email"),
|
||||
phone_number=row.get("phone_number"),
|
||||
organization_path=OrganizationPathVO(
|
||||
tenant_name=tenant_name,
|
||||
dep_name=dep_name,
|
||||
dep_short_name=dep_short_name,
|
||||
ou_name=ou_name,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
for org_key, users in users_by_org.items():
|
||||
users.sort(key=lambda item: (not item.is_leader, item.nick_name, item.username, item.id))
|
||||
node = organization_nodes.get(org_key)
|
||||
if node:
|
||||
node.users = users
|
||||
|
||||
def clone_node(source: OrganizationNodeVO, *, children: list[OrganizationNodeVO] | None = None, users: list[OrganizationTreeUserVO] | None = None) -> OrganizationNodeVO:
|
||||
return OrganizationNodeVO(
|
||||
ou_id=source.ou_id,
|
||||
ou_name=source.ou_name,
|
||||
parent_ou_id=source.parent_ou_id,
|
||||
level=source.level,
|
||||
children=children or [],
|
||||
users=users or [],
|
||||
)
|
||||
|
||||
if root_uuid:
|
||||
if root_uuid in tenant_nodes:
|
||||
tenant = tenant_nodes[root_uuid]
|
||||
child_nodes = [
|
||||
clone_node(department_nodes[dep_id])
|
||||
for dep_id in sorted(set(department_children.get(root_uuid, [])), key=lambda item: (department_nodes[item].ou_name, item))
|
||||
]
|
||||
organizations = [clone_node(tenant, children=child_nodes)]
|
||||
total_organizations = 1 + len(child_nodes)
|
||||
elif root_uuid in department_nodes:
|
||||
department = department_nodes[root_uuid]
|
||||
child_nodes = [
|
||||
clone_node(organization_nodes[org_id])
|
||||
for org_id in sorted(set(organization_children.get(root_uuid, [])), key=lambda item: (organization_nodes[item].ou_name, item))
|
||||
]
|
||||
organizations = [clone_node(department, children=child_nodes)]
|
||||
total_organizations = 1 + len(child_nodes)
|
||||
elif root_uuid in organization_nodes:
|
||||
organization = organization_nodes[root_uuid]
|
||||
organization_users = list(users_by_org.get(root_uuid, [])) if IncludeUsers else []
|
||||
organizations = [clone_node(organization, users=organization_users)]
|
||||
total_organizations = 1
|
||||
total_users = len(organization_users) if IncludeUsers else total_users
|
||||
else:
|
||||
organizations = []
|
||||
total_organizations = 0
|
||||
total_users = 0
|
||||
else:
|
||||
organizations = [
|
||||
clone_node(tenant_nodes[tenant_id])
|
||||
for tenant_id in sorted(tenant_nodes.keys(), key=lambda item: (tenant_nodes[item].ou_name, item))
|
||||
]
|
||||
total_organizations = len(organizations)
|
||||
|
||||
return OrganizationTreeVO(
|
||||
organizations=organizations,
|
||||
total_organizations=total_organizations,
|
||||
total_users=total_users,
|
||||
)
|
||||
|
||||
async def ListRoleUsers(self, CurrentUserId: int, RoleId: int, Page: int, PageSize: int, Area: str | None, UserName: str | None) -> UserListVO:
|
||||
"""查询指定角色下的用户列表。"""
|
||||
await self._assertManagePermission(CurrentUserId)
|
||||
@@ -452,7 +625,7 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
f"""
|
||||
SELECT
|
||||
u.id, u.username, u.nick_name, u.phone_number, u.email, u.area, u.ou_name, u.ou_id, u.status,
|
||||
u.is_leader, u.tenant_name, u.dep_name,
|
||||
u.is_leader, u.tenant_name, u.dep_name, u.dep_short_name,
|
||||
COALESCE(
|
||||
json_agg(
|
||||
DISTINCT jsonb_build_object('role_id', r.id, 'role_key', r.role_key, 'role_name', r.role_name)
|
||||
@@ -976,6 +1149,7 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
roles=roles,
|
||||
tenant_name=Row.get("tenant_name"),
|
||||
dep_name=Row.get("dep_name"),
|
||||
dep_short_name=Row.get("dep_short_name"),
|
||||
)
|
||||
|
||||
def _toRouteVo(self, Row, Permissions: list[RoutePermissionVO], Enabled: bool) -> RouteVO:
|
||||
|
||||
Reference in New Issue
Block a user