base_model.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. from datetime import datetime
  2. from typing import TYPE_CHECKING, Optional
  3. from sqlalchemy import BigInteger, DateTime, ForeignKey, Integer, String, Text
  4. from sqlalchemy.ext.asyncio import AsyncAttrs
  5. from sqlalchemy.orm import (
  6. DeclarativeBase,
  7. Mapped,
  8. declared_attr,
  9. mapped_column,
  10. relationship,
  11. )
  12. if TYPE_CHECKING:
  13. from app.api.v1.module_system.user.model import UserModel
  14. from app.common.enums import PermissionFilterStrategy
  15. from app.utils.common_util import uuid4_str
  16. class MappedBase(AsyncAttrs, DeclarativeBase):
  17. """
  18. 声明式基类
  19. `AsyncAttrs <https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html#sqlalchemy.ext.asyncio.AsyncAttrs>`__
  20. `DeclarativeBase <https://docs.sqlalchemy.org/en/20/orm/declarative_config.html>`__
  21. `mapped_column() <https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.mapped_column>`__
  22. 兼容 SQLite、MySQL 和 PostgreSQL
  23. """
  24. __abstract__: bool = True
  25. # 权限过滤策略,子类可以覆盖
  26. __permission_strategy__: PermissionFilterStrategy = PermissionFilterStrategy.DATA_SCOPE
  27. class ModelMixin(MappedBase):
  28. """
  29. 模型混入类 - 提供通用字段和功能
  30. 基础模型混合类 Mixin: 一种面向对象编程概念, 使结构变得更加清晰
  31. 数据隔离设计原则:
  32. ==================
  33. 数据权限 (created_id/updated_id):
  34. - 配合角色的data_scope字段实现精细化权限控制
  35. - 1:仅本人
  36. - 2:本部门
  37. - 3:本部门及以下
  38. - 4:全部数据
  39. - 5:自定义
  40. SQLAlchemy加载策略说明:
  41. - select(默认): 延迟加载,访问时单独查询
  42. - joined: 使用LEFT JOIN预加载
  43. - selectin: 使用IN查询批量预加载(推荐用于一对多)
  44. - subquery: 使用子查询预加载
  45. - raise/raise_on_sql: 禁止加载
  46. - noload: 不加载,返回None
  47. - immediate: 立即加载
  48. - write_only: 只写不读
  49. - dynamic: 返回查询对象,支持进一步过滤
  50. """
  51. __abstract__: bool = True
  52. # 基础字段
  53. id: Mapped[int] = mapped_column(
  54. Integer,
  55. primary_key=True,
  56. autoincrement=True,
  57. comment="主键ID",
  58. index=True,
  59. )
  60. uuid: Mapped[str] = mapped_column(
  61. String(64),
  62. default=uuid4_str,
  63. nullable=False,
  64. unique=True,
  65. comment="UUID全局唯一标识",
  66. index=True,
  67. )
  68. status: Mapped[str] = mapped_column(
  69. String(10),
  70. default="0",
  71. nullable=False,
  72. comment="状态(0:正常 1:禁用)",
  73. index=True,
  74. )
  75. description: Mapped[str | None] = mapped_column(
  76. Text, default=None, nullable=True, comment="备注/描述"
  77. )
  78. created_time: Mapped[datetime] = mapped_column(
  79. DateTime,
  80. default=datetime.now,
  81. nullable=False,
  82. comment="创建时间",
  83. index=True,
  84. )
  85. updated_time: Mapped[datetime] = mapped_column(
  86. DateTime,
  87. default=datetime.now,
  88. onupdate=datetime.now,
  89. nullable=False,
  90. comment="更新时间",
  91. index=True,
  92. )
  93. class TenantMixin(MappedBase):
  94. """
  95. 租户隔离字段 Mixin
  96. 业务表通过 tenant_id 关联 sys_tenant,实现行级数据隔离。
  97. 平台超级管理员(is_superuser 且 tenant_id=1)在数据层不按租户过滤。
  98. """
  99. __abstract__ = True
  100. tenant_id: Mapped[int] = mapped_column(
  101. Integer,
  102. ForeignKey("sys_tenant.id", ondelete="RESTRICT", onupdate="CASCADE"),
  103. nullable=False,
  104. default=1,
  105. index=True,
  106. comment="租户ID",
  107. )
  108. class EnterpriseMixin(MappedBase):
  109. """
  110. 企业隔离字段 Mixin
  111. 业务表通过 enterprise_id 关联 pay_enterprise,实现企业级数据隔离。
  112. 用于多租户场景下,同一租户下的不同企业之间的数据隔离。
  113. """
  114. __abstract__ = True
  115. enterprise_id: Mapped[str | None] = mapped_column(
  116. String(64),
  117. index=True,
  118. comment="企业ID",
  119. )
  120. class PaymentModelMixin(MappedBase):
  121. """
  122. 支付模型混入类 - 针对高数据量场景
  123. 特点:
  124. - 使用 BigInteger 作为主键,支持更大数据量
  125. - 不包含 uuid 字段(支付表已有业务主键)
  126. - 保留基础审计字段
  127. """
  128. __abstract__ = True
  129. id: Mapped[int] = mapped_column(
  130. BigInteger,
  131. primary_key=True,
  132. autoincrement=True,
  133. comment="主键ID",
  134. index=True,
  135. )
  136. status: Mapped[str] = mapped_column(
  137. String(10),
  138. default="0",
  139. nullable=False,
  140. comment="状态(0:正常 1:禁用)",
  141. index=True,
  142. )
  143. description: Mapped[str | None] = mapped_column(
  144. Text, default=None, nullable=True, comment="备注/描述"
  145. )
  146. created_time: Mapped[datetime] = mapped_column(
  147. DateTime,
  148. default=datetime.now,
  149. nullable=False,
  150. comment="创建时间",
  151. index=True,
  152. )
  153. updated_time: Mapped[datetime] = mapped_column(
  154. DateTime,
  155. default=datetime.now,
  156. onupdate=datetime.now,
  157. nullable=False,
  158. comment="更新时间",
  159. index=True,
  160. )
  161. class UserMixin(MappedBase):
  162. """
  163. 用户审计字段 Mixin
  164. 用于记录数据的创建者和更新者
  165. 用于实现数据权限中的"仅本人数据权限"
  166. """
  167. __abstract__: bool = True
  168. created_id: Mapped[int | None] = mapped_column(
  169. Integer,
  170. ForeignKey("sys_user.id", ondelete="SET NULL", onupdate="CASCADE"),
  171. default=None,
  172. nullable=True,
  173. index=True,
  174. comment="创建人ID",
  175. )
  176. updated_id: Mapped[int | None] = mapped_column(
  177. Integer,
  178. ForeignKey("sys_user.id", ondelete="SET NULL", onupdate="CASCADE"),
  179. default=None,
  180. nullable=True,
  181. index=True,
  182. comment="更新人ID",
  183. )
  184. @declared_attr
  185. def created_by(self) -> Mapped[Optional["UserModel"]]:
  186. """
  187. 创建人关联关系(延迟加载,避免循环依赖)。
  188. 返回:
  189. - Mapped[Optional[UserModel]]: 创建人 ORM 关系。
  190. """
  191. return relationship(
  192. "UserModel",
  193. lazy="selectin",
  194. foreign_keys=lambda: self.created_id, # pyright: ignore[reportArgumentType]
  195. uselist=False,
  196. )
  197. @declared_attr
  198. def updated_by(self) -> Mapped[Optional["UserModel"]]:
  199. """
  200. 更新人关联关系(延迟加载,避免循环依赖)。
  201. 返回:
  202. - Mapped[Optional[UserModel]]: 更新人 ORM 关系。
  203. """
  204. return relationship(
  205. "UserModel",
  206. lazy="selectin",
  207. foreign_keys=lambda: self.updated_id, # pyright: ignore[reportArgumentType]
  208. uselist=False,
  209. )