# 业务表设计指南 ## 📋 目录 1. [Mixin类使用说明](#mixin类使用说明) 2. [业务表分类与设计](#业务表分类与设计) 3. [代码示例](#代码示例) 4. [常见场景](#常见场景) --- ## Mixin类使用说明 ### 可用的Mixin类 系统提供了以下Mixin类用于快速构建业务表: | Mixin类 | 提供字段 | 用途 | 是否必须 | |---------|---------|------|----------| | **ModelMixin** | id, uuid, status, description, created_time, updated_time | 基础模型字段 | ✅ 必须 | | **UserMixin** | created_id, updated_id | 审计字段,记录创建人和更新人 | ✅ 推荐 | | **TenantMixin** | tenant_id | 租户隔离字段 | ✅ 几乎必须 | | **CustomerMixin** | customer_id | 客户隔离字段 | ❓ 按需使用 | ### Mixin继承顺序 ```python # 正确的继承顺序 (从左到右) class MyModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): pass # 说明: # 1. ModelMixin 必须在最前面 # 2. 其他Mixin顺序不影响功能,但建议按上述顺序 ``` --- ## 业务表分类与设计 ### 1. 系统级表 (无租户隔离) **特征**: 不需要租户隔离,全局共享 **继承**: `MappedBase` (不继承ModelMixin) **示例**: - 租户表本身 (`TenantModel`) - 系统配置表 - 全局字典表 ```python class GlobalConfigModel(MappedBase): """全局配置表 - 不需要租户隔离""" __tablename__ = 'sys_global_config' id: Mapped[int] = mapped_column(Integer, primary_key=True) config_key: Mapped[str] = mapped_column(String(100)) config_value: Mapped[str] = mapped_column(String(500)) ``` ### 2. 租户级表 (仅租户隔离) **特征**: - 属于特定租户 - 租户间完全隔离 - 不需要客户级隔离 **继承**: `ModelMixin + UserMixin + TenantMixin` **适用场景**: - 组织架构 (部门、角色、岗位) - 租户配置 (菜单、参数、字典) - 租户级业务数据 ```python class ProductModel(ModelMixin, UserMixin, TenantMixin): """产品表 - 租户级""" __tablename__ = 'business_product' __table_args__ = ({'comment': '产品表'}) name: Mapped[str] = mapped_column(String(100), comment='产品名称') price: Mapped[float] = mapped_column(Float, comment='价格') # 关联关系 created_by: Mapped["UserModel | None"] = relationship( foreign_keys="ProductModel.created_id", lazy="selectin" ) tenant: Mapped["TenantModel"] = relationship( foreign_keys="ProductModel.tenant_id", lazy="selectin" ) ``` ### 3. 客户级表 (租户+客户隔离) **特征**: - 属于特定租户 - 支持客户级隔离 - 客户用户只能访问本客户数据 **继承**: `ModelMixin + UserMixin + TenantMixin + CustomerMixin` **适用场景**: - 客户用户 - 客户订单 - 客户专属数据 - 客户通知 ```python class OrderModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """订单表 - 客户级""" __tablename__ = 'business_order' __table_args__ = ({'comment': '订单表'}) order_no: Mapped[str] = mapped_column(String(50), comment='订单号') amount: Mapped[float] = mapped_column(Float, comment='订单金额') # 关联关系 created_by: Mapped["UserModel | None"] = relationship( foreign_keys="OrderModel.created_id", lazy="selectin" ) tenant: Mapped["TenantModel"] = relationship( foreign_keys="OrderModel.tenant_id", lazy="selectin" ) customer: Mapped["CustomerModel | None"] = relationship( foreign_keys="OrderModel.customer_id", lazy="selectin" ) ``` ### 4. 混合级表 (支持多级隔离) **特征**: - `tenant_id`: 可选 (NULL=系统级, >1=租户级) - `customer_id`: 可选 (NULL=租户级, >1=客户级) **继承**: `ModelMixin + UserMixin + TenantMixin + CustomerMixin` **适用场景**: - 菜单 (系统菜单 + 租户菜单) - 字典 (系统字典 + 租户字典) - 应用 (系统应用 + 租户应用 + 客户应用) ```python class ApplicationModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """应用表 - 支持三级隔离""" __tablename__ = 'app_application' name: Mapped[str] = mapped_column(String(100), comment='应用名称') # 覆盖tenant_id为可选 tenant_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("system_tenant.id", ondelete="CASCADE"), nullable=True, # 可选,NULL表示系统级应用 index=True, comment="所属租户ID(NULL=系统级,>1=租户级)" ) # customer_id已经是可选的,无需覆盖 ``` --- ## 代码示例 ### 完整示例1: 租户级业务表 ```python # -*- coding: utf-8 -*- from typing import TYPE_CHECKING from sqlalchemy import String, Integer, Float, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.base_model import ModelMixin, UserMixin, TenantMixin if TYPE_CHECKING: from app.api.v1.module_system.user.model import UserModel from app.api.v1.module_system.tenant.model import TenantModel class ProductModel(ModelMixin, UserMixin, TenantMixin): """ 产品表 - 租户级业务表 数据隔离策略: - tenant_id: 必填,实现租户间隔离 - 不需要customer_id: 产品是租户级资源,所有租户用户共享 数据权限: - 通过created_id支持"仅本人数据权限" - 通过user.dept_id支持部门数据权限 """ __tablename__ = 'business_product' __table_args__ = ({'comment': '产品表'}) __loader_options__ = ["created_by", "updated_by", "tenant"] # 业务字段 name: Mapped[str] = mapped_column(String(100), nullable=False, comment='产品名称') code: Mapped[str] = mapped_column(String(50), nullable=False, comment='产品编码') price: Mapped[float] = mapped_column(Float, nullable=False, comment='价格') stock: Mapped[int] = mapped_column(Integer, default=0, comment='库存') # 关联关系 (覆盖Mixin中的property) created_by: Mapped["UserModel | None"] = relationship( foreign_keys="ProductModel.created_id", lazy="selectin" ) updated_by: Mapped["UserModel | None"] = relationship( foreign_keys="ProductModel.updated_id", lazy="selectin" ) tenant: Mapped["TenantModel"] = relationship( foreign_keys="ProductModel.tenant_id", lazy="selectin" ) ``` ### 完整示例2: 客户级业务表 ```python # -*- coding: utf-8 -*- from typing import TYPE_CHECKING from datetime import datetime from sqlalchemy import String, Integer, Float, DateTime, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from app.core.base_model import ModelMixin, UserMixin, TenantMixin, CustomerMixin if TYPE_CHECKING: from app.api.v1.module_system.user.model import UserModel from app.api.v1.module_system.tenant.model import TenantModel from app.api.v1.module_system.customer.model import CustomerModel class OrderModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """ 订单表 - 客户级业务表 数据隔离策略: - tenant_id: 必填,实现租户间隔离 - customer_id: 可选,客户订单时必填 * NULL: 租户级订单 * >0: 客户级订单 数据权限: - 客户用户: 只能看 customer_id = current_user.customer_id 的订单 - 租户用户: 可以看本租户所有订单(根据role.data_scope) """ __tablename__ = 'business_order' __table_args__ = ({'comment': '订单表'}) __loader_options__ = ["created_by", "updated_by", "tenant", "customer"] # 业务字段 order_no: Mapped[str] = mapped_column(String(50), nullable=False, unique=True, comment='订单号') order_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.now, comment='下单时间') amount: Mapped[float] = mapped_column(Float, nullable=False, comment='订单金额') order_status: Mapped[str] = mapped_column(String(10), default='0', comment='订单状态(0:待付款 1:已付款 2:已发货)') # 关联关系 created_by: Mapped["UserModel | None"] = relationship( foreign_keys="OrderModel.created_id", lazy="selectin" ) updated_by: Mapped["UserModel | None"] = relationship( foreign_keys="OrderModel.updated_id", lazy="selectin" ) tenant: Mapped["TenantModel"] = relationship( foreign_keys="OrderModel.tenant_id", lazy="selectin" ) customer: Mapped["CustomerModel | None"] = relationship( foreign_keys="OrderModel.customer_id", lazy="selectin" ) ``` --- ## 常见场景 ### 场景1: 代码生成器 **需求**: 租户管理员可以生成代码,不需要客户隔离 **设计**: `ModelMixin + UserMixin + TenantMixin` ```python class GenTableModel(ModelMixin, UserMixin, TenantMixin): """代码生成表 - 租户级""" pass ``` ### 场景2: 定时任务 **需求**: 支持系统任务、租户任务、客户任务 **设计**: `ModelMixin + UserMixin + TenantMixin + CustomerMixin` ```python class JobModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """ 定时任务表 - 支持三级隔离 - tenant_id=1, customer_id=NULL: 系统任务 - tenant_id>1, customer_id=NULL: 租户任务 - tenant_id>1, customer_id>0: 客户任务 """ pass ``` ### 场景3: 应用管理 **需求**: - 平台预置应用 (所有租户可见) - 租户自定义应用 (仅本租户可见) - 客户专属应用 (仅该客户可见) **设计**: `ModelMixin + UserMixin + TenantMixin + CustomerMixin` + 覆盖tenant_id为可选 ```python class ApplicationModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """应用表 - 支持三级隔离""" # 覆盖tenant_id为可选 tenant_id: Mapped[int | None] = mapped_column( Integer, ForeignKey("system_tenant.id", ondelete="CASCADE"), nullable=True, index=True, comment="所属租户ID(NULL=系统级,>1=租户级)" ) ``` ### 场景4: 示例/Demo表 **需求**: 用于演示,支持客户级隔离 **设计**: `ModelMixin + UserMixin + TenantMixin + CustomerMixin` ```python class DemoModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """示例表 - 客户级""" pass ``` ### 场景5: MCP服务器配置 **需求**: - 系统预置MCP (所有租户可用) - 租户自定义MCP (仅本租户可用) - 客户专属MCP (仅该客户可用) **设计**: `ModelMixin + UserMixin + TenantMixin + CustomerMixin` ```python class McpModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): """MCP服务器表 - 支持三级隔离""" pass ``` --- ## 决策流程图 ``` 创建新业务表 │ ├─ 是否需要多租户隔离? │ ├─ 否 → 继承 MappedBase (系统级表) │ └─ 是 → 继续 │ ├─ 是否需要记录创建人/更新人? │ ├─ 是 → 添加 UserMixin │ └─ 否 → 跳过 │ ├─ 添加 TenantMixin (租户隔离) │ └─ 是否需要客户级隔离? ├─ 是 → 添加 CustomerMixin │ └─ customer_id可选,根据业务决定是否必填 └─ 否 → 完成 最终继承组合: - 系统级: MappedBase - 租户级: ModelMixin + UserMixin + TenantMixin - 客户级: ModelMixin + UserMixin + TenantMixin + CustomerMixin - 混合级: ModelMixin + UserMixin + TenantMixin + CustomerMixin (覆盖tenant_id为可选) ``` --- ## 检查清单 创建新业务表时,请检查: ### 基础检查 - [ ] 是否继承了 `ModelMixin`? (除非是系统级表) - [ ] 是否继承了 `UserMixin`? (推荐,用于审计) - [ ] 是否继承了 `TenantMixin`? (几乎所有业务表都需要) - [ ] 是否需要 `CustomerMixin`? (根据业务需求) ### 字段检查 - [ ] 是否覆盖了Mixin中的基础字段? (通常不需要) - [ ] 是否添加了必要的业务字段? - [ ] 字段类型是否正确? - [ ] 是否添加了适当的注释? ### 关联关系检查 - [ ] 是否使用了 `TYPE_CHECKING` 导入关联模型? - [ ] 是否正确覆盖了 `created_by`、`tenant` 等relationship? - [ ] `foreign_keys` 是否使用了字符串形式? - [ ] 是否设置了 `lazy="selectin"`? ### 表配置检查 - [ ] 是否设置了 `__tablename__`? - [ ] 是否设置了 `__table_args__` (comment)? - [ ] 是否设置了 `__loader_options__`? ### 索引检查 - [ ] Mixin已经为 tenant_id、customer_id、created_id 创建了索引 - [ ] 是否需要为业务字段创建额外索引? --- ## 常见错误 ### ❌ 错误1: 重复定义基础字段 ```python class MyModel(ModelMixin): id: Mapped[int] = mapped_column(...) # ❌ ModelMixin已经定义了 status: Mapped[str] = mapped_column(...) # ❌ ``` **✅ 正确**: ```python class MyModel(ModelMixin, UserMixin, TenantMixin): # 只定义业务字段 name: Mapped[str] = mapped_column(...) ``` ### ❌ 错误2: 不需要CustomerMixin却继承了 ```python # 部门是租户级资源,不需要客户隔离 class DeptModel(ModelMixin, UserMixin, TenantMixin, CustomerMixin): # ❌ pass ``` **✅ 正确**: ```python class DeptModel(ModelMixin, UserMixin, TenantMixin): # ✅ pass ``` ### ❌ 错误3: 忘记覆盖relationship ```python class MyModel(ModelMixin, UserMixin, TenantMixin): # ❌ 没有定义relationship,property返回None pass ``` **✅ 正确**: ```python class MyModel(ModelMixin, UserMixin, TenantMixin): created_by: Mapped["UserModel | None"] = relationship( foreign_keys="MyModel.created_id", lazy="selectin" ) tenant: Mapped["TenantModel"] = relationship( foreign_keys="MyModel.tenant_id", lazy="selectin" ) ``` --- ## 参考文档 - [多租户数据隔离设计方案](./多租户数据隔离设计方案.md) - [数据隔离快速参考](./数据隔离快速参考.md)