Coze源码分析-资源库-创建知识库-后端源码-应用/领域/数据访问
3. 应用服务层
3.1 知识库应用服务
文件位置: backend/application/knowledge/knowledge.go
func (k *KnowledgeApplicationService) CreateKnowledge(ctx context.Context, req *dataset.CreateDatasetRequest) (*dataset.CreateDatasetResponse, error) {// 1. 转换文档类型documentType := convertDocumentTypeDataset2Entity(req.FormatType)if documentType == model.DocumentTypeUnknown {return dataset.NewCreateDatasetResponse(), errors.New("unknown document type")}// 2. 用户身份验证uid := ctxutil.GetUIDFromCtx(ctx)if uid == nil {return nil, errorx.New(errno.ErrKnowledgePermissionCode, errorx.KV("msg", "session required"))}// 3. 构建创建请求createReq := service.CreateKnowledgeRequest{Name: req.Name,Description: req.Description,CreatorID: ptr.From(uid),SpaceID: req.SpaceID,AppID: req.GetProjectID(),FormatType: documentType,IconUri: req.IconURI,}if req.IconURI == "" {createReq.IconUri = getIconURI(req.GetFormatType())}// 4. 执行创建操作domainResp, err := k.DomainSVC.CreateKnowledge(ctx, &createReq)if err != nil {logs.CtxErrorf(ctx, "create knowledge failed, err: %v", err)return dataset.NewCreateDatasetResponse(), err}// 5. 发布创建事件var ptrAppID *int64if req.ProjectID != 0 {ptrAppID = ptr.Of(req.ProjectID)}err = k.eventBus.PublishResources(ctx, &resourceEntity.ResourceDomainEvent{OpType: resourceEntity.Created,Resource: &resourceEntity.ResourceDocument{ResType: resource.ResType_Knowledge,ResID: domainResp.KnowledgeID,Name: ptr.Of(req.Name),ResSubType: ptr.Of(int32(req.FormatType)),SpaceID: ptr.Of(req.SpaceID),APPID: ptrAppID,OwnerID: ptr.Of(*uid),PublishStatus: ptr.Of(resource.PublishStatus_Published),PublishTimeMS: ptr.Of(domainResp.CreatedAtMs),CreateTimeMS: ptr.Of(domainResp.CreatedAtMs),UpdateTimeMS: ptr.Of(domainResp.CreatedAtMs),},})if err != nil {logs.CtxErrorf(ctx, "publish resource event failed, err: %v", err)return dataset.NewCreateDatasetResponse(), err}// 6. 返回创建结果return &dataset.CreateDatasetResponse{DatasetID: domainResp.KnowledgeID,}, nil
}
3.2 应用服务特点
知识库应用服务的主要特点:
- 类型转换: 将前端请求的数据类型转换为领域层所需的类型
- 身份验证: 从上下文中获取用户身份信息
- 事件发布: 创建成功后发布资源创建事件,用于搜索索引等
- 错误处理: 统一的错误处理和日志记录
- 图标处理: 自动为知识库设置默认图标
3.3 事件驱动架构
知识库创建完成后,系统会发布资源创建事件:
// 发布创建事件
err = k.eventBus.PublishResources(ctx, &resourceEntity.ResourceDomainEvent{OpType: resourceEntity.Created,Resource: &resourceEntity.ResourceDocument{ResType: resource.ResType_Knowledge,ResID: domainResp.KnowledgeID,Name: ptr.Of(req.Name),ResSubType: ptr.Of(int32(req.FormatType)),SpaceID: ptr.Of(req.SpaceID),APPID: ptrAppID,OwnerID: ptr.Of(*uid),PublishStatus: ptr.Of(resource.PublishStatus_Published),PublishTimeMS: ptr.Of(domainResp.CreatedAtMs),CreateTimeMS: ptr.Of(domainResp.CreatedAtMs),UpdateTimeMS: ptr.Of(domainResp.CreatedAtMs),},
})
这个事件会被搜索服务监听,用于更新搜索索引,确保新创建的知识库能够被搜索到。
4. 领域服务层
4.1 知识库领域服务
文件位置: backend/domain/knowledge/service/interface.go
知识库领域服务接口定义:
type Knowledge interface {CreateKnowledge(ctx context.Context, request *CreateKnowledgeRequest) (response *CreateKnowledgeResponse, err error)UpdateKnowledge(ctx context.Context, request *UpdateKnowledgeRequest) errorDeleteKnowledge(ctx context.Context, request *DeleteKnowledgeRequest) errorCopyKnowledge(ctx context.Context, request *CopyKnowledgeRequest) (*CopyKnowledgeResponse, error)MoveKnowledgeToLibrary(ctx context.Context, request *MoveKnowledgeToLibraryRequest) errorListKnowledge(ctx context.Context, request *ListKnowledgeRequest) (response *ListKnowledgeResponse, err error)GetKnowledgeByID(ctx context.Context, request *GetKnowledgeByIDRequest) (response *GetKnowledgeByIDResponse, err error)MGetKnowledgeByID(ctx context.Context, request *MGetKnowledgeByIDRequest) (response *MGetKnowledgeByIDResponse, err error)CreateDocument(ctx context.Context, request *CreateDocumentRequest) (response *CreateDocumentResponse, err error)UpdateDocument(ctx context.Context, request *UpdateDocumentRequest) errorDeleteDocument(ctx context.Context, request *DeleteDocumentRequest) errorListDocument(ctx context.Context, request *ListDocumentRequest) (response *ListDocumentResponse, err error)CreateSlice(ctx context.Context, request *CreateSliceRequest) (response *CreateSliceResponse, err error)UpdateSlice(ctx context.Context, request *UpdateSliceRequest) errorDeleteSlice(ctx context.Context, request *DeleteSliceRequest) errorListSlice(ctx context.Context, request *ListSliceRequest) (response *ListSliceResponse, err error)Retrieve(ctx context.Context, request *RetrieveRequest) (response *RetrieveResponse, err error)
}type CreateKnowledgeRequest struct {Name stringDescription stringCreatorID int64SpaceID int64IconUri stringFormatType knowledge.DocumentTypeAppID int64
}type CreateKnowledgeResponse struct {KnowledgeID int64CreatedAtMs int64
}
4.2 领域服务实现特点
知识库领域服务的实现特点:
- 接口抽象: 通过接口定义清晰的服务边界
- 类型安全: 使用强类型的请求和响应结构
- 业务逻辑封装: 将核心业务逻辑封装在领域层
- 依赖注入: 通过接口依赖其他服务,便于测试和扩展
- 错误处理: 统一的错误处理机制
4.3 实体定义
文件位置: backend/domain/knowledge/entity/knowledge.go
// Knowledge 知识库实体
type Knowledge struct {ID int64 `json:"id"`SpaceID int64 `json:"space_id"`CreatorID int64 `json:"creator_id"`Name string `json:"name"`Description string `json:"description"`IconURI string `json:"icon_uri"`FormatType int32 `json:"format_type"`Status int32 `json:"status"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`
}// Document 文档实体
type Document struct {ID int64 `json:"id"`KnowledgeID int64 `json:"knowledge_id"`Name string `json:"name"`FileExtension string `json:"file_extension"`FileSize int64 `json:"file_size"`Content string `json:"content"`Status int32 `json:"status"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`
}// Slice 分片实体
type Slice struct {ID int64 `json:"id"`DocumentID int64 `json:"document_id"`Content string `json:"content"`Vector []float32 `json:"vector"`Position int64 `json:"position"`WordCount int32 `json:"word_count"`CreatedAt time.Time `json:"created_at"`UpdatedAt time.Time `json:"updated_at"`
}
5. 数据访问层
数据访问层负责知识库相关数据的持久化操作,采用Repository模式和DAO模式相结合的设计,确保数据访问的一致性和可维护性。
5.1 知识库Repository接口定义
文件位置: backend/domain/knowledge/repository/repository.go
知识库数据访问层采用Repository模式,提供统一的数据访问接口:
// Repository 知识库仓储接口
type Repository interface {// 知识库相关操作CreateKnowledge(ctx context.Context, knowledge *entity.Knowledge) (*entity.Knowledge, error)UpdateKnowledge(ctx context.Context, knowledge *entity.Knowledge) errorDeleteKnowledge(ctx context.Context, knowledgeID int64) errorGetKnowledge(ctx context.Context, knowledgeID int64) (*entity.Knowledge, error)ListKnowledge(ctx context.Context, req *ListKnowledgeRequest) ([]*entity.Knowledge, int64, error)// 文档相关操作CreateDocument(ctx context.Context, document *entity.Document) (*entity.Document, error)UpdateDocument(ctx context.Context, document *entity.Document) errorDeleteDocument(ctx context.Context, documentID int64) errorGetDocument(ctx context.Context, documentID int64) (*entity.Document, error)ListDocument(ctx context.Context, req *ListDocumentRequest) ([]*entity.Document, int64, error)// 分片相关操作CreateSlice(ctx context.Context, slice *entity.Slice) (*entity.Slice, error)UpdateSlice(ctx context.Context, slice *entity.Slice) errorDeleteSlice(ctx context.Context, sliceID int64) errorGetSlice(ctx context.Context, sliceID int64) (*entity.Slice, error)ListSlice(ctx context.Context, req *ListSliceRequest) ([]*entity.Slice, int64, error)// 检索相关操作VectorSearch(ctx context.Context, req *VectorSearchRequest) ([]*entity.Slice, error)FullTextSearch(ctx context.Context, req *FullTextSearchRequest) ([]*entity.Slice, error)
}type ListKnowledgeRequest struct {SpaceID *int64AppID *int64CreatorID *int64Query stringPage *intPageSize *intOrder *stringOrderType *string
}
创建方法特点
- 事务支持: 所有写操作都支持事务,确保数据一致性
- 类型安全: 使用强类型参数,避免运行时错误
- 错误处理: 详细的错误信息,便于问题定位
- 批量操作: 支持批量创建,提高性能
5.2 数据访问对象(DAO)
文件位置: backend/domain/knowledge/internal/dal/dao/
// KnowledgeDAO 知识库数据访问对象
type KnowledgeDAO struct {db *gorm.DB
}func (dao *KnowledgeDAO) Create(ctx context.Context, tx *sql.Tx, knowledge *model.Knowledge) (int64, error) {// 1. 生成知识库IDknowledgeID, err := dao.idGenerator.GenerateID()if err != nil {return 0, fmt.Errorf("生成知识库ID失败: %w", err)}// 2. 设置创建时间now := time.Now()knowledge.ID = knowledgeIDknowledge.CreatedAt = nowknowledge.UpdatedAt = now// 3. 执行插入操作result := dao.db.WithContext(ctx).Create(knowledge)if result.Error != nil {return 0, fmt.Errorf("创建知识库失败: %w", result.Error)}return knowledgeID, nil
}// KnowledgeDocumentDAO 知识库文档数据访问对象
type KnowledgeDocumentDAO struct {db *gorm.DB
}func (dao *KnowledgeDocumentDAO) Create(ctx context.Context, tx *sql.Tx, doc *model.KnowledgeDocument) (int64, error) {// 1. 生成文档IDdocID, err := dao.idGenerator.GenerateID()if err != nil {return 0, fmt.Errorf("生成文档ID失败: %w", err)}// 2. 设置文档属性now := time.Now()doc.ID = docIDdoc.CreatedAt = nowdoc.UpdatedAt = now// 3. 执行插入操作result := dao.db.WithContext(ctx).Create(doc)if result.Error != nil {return 0, fmt.Errorf("创建知识库文档失败: %w", result.Error)}return docID, nil
}// KnowledgeDocumentSliceDAO 知识库文档分片数据访问对象
type KnowledgeDocumentSliceDAO struct {db *gorm.DB
}func (dao *KnowledgeDocumentSliceDAO) Create(ctx context.Context, tx *sql.Tx, slice *model.KnowledgeDocumentSlice) (int64, error) {// 1. 生成分片IDsliceID, err := dao.idGenerator.GenerateID()if err != nil {return 0, fmt.Errorf("生成分片ID失败: %w", err)}// 2. 设置分片属性now := time.Now()slice.ID = sliceIDslice.CreatedAt = nowslice.UpdatedAt = now// 3. 执行插入操作result := dao.db.WithContext(ctx).Create(slice)if result.Error != nil {return 0, fmt.Errorf("创建知识库文档分片失败: %w", result.Error)}return sliceID, nil
}
创建操作特点
- ID生成: 使用分布式ID生成器,确保ID全局唯一
- 时间戳: 自动设置创建和更新时间
- 事务处理: 支持事务操作,保证数据一致性
- 错误处理: 详细的错误信息和日志记录
5.3 完整数据访问流程
知识库创建涉及的数据表:
- knowledge: 知识库主表
- knowledge_document: 知识库文档表
- knowledge_document_slice: 文档分片表
- knowledge_config: 知识库配置表
数据访问流程:
- 开启数据库事务
- 创建知识库主记录
- 初始化知识库配置
- 提交事务
- 发布创建事件
5.4 数据模型定义
文件位置: backend/domain/knowledge/internal/dal/model/
// Knowledge 知识库数据模型
type Knowledge struct {ID int64 `gorm:"column:id;primaryKey" json:"id"`SpaceID int64 `gorm:"column:space_id;not null" json:"space_id"`CreatorID int64 `gorm:"column:creator_id;not null" json:"creator_id"`Name string `gorm:"column:name;size:100;not null" json:"name"`Description string `gorm:"column:description;size:1000" json:"description"`Type int32 `gorm:"column:type;not null" json:"type"`Config string `gorm:"column:config;type:text" json:"config"`Status int32 `gorm:"column:status;not null" json:"status"`CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`UpdatedAt time.Time `gorm:"column:updated_at;not null" json:"updated_at"`
}func (Knowledge) TableName() string {return "knowledge"
}// KnowledgeDocument 知识库文档数据模型
type KnowledgeDocument struct {ID int64 `gorm:"column:id;primaryKey" json:"id"`KnowledgeID int64 `gorm:"column:knowledge_id;not null" json:"knowledge_id"`Name string `gorm:"column:name;size:255;not null" json:"name"`FileExtension string `gorm:"column:file_extension;size:10" json:"file_extension"`FileSize int64 `gorm:"column:file_size" json:"file_size"`Content string `gorm:"column:content;type:longtext" json:"content"`Status int32 `gorm:"column:status;not null" json:"status"`CreatedAt time.Time `gorm:"column:created_at;not null" json:"created_at"`UpdatedAt time.Time `gorm:"column:updated_at;not null" json:"updated_at"`
}func (KnowledgeDocument) TableName() string {return "knowledge_document"
}
5.5 查询生成器
文件位置: backend/domain/knowledge/internal/dal/query/
GORM生成的查询接口,提供类型安全的查询方法:
// 知识库查询接口
type IKnowledgeDo interface {gen.SubQueryDebug() IKnowledgeDoWithContext(ctx context.Context) IKnowledgeDoWithResult(fc func(tx gen.Dao)) gen.ResultInfoReplaceDB(db *gorm.DB)ReadDB() IKnowledgeDoWriteDB() IKnowledgeDoAs(alias string) gen.DaoSession(config *gorm.Session) IKnowledgeDoColumns(cols ...field.Expr) gen.ColumnsClauses(conds ...clause.Expression) IKnowledgeDoNot(conds ...gen.Condition) IKnowledgeDoOr(conds ...gen.Condition) IKnowledgeDoSelect(conds ...field.Expr) IKnowledgeDoWhere(conds ...gen.Condition) IKnowledgeDoOrder(conds ...field.Expr) IKnowledgeDoDistinct(cols ...field.Expr) IKnowledgeDoOmit(cols ...field.Expr) IKnowledgeDoJoin(table schema.Tabler, on ...field.Expr) IKnowledgeDoLeftJoin(table schema.Tabler, on ...field.Expr) IKnowledgeDoRightJoin(table schema.Tabler, on ...field.Expr) IKnowledgeDoGroup(cols ...field.Expr) IKnowledgeDoHaving(conds ...gen.Condition) IKnowledgeDoLimit(limit int) IKnowledgeDoOffset(offset int) IKnowledgeDoCount() (count int64, err error)Scopes(funcs ...func(gen.Dao) gen.Dao) IKnowledgeDoUnscoped() IKnowledgeDoCreate(values ...*model.Knowledge) errorCreateInBatches(values []*model.Knowledge, batchSize int) errorSave(values ...*model.Knowledge) errorFirst() (*model.Knowledge, error)Take() (*model.Knowledge, error)Last() (*model.Knowledge, error)Find() ([]*model.Knowledge, error)FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*model.Knowledge, err error)FindInBatches(result *[]*model.Knowledge, batchSize int, fc func(tx gen.Dao, batch int) error) errorPluck(column field.Expr, dest interface{}) errorDelete(...*model.Knowledge) (info gen.ResultInfo, err error)Update(column field.Expr, value interface{}) (info gen.ResultInfo, err error)UpdateSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)Updates(value interface{}) (info gen.ResultInfo, err error)UpdateColumn(column field.Expr, value interface{}) (info gen.ResultInfo, err error)UpdateColumnSimple(columns ...field.AssignExpr) (info gen.ResultInfo, err error)UpdateColumns(value interface{}) (info gen.ResultInfo, err error)UpdateFrom(q gen.SubQuery) gen.DaoAttrs(attrs ...field.AssignExpr) IKnowledgeDoAssign(attrs ...field.AssignExpr) IKnowledgeDoJoins(fields ...field.RelationField) IKnowledgeDoPreload(fields ...field.RelationField) IKnowledgeDoFirstOrInit() (*model.Knowledge, error)FirstOrCreate() (*model.Knowledge, error)FindByPage(offset int, limit int) (result []*model.Knowledge, count int64, err error)ScanByPage(result interface{}, offset int, limit int) (count int64, err error)Scan(result interface{}) (err error)Returning(value interface{}, columns ...string) IKnowledgeDoUnderlyingDB() *gorm.DBschema.Tabler
}