Coze源码分析-资源库-创建知识库-后端源码-应用/领域/数据访问

3. 应用服务层

3.1 知识库应用服务

文件位置 : backend/application/knowledge/knowledge.go

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 *int64
    if 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 事件驱动架构

知识库创建完成后,系统会发布资源创建事件:

go 复制代码
// 发布创建事件
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

知识库领域服务接口定义:

go 复制代码
type Knowledge interface {
    CreateKnowledge(ctx context.Context, request *CreateKnowledgeRequest) (response *CreateKnowledgeResponse, err error)
    UpdateKnowledge(ctx context.Context, request *UpdateKnowledgeRequest) error
    DeleteKnowledge(ctx context.Context, request *DeleteKnowledgeRequest) error
    CopyKnowledge(ctx context.Context, request *CopyKnowledgeRequest) (*CopyKnowledgeResponse, error)
    MoveKnowledgeToLibrary(ctx context.Context, request *MoveKnowledgeToLibraryRequest) error
    ListKnowledge(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) error
    DeleteDocument(ctx context.Context, request *DeleteDocumentRequest) error
    ListDocument(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) error
    DeleteSlice(ctx context.Context, request *DeleteSliceRequest) error
    ListSlice(ctx context.Context, request *ListSliceRequest) (response *ListSliceResponse, err error)
    Retrieve(ctx context.Context, request *RetrieveRequest) (response *RetrieveResponse, err error)
}

type CreateKnowledgeRequest struct {
    Name        string
    Description string
    CreatorID   int64
    SpaceID     int64
    IconUri     string
    FormatType  knowledge.DocumentType
    AppID       int64
}

type CreateKnowledgeResponse struct {
    KnowledgeID int64
    CreatedAtMs int64
}

4.2 领域服务实现特点

知识库领域服务的实现特点:

  • 接口抽象: 通过接口定义清晰的服务边界
  • 类型安全: 使用强类型的请求和响应结构
  • 业务逻辑封装: 将核心业务逻辑封装在领域层
  • 依赖注入: 通过接口依赖其他服务,便于测试和扩展
  • 错误处理: 统一的错误处理机制

4.3 实体定义

文件位置 : backend/domain/knowledge/entity/knowledge.go

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模式,提供统一的数据访问接口:

