内容与知识管理仓库模块
概述
这个模块是整个知识管理系统的数据访问层核心,负责所有核心业务实体的持久化和检索。想象它是一个"图书馆管理员",专门管理知识文档、对话记录、标签和模型配置的存取,确保数据一致性、租户隔离和高效查询。
为什么需要这个模块?
- 在一个多租户的知识管理系统中,需要统一管理知识库、文档块、会话、消息等核心数据
- 不同业务场景对数据访问有不同需求:批量写入、复杂查询、分页、统计等
- 需要确保租户数据隔离,防止数据越权访问
- 需要处理数据库差异(如 PostgreSQL 和 MySQL 的 JSON 查询语法不同)
架构概览
graph TB
A[内容与知识管理仓库] --> B[知识与语料存储仓库]
A --> C[对话历史仓库]
A --> D[标签与引用计数仓库]
A --> E[模型目录仓库]
B --> B1[知识记录持久化]
B --> B2[文档块持久化]
B --> B3[知识库元数据持久化]
C --> C1[会话记录持久化]
C --> C2[消息历史与追踪持久化]
D --> D1[标签管理]
D --> D2[引用计数统计]
E --> E1[模型配置管理]
核心组件说明
- knowledgeBaseRepository:知识库元数据管理,负责知识库的创建、查询、更新和删除
- knowledgeRepository:知识记录管理,处理文档、URL、FAQ 等知识项的持久化
- chunkRepository:文档块管理,是最复杂的仓库之一,处理知识切分后的小块数据
- sessionRepository:会话管理,负责用户对话会话的持久化
- messageRepository:消息管理,处理会话中的消息历史
- knowledgeTagRepository:标签管理,负责知识和文档块的标签组织
- modelRepository:模型管理,处理 LLM 和嵌入模型的配置
数据流程
知识导入流程
- 首先通过
knowledgeBaseRepository获取或创建知识库 - 使用
knowledgeRepository创建知识记录(包含文件元数据) - 文档解析后,通过
chunkRepository批量创建文档块 - 可选地通过
knowledgeTagRepository为知识和文档块添加标签
对话流程
- 通过
sessionRepository创建或获取会话 - 用户发送消息后,通过
messageRepository保存用户消息 - 系统生成回复后,通过
messageRepository保存助手消息 - 会话结束时,通过
sessionRepository更新会话元数据
关键设计决策
1. 租户隔离策略
选择:几乎所有查询都包含 tenant_id 条件
原因:
- 确保多租户环境下的数据安全
- 防止跨租户数据访问
- 简化权限检查逻辑
2. 批量操作优化
选择:使用 CreateInBatches、批量 SQL 等方式处理大量数据
例子:
chunkRepository.CreateChunks使用 100 条一批的批量插入chunkRepository.UpdateChunks使用 CASE 表达式实现批量更新 原因:- 减少数据库 round-trips
- 提高大规模知识导入的性能
- 降低数据库负载
3. 数据库兼容性处理
选择:在代码中检测数据库类型并使用不同的 SQL 语法
例子:chunkRepository.ListPagedChunksByKnowledgeID 中对 PostgreSQL 和 MySQL 的 JSON 查询处理
原因:
- 支持多种数据库部署环境
- 利用不同数据库的特性优化查询
- 避免 ORM 抽象带来的性能损失
4. 选择性字段更新
选择:区分全量更新和部分字段更新 例子:
chunkRepository.UpdateChunk使用Save更新所有字段chunkRepository.UpdateChunks只更新特定字段(content, is_enabled, tag_id, flags, status, updated_at) 原因:- 防止意外覆盖重要字段(如 metadata, content_hash)
- 提高批量更新的性能
- 明确表达业务意图
5. 软删除 vs 硬删除
选择:部分表使用软删除(如 knowledges),部分使用硬删除 原因:
- 知识记录可能需要恢复,使用软删除
- 会话和消息等实时数据不需要恢复,使用硬删除提高性能
- 根据业务需求灵活选择
子模块
知识与语料存储仓库
负责知识库、知识记录和文档块的持久化,是模块的核心。
对话历史仓库
管理会话和消息的持久化,支持对话历史的检索和回放。
标签与引用计数仓库
处理知识和文档块的标签组织,以及标签引用统计。
模型目录仓库
管理 LLM 和嵌入模型的配置,支持内置模型和租户自定义模型。
跨模块依赖
这个模块是整个系统的基础设施层,被多个上层模块依赖:
它依赖的核心模块:
- 核心领域类型:定义了所有数据模型和接口契约
- 平台基础设施:提供数据库连接和事务管理
使用指南
基本使用模式
所有仓库都通过构造函数创建,接受 *gorm.DB 作为参数:
// 创建知识库仓库
kbRepo := NewKnowledgeBaseRepository(db)
// 创建知识仓库
knowledgeRepo := NewKnowledgeRepository(db)
// 创建文档块仓库
chunkRepo := NewChunkRepository(db)
租户隔离
所有查询都应该包含 tenant_id 条件,除非有特殊需求(如权限检查):
// ✅ 正确:包含租户 ID
knowledge, err := knowledgeRepo.GetKnowledgeByID(ctx, tenantID, knowledgeID)
// ⚠️ 谨慎:不包含租户 ID(仅用于权限解析等特殊场景)
knowledge, err := knowledgeRepo.GetKnowledgeByIDOnly(ctx, knowledgeID)
批量操作
对于大量数据,优先使用批量操作:
// 批量创建文档块
chunks := []*types.Chunk{...}
err := chunkRepo.CreateChunks(ctx, chunks)
// 批量更新文档块
err := chunkRepo.UpdateChunks(ctx, chunks)
注意事项
1. GORM 的零值处理
GORM 默认会忽略零值字段的更新,需要特别注意:
- 使用
Select("*")来强制更新所有字段 - 或者使用原生 SQL 来避免这个问题
2. 数据库类型差异
代码中已经处理了 PostgreSQL 和 MySQL 的差异,但在添加新功能时需要注意:
- JSON 查询语法不同
- 布尔类型处理不同
- 字符串大小写敏感性不同
3. 事务处理
仓库层不处理事务,事务应该在服务层管理:
// 在服务层开始事务
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
// 使用事务创建仓库实例
knowledgeRepo := NewKnowledgeRepository(tx)
chunkRepo := NewChunkRepository(tx)
// 执行操作
err := knowledgeRepo.CreateKnowledge(ctx, knowledge)
err = chunkRepo.CreateChunks(ctx, chunks)
// 提交事务
tx.Commit()
4. 性能考虑
- 对于大量数据,使用批量操作
- 对于复杂查询,考虑添加索引
- 对于统计查询,使用批量计数而不是逐个计数
5. 数据一致性
- 更新知识时,注意同步更新相关的文档块
- 删除知识库时,注意级联删除相关的知识和文档块
- 更新标签时,注意同步更新引用该标签的知识和文档块