web_auth_roles.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. """Web 管理端登录:基于 V0.3 sys_user / sys_user_role / sys_role 的角色与菜单权限解析"""
  2. from __future__ import annotations
  3. from typing import Optional, Set
  4. from sqlalchemy import and_
  5. from sqlalchemy.orm import Session
  6. from app.core.config import settings
  7. from app.models.models import SysMenu, SysRole, SysRoleMenu, SysUserRole
  8. def _map_role_name_to_web(role_name: str) -> str:
  9. """将 sys_role.role_name 映射为 web-admin 使用的 role 字符串。"""
  10. n = (role_name or "").strip()
  11. if not n:
  12. return "admin"
  13. if "超级" in n or n in ("系统管理员", "超级管理员"):
  14. return "super_admin"
  15. if "管理" in n or "运营" in n:
  16. return "admin"
  17. return "admin"
  18. def _role_rows_for_user(db: Session, user_id: str, tenant_no: str):
  19. return (
  20. db.query(SysRole.role_name)
  21. .join(SysUserRole, SysUserRole.role_id == SysRole.id)
  22. .filter(
  23. SysUserRole.user_id == user_id,
  24. SysUserRole.del_flag == 0,
  25. SysUserRole.tenant_no == tenant_no,
  26. SysRole.del_flag == 0,
  27. SysRole.status == "1",
  28. SysRole.tenant_no == tenant_no,
  29. )
  30. .order_by(SysRole.create_time.desc())
  31. .all()
  32. )
  33. def user_has_bound_roles(db: Session, user_id: str, tenant_no: Optional[str] = None) -> bool:
  34. """用户是否在 sys_user_role 中绑定了至少一个有效角色。"""
  35. if not user_id:
  36. return False
  37. tenant = (tenant_no or "").strip() or settings.DEFAULT_TENANT_NO
  38. rows = _role_rows_for_user(db, user_id, tenant)
  39. if not rows and tenant != settings.DEFAULT_TENANT_NO:
  40. rows = _role_rows_for_user(db, user_id, settings.DEFAULT_TENANT_NO)
  41. return bool(rows)
  42. def resolve_web_role_for_user(db: Session, user_id: str, tenant_no: Optional[str] = None) -> str:
  43. """根据用户关联角色解析 Web 端 role(admin / super_admin)。"""
  44. if not user_id:
  45. return settings.WEB_AUTH_DEFAULT_ROLE
  46. tenant = (tenant_no or "").strip() or settings.DEFAULT_TENANT_NO
  47. rows = _role_rows_for_user(db, user_id, tenant)
  48. if not rows and tenant != settings.DEFAULT_TENANT_NO:
  49. rows = _role_rows_for_user(db, user_id, settings.DEFAULT_TENANT_NO)
  50. names = [str(r[0]).strip() for r in rows if r and r[0]]
  51. if not names:
  52. # 未绑定角色:视为普通 admin,无菜单权限(不可默认 super_admin 全菜单)
  53. return "admin"
  54. mapped = [_map_role_name_to_web(n) for n in names]
  55. if "super_admin" in mapped:
  56. return "super_admin"
  57. return "admin"
  58. def _granted_menu_ids_for_user(db: Session, user_id: str, tenant: str) -> Set[str]:
  59. rows = (
  60. db.query(SysMenu.id)
  61. .join(
  62. SysRoleMenu,
  63. and_(
  64. SysRoleMenu.menu_id == SysMenu.id,
  65. SysRoleMenu.del_flag == 0,
  66. SysRoleMenu.tenant_no == tenant,
  67. ),
  68. )
  69. .join(
  70. SysUserRole,
  71. and_(
  72. SysUserRole.role_id == SysRoleMenu.role_id,
  73. SysUserRole.user_id == user_id,
  74. SysUserRole.del_flag == 0,
  75. SysUserRole.tenant_no == tenant,
  76. ),
  77. )
  78. .filter(
  79. SysMenu.del_flag == 0,
  80. SysMenu.tenant_no == tenant,
  81. SysMenu.status == "1",
  82. SysMenu.visible == "1",
  83. )
  84. .distinct()
  85. .all()
  86. )
  87. return {str(r[0]).strip() for r in rows if r and r[0]}
  88. def _expand_with_ancestor_menu_ids(db: Session, menu_ids: Set[str], tenant: str) -> Set[str]:
  89. """角色仅勾选按钮(F)时,补齐祖先 C/M 菜单 id,便于前端匹配 :list 路由。"""
  90. expanded = set(menu_ids)
  91. frontier = list(menu_ids)
  92. while frontier:
  93. rows = (
  94. db.query(SysMenu.parent_id)
  95. .filter(
  96. SysMenu.id.in_(frontier),
  97. SysMenu.del_flag == 0,
  98. SysMenu.tenant_no == tenant,
  99. )
  100. .all()
  101. )
  102. next_frontier: list[str] = []
  103. for (parent_id,) in rows:
  104. pid = str(parent_id or "").strip()
  105. if pid and pid not in expanded:
  106. expanded.add(pid)
  107. next_frontier.append(pid)
  108. frontier = next_frontier
  109. return expanded
  110. def iter_user_menu_perm_codes(
  111. db: Session, user_id: str, tenant_no: Optional[str] = None
  112. ) -> Set[str]:
  113. """用户通过角色关联得到的菜单权限标识(sys_menu.perms,与前端 meta.menuCode 对齐)。"""
  114. if not user_id:
  115. return set()
  116. if not user_has_bound_roles(db, user_id, tenant_no):
  117. return set()
  118. tenant = (tenant_no or "").strip() or settings.DEFAULT_TENANT_NO
  119. try:
  120. menu_ids = _granted_menu_ids_for_user(db, user_id, tenant)
  121. if not menu_ids and tenant != settings.DEFAULT_TENANT_NO:
  122. menu_ids = _granted_menu_ids_for_user(db, user_id, settings.DEFAULT_TENANT_NO)
  123. if not menu_ids:
  124. return set()
  125. all_menu_ids = _expand_with_ancestor_menu_ids(db, menu_ids, tenant)
  126. rows = (
  127. db.query(SysMenu.perms)
  128. .filter(
  129. SysMenu.id.in_(all_menu_ids),
  130. SysMenu.del_flag == 0,
  131. SysMenu.tenant_no == tenant,
  132. SysMenu.status == "1",
  133. )
  134. .all()
  135. )
  136. out: Set[str] = set()
  137. for (perms,) in rows:
  138. p = (perms or "").strip()
  139. if p:
  140. out.add(p)
  141. return out
  142. except Exception:
  143. return set()