go 复制代码
// Repository 知识库仓储接口
type Repository interface {
    // 知识库相关操作
    CreateKnowledge(ctx context.Context, knowledge *entity.Knowledge) (*entity.Knowledge, error)
    UpdateKnowledge(ctx context.Context, knowledge *entity.Knowledge) error
    DeleteKnowledge(ctx context.Context, knowledgeID int64) error
    GetKnowledge(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) error
    DeleteDocument(ctx context.Context, documentID int64) error
    GetDocument(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) error
    DeleteSlice(ctx context.Context, sliceID int64) error
    GetSlice(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    *int64
    AppID      *int64
    CreatorID  *int64
    Query      string
    Page       *int
    PageSize   *int
    Order      *string
    OrderType  *string
}
创建方法特点
  • 事务支持: 所有写操作都支持事务,确保数据一致性
  • 类型安全: 使用强类型参数,避免运行时错误
  • 错误处理: 详细的错误信息,便于问题定位
  • 批量操作: 支持批量创建,提高性能

5.2 数据访问对象(DAO)

文件位置 : backend/domain/knowledge/internal/dal/dao/

go 复制代码
// KnowledgeDAO 知识库数据访问对象
type KnowledgeDAO struct {
    db *gorm.DB
}

func (dao *KnowledgeDAO) Create(ctx context.Context, tx *sql.Tx, knowledge *model.Knowledge) (int64, error) {
    // 1. 生成知识库ID
    knowledgeID, err := dao.idGenerator.GenerateID()
    if err != nil {
        return 0, fmt.Errorf("生成知识库ID失败: %w", err)
    }
    
    // 2. 设置创建时间
    now := time.Now()
    knowledge.ID = knowledgeID
    knowledge.CreatedAt = now
    knowledge.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. 生成文档ID
    docID, err := dao.idGenerator.GenerateID()
    if err != nil {
        return 0, fmt.Errorf("生成文档ID失败: %w", err)
    }
    
    // 2. 设置文档属性
    now := time.Now()
    doc.ID = docID
    doc.CreatedAt = now
    doc.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. 生成分片ID
    sliceID, err := dao.idGenerator.GenerateID()
    if err != nil {
        return 0, fmt.Errorf("生成分片ID失败: %w", err)
    }
    
    // 2. 设置分片属性
    now := time.Now()
    slice.ID = sliceID
    slice.CreatedAt = now
    slice.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 完整数据访问流程

知识库创建涉及的数据表:

  1. knowledge: 知识库主表
  2. knowledge_document: 知识库文档表
  3. knowledge_document_slice: 文档分片表
  4. knowledge_config: 知识库配置表

数据访问流程:

  1. 开启数据库事务
  2. 创建知识库主记录
  3. 初始化知识库配置
  4. 提交事务
  5. 发布创建事件

5.4 数据模型定义

文件位置 : backend/domain/knowledge/internal/dal/model/

go 复制代码
// 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生成的查询接口,提供类型安全的查询方法:

go 复制代码
// 知识库查询接口
type IKnowledgeDo interface {
    gen.SubQuery
    Debug() IKnowledgeDo
    WithContext(ctx context.Context) IKnowledgeDo
    WithResult(fc func(tx gen.Dao)) gen.ResultInfo
    ReplaceDB(db *gorm.DB)
    ReadDB() IKnowledgeDo
    WriteDB() IKnowledgeDo
    As(alias string) gen.Dao
    Session(config *gorm.Session) IKnowledgeDo
    Columns(cols ...field.Expr) gen.Columns
    Clauses(conds ...clause.Expression) IKnowledgeDo
    Not(conds ...gen.Condition) IKnowledgeDo
    Or(conds ...gen.Condition) IKnowledgeDo
    Select(conds ...field.Expr) IKnowledgeDo
    Where(conds ...gen.Condition) IKnowledgeDo
    Order(conds ...field.Expr) IKnowledgeDo
    Distinct(cols ...field.Expr) IKnowledgeDo
    Omit(cols ...field.Expr) IKnowledgeDo
    Join(table schema.Tabler, on ...field.Expr) IKnowledgeDo
    LeftJoin(table schema.Tabler, on ...field.Expr) IKnowledgeDo
    RightJoin(table schema.Tabler, on ...field.Expr) IKnowledgeDo
    Group(cols ...field.Expr) IKnowledgeDo
    Having(conds ...gen.Condition) IKnowledgeDo
    Limit(limit int) IKnowledgeDo
    Offset(offset int) IKnowledgeDo
    Count() (count int64, err error)
    Scopes(funcs ...func(gen.Dao) gen.Dao) IKnowledgeDo
    Unscoped() IKnowledgeDo
    Create(values ...*model.Knowledge) error
    CreateInBatches(values []*model.Knowledge, batchSize int) error
    Save(values ...*model.Knowledge) error
    First() (*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) error
    Pluck(column field.Expr, dest interface{}) error
    Delete(...*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.Dao
    Attrs(attrs ...field.AssignExpr) IKnowledgeDo
    Assign(attrs ...field.AssignExpr) IKnowledgeDo
    Joins(fields ...field.RelationField) IKnowledgeDo
    Preload(fields ...field.RelationField) IKnowledgeDo
    FirstOrInit() (*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) IKnowledgeDo
    UnderlyingDB() *gorm.DB
    schema.Tabler
}
相关推荐
LaoZhangAI2 小时前
Google Gemini AI图片编辑完全指南:50+中英对照提示词与批量处理教程(2025年9月)
前端·后端
小枫编程2 小时前
Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
spring boot·分布式·后端
用户11481867894842 小时前
从零搭建 Vue3 + Nest.js 实时通信项目:4 种方案(短轮询 / 长轮询 / SSE/WebSocket)
前端·后端
LaoZhangAI2 小时前
Google Gemini Nano与Banana AI完整部署指南:2025年轻量级AI解决方案
前端·后端
Java水解2 小时前
spring中的@SpringBootTest注解详解
spring boot·后端
似水流年流不尽思念2 小时前
Java线程状态转换的详细过程
后端
尚学教辅学习资料2 小时前
基于Spring Boot的家政服务管理系统+论文示例参考
java·spring boot·后端·java毕设
Java水解2 小时前
从 “Hello AI” 到企业级应用:Spring AI 如何重塑 Java 生态的 AI 开发
后端·spring
平平无奇的开发仔3 小时前
Spring Boot 注解方式如何扫描并注册 BeanDefinition?
后端