feat(rbac): add lazy organization tree endpoint

This commit is contained in:
wren
2026-05-11 09:38:14 +08:00
parent 90e56d6259
commit e19f63183b
2 changed files with 186 additions and 2 deletions
@@ -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: