role.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. """自定义业务角色模型:角色、权限矩阵。
  2. 内置 ADMIN/USER 之外的第三层授权体系:
  3. - Role 本体(含全局唯一默认角色标记);
  4. - RolePermission 权限矩阵(密级 × 部门范围 × 7 操作,每角色固定 10 行)。
  5. """
  6. from __future__ import annotations
  7. from sqlalchemy import (
  8. BigInteger,
  9. Boolean,
  10. CheckConstraint,
  11. Computed,
  12. ForeignKey,
  13. Index,
  14. Integer,
  15. String,
  16. UniqueConstraint,
  17. text,
  18. )
  19. from sqlalchemy.orm import Mapped, mapped_column
  20. from dms.database.base import BusinessTableMixin
  21. from dms.extensions import db
  22. class Role(BusinessTableMixin, db.Model):
  23. __tablename__ = "sys_role"
  24. __table_args__ = (
  25. CheckConstraint(
  26. "status IN ('ENABLED', 'DISABLED')",
  27. name="role_status",
  28. ),
  29. # 活跃行角色名唯一:软删后可重建同名角色
  30. UniqueConstraint(
  31. "role_name",
  32. "active_marker",
  33. name="uq_sys_role_role_name_active",
  34. ),
  35. # 全局至多一个默认角色:default_marker 虚拟生成列(非默认/已软删 → NULL)
  36. UniqueConstraint(
  37. "default_marker",
  38. name="uq_sys_role_default_active",
  39. ),
  40. Index(
  41. "ix_sys_role_status_active",
  42. "status",
  43. "is_deleted",
  44. ),
  45. )
  46. role_name: Mapped[str] = mapped_column(String(64), nullable=False)
  47. sort_no: Mapped[int] = mapped_column(
  48. Integer,
  49. nullable=False,
  50. server_default=text("0"),
  51. )
  52. status: Mapped[str] = mapped_column(
  53. String(20),
  54. nullable=False,
  55. server_default=text("'ENABLED'"),
  56. )
  57. remark: Mapped[str | None] = mapped_column(String(255), nullable=True)
  58. is_default: Mapped[bool] = mapped_column(
  59. Boolean,
  60. nullable=False,
  61. server_default=text("0"),
  62. )
  63. # 管理权限位(角色级):分类/机构/用户编辑,判定按用户生效角色读取
  64. manage_category_edit: Mapped[bool] = mapped_column(
  65. Boolean,
  66. nullable=False,
  67. server_default=text("0"),
  68. )
  69. manage_org_edit: Mapped[bool] = mapped_column(
  70. Boolean,
  71. nullable=False,
  72. server_default=text("0"),
  73. )
  74. manage_user_edit: Mapped[bool] = mapped_column(
  75. Boolean,
  76. nullable=False,
  77. server_default=text("0"),
  78. )
  79. # 管理范围锚点部门(NULL=不受范围约束即全局)
  80. managed_org_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
  81. active_marker: Mapped[int | None] = mapped_column(
  82. Integer,
  83. Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
  84. nullable=True,
  85. )
  86. default_marker: Mapped[int | None] = mapped_column(
  87. Integer,
  88. Computed(
  89. "IF(is_default = 1 AND is_deleted = 0, 1, NULL)",
  90. persisted=False,
  91. ),
  92. nullable=True,
  93. )
  94. class RolePermission(BusinessTableMixin, db.Model):
  95. """角色权限矩阵行:密级 × 部门范围 → 7 个操作布尔位。
  96. 每角色固定 10 行(5 密级 × 2 部门范围),整存整取;
  97. 与文档 ACL 为 OR 叠加,矩阵是主权限源。
  98. """
  99. __tablename__ = "sys_role_permission"
  100. __table_args__ = (
  101. CheckConstraint(
  102. "security_level IN "
  103. "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')",
  104. name="role_permission_security_level",
  105. ),
  106. CheckConstraint(
  107. "dept_scope IN ('OWN', 'OTHER')",
  108. name="dept_scope_v2",
  109. ),
  110. UniqueConstraint(
  111. "role_id",
  112. "security_level",
  113. "dept_scope",
  114. "active_marker",
  115. name="uq_sys_role_permission_active",
  116. ),
  117. Index(
  118. "ix_sys_role_permission_role_active",
  119. "role_id",
  120. "is_deleted",
  121. ),
  122. )
  123. role_id: Mapped[int] = mapped_column(
  124. BigInteger,
  125. ForeignKey("sys_role.id", ondelete="RESTRICT"),
  126. nullable=False,
  127. )
  128. security_level: Mapped[str] = mapped_column(String(32), nullable=False)
  129. dept_scope: Mapped[str] = mapped_column(String(32), nullable=False)
  130. can_view: Mapped[bool] = mapped_column(
  131. Boolean,
  132. nullable=False,
  133. server_default=text("0"),
  134. )
  135. can_download: Mapped[bool] = mapped_column(
  136. Boolean,
  137. nullable=False,
  138. server_default=text("0"),
  139. )
  140. can_edit: Mapped[bool] = mapped_column(
  141. Boolean,
  142. nullable=False,
  143. server_default=text("0"),
  144. )
  145. can_delete: Mapped[bool] = mapped_column(
  146. Boolean,
  147. nullable=False,
  148. server_default=text("0"),
  149. )
  150. can_config_permission: Mapped[bool] = mapped_column(
  151. Boolean,
  152. nullable=False,
  153. server_default=text("0"),
  154. )
  155. can_bind_attachment: Mapped[bool] = mapped_column(
  156. Boolean,
  157. nullable=False,
  158. server_default=text("0"),
  159. )
  160. can_unbind_attachment: Mapped[bool] = mapped_column(
  161. Boolean,
  162. nullable=False,
  163. server_default=text("0"),
  164. )
  165. active_marker: Mapped[int | None] = mapped_column(
  166. Integer,
  167. Computed("IF(is_deleted = 0, 1, NULL)", persisted=False),
  168. nullable=True,
  169. )