Optimize RBAC org tree loading
This commit is contained in:
@@ -58,6 +58,7 @@ coverage.xml
|
|||||||
# Playwright MCP cache
|
# Playwright MCP cache
|
||||||
.playwright-mcp/
|
.playwright-mcp/
|
||||||
.codex-run/
|
.codex-run/
|
||||||
|
.chromadb_rag/
|
||||||
|
|
||||||
# Rules cache
|
# Rules cache
|
||||||
rules/**/__pycache__/
|
rules/**/__pycache__/
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ celery_app = Celery(
|
|||||||
|
|
||||||
celery_app.conf.update(
|
celery_app.conf.update(
|
||||||
task_default_queue=LEAUDIT_WORKER_QUEUE_NORMAL,
|
task_default_queue=LEAUDIT_WORKER_QUEUE_NORMAL,
|
||||||
|
imports=("fastapi_modules.fastapi_leaudit.leaudit_bridge.tasks",),
|
||||||
task_queues=(
|
task_queues=(
|
||||||
Queue(LEAUDIT_WORKER_QUEUE_URGENT),
|
Queue(LEAUDIT_WORKER_QUEUE_URGENT),
|
||||||
Queue(LEAUDIT_WORKER_QUEUE_NORMAL),
|
Queue(LEAUDIT_WORKER_QUEUE_NORMAL),
|
||||||
@@ -58,3 +59,6 @@ celery_app.autodiscover_tasks(
|
|||||||
],
|
],
|
||||||
force=True,
|
force=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 显式导入任务模块,避免 worker 在某些启动方式下漏注册 bridge tasks。
|
||||||
|
from fastapi_modules.fastapi_leaudit.leaudit_bridge import tasks as _leaudit_bridge_tasks # noqa: F401,E402
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -43,6 +45,11 @@ from fastapi_modules.fastapi_leaudit.services.rbacAdminService import IRbacAdmin
|
|||||||
class RbacAdminServiceImpl(IRbacAdminService):
|
class RbacAdminServiceImpl(IRbacAdminService):
|
||||||
"""RBAC 管理服务实现。"""
|
"""RBAC 管理服务实现。"""
|
||||||
|
|
||||||
|
_logger = logging.getLogger("APP")
|
||||||
|
_UNGROUPED_TENANT_LABEL = "未分组租户"
|
||||||
|
_UNGROUPED_DEPARTMENT_LABEL = "未分组部门"
|
||||||
|
_UNGROUPED_ORGANIZATION_LABEL = "未分组组织"
|
||||||
|
|
||||||
_MANAGEABLE_ROUTE_BLUEPRINTS: list[dict[str, Any]] = [
|
_MANAGEABLE_ROUTE_BLUEPRINTS: list[dict[str, Any]] = [
|
||||||
{
|
{
|
||||||
"route_path": "/home",
|
"route_path": "/home",
|
||||||
@@ -421,172 +428,277 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
|||||||
|
|
||||||
async def GetOrganizationTree(self, CurrentUserId: int, IncludeUsers: bool, RootUuid: str | None) -> OrganizationTreeVO:
|
async def GetOrganizationTree(self, CurrentUserId: int, IncludeUsers: bool, RootUuid: str | None) -> OrganizationTreeVO:
|
||||||
"""查询组织树。"""
|
"""查询组织树。"""
|
||||||
|
started_at = time.perf_counter()
|
||||||
await self._assertManagePermission(CurrentUserId)
|
await self._assertManagePermission(CurrentUserId)
|
||||||
await self._assertPermission(CurrentUserId, "rbac:users:read")
|
await self._assertPermission(CurrentUserId, "rbac:users:read")
|
||||||
currentUser = await self._getCurrentUserContext(CurrentUserId)
|
currentUser = await self._getCurrentUserContext(CurrentUserId)
|
||||||
|
root_uuid = str(RootUuid or "").strip() or None
|
||||||
|
|
||||||
|
def _normalize_group_label(value: str, empty_label: str) -> str:
|
||||||
|
return "" if value == empty_label else value
|
||||||
|
|
||||||
|
def _display_group_label(value: object, empty_label: str) -> str:
|
||||||
|
text_value = str(value or "").strip()
|
||||||
|
return text_value or empty_label
|
||||||
|
|
||||||
|
def _make_org_user(row: Any) -> OrganizationTreeUserVO:
|
||||||
|
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()
|
||||||
|
ou_name = str(row.get("ou_name") or "").strip()
|
||||||
|
return 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=str(row.get("ou_id") or ""),
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
user_filters = ["deleted_at IS NULL", "status = 0"]
|
user_filters = ["deleted_at IS NULL", "status = 0"]
|
||||||
params: dict[str, object] = {}
|
params: dict[str, object] = {}
|
||||||
if not currentUser["is_global"]:
|
if not currentUser["is_global"]:
|
||||||
user_filters.append("COALESCE(area, '') = :user_area")
|
user_filters.append("COALESCE(area, '') = :user_area")
|
||||||
params["user_area"] = currentUser["area"]
|
params["user_area"] = currentUser["area"]
|
||||||
|
|
||||||
|
if root_uuid:
|
||||||
|
if root_uuid.startswith("tenant__"):
|
||||||
|
tenant_label = _normalize_group_label(root_uuid.removeprefix("tenant__"), self._UNGROUPED_TENANT_LABEL)
|
||||||
|
user_filters.append("COALESCE(tenant_name, '') = :tenant_name")
|
||||||
|
params["tenant_name"] = tenant_label
|
||||||
|
elif root_uuid.startswith("dep__"):
|
||||||
|
tenant_label, _, dep_label = root_uuid.removeprefix("dep__").partition("__")
|
||||||
|
tenant_label = _normalize_group_label(tenant_label, self._UNGROUPED_TENANT_LABEL)
|
||||||
|
dep_label = _normalize_group_label(dep_label, self._UNGROUPED_DEPARTMENT_LABEL)
|
||||||
|
user_filters.append("COALESCE(tenant_name, '') = :tenant_name")
|
||||||
|
user_filters.append("COALESCE(dep_name, dep_short_name, '') = :dep_name")
|
||||||
|
params["tenant_name"] = tenant_label
|
||||||
|
params["dep_name"] = dep_label
|
||||||
|
elif root_uuid.startswith("org__"):
|
||||||
|
tenant_label, _, remainder = root_uuid.removeprefix("org__").partition("__")
|
||||||
|
dep_label, _, org_label = remainder.partition("__")
|
||||||
|
tenant_label = _normalize_group_label(tenant_label, self._UNGROUPED_TENANT_LABEL)
|
||||||
|
dep_label = _normalize_group_label(dep_label, self._UNGROUPED_DEPARTMENT_LABEL)
|
||||||
|
org_label = _normalize_group_label(org_label, self._UNGROUPED_ORGANIZATION_LABEL)
|
||||||
|
user_filters.append("COALESCE(tenant_name, '') = :tenant_name")
|
||||||
|
user_filters.append("COALESCE(dep_name, dep_short_name, '') = :dep_name")
|
||||||
|
user_filters.append("COALESCE(ou_name, '') = :ou_name")
|
||||||
|
params["tenant_name"] = tenant_label
|
||||||
|
params["dep_name"] = dep_label
|
||||||
|
params["ou_name"] = org_label
|
||||||
|
else:
|
||||||
|
user_filters.append("COALESCE(ou_id, '') = :root_ou_id")
|
||||||
|
params["root_ou_id"] = root_uuid
|
||||||
where_clause = " AND ".join(user_filters)
|
where_clause = " AND ".join(user_filters)
|
||||||
|
|
||||||
async with GetAsyncSession() as Session:
|
async with GetAsyncSession() as Session:
|
||||||
user_rows = (
|
total_users = int(
|
||||||
await Session.execute(
|
(
|
||||||
text(
|
await Session.execute(
|
||||||
f"""
|
text(
|
||||||
SELECT
|
f"""
|
||||||
id,
|
SELECT COUNT(1)
|
||||||
username,
|
FROM sso_users
|
||||||
nick_name,
|
WHERE {where_clause}
|
||||||
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,
|
|
||||||
),
|
),
|
||||||
|
params,
|
||||||
)
|
)
|
||||||
)
|
).scalar()
|
||||||
|
or 0
|
||||||
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:
|
query_rows_count = 0
|
||||||
if root_uuid in tenant_nodes:
|
organizations: list[OrganizationNodeVO]
|
||||||
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(
|
if not root_uuid:
|
||||||
|
tenant_rows = (
|
||||||
|
await Session.execute(
|
||||||
|
text(
|
||||||
|
f"""
|
||||||
|
SELECT DISTINCT COALESCE(tenant_name, '') AS tenant_name
|
||||||
|
FROM sso_users
|
||||||
|
WHERE {where_clause}
|
||||||
|
ORDER BY COALESCE(tenant_name, '') ASC
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
).mappings().all()
|
||||||
|
query_rows_count = len(tenant_rows)
|
||||||
|
organizations = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=f"tenant__{_display_group_label(row.get('tenant_name'), self._UNGROUPED_TENANT_LABEL)}",
|
||||||
|
ou_name=_display_group_label(row.get("tenant_name"), self._UNGROUPED_TENANT_LABEL),
|
||||||
|
parent_ou_id=None,
|
||||||
|
level=1,
|
||||||
|
)
|
||||||
|
for row in tenant_rows
|
||||||
|
]
|
||||||
|
total_organizations = len(organizations)
|
||||||
|
elif root_uuid.startswith("tenant__"):
|
||||||
|
tenant_label = _display_group_label(params.get("tenant_name"), self._UNGROUPED_TENANT_LABEL)
|
||||||
|
dep_rows = (
|
||||||
|
await Session.execute(
|
||||||
|
text(
|
||||||
|
f"""
|
||||||
|
SELECT DISTINCT
|
||||||
|
COALESCE(dep_name, dep_short_name, '') AS department_name
|
||||||
|
FROM sso_users
|
||||||
|
WHERE {where_clause}
|
||||||
|
ORDER BY COALESCE(dep_name, dep_short_name, '') ASC
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
).mappings().all()
|
||||||
|
query_rows_count = len(dep_rows)
|
||||||
|
child_nodes = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=f"dep__{tenant_label}__{_display_group_label(row.get('department_name'), self._UNGROUPED_DEPARTMENT_LABEL)}",
|
||||||
|
ou_name=_display_group_label(row.get("department_name"), self._UNGROUPED_DEPARTMENT_LABEL),
|
||||||
|
parent_ou_id=root_uuid,
|
||||||
|
level=2,
|
||||||
|
)
|
||||||
|
for row in dep_rows
|
||||||
|
]
|
||||||
|
organizations = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=root_uuid,
|
||||||
|
ou_name=tenant_label,
|
||||||
|
parent_ou_id=None,
|
||||||
|
level=1,
|
||||||
|
children=child_nodes,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
total_organizations = 1 + len(child_nodes)
|
||||||
|
elif root_uuid.startswith("dep__"):
|
||||||
|
tenant_label, _, dep_label = root_uuid.removeprefix("dep__").partition("__")
|
||||||
|
org_rows = (
|
||||||
|
await Session.execute(
|
||||||
|
text(
|
||||||
|
f"""
|
||||||
|
SELECT DISTINCT
|
||||||
|
COALESCE(ou_id, '') AS ou_id,
|
||||||
|
COALESCE(ou_name, '') AS ou_name
|
||||||
|
FROM sso_users
|
||||||
|
WHERE {where_clause}
|
||||||
|
ORDER BY COALESCE(ou_name, '') ASC, COALESCE(ou_id, '') ASC
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
).mappings().all()
|
||||||
|
query_rows_count = len(org_rows)
|
||||||
|
child_nodes = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=str(row.get("ou_id") or "").strip()
|
||||||
|
or f"org__{tenant_label}__{dep_label}__{_display_group_label(row.get('ou_name'), self._UNGROUPED_ORGANIZATION_LABEL)}",
|
||||||
|
ou_name=_display_group_label(row.get("ou_name"), self._UNGROUPED_ORGANIZATION_LABEL),
|
||||||
|
parent_ou_id=root_uuid,
|
||||||
|
level=3,
|
||||||
|
)
|
||||||
|
for row in org_rows
|
||||||
|
]
|
||||||
|
organizations = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=root_uuid,
|
||||||
|
ou_name=_display_group_label(_normalize_group_label(dep_label, self._UNGROUPED_DEPARTMENT_LABEL), self._UNGROUPED_DEPARTMENT_LABEL),
|
||||||
|
parent_ou_id=f"tenant__{tenant_label}",
|
||||||
|
level=2,
|
||||||
|
children=child_nodes,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
total_organizations = 1 + len(child_nodes)
|
||||||
|
else:
|
||||||
|
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 is_leader DESC, COALESCE(nick_name, '') ASC, COALESCE(username, '') ASC, id ASC
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
params,
|
||||||
|
)
|
||||||
|
).mappings().all()
|
||||||
|
query_rows_count = len(user_rows)
|
||||||
|
users = [_make_org_user(row) for row in user_rows] if IncludeUsers else []
|
||||||
|
total_users = len(user_rows) if IncludeUsers else total_users
|
||||||
|
|
||||||
|
if root_uuid.startswith("org__"):
|
||||||
|
tenant_label, _, remainder = root_uuid.removeprefix("org__").partition("__")
|
||||||
|
dep_label, _, org_label = remainder.partition("__")
|
||||||
|
organizations = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=root_uuid,
|
||||||
|
ou_name=_display_group_label(_normalize_group_label(org_label, self._UNGROUPED_ORGANIZATION_LABEL), self._UNGROUPED_ORGANIZATION_LABEL),
|
||||||
|
parent_ou_id=f"dep__{tenant_label}__{dep_label}",
|
||||||
|
level=3,
|
||||||
|
users=users,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
first_row = user_rows[0] if user_rows else {}
|
||||||
|
tenant_label = _display_group_label(first_row.get("tenant_name"), self._UNGROUPED_TENANT_LABEL)
|
||||||
|
dep_label = _display_group_label(first_row.get("dep_name") or first_row.get("dep_short_name"), self._UNGROUPED_DEPARTMENT_LABEL)
|
||||||
|
org_label = _display_group_label(first_row.get("ou_name"), self._UNGROUPED_ORGANIZATION_LABEL)
|
||||||
|
organizations = [
|
||||||
|
OrganizationNodeVO(
|
||||||
|
ou_id=root_uuid,
|
||||||
|
ou_name=org_label,
|
||||||
|
parent_ou_id=f"dep__{tenant_label}__{dep_label}",
|
||||||
|
level=3,
|
||||||
|
users=users,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
total_organizations = 1 if organizations else 0
|
||||||
|
|
||||||
|
result = OrganizationTreeVO(
|
||||||
organizations=organizations,
|
organizations=organizations,
|
||||||
total_organizations=total_organizations,
|
total_organizations=total_organizations,
|
||||||
total_users=total_users,
|
total_users=total_users,
|
||||||
)
|
)
|
||||||
|
duration_ms = round((time.perf_counter() - started_at) * 1000, 2)
|
||||||
|
self._logger.info(
|
||||||
|
"rbac organization tree built: user=%s include_users=%s root=%s rows=%s orgs=%s users=%s duration_ms=%s",
|
||||||
|
CurrentUserId,
|
||||||
|
IncludeUsers,
|
||||||
|
root_uuid or "root",
|
||||||
|
query_rows_count,
|
||||||
|
result.total_organizations,
|
||||||
|
result.total_users,
|
||||||
|
duration_ms,
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
async def ListRoleUsers(self, CurrentUserId: int, RoleId: int, Page: int, PageSize: int, Area: str | None, UserName: str | None) -> UserListVO:
|
async def ListRoleUsers(self, CurrentUserId: int, RoleId: int, Page: int, PageSize: int, Area: str | None, UserName: str | None) -> UserListVO:
|
||||||
"""查询指定角色下的用户列表。"""
|
"""查询指定角色下的用户列表。"""
|
||||||
|
|||||||
Submodule
+1
Submodule legal-platform-frontend added at 444c435fe0
@@ -0,0 +1,36 @@
|
|||||||
|
-- ============================================================================
|
||||||
|
-- sso_users 组织树查询索引补丁
|
||||||
|
-- 用途:
|
||||||
|
-- 1. 优化 /api/admin/users/organizations/tree 的分层懒加载查询
|
||||||
|
-- 2. 重点覆盖 tenant / dep / org / ou_id 四类过滤路径
|
||||||
|
-- 注意:
|
||||||
|
-- 1. 该脚本适合已存在 sso_users 表的环境增量执行
|
||||||
|
-- 2. 如果正式库较大,建议低峰执行;如需 CREATE INDEX CONCURRENTLY,请拆成单条执行
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
BEGIN;
|
||||||
|
|
||||||
|
-- 根节点:按地区 + 有效用户状态枚举租户
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_tenant
|
||||||
|
ON sso_users(status, deleted_at, area, tenant_name);
|
||||||
|
|
||||||
|
-- 租户节点:按地区 / 租户 枚举部门,兼容 dep_name / dep_short_name
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_expr
|
||||||
|
ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')));
|
||||||
|
|
||||||
|
-- 部门节点:按租户 + 部门 枚举组织
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_ou_name
|
||||||
|
ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')), ou_name);
|
||||||
|
|
||||||
|
-- 叶子节点:按地区快速命中组织名称 / 组织ID
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_name
|
||||||
|
ON sso_users(status, deleted_at, area, ou_name);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_id
|
||||||
|
ON sso_users(status, deleted_at, area, ou_id);
|
||||||
|
|
||||||
|
-- 部门展开时若只按 area + department 过滤,也给表达式索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_dep_expr
|
||||||
|
ON sso_users(status, deleted_at, area, (COALESCE(dep_name, dep_short_name, '')));
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
@@ -50,6 +50,18 @@ CREATE INDEX IF NOT EXISTS idx_sso_users_ou_id ON sso_users(ou_id);
|
|||||||
CREATE INDEX IF NOT EXISTS idx_sso_users_is_leader ON sso_users(is_leader);
|
CREATE INDEX IF NOT EXISTS idx_sso_users_is_leader ON sso_users(is_leader);
|
||||||
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_person ON sso_users(mq_person_uuid);
|
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_person ON sso_users(mq_person_uuid);
|
||||||
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_account ON sso_users(mq_account_uuid);
|
CREATE INDEX IF NOT EXISTS idx_sso_users_mq_account ON sso_users(mq_account_uuid);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_tenant
|
||||||
|
ON sso_users(status, deleted_at, area, tenant_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_dep_expr
|
||||||
|
ON sso_users(status, deleted_at, area, (COALESCE(dep_name, dep_short_name, '')));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_name
|
||||||
|
ON sso_users(status, deleted_at, area, ou_name);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_area_ou_id
|
||||||
|
ON sso_users(status, deleted_at, area, ou_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_expr
|
||||||
|
ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_sso_users_active_tenant_dep_ou_name
|
||||||
|
ON sso_users(status, deleted_at, tenant_name, (COALESCE(dep_name, dep_short_name, '')), ou_name);
|
||||||
|
|
||||||
COMMENT ON TABLE sso_users IS '用户主表:认证身份、组织信息、地区隔离基础字段统一沉淀在这里';
|
COMMENT ON TABLE sso_users IS '用户主表:认证身份、组织信息、地区隔离基础字段统一沉淀在这里';
|
||||||
COMMENT ON COLUMN sso_users.sub IS '统一身份唯一标识,OAuth / SSO 主键';
|
COMMENT ON COLUMN sso_users.sub IS '统一身份唯一标识,OAuth / SSO 主键';
|
||||||
|
|||||||
Reference in New Issue
Block a user