role.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. """角色 CRUD(sys_role / sys_role_menu / sys_role_data_scope_detail)"""
  2. from __future__ import annotations
  3. import math
  4. from datetime import datetime
  5. from typing import Any, Dict, List, Optional, Tuple
  6. from sqlalchemy import and_, func, or_
  7. from sqlalchemy.orm import Session
  8. from app.core.delete_guard import assert_row_deletable_by_creator
  9. from app.core.utils import generate_uuid
  10. from app.models.models import SysRole, SysRoleMenu, SysRoleDataScopeDetail
  11. # ---------------------------------------------------------------------------
  12. # helpers
  13. # ---------------------------------------------------------------------------
  14. def _row_to_dict(r: SysRole) -> Dict[str, Any]:
  15. return {
  16. "id": r.id,
  17. "roleName": r.role_name,
  18. "dataScope": r.data_scope or "1",
  19. "status": r.status or "1",
  20. "tenantNo": r.tenant_no or "",
  21. "orderNum": r.order_num if r.order_num is not None else 0,
  22. "remark": r.remark or "",
  23. "createBy": r.create_by or "",
  24. "createTime": r.create_time.strftime("%Y-%m-%d %H:%M:%S") if r.create_time else "",
  25. "updateTime": r.update_time.strftime("%Y-%m-%d %H:%M:%S") if r.update_time else "",
  26. }
  27. def total_pages(total: int, page_size: int) -> int:
  28. if total <= 0:
  29. return 0
  30. return int(math.ceil(total / float(page_size)))
  31. # ---------------------------------------------------------------------------
  32. # list / get
  33. # ---------------------------------------------------------------------------
  34. def list_roles(
  35. db: Session,
  36. *,
  37. page: int = 1,
  38. page_size: int = 10,
  39. keyword: Optional[str] = None,
  40. tenant_no: Optional[str] = None,
  41. user_info=None,
  42. ) -> Tuple[List[Dict[str, Any]], int]:
  43. def _filtered():
  44. q0 = db.query(SysRole).filter(SysRole.del_flag == 0)
  45. if tenant_no:
  46. q0 = q0.filter(SysRole.tenant_no == tenant_no)
  47. kw = (keyword or "").strip()
  48. if kw:
  49. like = f"%{kw}%"
  50. q0 = q0.filter(SysRole.role_name.like(like))
  51. if user_info:
  52. from app.core.data_scope import apply_data_scope
  53. q0 = apply_data_scope(q0, SysRole, user_info, db, tenant_no)
  54. return q0
  55. total = _filtered().order_by(None).with_entities(func.count(SysRole.id)).scalar() or 0
  56. page = max(1, page)
  57. page_size = min(max(1, page_size), 100)
  58. rows = (
  59. _filtered()
  60. .order_by(SysRole.create_time.desc())
  61. .offset((page - 1) * page_size)
  62. .limit(page_size)
  63. .all()
  64. )
  65. return [_row_to_dict(x) for x in rows], int(total)
  66. def get_role(db: Session, role_id: str) -> Optional[SysRole]:
  67. return (
  68. db.query(SysRole)
  69. .filter(and_(SysRole.id == role_id, SysRole.del_flag == 0))
  70. .first()
  71. )
  72. def role_name_exists(db: Session, name: str, tenant_no: str, exclude_id: Optional[str] = None) -> bool:
  73. q = db.query(SysRole).filter(
  74. and_(SysRole.role_name == name.strip(), SysRole.tenant_no == tenant_no, SysRole.del_flag == 0)
  75. )
  76. if exclude_id:
  77. q = q.filter(SysRole.id != exclude_id)
  78. return q.first() is not None
  79. # ---------------------------------------------------------------------------
  80. # create / update / delete
  81. # ---------------------------------------------------------------------------
  82. def create_role(
  83. db: Session,
  84. *,
  85. role_name: str,
  86. data_scope: str = "4",
  87. status: str = "1",
  88. tenant_no: str = "shanxia2.0",
  89. order_num: Optional[int] = None,
  90. remark: Optional[str] = None,
  91. user_id: Optional[str] = None,
  92. ) -> SysRole:
  93. rn = (role_name or "").strip()
  94. if not rn:
  95. raise ValueError("角色名称不能为空")
  96. if role_name_exists(db, rn, tenant_no):
  97. raise ValueError("角色名称已存在")
  98. obj = SysRole(
  99. id=generate_uuid(),
  100. role_name=rn,
  101. data_scope=data_scope,
  102. status=status,
  103. tenant_no=tenant_no,
  104. order_num=order_num if order_num is not None else 0,
  105. remark=(remark or "").strip() or None,
  106. create_by=user_id,
  107. create_dept_id="",
  108. create_time=datetime.now(),
  109. update_by=user_id,
  110. update_time=datetime.now(),
  111. del_flag=0,
  112. )
  113. db.add(obj)
  114. db.commit()
  115. db.refresh(obj)
  116. return obj
  117. def update_role(
  118. db: Session,
  119. role_id: str,
  120. *,
  121. role_name: Optional[str] = None,
  122. data_scope: Optional[str] = None,
  123. status: Optional[str] = None,
  124. order_num: Optional[int] = None,
  125. remark: Optional[str] = None,
  126. user_id: Optional[str] = None,
  127. ) -> SysRole:
  128. obj = get_role(db, role_id)
  129. if not obj:
  130. raise ValueError("角色不存在")
  131. if role_name is not None:
  132. rn = role_name.strip()
  133. if not rn:
  134. raise ValueError("角色名称不能为空")
  135. if rn != obj.role_name and role_name_exists(db, rn, obj.tenant_no, exclude_id=role_id):
  136. raise ValueError("角色名称已存在")
  137. obj.role_name = rn
  138. if data_scope is not None:
  139. obj.data_scope = data_scope
  140. if status is not None:
  141. obj.status = status
  142. if order_num is not None:
  143. obj.order_num = order_num
  144. if remark is not None:
  145. obj.remark = (remark or "").strip() or None
  146. obj.update_by = user_id
  147. obj.update_time = datetime.now()
  148. db.add(obj)
  149. db.commit()
  150. db.refresh(obj)
  151. return obj
  152. def soft_delete_role(db: Session, role_id: str, *, user_info: dict = None) -> None:
  153. obj = get_role(db, role_id)
  154. if not obj:
  155. raise ValueError("角色不存在")
  156. assert_row_deletable_by_creator(obj, user_info)
  157. obj.del_flag = 1
  158. obj.update_time = datetime.now()
  159. db.add(obj)
  160. # cascade soft-delete role_menu and role_data_scope_detail
  161. db.query(SysRoleMenu).filter(
  162. and_(SysRoleMenu.role_id == role_id, SysRoleMenu.del_flag == 0)
  163. ).update({"del_flag": 1, "update_time": datetime.now()})
  164. db.query(SysRoleDataScopeDetail).filter(
  165. and_(SysRoleDataScopeDetail.role_id == role_id, SysRoleDataScopeDetail.del_flag == 0)
  166. ).update({"del_flag": 1, "update_time": datetime.now()})
  167. db.commit()
  168. # ---------------------------------------------------------------------------
  169. # role-menu
  170. # ---------------------------------------------------------------------------
  171. def get_role_menu_ids(db: Session, role_id: str) -> List[str]:
  172. rows = (
  173. db.query(SysRoleMenu.menu_id)
  174. .filter(and_(SysRoleMenu.role_id == role_id, SysRoleMenu.del_flag == 0))
  175. .all()
  176. )
  177. return [r.menu_id for r in rows]
  178. def update_role_menus(
  179. db: Session,
  180. role_id: str,
  181. menu_ids: List[str],
  182. tenant_no: str,
  183. user_id: Optional[str] = None,
  184. ) -> None:
  185. # uk_role_menu 为 (role_id, menu_id, del_flag):先全量软删再插入会在再次保存时
  186. # 出现两条 (role_id, menu_id, del_flag=1),触发 1062。关联表采用整角色物理清理后重建。
  187. unique_ids = list(dict.fromkeys(menu_ids or []))
  188. db.query(SysRoleMenu).filter(SysRoleMenu.role_id == role_id).delete(synchronize_session=False)
  189. now = datetime.now()
  190. for mid in unique_ids:
  191. obj = SysRoleMenu(
  192. id=generate_uuid(),
  193. role_id=role_id,
  194. menu_id=mid,
  195. tenant_no=tenant_no,
  196. order_num=0,
  197. create_by=user_id,
  198. create_dept_id="",
  199. create_time=now,
  200. update_by=user_id,
  201. update_time=now,
  202. del_flag=0,
  203. )
  204. db.add(obj)
  205. db.commit()
  206. # ---------------------------------------------------------------------------
  207. # role data-scope
  208. # ---------------------------------------------------------------------------
  209. def get_role_data_scope(db: Session, role_id: str) -> List[Dict[str, str]]:
  210. rows = (
  211. db.query(SysRoleDataScopeDetail)
  212. .filter(and_(SysRoleDataScopeDetail.role_id == role_id, SysRoleDataScopeDetail.del_flag == 0))
  213. .all()
  214. )
  215. return [{"scopeType": r.scope_type, "scopeValue": r.scope_value} for r in rows]
  216. def update_role_data_scope(
  217. db: Session,
  218. role_id: str,
  219. data_scope: str,
  220. scope_details: Optional[List[Dict[str, str]]] = None,
  221. tenant_no: str = "shanxia2.0",
  222. user_id: Optional[str] = None,
  223. ) -> None:
  224. obj = get_role(db, role_id)
  225. if not obj:
  226. raise ValueError("角色不存在")
  227. obj.data_scope = data_scope
  228. obj.update_by = user_id
  229. obj.update_time = datetime.now()
  230. db.add(obj)
  231. # soft-delete old scope details
  232. db.query(SysRoleDataScopeDetail).filter(
  233. and_(SysRoleDataScopeDetail.role_id == role_id, SysRoleDataScopeDetail.del_flag == 0)
  234. ).update({"del_flag": 1, "update_time": datetime.now()})
  235. # insert new scope details (only when custom data_scope == '5')
  236. if data_scope == "5" and scope_details:
  237. now = datetime.now()
  238. for detail in scope_details:
  239. sd = SysRoleDataScopeDetail(
  240. id=generate_uuid(),
  241. role_id=role_id,
  242. scope_type=detail.get("scopeType", "dept"),
  243. scope_value=detail.get("scopeValue", ""),
  244. tenant_no=tenant_no,
  245. order_num=0,
  246. create_by=user_id,
  247. create_dept_id="",
  248. create_time=now,
  249. update_by=user_id,
  250. update_time=now,
  251. del_flag=0,
  252. )
  253. db.add(sd)
  254. db.commit()