"""自定义业务角色模型:角色、权限矩阵。 内置 ADMIN/USER 之外的第三层授权体系: - Role 本体(含全局唯一默认角色标记); - RolePermission 权限矩阵(密级 × 部门范围 × 7 操作,每角色固定 10 行)。 """ from __future__ import annotations from sqlalchemy import ( BigInteger, Boolean, CheckConstraint, Computed, ForeignKey, Index, Integer, String, UniqueConstraint, text, ) from sqlalchemy.orm import Mapped, mapped_column from dms.database.base import BusinessTableMixin from dms.extensions import db class Role(BusinessTableMixin, db.Model): __tablename__ = "sys_role" __table_args__ = ( CheckConstraint( "status IN ('ENABLED', 'DISABLED')", name="role_status", ), # 活跃行角色名唯一:软删后可重建同名角色 UniqueConstraint( "role_name", "active_marker", name="uq_sys_role_role_name_active", ), # 全局至多一个默认角色:default_marker 虚拟生成列(非默认/已软删 → NULL) UniqueConstraint( "default_marker", name="uq_sys_role_default_active", ), Index( "ix_sys_role_status_active", "status", "is_deleted", ), ) role_name: Mapped[str] = mapped_column(String(64), nullable=False) sort_no: Mapped[int] = mapped_column( Integer, nullable=False, server_default=text("0"), ) status: Mapped[str] = mapped_column( String(20), nullable=False, server_default=text("'ENABLED'"), ) remark: Mapped[str | None] = mapped_column(String(255), nullable=True) is_default: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) active_marker: Mapped[int | None] = mapped_column( Integer, Computed("IF(is_deleted = 0, 1, NULL)", persisted=False), nullable=True, ) default_marker: Mapped[int | None] = mapped_column( Integer, Computed( "IF(is_default = 1 AND is_deleted = 0, 1, NULL)", persisted=False, ), nullable=True, ) class RolePermission(BusinessTableMixin, db.Model): """角色权限矩阵行:密级 × 部门范围 → 7 个操作布尔位。 每角色固定 10 行(5 密级 × 2 部门范围),整存整取; 与文档 ACL 为 OR 叠加,矩阵是主权限源。 """ __tablename__ = "sys_role_permission" __table_args__ = ( CheckConstraint( "security_level IN " "('PUBLIC', 'INTERNAL', 'SECRET', 'CONFIDENTIAL', 'TOP_SECRET')", name="role_permission_security_level", ), CheckConstraint( "dept_scope IN ('OWN', 'OTHER')", name="dept_scope_v2", ), UniqueConstraint( "role_id", "security_level", "dept_scope", "active_marker", name="uq_sys_role_permission_active", ), Index( "ix_sys_role_permission_role_active", "role_id", "is_deleted", ), ) role_id: Mapped[int] = mapped_column( BigInteger, ForeignKey("sys_role.id", ondelete="RESTRICT"), nullable=False, ) security_level: Mapped[str] = mapped_column(String(32), nullable=False) dept_scope: Mapped[str] = mapped_column(String(32), nullable=False) can_view: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) can_download: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) can_edit: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) can_delete: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) can_config_permission: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) can_bind_attachment: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) can_unbind_attachment: Mapped[bool] = mapped_column( Boolean, nullable=False, server_default=text("0"), ) active_marker: Mapped[int | None] = mapped_column( Integer, Computed("IF(is_deleted = 0, 1, NULL)", persisted=False), nullable=True, )