租户模型与资源隔离(中文译文)
原始 DeepWiki 页面:https://deepwiki.com/langgenius/dify/8.1-tenant-model-and-resource-isolation
翻译时间:2026-05-27T08:44:29.808Z
翻译模型:deepseek-chat
原文字符数:14256
项目:Dify (dify)
---
租户模型与资源隔离
相关源文件
以下文件被用作生成此 Wiki 页面的上下文:
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 之间的多对多关系。每个账户可以属于多个租户(工作空间),每个租户可以有多个具有不同角色的账户。
图示:租户-账户-关联实体关系
erDiagram
"Account (accounts)" ||--o{ "TenantAccountJoin (tenant_account_joins)" : "属于"
"Tenant (tenants)" ||--o{ "TenantAccountJoin (tenant_account_joins)" : "拥有成员"
"TenantAccountJoin (tenant_account_joins)" {
string id PK
string tenant_id FK "引用 tenants.id"
string account_id FK "引用 accounts.id"
boolean current "是否为当前活动工作空间"
string role "TenantAccountRole"
string invited_by FK "引用 accounts.id"
}
来源: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 枚举定义了层级角色,用于管理租户内的权限。
图示:角色层级与代码实体
graph TD
subgraph "自然语言角色"
OwnerRole["所有者"]
AdminRole["管理员"]
EditorRole["编辑者"]
NormalRole["普通用户"]
DatasetOpRole["数据集操作员"]
end
subgraph "代码实体空间 (TenantAccountRole)"
OWNER["TenantAccountRole.OWNER"]
ADMIN["TenantAccountRole.ADMIN"]
EDITOR["TenantAccountRole.EDITOR"]
NORMAL["TenantAccountRole.NORMAL"]
DATASET_OPERATOR["TenantAccountRole.DATASET_OPERATOR"]
end
OwnerRole --> OWNER
AdminRole --> ADMIN
EditorRole --> EDITOR
NormalRole --> NORMAL
DatasetOpRole --> DATASET_OPERATOR
OWNER -->|"is_privileged_role()"| ADMIN
ADMIN -->|"is_editing_role()"| EDITOR
EDITOR -->|"is_dataset_edit_role()"| DATASET_OPERATOR
来源:api/models/account.py:19-77
---
资源作用域模式
通用 tenant_id 隔离
Dify 中的所有主要资源都包含一个 tenant_id 字段。这确保了每个查询都可以严格限定在当前工作空间上下文中。
图示:通过 tenant_id 进行资源作用域限定
graph TB
subgraph "租户边界"
T1["租户 (ID: uuid-1)"]
end
subgraph "作用域限定的资源"
App1["App.tenant_id == uuid-1"]
Workflow1["Workflow.tenant_id == uuid-1"]
Dataset1["Dataset.tenant_id == uuid-1"]
Provider1["Provider.tenant_id == uuid-1"]
Tool1["BuiltinToolProvider.tenant_id == uuid-1"]
end
T1 --> App1
T1 --> Workflow1
T1 --> Dataset1
T1 --> Provider1
T1 --> Tool1
来源: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 中的租户上下文解析
sequenceDiagram
participant C as "AccountService.load_user"
participant DB as "PostgreSQL (db.session)"
participant A as "Account 实例"
C->>DB: "select(Account).where(id=user_id)"
DB-->>C: "Account 对象"
C->>DB: "select(TenantAccountJoin).where(account_id=id, current=True)"
alt 找到当前租户
DB-->>C: "TenantAccountJoin"
C->>A: "set_tenant_id(tenant_id)"
else 没有当前租户
C->>DB: "select(TenantAccountJoin).where(account_id=id).order_by(id.asc())"
DB-->>C: "available_ta"
C->>A: "set_tenant_id(available_ta.tenant_id)"
C->>DB: "available_ta.current = True; commit()"
end
来源: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 记录。
图示:工具提供商隔离架构
graph LR
subgraph "租户 A"
T1["TenantID: A"]
BTP1["BuiltinToolProvider"]
ATP1["ApiToolProvider"]
end
subgraph "租户 B"
T2["TenantID: B"]
BTP2["BuiltinToolProvider"]
ATP2["ApiToolProvider"]
end
T1 --> BTP1
T1 --> ATP1
T2 --> BTP2
T2 --> ATP2
来源: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