从简单二元权限到细粒度、可配置的企业级权限管理体系
基于 api/db/db_models.py 中 Peewee ORM 模型提取的核心表结构。
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ User │ │ UserTenant │ │ Tenant │
├──────────────┤ ├──────────────────┤ ├──────────────┤
│ id (PK) │──1:N──│ user_id (FK) │──N:1──│ id (PK) │
│ access_token │ │ tenant_id (FK) │ │ name │
│ nickname │ │ role: owner| │ │ llm_id │
│ email │ │ admin|normal| │ │ embd_id │
│ password │ │ invite │ │ credit │
│ is_superuser │ │ invited_by │ │ status │
│ status │ │ status │ │ ... │
└──────────────┘ └──────────────────┘ └──────┬───────┘
│
┌────────────────────────────────────┤
│ 1:N │ 1:N
▼ ▼
┌──────────────────┐ ┌──────────────────────┐
│ Knowledgebase │ │ Dialog │
├──────────────────┤ ├──────────────────────┤
│ id (PK) │ │ id (PK) │
│ tenant_id (FK) │ │ tenant_id (FK) │
│ name │ │ name │
│ permission: │ │ kb_ids (JSON) ───────│── 引用多个 KB
│ me | team │◄─────────────│ llm_setting │
│ created_by │ │ prompt_config │
│ embd_id │ │ ... │
│ parser_id │ └──────────────────────┘
│ doc_num │
│ status │
└───────┬──────────┘
│ 1:N
▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Document │ │ File │ │ Task │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ kb_id (FK) │ │ parent_id │ │ doc_id (FK) │
│ parser_id │ │ tenant_id │ │ task_type │
│ name │ │ name │ │ progress │
│ size │ │ size │ │ ... │
│ token_num │ │ type │ └──────────────┘
│ chunk_num │ └──────────────┘
│ status │
└──────────────┘
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
user |
id | CHAR(32) | 主键,UUID |
| access_token | VARCHAR(255) | API 访问令牌,JWT 签发 | |
| nickname | VARCHAR(100) | 用户昵称 | |
| password | VARCHAR(255) | 加密密码 | |
| VARCHAR(255) UNIQUE | 邮箱(唯一) | ||
| avatar | TEXT | 头像 Base64 | |
| language | VARCHAR(32) | 语言偏好 | |
| color_schema | VARCHAR(32) | 主题(Bright|Dark) | |
| timezone | VARCHAR(64) | 时区 | |
| last_login_time | DATETIME | 最近登录 | |
| is_authenticated | CHAR(1) | 是否通过认证 | |
| is_active | CHAR(1) | 是否激活 | |
| is_superuser | BOOLEAN | 是否超级管理员 | |
| status | CHAR(1) | 0=废弃 1=有效 | |
tenant |
id | CHAR(32) | 主键,UUID |
| name | VARCHAR(100) | 租户名称 | |
| llm_id | VARCHAR(128) | 默认大模型 ID | |
| embd_id | VARCHAR(128) | 默认嵌入模型 ID | |
| credit | INTEGER | 信用额度(默认 512) | |
| parser_ids | VARCHAR(256) | 文档解析器列表 | |
| status | CHAR(1) | 0=废弃 1=有效 | |
user_tenant |
id | CHAR(32) | 主键,UUID |
| user_id | CHAR(32) | 用户 ID(FK → user.id) | |
| tenant_id | CHAR(32) | 租户 ID(FK → tenant.id) | |
| role | VARCHAR(32) | 角色:owner | admin | normal | invite | |
| invited_by | CHAR(32) | 邀请人 ID |
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
knowledgebase |
id | CHAR(32) | 主键,UUID |
| avatar | TEXT | 头像 Base64 | |
| tenant_id | CHAR(32) | 租户 ID(FK → tenant.id) | |
| name | VARCHAR(128) | 知识库名称 | |
| language | VARCHAR(32) | 语言 | |
| description | TEXT | 描述 | |
| embd_id | VARCHAR(128) | 嵌入模型 ID | |
| permission | VARCHAR(16) | 权限:me | team | |
| created_by | CHAR(32) | 创建者 | |
| doc_num | INTEGER | 文档数量 | |
| token_num | INTEGER | Token 总数 | |
| chunk_num | INTEGER | Chunk 总数 | |
| similarity_threshold | FLOAT | 相似度阈值 | |
| vector_similarity_weight | FLOAT | 向量相似度权重 | |
| parser_id | CHAR(32) | 解析器 ID | |
| parser_config | JSON | 解析器配置 | |
| pagerank | INTEGER | PageRank 值 | |
| graphrag_task_id raptor_task_id mindmap_task_id | CHAR(32) | 高级 RAG 任务 ID | |
| status | CHAR(1) | 0=废弃 1=有效 | |
document |
id | CHAR(32) | 主键,UUID |
| kb_id | CHAR(256) | 所属知识库 ID | |
| parser_id | CHAR(32) | 解析器 ID | |
| parser_config | JSON | 解析器配置 | |
| type | CHAR(32) | 文件类型 | |
| created_by | CHAR(32) | 创建者 | |
| name | VARCHAR(255) | 文档名称 | |
| location | VARCHAR(255) | 存储位置 | |
| size | BIGINT | 文件大小 | |
| token_num | INTEGER | Token 数 | |
| chunk_num | INTEGER | Chunk 数 | |
| progress | FLOAT | 处理进度 | |
| run | CHAR(1) | 运行状态 | |
| content_hash | CHAR(32) | 内容哈希(变更检测) | |
| source_type | VARCHAR(128) | 文档来源 | |
| status | CHAR(1) | 0=废弃 1=有效 |
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
dialog |
id | CHAR(32) | 主键,UUID |
| tenant_id | CHAR(32) | 租户 ID | |
| name | VARCHAR(255) | 应用名称 | |
| llm_id | CHAR(128) | LLM ID | |
| llm_setting | JSON | LLM 参数 | |
| prompt_config | JSON | 提示词配置 | |
| similarity_threshold | FLOAT | 相似度阈值 | |
| top_n | INTEGER | 返回 Top N | |
| rerank_id | CHAR(128) | 重排序模型 | |
| kb_ids | JSON | 关联知识库列表(JSON数组) | |
| status | CHAR(1) | 0=废弃 1=有效 | |
search |
id | CHAR(32) | 主键,UUID |
| tenant_id | CHAR(32) | 租户 ID | |
| name | VARCHAR(128) | 搜索名称 | |
| created_by | CHAR(32) | 创建者 | |
| search_config | JSON | 搜索配置(含 kb_ids, doc_ids 等) | |
| status | CHAR(1) | 0=废弃 1=有效 | |
user_canvas |
id | CHAR(32) | 主键,UUID |
| user_id | CHAR(255) | 用户 ID | |
| title | VARCHAR(255) | Canvas 标题 | |
| permission | CHAR(16) | 权限:me | team | |
| release | BOOLEAN | 是否发布 | |
| canvas_type | CHAR(32) | Canvas 类型 | |
| canvas_category | CHAR(32) | 分类:agent_canvas | dataflow_canvas | |
| dsl | JSON | 画布 DSL | |
| tags | VARCHAR(512) | 标签 |
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
api_token |
tenant_id | CHAR(32) | 归属租户(复合主键) |
| token | VARCHAR(255) | Token 值(复合主键) | |
| dialog_id | CHAR(32) | 关联对话 ID | |
| source | VARCHAR(16) | 来源:none | agent | dialog | |
| beta | VARCHAR(255) | Beta 标记 | |
api_4_conversation |
id | CHAR(32) | 主键,UUID |
| dialog_id | CHAR(32) | 对话 ID | |
| user_id | CHAR(255) | 用户标识 | |
| message | JSON | 消息内容 | |
| tokens | INTEGER | 消耗 Token | |
| duration | FLOAT | 会话时长 | |
| round | INTEGER | 轮次 | |
| thumb_up | INTEGER | 点赞数 | |
| source | VARCHAR(16) | 来源:none | agent | dialog |
| 表名 | 核心字段 | 说明 |
|---|---|---|
llm_factories | name (PK), logo, tags, rank | LLM 厂商注册表 |
llm | fid, llm_name (复合PK), model_type, max_tokens | LLM 模型定义 |
tenant_llm | id (PK), tenant_id, llm_factory, llm_name, api_key, api_base | 租户 LLM 配置 |
tenant_langfuse | tenant_id (PK), secret_key, public_key, host | Langfuse 可观测配置 |
tenant_model_provider | id, provider_name, tenant_id | 租户自定义模型供应商 |
tenant_model_instance | id, instance_name, provider_id, api_key | 模型实例 |
tenant_model | id, model_name, provider_id, instance_id, model_type | 模型定义 |
tenant_model_group | id, group_type, model_name, strategy | 模型分组(负载均衡) |
tenant_model_group_mapping | group_id, provider_id, instance_id, model_id, weight (复合PK) | 模型分组成员 |
file | id, parent_id, tenant_id, name, location, size, type | 文件管理 |
file2document | id, file_id, document_id | 文件-文档关联 |
task | id, doc_id, from_page, to_page, task_type, progress | 文档处理任务 |
conversation | id, dialog_id, name, message, reference, user_id | 对话记录 |
canvas_template | id, title, description, canvas_type, dsl | Canvas 模板 |
user_canvas_version | id, user_canvas_id, title, dsl, release | Canvas 版本 |
mcp_server | id, name, tenant_id, url, server_type, variables | MCP 服务器配置 |
connector | id, tenant_id, name, source, config | 数据源连接器 |
connector2kb | id, connector_id, kb_id, auto_parse | 连接器-KB 关联 |
sync_logs | id, connector_id, kb_id, status, error_msg | 同步日志 |
pipeline_operation_log | id, document_id, kb_id, parser_id, operation_status | Pipeline 操作日志 |
evaluation_datasets | id, tenant_id, name, kb_ids, created_by | 评测数据集 |
evaluation_cases | id, dataset_id, question, reference_answer | 评测用例 |
evaluation_runs | id, dataset_id, dialog_id, metrics_summary, status | 评测运行 |
evaluation_results | id, run_id, case_id, generated_answer, metrics | 评测结果 |
memory | id, tenant_id, memory_type, storage_type, permissions | 记忆模块 |
system_settings | name (PK), source, data_type, value | 系统设置 |
invitation_code | id, code, tenant_id, user_id | 邀请码 |
当前 RAGFlow 的权限控制粒度及其局限性。
is_superuser=true)User 表级别标记,全局最高权限,可跨租户操作。
UserTenant.role)在租户范围内定义 4 种角色:owner(拥有者)、admin(管理员)、normal(普通成员)、invite(受邀未激活)。
Knowledgebase.permission)仅有 me(仅创建者可见)和 team(整个租户可见)两种粒度。
UserCanvas.permission)同样仅有 me 和 team 两种粒度,与 KB 权限模式相同。
# api/db/services/knowledgebase_service.py - _visibility_and_status_filter def _visibility_and_status_filter(cls, joined_tenant_ids, user_id): return ( ( # Team KBs: 租户内所有成员可见 (cls.model.tenant_id.in_(joined_tenant_ids) & (cls.model.permission == 'team')) # 或 KB 创建者自己 | (cls.model.tenant_id == user_id) ) & (cls.model.status == StatusEnum.VALID.value) )
# api/db/services/knowledgebase_service.py - accessible def accessible(cls, kb_id, user_id): e, kb = cls.get_by_id(kb_id) if not e: return False if kb.status != StatusEnum.VALID.value: return False # 创建者始终可访问 if kb.tenant_id == user_id: return True # 仅 Team KB 允许同租户成员访问 if kb.permission != 'team': return False joined_tenants = TenantService.get_joined_tenants_by_user_id(user_id) return any(tenant["tenant_id"] == kb.tenant_id for tenant in joined_tenants)
# api/db/services/knowledgebase_service.py - accessible4deletion def accessible4deletion(cls, kb_id, user_id): # 仅 KB 创建者可以删除 docs = cls.model.select(cls.model.id).where( cls.model.id == kb_id, cls.model.created_by == user_id ).paginate(0, 1) return bool(list(docs.dicts()))
# Dialog 通过 JSON 字段直接引用 KB IDs,没有任何权限校验中间层 # api/db/db_models.py - Dialog class Dialog(DataBaseModel): kb_ids = JSONField(null=False, default=[]) # 直接存 KB ID 列表
这意味着:一旦 Dialog 配置了某个 KB 的 ID,所有使用该 Dialog 的用户都可以通过它检索该 KB 的内容。没有对 谁能用哪个 Dialog 访问哪个 KB 的精细化控制。
目标权限能力 vs 现状能力矩阵。
me / team 两种"财务部"用户组可读写财务KB,"研发部"仅只读,外部顾问无任何权限。
对外客服机器人(Dialog A)仅能访问"公开FAQ" KB,内部员工助手(Dialog B)可访问全部 KB。
VIP 客户 API Token 允许 100 QPS/Topic,免费试用账户限 10 QPS 日 1000 次调用。
同一 KB 内,P0 机密文档仅安全组可读,普通文档全员可读。通过元数据标签实现。
采用 RBAC(基于角色的访问控制)+ ABAC(基于属性的访问控制)混合模型,分层设计。
所有扩展字段有默认值,原有 me/team 逻辑保留作为快速模式。新表不影响已有 API。
每层权限独立启用/禁用。小团队可只开启最简单模式,企业可开启全部能力。
默认无权限,必须显式授权。多条策略聚合使用"最宽松"原则(有任一策略允许即可)。黑名单覆盖白名单。
权限计算结果缓存(Redis),变更时失效。热点路径避免多表 JOIN,使用预计算的权限摘要。
┌──────────────────────────────────────────────────────┐
│ ★ 新增层: 权限控制中心 ★ │
└──────────────────────────────────────────────────────┘
┌──────────────┐ ┌─────────────────┐ ┌──────────────────────┐
│ user_group │ │ role │ │ role_permission │
├──────────────┤ ├─────────────────┤ ├──────────────────────┤
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ tenant_id │ │ tenant_id │ │ role_id (FK) │
│ name │ │ name │ │ resource_type │
│ description │ │ description │ │ permission │
│ created_by │ │ is_system │ │ effect: allow|deny │
│ status │ │ parent_role_id │ │ conditions (JSON) │
└──────┬───────┘ │ status │ └──────────┬───────────┘
│ N:M └────────┬────────┘ │ N:1
▼ │ │
┌──────────────┐ │ │
│user_group_ │ │ │
│ member │ │ │
├──────────────┤ │ │
│ group_id(FK) │ │ │
│ user_id (FK) │ │ │
└──────────────┘ │ │
▼ ▼
┌────────────────────────────────────────────────────────────────────────┐
│ kb_permission │
├────────────────────────────────────────────────────────────────────────┤
│ id (PK), kb_id (FK), grantee_type, grantee_id, role_id (FK) │
│ operations (JSON), data_scope (JSON), effect: allow|deny │
│ expires_at, priority, created_by, status │
└────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ kb_rate_limit │
├────────────────────────────────────────────────────────────────────────┤
│ id (PK), kb_id (FK), scope_type, scope_id, limit_type │
│ max_qps, max_tokens_per_day, max_concurrent, max_requests_per_day │
│ window_seconds, enabled, status │
└────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ app_kb_binding │
├────────────────────────────────────────────────────────────────────────┤
│ id (PK), app_type, app_id, kb_id (FK) │
│ operations (JSON), rate_limit_id (FK) │
│ created_by, status │
└────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────┐
│ kb_audit_log │
├────────────────────────────────────────────────────────────────────────┤
│ id (PK), kb_id, user_id, app_id, operation, resource │
│ result: allow|deny, reason, ip_address, user_agent │
│ created_at │
└────────────────────────────────────────────────────────────────────────┘
user_group — 用户组 NEW支持将多个用户组织成组,方便批量授权。组内可包含用户和其他子组(通过 parent_group_id 实现嵌套)。
| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| tenant_id | CHAR(32) | N | - | 所属租户 (FK → tenant.id) |
| name | VARCHAR(128) | N | - | 组名称(同租户内唯一) |
| description | TEXT | Y | NULL | 组描述 |
| parent_group_id | CHAR(32) | Y | NULL | 父组 ID(支持嵌套,最多 3 层) |
| created_by | CHAR(32) | N | - | 创建者 |
| create_time | BIGINT | Y | NOW | 创建时间戳 |
| update_time | BIGINT | Y | NOW | 更新时间戳 |
| status | CHAR(1) | Y | '1' | 状态 0=废弃 1=有效 |
user_group_member — 组成员 NEW| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| group_id | CHAR(32) | N | - | 用户组 ID (FK → user_group.id) |
| user_id | CHAR(32) | N | - | 用户 ID (FK → user.id) |
| create_time | BIGINT | Y | NOW | 加入时间 |
| status | CHAR(1) | Y | '1' | 状态 |
role — 自定义角色 NEW租户可定义自定义角色,配置一组权限。系统预设角色(admin/viewer/editor)不可删除。
| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| tenant_id | CHAR(32) | N | - | 所属租户 |
| name | VARCHAR(128) | N | - | 角色名称(租户内唯一) |
| description | TEXT | Y | NULL | 描述 |
| is_system | BOOLEAN | N | false | 是否为系统预设(不可删除) |
| parent_role_id | CHAR(32) | Y | NULL | 继承自哪个角色 |
| created_by | CHAR(32) | N | - | 创建者 |
| create_time | BIGINT | Y | NOW | 创建时间 |
| update_time | BIGINT | Y | NOW | 更新时间 |
| status | CHAR(1) | Y | '1' | 状态 |
role_permission — 角色权限定义 NEW定义角色拥有的具体操作权限,支持条件表达式实现 ABAC。
| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| role_id | CHAR(32) | N | - | 角色 ID (FK → role.id) |
| resource_type | VARCHAR(32) | N | - | 资源类型:knowledgebase, document, dialog, search, file |
| permission | VARCHAR(64) | N | - | 权限标识(见下方权限码表) |
| effect | VARCHAR(8) | N | 'allow' | 效果:allow | deny |
| conditions | JSON | Y | {} | ABAC 条件(如 {'doc.tag': 'public'}) |
| 权限码 | 资源类型 | 说明 | 危险级别 |
|---|---|---|---|
| kb:read | knowledgebase | 查看知识库列表/详情 | 低 |
| kb:create | knowledgebase | 创建新知识库 | 中 |
| kb:update | knowledgebase | 编辑知识库配置(名称、描述、解析器等) | 中 |
| kb:delete | knowledgebase | 删除知识库 | 高 |
| kb:manage_permission | knowledgebase | 管理知识库权限 | 高 |
| kb:export | knowledgebase | 导出知识库内容 | 中 |
| kb:import | knowledgebase | 导入文档到知识库 | 中 |
| kb:retrieve | knowledgebase | 通过 Dialog/API 检索知识库 | 低 |
| kb:list_docs | knowledgebase | 查看知识库中文档列表 | 低 |
| kb:manage_docs | knowledgebase | 管理文档(解析、重新解析、删除文档) | 中 |
| kb:manage_connector | knowledgebase | 管理数据源连接器 | 中 |
| kb:manage_rate_limit | knowledgebase | 管理限流规则 | 高 |
| kb:run_evaluation | knowledgebase | 运行评测 | 中 |
| kb:use_graphrag | knowledgebase | 使用 GraphRAG 功能 | 中 |
| kb:use_raptor | knowledgebase | 使用 RAPTOR 功能 | 中 |
| kb:use_mindmap | knowledgebase | 使用 Mindmap 功能 | 中 |
kb_permission — 知识库权限策略 NEW ★核心表★将角色与具体的知识库绑定,同时指定授权对象(用户/用户组/角色/应用),可设置数据范围和过期时间。
| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| kb_id | CHAR(32) | N | - | 知识库 ID (FK → knowledgebase.id) |
| grantee_type | VARCHAR(16) | N | - | 授权对象类型:user | group | role | app | token |
| grantee_id | CHAR(32) | N | - | 授权对象 ID |
| role_id | CHAR(32) | Y | NULL | 关联角色 ID (FK → role.id),为 NULL 时需配合 operations 字段使用 |
| operations | JSON | Y | NULL | 直接指定的操作权限列表:["kb:read","kb:retrieve"]。如果指定了 role_id,则从 role 继承 |
| data_scope | JSON | Y | {} | 数据范围过滤:{"doc_ids": [...], "tags": ["public"], "meta_filter": {...}} |
| effect | VARCHAR(8) | N | 'allow' | 效果:allow | deny(deny 优先级高于 allow) |
| priority | INTEGER | N | 0 | 优先级(数值越大优先级越高,用于多策略排序) |
| expires_at | DATETIME | Y | NULL | 过期时间(临时授权场景) |
| created_by | CHAR(32) | N | - | 创建者 |
| create_time | BIGINT | Y | NOW | 创建时间 |
| update_time | BIGINT | Y | NOW | 更新时间 |
| status | CHAR(1) | Y | '1' | 状态 |
{
"doc_ids": ["doc_001", "doc_002"], // 仅限指定文档
"exclude_doc_ids": ["doc_secret"], // 排除特定文档
"tags": ["public", "internal"], // 文档标签过滤
"meta_filter": { // 元数据过滤
"security_level": ["l1", "l2"],
"department": "engineering"
},
"max_token_per_query": 50000 // 单次查询最大 Token
}
kb_rate_limit — 知识库限流规则 NEW| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| kb_id | CHAR(32) | Y | NULL | 知识库 ID(NULL 表示全局规则) |
| name | VARCHAR(128) | N | - | 规则名称 |
| scope_type | VARCHAR(16) | N | 'global' | 作用范围:global | user | group | role | app | token |
| scope_id | CHAR(32) | Y | NULL | 作用对象 ID |
| limit_type | VARCHAR(32) | N | 'qps' | 限流类型:qps | tokens_per_day | requests_per_day | concurrent |
| max_qps | FLOAT | Y | NULL | 每秒最大查询数 |
| max_tokens_per_day | BIGINT | Y | NULL | 每日最大 Token 消耗 |
| max_requests_per_day | INTEGER | Y | NULL | 每日最大请求数 |
| max_concurrent | INTEGER | Y | NULL | 最大并发数 |
| max_tokens_per_request | INTEGER | Y | NULL | 单次请求最大 Token |
| window_seconds | INTEGER | N | 1 | 滑动窗口大小(秒),用于 QPS 计算 |
| burst_multiplier | FLOAT | N | 1.0 | 突发倍数(允许短时突发流量) |
| throttle_action | VARCHAR(16) | N | 'reject' | 超限动作:reject | queue | degrade |
| enabled | BOOLEAN | N | true | 是否启用 |
| created_by | CHAR(32) | N | - | 创建者 |
| create_time | BIGINT | Y | NOW | 创建时间 |
| update_time | BIGINT | Y | NOW | 更新时间 |
| status | CHAR(1) | Y | '1' | 状态 |
token 级别(API Token 专属限流)user 级别(用户专属限流)app 级别(应用级限流)group 级别(用户组限流)kb 全局级别(知识库默认限流)app_kb_binding — 应用-知识库绑定 NEW ★关键★替代 Dialog 中 kb_ids JSON 字段的硬编码方式。应用必须先建立绑定关系才能检索知识库。
| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| app_type | VARCHAR(16) | N | - | 应用类型:dialog | search | agent | api |
| app_id | CHAR(32) | N | - | 应用 ID(dialog.id / search.id / user_canvas.id) |
| kb_id | CHAR(32) | N | - | 知识库 ID (FK → knowledgebase.id) |
| operations | JSON | Y | ["kb:retrieve"] | 允许的操作(通常只用 kb:retrieve) |
| rate_limit_id | CHAR(32) | Y | NULL | 关联的限流规则 (FK → kb_rate_limit.id) |
| data_scope | JSON | Y | {} | 应用级别的数据范围(如客服机器人只能看 FAQ 类文档) |
| created_by | CHAR(32) | N | - | 创建者 |
| create_time | BIGINT | Y | NOW | 创建时间 |
| update_time | BIGINT | Y | NOW | 更新时间 |
| status | CHAR(1) | Y | '1' | 状态 |
kb_ids JSON 字段中的数据需要迁移到 app_kb_binding 表。迁移期间 Dialog 同时支持两种模式。迁移完成后废弃 kb_ids 字段。kb_audit_log — 审计日志 NEW| 字段 | 类型 | 可空 | 默认值 | 说明 |
|---|---|---|---|---|
| id | CHAR(32) | N | UUID | 主键 |
| tenant_id | CHAR(32) | N | - | 租户 ID |
| kb_id | CHAR(32) | Y | NULL | 知识库 ID |
| user_id | CHAR(32) | Y | NULL | 操作用户 ID |
| app_id | CHAR(32) | Y | NULL | 应用 ID(如果是通过应用访问) |
| app_type | VARCHAR(16) | Y | NULL | 应用类型 |
| operation | VARCHAR(64) | N | - | 操作码(如 kb:read, kb:retrieve) |
| resource | VARCHAR(255) | Y | NULL | 操作的资源路径 |
| detail | JSON | Y | {} | 操作详情(请求内容摘要等) |
| result | VARCHAR(8) | N | - | 结果:allow | deny | error |
| reason | VARCHAR(255) | Y | NULL | 拒绝/错误原因 |
| ip_address | VARCHAR(45) | Y | NULL | 请求 IP |
| user_agent | VARCHAR(512) | Y | NULL | User-Agent |
| latency_ms | INTEGER | Y | NULL | 耗时(毫秒) |
| token_used | INTEGER | Y | NULL | 消耗 Token |
| created_at | DATETIME | N | NOW | 创建时间(建议按月分表) |
kb_audit_log_202606),定期归档(保留 6 个月热数据,1 年冷数据)。支持异步写入(Redis Stream → 批量 INSERT)。| 表名 | 改动类型 | 字段 | 说明 |
|---|---|---|---|
knowledgebase |
MODIFY | permission | 原 me | team 扩展为 me | team | custom。custom 模式下启用细粒度权限表,me/team 保留作为快速模式。 |
knowledgebase |
ADD | default_rate_limit_id | 关联默认限流规则 (FK → kb_rate_limit.id) |
api_token |
ADD | kb_scope | JSON: {"kb_ids":["*"], "operations":["kb:retrieve"], "max_qps":10}限制 Token 可访问的 KB 范围和操作 |
dialog |
DEPRECATE | kb_ids | 逐步废弃,由 app_kb_binding 替代 |
user_tenant |
ADD | role_id | 关联自定义角色 (FK → role.id),为 NULL 时使用原有 4 种角色 |
async def check_kb_permission( user_id: str, kb_id: str, operation: str, context: dict = {} # {app_type, app_id, token_id, ...} ) -> PermissionResult: 统一的知识库权限检查入口。 返回 (allowed: bool, data_scope: dict, rate_limit: RateLimitConfig) """ # Step 0: 超级管理员直接放行 if is_superuser(user_id): return ALLOWED_WITH_FULL_SCOPE # Step 1: 检查知识库是否存在且有效 kb = get_kb(kb_id) if not kb or kb.status != '1': return DENIED("KB not found or disabled") # Step 2: 快速模式 - me/team 权限(无 custom 配置时生效) if kb.permission != 'custom': return check_legacy_permission(kb, user_id, operation) # Step 3: Custom 模式 - 收集所有匹配的策略 policies = collect_matching_policies( kb_id=kb_id, user_id=user_id, user_groups=get_user_groups(user_id), user_roles=get_user_roles(user_id, kb.tenant_id), app_type=context.get('app_type'), app_id=context.get('app_id'), token_id=context.get('token_id'), operation=operation, ) # Step 4: 按优先级排序,deny 优先 policies = sorted(policies, key=lambda p: ( 0 if p.effect == 'deny' else 1, # deny 优先 -p.priority # 同类型内按优先级 )) # Step 5: 评估第一条决定性策略 for policy in policies: # ABAC 条件评估 if policy.conditions and not evaluate_conditions(policy.conditions, context): continue if policy.effect == 'deny': return DENIED(f"Denied by policy {policy.id}") # allow 策略:合并数据范围 data_scope = merge_data_scopes([ p.data_scope for p in policies if p.effect == 'allow' ]) # 获取对应的限流配置 rate_limit = get_effective_rate_limit(kb_id, user_id, context) return ALLOWED(data_scope=data_scope, rate_limit=rate_limit) # Step 6: 无匹配策略 → 默认拒绝 return DENIED("No matching permission policy")
def collect_matching_policies(kb_id, user_id, user_groups, user_roles, app_type, app_id, token_id, operation): 从 kb_permission 表查找所有可能匹配的策略。 按授权对象类型分别匹配。 """ policies = [] # 查询 kb_permission 表中该 KB 的所有有效策略 all_kb_policies = KB_Permission.query( kb_id=kb_id, status='1', # 过滤已过期策略 where_clause="(expires_at IS NULL OR expires_at > NOW())" ) for policy in all_kb_policies: matched = False if policy.grantee_type == 'user' and policy.grantee_id == user_id: matched = True elif policy.grantee_type == 'group' and policy.grantee_id in user_groups: matched = True elif policy.grantee_type == 'role' and policy.grantee_id in user_roles: matched = True elif policy.grantee_type == 'app' and f"{app_type}:{app_id}" == policy.grantee_id: matched = True elif policy.grantee_type == 'token' and policy.grantee_id == token_id: matched = True if matched: # 如果策略关联了 role,从 role_permission 加载具体权限 if policy.role_id: role_ops = get_role_permissions(policy.role_id, 'knowledgebase') if operation in role_ops['permissions']: policies.append(policy) elif policy.operations and operation in policy.operations: policies.append(policy) return policies
def check_rate_limit(kb_id, user_id, scope_type, scope_id) -> bool: 检查当前请求是否超过限流阈值。 使用 Redis 滑动窗口实现。 """ # 获取所有匹配的限流规则(从最特化到最通用) rules = get_rate_limit_rules(kb_id, scope_type, scope_id) for rule in rules: if not rule.enabled: continue # Redis 滑动窗口 Key window_key = f"ratelimit:{rule.id}:{scope_id}" if rule.limit_type == 'qps': current = redis_incr_window(window_key, rule.window_seconds) if current > rule.max_qps * rule.burst_multiplier: return False # 限流触发 elif rule.limit_type == 'tokens_per_day': day_key = f"ratelimit:{rule.id}:{scope_id}:tokens:{today()}" current = redis_get(day_key) or 0 if current > rule.max_tokens_per_day: return False # ... 其他限流类型类似 return True # 未触发限流
新增 FEATURE_FINE_GRAINED_PERMISSION 功能开关。关闭时完全按原有 me/team 逻辑运行,零性能损耗。
启动时检查是否需要迁移:将 permission=team 的 KB 自动生成一条 kb_permission 记录,授权给租户全部成员。
并存期内,Dialog 优先读 app_kb_binding 表;若无绑定记录,降级使用 kb_ids JSON 字段。
开启细粒度权限后,若 KB 无任何 kb_permission 记录,默认仅 KB 创建者可访问(安全优先)。
创建所有新表。KB 创建/更新时同时维护 legacy permission 字段和新的 kb_permission 记录。
按租户灰度开启细粒度鉴权。监控性能与错误率。修复问题。
默认开启细粒度权限。遗留 permission 字段标记为 deprecated。提供一键迁移脚本。
移除 legacy 权限代码。Dialog.kb_ids 字段删除。完成全部迁移。
# 新增模型定义(添加到 api/db/db_models.py) class UserGroup(DataBaseModel): id = CharField(max_length=32, primary_key=True) tenant_id = CharField(max_length=32, null=False, index=True) name = CharField(max_length=128, null=False, index=True) description = TextField(null=True) parent_group_id = CharField(max_length=32, null=True, index=True) created_by = CharField(max_length=32, null=False, index=True) status = CharField(max_length=1, null=True, default="1", index=True) class Meta: db_table = "user_group" indexes = ( (("tenant_id", "name"), True), # UNIQUE ) class UserGroupMember(DataBaseModel): id = CharField(max_length=32, primary_key=True) group_id = CharField(max_length=32, null=False, index=True) user_id = CharField(max_length=32, null=False, index=True) status = CharField(max_length=1, null=True, default="1", index=True) class Meta: db_table = "user_group_member" indexes = ( (("group_id", "user_id"), True), ) class Role(DataBaseModel): id = CharField(max_length=32, primary_key=True) tenant_id = CharField(max_length=32, null=False, index=True) name = CharField(max_length=128, null=False, index=True) description = TextField(null=True) is_system = BooleanField(null=False, default=False) parent_role_id = CharField(max_length=32, null=True, index=True) created_by = CharField(max_length=32, null=False, index=True) status = CharField(max_length=1, null=True, default="1", index=True) class Meta: db_table = "role" indexes = ( (("tenant_id", "name"), True), ) class RolePermission(DataBaseModel): id = CharField(max_length=32, primary_key=True) role_id = CharField(max_length=32, null=False, index=True) resource_type = CharField(max_length=32, null=False, index=True) permission = CharField(max_length=64, null=False, index=True) effect = CharField(max_length=8, null=False, default="allow") conditions = JSONField(null=True, default={}) class Meta: db_table = "role_permission" class KBPermission(DataBaseModel): id = CharField(max_length=32, primary_key=True) kb_id = CharField(max_length=32, null=False, index=True) grantee_type = CharField(max_length=16, null=False, index=True) grantee_id = CharField(max_length=32, null=False, index=True) role_id = CharField(max_length=32, null=True, index=True) operations = JSONField(null=True, default=[]) data_scope = JSONField(null=True, default={}) effect = CharField(max_length=8, null=False, default="allow") priority = IntegerField(null=False, default=0) expires_at = DateTimeField(null=True, index=True) created_by = CharField(max_length=32, null=False, index=True) status = CharField(max_length=1, null=True, default="1", index=True) class Meta: db_table = "kb_permission" class KBRateLimit(DataBaseModel): id = CharField(max_length=32, primary_key=True) kb_id = CharField(max_length=32, null=True, index=True) name = CharField(max_length=128, null=False) scope_type = CharField(max_length=16, null=False, default="global", index=True) scope_id = CharField(max_length=32, null=True, index=True) limit_type = CharField(max_length=32, null=False, default="qps") max_qps = FloatField(null=True) max_tokens_per_day = BigIntegerField(null=True) max_requests_per_day = IntegerField(null=True) max_concurrent = IntegerField(null=True) max_tokens_per_request = IntegerField(null=True) window_seconds = IntegerField(null=False, default=1) burst_multiplier = FloatField(null=False, default=1.0) throttle_action = CharField(max_length=16, null=False, default="reject") enabled = BooleanField(null=False, default=True) created_by = CharField(max_length=32, null=False, index=True) status = CharField(max_length=1, null=True, default="1", index=True) class Meta: db_table = "kb_rate_limit" class AppKBBinding(DataBaseModel): id = CharField(max_length=32, primary_key=True) app_type = CharField(max_length=16, null=False, index=True) app_id = CharField(max_length=32, null=False, index=True) kb_id = CharField(max_length=32, null=False, index=True) operations = JSONField(null=True, default=["kb:retrieve"]) rate_limit_id = CharField(max_length=32, null=True, index=True) data_scope = JSONField(null=True, default={}) created_by = CharField(max_length=32, null=False, index=True) status = CharField(max_length=1, null=True, default="1", index=True) class Meta: db_table = "app_kb_binding" indexes = ( (("app_type", "app_id", "kb_id"), True), ) class KBAuditLog(DataBaseModel): id = CharField(max_length=32, primary_key=True) tenant_id = CharField(max_length=32, null=False, index=True) kb_id = CharField(max_length=32, null=True, index=True) user_id = CharField(max_length=32, null=True, index=True) app_id = CharField(max_length=32, null=True, index=True) app_type = CharField(max_length=16, null=True) operation = CharField(max_length=64, null=False, index=True) resource = CharField(max_length=255, null=True) detail = JSONField(null=True, default={}) result = CharField(max_length=8, null=False, index=True) reason = CharField(max_length=255, null=True) ip_address = CharField(max_length=45, null=True) user_agent = CharField(max_length=512, null=True) latency_ms = IntegerField(null=True) token_used = IntegerField(null=True) created_at = DateTimeField(null=False, index=True, default=datetime.now) class Meta: db_table = "kb_audit_log"
| 阶段 | 版本 | 内容 | 优先级 | 预估工期 |
|---|---|---|---|---|
| Phase 1 | v2.0-alpha |
基础设施搭建 • 新增 user_group, user_group_member 表及 CRUD API• 新增 role, role_permission 表及 CRUD API• 新增 kb_permission 表及核心鉴权 API• 新增 app_kb_binding 表及绑定管理 API• 实现统一鉴权中间件 check_kb_permission• 默认关闭细粒度权限(feature flag) |
P0 | 4-5 周 |
| Phase 2 | v2.0-beta |
限流与审计 • 新增 kb_rate_limit 表及 CRUD API• 实现 Redis 滑动窗口限流器 • 新增 kb_audit_log 表及异步日志写入• Legacy → new 数据迁移脚本 • 管理后台 UI: 用户组管理页面 |
P0 | 4-5 周 |
| Phase 3 | v2.1 |
前端与灰度 • KB 细粒度权限配置 UI(替换 me/team 下拉框) • Dialog/Search 绑定 KB UI 改动 • 限流规则配置 UI • Token KB 范围配置 UI • 按租户灰度开启 + 监控 • 审计日志查询界面 |
P1 | 5-6 周 |
| Phase 4 | v2.2 |
高级特性 • ABAC 条件表达式引擎 • 数据级权限(含 document tag 过滤) • 权限模板/权限集(一键授权) • 权限变更通知 • 权限导入/导出 • 性能优化(权限缓存层) |
P1 | 5-6 周 |
| Phase 5 | v3.0 |
清理与收尾 • 移除遗留 permission 字段 • 移除 Dialog.kb_ids JSON 字段 • 全量默认开启细粒度权限 • 文档与 SDK 更新 |
P2 | 2-3 周 |
| 角色名称 | is_system | 包含权限 | 说明 |
|---|---|---|---|
| KB Admin | true | kb:read, kb:update, kb:delete, kb:manage_permission, kb:export, kb:import, kb:list_docs, kb:manage_docs, kb:manage_connector, kb:manage_rate_limit, kb:retrieve, kb:run_evaluation, kb:use_graphrag, kb:use_raptor, kb:use_mindmap | 知识库完全管理权限 |
| KB Editor | true | kb:read, kb:update, kb:import, kb:list_docs, kb:manage_docs, kb:manage_connector, kb:retrieve, kb:run_evaluation | 可编辑、导入文档,无法删除 KB 和管理权限 |
| KB Viewer | true | kb:read, kb:list_docs, kb:retrieve | 只读访问(查看和检索) |
| KB Retriever | true | kb:retrieve | 仅检索(适用于 API Token) |
| KB No Access | true | 无 | 显式禁止访问(deny 策略) |
每个用户的 KB 权限计算结果缓存在 Redis,TTL 5 分钟。权限变更时主动失效。热点 KB 权限可本地内存缓存。
用户登录时异步预计算该用户对所有 KB 的有效权限摘要({kb_id: {operations: [...], data_scope: {...}}}),存入 Redis。
kb_permission 按 (kb_id, grantee_type, grantee_id) 复合索引查询;app_kb_binding 按 (app_type, app_id) 查询。
审计日志通过 Redis Stream 异步批量写入,不影响请求主链路。高峰期可降级采样写入。
| 决策点 | 选择 | 理由 |
|---|---|---|
| 权限模型 | RBAC + ABAC 混合 | RBAC 覆盖角色层面的批量管理,ABAC 覆盖按属性的动态过滤 |
| 策略效果 | Deny 优先于 Allow | 安全优先:一条 deny 策略即可阻止访问 |
| 策略冲突 | 高优先级覆盖低优先级 | 允许精细化例外处理 |
| 默认行为 | 默认拒绝(无策略 = 不可访问) | 显式授权原则,避免权限泄露 |
| 限流实现 | Redis 滑动窗口 | 高性能、分布式友好、精确 |
| 权限缓存 | Redis + 本地内存双层 | 减少 DB 压力,支持高 QPS 场景 |
| 向后兼容 | Feature flag + 双模式 | 平滑迁移,零停机 |
| 审计日志 | 异步批量写 + 按月分表 | 不影响主链路性能,便于归档管理 |