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
}