role.py 5.7 KB

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