租户模型与资源隔离
租户模型与资源隔离
相关源文件
本章引用的主要源码文件:
api/controllers/console/auth/data_source_oauth.pyapi/controllers/console/auth/email_register.pyapi/controllers/console/auth/error.pyapi/controllers/console/auth/forgot_password.pyapi/controllers/console/auth/login.pyapi/controllers/console/auth/oauth.pyapi/controllers/console/explore/installed_app.pyapi/controllers/console/workspace/account.pyapi/controllers/console/workspace/members.pyapi/controllers/console/workspace/model_providers.pyapi/controllers/console/workspace/models.pyapi/controllers/console/workspace/plugin.pyapi/controllers/console/workspace/workspace.pyapi/libs/oauth.pyapi/libs/oauth_data_source.pyapi/models/account.pyapi/models/api_based_extension.pyapi/models/dataset.pyapi/models/model.pyapi/models/provider.pyapi/models/source.pyapi/models/task.pyapi/models/tools.pyapi/models/web.pyapi/models/workflow.pyapi/schedule/mail_clean_document_notify_task.pyapi/services/account_service.pyapi/templates/change_mail_confirm_old_template_zh-CN.htmlapi/templates/transfer_workspace_owner_confirm_template_en-US.htmlapi/templates/without-brand/transfer_workspace_owner_confirm_template_en-US.htmlapi/tests/unit_tests/libs/test_oauth_clients.pyapi/tests/unit_tests/services/test_account_service.pyweb/app/components/header/account-setting/members-page/__tests__/index.spec.tsxweb/app/components/header/account-setting/members-page/index.tsxweb/app/components/header/account-setting/members-page/operation/__tests__/index.spec.tsxweb/app/components/header/account-setting/members-page/operation/index.tsx
目的与范围
本文档描述了 Dify 的多租户架构,重点介绍了作为基本隔离边界的 Tenant 模型,以及所有资源如何通过 tenant_id 进行作用域限定。内容涵盖数据库模式设计、租户-账户关系、基于角色的访问控制,以及应用于应用、工作流、数据集和工具提供商的资源隔离模式。
关于认证方法(OAuth、SSO、API 密钥),请参见 8.2。关于基于角色的权限和访问控制详情,请参见 8.3。关于工作空间管理 API 和成员操作,请参见 8.4。
核心租户实体
租户模型
Tenant 类代表一个工作空间,是 Dify 中的主要隔离边界。每个租户都是一个完全隔离的工作空间,拥有自己的资源、配置和成员列表。
数据库模式:
| 列名 | 类型 | 描述 |
|---|---|---|
id | StringUUID | 主键,工作空间标识符(UUID v4) |
name | String(255) | 工作空间名称 |
encrypt_public_key | LongText | 用于租户特定加密的 RSA 公钥 |
plan | String(255) | 订阅计划(基础版/专业版/团队版/企业版) |
status | String(255) | 工作空间状态(正常/归档) |
custom_config | LongText | 自定义工作空间设置的 JSON 配置 |
created_at | DateTime | 创建时间戳 |
updated_at | DateTime | 最后更新时间戳 |
实现细节:
api/models/account.py 定义了 Tenant 类,该类使用 StringUUID 作为主键,并包含工作空间名称、状态和订阅计划等字段。
来源:api/models/account.py:242-277
租户-账户关系
多对多关联
Dify 通过 TenantAccountJoin 表实现了 Tenant 和 Account 之间的多对多关系。每个账户可以属于多个租户(工作空间),每个租户可以有多个具有不同角色的账户。
图示:租户-账户-关联实体关系
来源:api/models/account.py:279-302
TenantAccountJoin 模式
关联表在成员级别强制执行租户隔离,并存储用户在该工作空间中的特定角色。
| 列名 | 类型 | 约束 | 描述 |
|---|---|---|---|
id | StringUUID | 主键 | 关联记录标识符 |
tenant_id | StringUUID | 外键,索引 | 引用 tenants.id |
account_id | StringUUID | 外键,索引 | 引用 accounts.id |
current | Boolean | 默认值:false | 用户当前活动的工作空间 |
role | String(16) | 默认值:'normal' | 用户在此特定工作空间中的角色 |
invited_by | StringUUID | 可为空 | 发送邀请的账户 ID |
来源:api/models/account.py:279-302
角色系统
TenantAccountRole 枚举定义了层级角色,用于管理租户内的权限。
图示:角色层级与代码实体
来源:api/models/account.py:19-77
资源作用域模式
通用 tenant_id 隔离
Dify 中的所有主要资源都包含一个 tenant_id 字段。这确保了每个查询都可以严格限定在当前工作空间上下文中。
图示:通过 tenant_id 进行资源作用域限定
来源:api/models/model.py:72, api/models/workflow.py:187, api/models/dataset.py:180, api/models/provider.py:54, api/models/tools.py:94
包含 tenant_id 的资源模型
| 模型类别 | 代码实体 | tenant_id 位置 | 索引/约束 |
|---|---|---|---|
| 应用 | App | api/models/model.py:72 | Index("app_tenant_id_idx", "tenant_id") |
| 工作流 | Workflow | api/models/workflow.py:187 | Index("workflow_version_idx", "tenant_id", "app_id", "version") |
| 知识库 | Dataset | api/models/dataset.py:180 | Index("dataset_tenant_idx", "tenant_id") |
| 大语言模型(LLM)提供商 | Provider | api/models/provider.py:54 | UniqueConstraint("tenant_id", "provider_name", "provider_type", "quota_type") |
| 工具 | BuiltinToolProvider | api/models/tools.py:94 | UniqueConstraint("tenant_id", "provider", "name") |
| API 工具 | ApiToolProvider | api/models/tools.py:158 | UniqueConstraint("name", "tenant_id") |
租户上下文管理
账户租户关联
Account 模型在请求生命周期内维护当前活动租户上下文。current_tenant 设置器使用 TenantAccountJoin 解析用户在请求工作空间中的角色和成员身份。AccountService.load_user 函数根据 TenantAccountJoin.current 标志或第一个可用工作空间自动解析并设置 current_tenant。
图示:AccountService 中的租户上下文解析
来源:api/models/account.py:129-150, api/services/account_service.py:162-190
租户解析函数
代码库使用辅助函数,在只有资源 ID(如 app_id)可用时解析正确的 tenant_id,确保操作不会跨工作空间边界泄漏。
_resolve_app_tenant_id(app_id):api/models/model.py:71-75_resolve_workflow_app_tenant_id(app_id):api/models/workflow.py:82-88
工具与提供商隔离
工具提供商多租户
工具配置(API 密钥、OAuth 令牌)严格按租户隔离。即使是内置工具,如果需要凭证,每个租户也需要一个 BuiltinToolProvider 记录。
图示:工具提供商隔离架构
来源:api/models/tools.py:73-120, api/models/tools.py:128-180
模型提供商隔离
大语言模型(LLM)提供商通过 tenant_id 进行作用域限定。这包括 Provider 配置、ProviderModel 定义和 TenantDefaultModel 选择。
# api/models/provider.py:33-45
class Provider(TypeBase):
# ...
__table_args__ = (
sa.PrimaryKeyConstraint("id", name="provider_pkey"),
sa.Index("provider_tenant_id_provider_idx", "tenant_id", "provider_name"),
sa.UniqueConstraint(
"tenant_id", "provider_name", "provider_type", "quota_type", name="unique_provider_name_type_quota"
),
)
tenant_id: Mapped[str] = mapped_column(StringUUID, nullable=False)
来源:api/models/provider.py:33-68, api/models/provider.py:163-180
安全与数据完整性
复合唯一约束
为防止跨租户的名称冲突,同时允许全局范围内存在重复名称,Dify 使用了包含 tenant_id 的复合唯一约束。
| 表 | 约束 | 目的 |
|---|---|---|
providers | unique_provider_name_type_quota | 每个租户/提供商的唯一配置 |
tool_builtin_providers | unique_builtin_tool_provider | 每个租户的唯一凭证实例 |
tool_api_providers | unique_api_tool_provider | 每个工作空间的唯一 API 工具名称 |
provider_models | unique_provider_model_name | 每个租户的唯一模型配置 |
tenant_default_models | unique_tenant_default_model_type | 每个租户每种类型一个默认模型 |
来源:api/models/provider.py:42-44, api/models/tools.py:81, api/models/tools.py:136, api/models/provider.py:124-126, api/models/provider.py:168
数据库索引策略
索引设计用于优化租户作用域的查询,通常将 tenant_id 作为复合索引的第一列。
Index("dataset_tenant_idx", "tenant_id"):api/models/dataset.py:171Index("workflow_version_idx", "tenant_id", "app_id", "version"):api/models/workflow.py:183-184Index("provider_tenant_id_provider_idx", "tenant_id", "provider_name"):api/models/provider.py:41
来源:api/models/dataset.py:171, api/models/workflow.py:183-184, api/models/provider.py:41