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
}
相关推荐
神奇的程序员1 小时前
从已损坏的备份中拯救数据
运维·后端·前端工程化
oden2 小时前
AI服务商切换太麻烦?一个AI Gateway搞定监控、缓存和故障转移(成本降40%)
后端·openai·api
李慕婉学姐2 小时前
【开题答辩过程】以《基于Android的出租车运行监测系统设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·后端·vue
m0_740043733 小时前
SpringBoot05-配置文件-热加载/日志框架slf4j/接口文档工具Swagger/Knife4j
java·spring boot·后端·log4j
招风的黑耳4 小时前
我用SpringBoot撸了一个智慧水务监控平台
java·spring boot·后端
Miss_Chenzr4 小时前
Springboot优卖电商系统s7zmj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
期待のcode4 小时前
Springboot核心构建插件
java·spring boot·后端
2501_921649494 小时前
如何获取美股实时行情:Python 量化交易指南
开发语言·后端·python·websocket·金融
serendipity_hky5 小时前
【SpringCloud | 第5篇】Seata分布式事务
分布式·后端·spring·spring cloud·seata·openfeign
五阿哥永琪5 小时前
Spring Boot 中自定义线程池的正确使用姿势:定义、注入与最佳实践
spring boot·后端·python