Optimize RBAC org tree loading
This commit is contained in:
@@ -58,6 +58,7 @@ coverage.xml
|
||||
# Playwright MCP cache
|
||||
.playwright-mcp/
|
||||
.codex-run/
|
||||
.chromadb_rag/
|
||||
|
||||
# Rules cache
|
||||
rules/**/__pycache__/
|
||||
|
||||
@@ -33,6 +33,7 @@ celery_app = Celery(
|
||||
|
||||
celery_app.conf.update(
|
||||
task_default_queue=LEAUDIT_WORKER_QUEUE_NORMAL,
|
||||
imports=("fastapi_modules.fastapi_leaudit.leaudit_bridge.tasks",),
|
||||
task_queues=(
|
||||
Queue(LEAUDIT_WORKER_QUEUE_URGENT),
|
||||
Queue(LEAUDIT_WORKER_QUEUE_NORMAL),
|
||||
@@ -58,3 +59,6 @@ celery_app.autodiscover_tasks(
|
||||
],
|
||||
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
|
||||
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
@@ -43,6 +45,11 @@ from fastapi_modules.fastapi_leaudit.services.rbacAdminService import IRbacAdmin
|
||||
class RbacAdminServiceImpl(IRbacAdminService):
|
||||
"""RBAC 管理服务实现。"""
|
||||
|
||||
_logger = logging.getLogger("APP")
|
||||
_UNGROUPED_TENANT_LABEL = "未分组租户"
|
||||
_UNGROUPED_DEPARTMENT_LABEL = "未分组部门"
|
||||
_UNGROUPED_ORGANIZATION_LABEL = "未分组组织"
|
||||
|
||||
_MANAGEABLE_ROUTE_BLUEPRINTS: list[dict[str, Any]] = [
|
||||
{
|
||||
"route_path": "/home",
|
||||
@@ -421,172 +428,277 @@ class RbacAdminServiceImpl(IRbacAdminService):
|
||||
|
||||
async def GetOrganizationTree(self, CurrentUserId: int, IncludeUsers: bool, RootUuid: str | None) -> OrganizationTreeVO:
|
||||
"""查询组织树。"""
|
||||
started_at = time.perf_counter()
|
||||
await self._assertManagePermission(CurrentUserId)
|
||||
await self._assertPermission(CurrentUserId, "rbac:users:read")
|
||||
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"]
|
||||
params: dict[str, object] = {}
|
||||
if not currentUser["is_global"]:
|
||||
user_filters.append("COALESCE(area, '') = :user_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)
|
||||
|
||||
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,
|
||||
total_users = int(
|
||||
(
|
||||
await Session.execute(
|
||||
text(
|
||||
f"""
|
||||
SELECT COUNT(1)
|
||||
FROM sso_users
|
||||
WHERE {where_clause}
|
||||
"""
|
||||
),
|
||||
params,
|
||||
)
|
||||
)
|
||||
|
||||
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 [],
|
||||
).scalar()
|
||||
or 0
|
||||
)
|
||||
|
||||
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)
|
||||
query_rows_count = 0
|
||||
organizations: list[OrganizationNodeVO]
|
||||
|
||||
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,
|
||||
total_organizations=total_organizations,
|
||||
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:
|
||||
"""查询指定角色下的用户列表。"""
|
||||
|
||||
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_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_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 COLUMN sso_users.sub IS '统一身份唯一标识,OAuth / SSO 主键';
|
||||
|
||||
Reference in New Issue
Block a user