551 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			551 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			Go
		
	
	
	
/*
 | 
						||
 * Copyright 2025 coze-dev Authors
 | 
						||
 *
 | 
						||
 * Licensed under the Apache License, Version 2.0 (the "License");
 | 
						||
 * you may not use this file except in compliance with the License.
 | 
						||
 * You may obtain a copy of the License at
 | 
						||
 *
 | 
						||
 *     http://www.apache.org/licenses/LICENSE-2.0
 | 
						||
 *
 | 
						||
 * Unless required by applicable law or agreed to in writing, software
 | 
						||
 * distributed under the License is distributed on an "AS IS" BASIS,
 | 
						||
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						||
 * See the License for the specific language governing permissions and
 | 
						||
 * limitations under the License.
 | 
						||
 */
 | 
						||
 | 
						||
package service
 | 
						||
 | 
						||
import (
 | 
						||
	"context"
 | 
						||
	"errors"
 | 
						||
	"strconv"
 | 
						||
	"sync"
 | 
						||
	"time"
 | 
						||
 | 
						||
	"github.com/bytedance/sonic"
 | 
						||
	"github.com/cloudwego/eino/schema"
 | 
						||
	"golang.org/x/sync/errgroup"
 | 
						||
 | 
						||
	knowledgeModel "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/knowledge"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/crossdomain/contract/crossdatacopy"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/domain/datacopy"
 | 
						||
	copyEntity "github.com/coze-dev/coze-studio/backend/domain/datacopy/entity"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/domain/knowledge/entity"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/domain/knowledge/internal/consts"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/domain/knowledge/internal/convert"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/domain/knowledge/internal/dal/model"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/infra/contract/document"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/infra/contract/document/searchstore"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/infra/contract/rdb"
 | 
						||
	rdbEntity "github.com/coze-dev/coze-studio/backend/infra/contract/rdb/entity"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/pkg/errorx"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/pkg/lang/slices"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/pkg/logs"
 | 
						||
	"github.com/coze-dev/coze-studio/backend/types/errno"
 | 
						||
)
 | 
						||
 | 
						||
func (k *knowledgeSVC) CopyKnowledge(ctx context.Context, request *CopyKnowledgeRequest) (*CopyKnowledgeResponse, error) {
 | 
						||
	if request == nil {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeInvalidParamCode, errorx.KV("msg", "request is empty"))
 | 
						||
	}
 | 
						||
	if len(request.TaskUniqKey) == 0 {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeInvalidParamCode, errorx.KV("msg", "task uniq key is empty"))
 | 
						||
	}
 | 
						||
	if request.KnowledgeID == 0 {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeInvalidParamCode, errorx.KV("msg", "knowledge id is empty"))
 | 
						||
	}
 | 
						||
	kn, err := k.knowledgeRepo.GetByID(ctx, request.KnowledgeID)
 | 
						||
	if err != nil {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	if kn == nil || kn.ID == 0 {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeNotExistCode, errorx.KV("msg", "knowledge not exist"))
 | 
						||
	}
 | 
						||
	newID, err := k.idgen.GenID(ctx)
 | 
						||
	if err != nil {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeIDGenCode)
 | 
						||
	}
 | 
						||
	copyTaskEntity := copyEntity.CopyDataTask{
 | 
						||
		TaskUniqKey:   request.TaskUniqKey,
 | 
						||
		OriginDataID:  request.KnowledgeID,
 | 
						||
		TargetDataID:  newID,
 | 
						||
		OriginSpaceID: kn.SpaceID,
 | 
						||
		TargetSpaceID: request.TargetSpaceID,
 | 
						||
		OriginUserID:  kn.CreatorID,
 | 
						||
		TargetUserID:  request.TargetUserID,
 | 
						||
		OriginAppID:   kn.AppID,
 | 
						||
		TargetAppID:   request.TargetAppID,
 | 
						||
		DataType:      copyEntity.DataTypeKnowledge,
 | 
						||
		StartTime:     time.Now().UnixMilli(),
 | 
						||
		FinishTime:    0,
 | 
						||
		ExtInfo:       "",
 | 
						||
		ErrorMsg:      "",
 | 
						||
		Status:        copyEntity.DataCopyTaskStatusCreate,
 | 
						||
	}
 | 
						||
	checkResult, err := crossdatacopy.DefaultSVC().CheckAndGenCopyTask(ctx, &datacopy.CheckAndGenCopyTaskReq{Task: ©TaskEntity})
 | 
						||
	if err != nil {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeCrossDomainCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	switch checkResult.CopyTaskStatus {
 | 
						||
	case copyEntity.DataCopyTaskStatusSuccess:
 | 
						||
		return &CopyKnowledgeResponse{
 | 
						||
			OriginKnowledgeID: request.KnowledgeID,
 | 
						||
			TargetKnowledgeID: checkResult.TargetID,
 | 
						||
			CopyStatus:        knowledgeModel.CopyStatus_Successful,
 | 
						||
			ErrMsg:            "",
 | 
						||
		}, nil
 | 
						||
	case copyEntity.DataCopyTaskStatusInProgress:
 | 
						||
		return &CopyKnowledgeResponse{
 | 
						||
			OriginKnowledgeID: request.KnowledgeID,
 | 
						||
			TargetKnowledgeID: checkResult.TargetID,
 | 
						||
			CopyStatus:        knowledgeModel.CopyStatus_Processing,
 | 
						||
			ErrMsg:            "",
 | 
						||
		}, nil
 | 
						||
	case copyEntity.DataCopyTaskStatusFail:
 | 
						||
		return &CopyKnowledgeResponse{
 | 
						||
			OriginKnowledgeID: request.KnowledgeID,
 | 
						||
			TargetKnowledgeID: checkResult.TargetID,
 | 
						||
			CopyStatus:        knowledgeModel.CopyStatus_Failed,
 | 
						||
			ErrMsg:            checkResult.FailReason,
 | 
						||
		}, nil
 | 
						||
	}
 | 
						||
	copyResp, err := k.copyDo(ctx, &knowledgeCopyCtx{
 | 
						||
		OriginData: kn,
 | 
						||
		CopyTask:   ©TaskEntity,
 | 
						||
	})
 | 
						||
	if err != nil {
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	return copyResp, nil
 | 
						||
}
 | 
						||
 | 
						||
func (k *knowledgeSVC) copyDo(ctx context.Context, copyCtx *knowledgeCopyCtx) (*CopyKnowledgeResponse, error) {
 | 
						||
	var err error
 | 
						||
	defer func() {
 | 
						||
		if e := recover(); e != nil {
 | 
						||
			logs.CtxErrorf(ctx, "copy knowledge failed, err: %v", e)
 | 
						||
			err = errorx.New(errno.ErrKnowledgeSystemCode, errorx.KVf("msg", "panic: %v", e))
 | 
						||
		}
 | 
						||
		if err != nil {
 | 
						||
			deleteErr := k.DeleteKnowledge(ctx, &DeleteKnowledgeRequest{KnowledgeID: copyCtx.CopyTask.TargetDataID})
 | 
						||
			if deleteErr != nil {
 | 
						||
				logs.CtxErrorf(ctx, "delete knowledge failed, err: %v", deleteErr)
 | 
						||
			}
 | 
						||
			if len(copyCtx.NewRDBTableNames) != 0 {
 | 
						||
				for i := range copyCtx.NewRDBTableNames {
 | 
						||
					_, dropErr := k.rdb.DropTable(ctx, &rdb.DropTableRequest{
 | 
						||
						TableName: copyCtx.NewRDBTableNames[i],
 | 
						||
						IfExists:  true,
 | 
						||
					})
 | 
						||
					if dropErr != nil {
 | 
						||
						logs.CtxErrorf(ctx, "[copyDo] drop table failed, err: %v", dropErr)
 | 
						||
					}
 | 
						||
				}
 | 
						||
			}
 | 
						||
			copyCtx.CopyTask.Status = copyEntity.DataCopyTaskStatusFail
 | 
						||
			err = crossdatacopy.DefaultSVC().UpdateCopyTask(ctx, &datacopy.UpdateCopyTaskReq{Task: copyCtx.CopyTask})
 | 
						||
			if err != nil {
 | 
						||
				logs.CtxErrorf(ctx, "update copy task failed, err: %v", err)
 | 
						||
			}
 | 
						||
		}
 | 
						||
	}()
 | 
						||
	copyCtx.CopyTask.Status = copyEntity.DataCopyTaskStatusInProgress
 | 
						||
	err = crossdatacopy.DefaultSVC().UpdateCopyTask(ctx, &datacopy.UpdateCopyTaskReq{Task: copyCtx.CopyTask})
 | 
						||
	if err != nil {
 | 
						||
		return nil, errorx.New(errno.ErrKnowledgeCrossDomainCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	err = k.copyKnowledge(ctx, copyCtx)
 | 
						||
	if err != nil {
 | 
						||
		logs.CtxErrorf(ctx, "copy knowledge failed, err: %v", err)
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	err = k.copyKnowledgeDocuments(ctx, copyCtx)
 | 
						||
	if err != nil {
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	copyCtx.CopyTask.FinishTime = time.Now().UnixMilli()
 | 
						||
	copyCtx.CopyTask.Status = copyEntity.DataCopyTaskStatusSuccess
 | 
						||
	err = crossdatacopy.DefaultSVC().UpdateCopyTask(ctx, &datacopy.UpdateCopyTaskReq{Task: copyCtx.CopyTask})
 | 
						||
	if err != nil {
 | 
						||
		logs.CtxWarnf(ctx, "update copy task failed, err: %v", err)
 | 
						||
	}
 | 
						||
	return &CopyKnowledgeResponse{
 | 
						||
		OriginKnowledgeID: copyCtx.OriginData.ID,
 | 
						||
		TargetKnowledgeID: copyCtx.CopyTask.TargetDataID,
 | 
						||
		CopyStatus:        knowledgeModel.CopyStatus_Successful,
 | 
						||
		ErrMsg:            "",
 | 
						||
	}, nil
 | 
						||
}
 | 
						||
 | 
						||
func (k *knowledgeSVC) copyKnowledge(ctx context.Context, copyCtx *knowledgeCopyCtx) error {
 | 
						||
	copyKnowledgeInfo := model.Knowledge{
 | 
						||
		ID:          copyCtx.CopyTask.TargetDataID,
 | 
						||
		Name:        copyCtx.OriginData.Name,
 | 
						||
		AppID:       copyCtx.CopyTask.TargetAppID,
 | 
						||
		CreatorID:   copyCtx.CopyTask.TargetUserID,
 | 
						||
		SpaceID:     copyCtx.CopyTask.TargetSpaceID,
 | 
						||
		CreatedAt:   time.Now().UnixMilli(),
 | 
						||
		UpdatedAt:   time.Now().UnixMilli(),
 | 
						||
		Status:      int32(knowledgeModel.KnowledgeStatusEnable),
 | 
						||
		Description: copyCtx.OriginData.Description,
 | 
						||
		IconURI:     copyCtx.OriginData.IconURI,
 | 
						||
		FormatType:  copyCtx.OriginData.FormatType,
 | 
						||
	}
 | 
						||
	err := k.knowledgeRepo.Upsert(ctx, ©KnowledgeInfo)
 | 
						||
	if err != nil {
 | 
						||
		return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
func (k *knowledgeSVC) copyKnowledgeDocuments(ctx context.Context, copyCtx *knowledgeCopyCtx) (err error) {
 | 
						||
	// 查询document信息(仅处理完成的文档)
 | 
						||
	documents, _, err := k.documentRepo.FindDocumentByCondition(ctx, &entity.WhereDocumentOpt{
 | 
						||
		KnowledgeIDs: []int64{copyCtx.OriginData.ID},
 | 
						||
		StatusIn:     []int32{int32(entity.DocumentStatusEnable), int32(entity.DocumentStatusInit)},
 | 
						||
		SelectAll:    true,
 | 
						||
	})
 | 
						||
	if err != nil {
 | 
						||
		return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	if len(documents) == 0 {
 | 
						||
		logs.CtxInfof(ctx, "knowledge %d has no document", copyCtx.OriginData.ID)
 | 
						||
		return nil
 | 
						||
	}
 | 
						||
 | 
						||
	// create search store collections
 | 
						||
	docItem, err := k.fromModelDocument(ctx, documents[0])
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	fields, err := k.mapSearchFields(docItem)
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	collectionName := getCollectionName(copyCtx.CopyTask.TargetDataID)
 | 
						||
	for _, ssMgr := range k.searchStoreManagers {
 | 
						||
		if err = ssMgr.Create(ctx, &searchstore.CreateRequest{
 | 
						||
			CollectionName: collectionName,
 | 
						||
			Fields:         fields,
 | 
						||
			CollectionMeta: nil,
 | 
						||
		}); err != nil {
 | 
						||
			return errorx.New(errno.ErrKnowledgeSearchStoreCode, errorx.KV("msg", err.Error()))
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	targetDocuments, _, err := k.documentRepo.FindDocumentByCondition(ctx, &entity.WhereDocumentOpt{
 | 
						||
		KnowledgeIDs: []int64{copyCtx.CopyTask.TargetDataID},
 | 
						||
		SelectAll:    true,
 | 
						||
	})
 | 
						||
	if err != nil {
 | 
						||
		logs.CtxErrorf(ctx, "find target document failed, err: %v", err)
 | 
						||
		return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	for i := range targetDocuments {
 | 
						||
		err = k.DeleteDocument(ctx, &DeleteDocumentRequest{DocumentID: targetDocuments[i].ID})
 | 
						||
		if err != nil {
 | 
						||
			return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
		}
 | 
						||
	}
 | 
						||
	// 表格类复制
 | 
						||
	eg := errgroup.Group{}
 | 
						||
	eg.SetLimit(10)
 | 
						||
	mu := sync.Mutex{}
 | 
						||
	var failList []int64
 | 
						||
 | 
						||
	newIDs, err := k.genMultiIDs(ctx, len(documents))
 | 
						||
	if err != nil {
 | 
						||
		logs.CtxErrorf(ctx, "gen document id failed, err: %v", err)
 | 
						||
		return errorx.New(errno.ErrKnowledgeIDGenCode)
 | 
						||
	}
 | 
						||
 | 
						||
	for i := range documents {
 | 
						||
		doc := documents[i]
 | 
						||
		newID := newIDs[i]
 | 
						||
 | 
						||
		eg.Go(func() error {
 | 
						||
			cpErr := k.copyDocument(ctx, copyCtx, doc, newID)
 | 
						||
			if cpErr != nil {
 | 
						||
				mu.Lock()
 | 
						||
				failList = append(failList, doc.ID)
 | 
						||
				mu.Unlock()
 | 
						||
				logs.CtxErrorf(ctx, "copy document failed, src document id: %d, new id: %d, err: %v", doc.ID, newID, err)
 | 
						||
				return cpErr
 | 
						||
			}
 | 
						||
			return nil
 | 
						||
		})
 | 
						||
	}
 | 
						||
 | 
						||
	if err := eg.Wait(); err != nil {
 | 
						||
		logs.CtxErrorf(ctx, "copy document failed, document ids: %v, first-err: %v", failList, err)
 | 
						||
		return errorx.New(errno.ErrKnowledgeCopyFailCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
func (k *knowledgeSVC) copyDocument(ctx context.Context, copyCtx *knowledgeCopyCtx, doc *model.KnowledgeDocument, newDocID int64) (err error) {
 | 
						||
	// 表格类文档复制
 | 
						||
	newDoc := model.KnowledgeDocument{
 | 
						||
		ID:            newDocID,
 | 
						||
		KnowledgeID:   copyCtx.CopyTask.TargetDataID,
 | 
						||
		Name:          doc.Name,
 | 
						||
		FileExtension: doc.FileExtension,
 | 
						||
		DocumentType:  doc.DocumentType,
 | 
						||
		URI:           doc.URI,
 | 
						||
		Size:          doc.Size,
 | 
						||
		SliceCount:    doc.SliceCount,
 | 
						||
		CharCount:     doc.CharCount,
 | 
						||
		CreatorID:     copyCtx.CopyTask.TargetUserID,
 | 
						||
		SpaceID:       copyCtx.CopyTask.TargetSpaceID,
 | 
						||
		CreatedAt:     time.Now().UnixMilli(),
 | 
						||
		UpdatedAt:     time.Now().UnixMilli(),
 | 
						||
		SourceType:    doc.SourceType,
 | 
						||
		Status:        int32(entity.DocumentStatusChunking),
 | 
						||
		FailReason:    "",
 | 
						||
		ParseRule:     doc.ParseRule,
 | 
						||
	}
 | 
						||
	columnMap := map[int64]int64{}
 | 
						||
	// 如果是表格型知识库->创建新的表格
 | 
						||
	if doc.DocumentType == int32(knowledgeModel.DocumentTypeTable) {
 | 
						||
		if doc.TableInfo != nil {
 | 
						||
			newTableInfo := entity.TableInfo{}
 | 
						||
			data, err := sonic.Marshal(doc.TableInfo)
 | 
						||
			if err != nil {
 | 
						||
				return err
 | 
						||
			}
 | 
						||
			err = sonic.Unmarshal(data, &newTableInfo)
 | 
						||
			if err != nil {
 | 
						||
				return err
 | 
						||
			}
 | 
						||
			newDoc.TableInfo = &newTableInfo
 | 
						||
		}
 | 
						||
		err = k.createTable(ctx, &newDoc)
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
		newColumnName2IDMap := map[string]int64{}
 | 
						||
		for i := range newDoc.TableInfo.Columns {
 | 
						||
			newColumnName2IDMap[newDoc.TableInfo.Columns[i].Name] = newDoc.TableInfo.Columns[i].ID
 | 
						||
		}
 | 
						||
		oldColumnName2IDMap := map[string]int64{}
 | 
						||
		for i := range doc.TableInfo.Columns {
 | 
						||
			oldColumnName2IDMap[doc.TableInfo.Columns[i].Name] = doc.TableInfo.Columns[i].ID
 | 
						||
			newDoc.TableInfo.Columns[i].ID = newColumnName2IDMap[doc.TableInfo.Columns[i].Name]
 | 
						||
		}
 | 
						||
		for i := range doc.TableInfo.Columns {
 | 
						||
			columnMap[oldColumnName2IDMap[doc.TableInfo.Columns[i].Name]] = newDoc.TableInfo.Columns[i].ID
 | 
						||
		}
 | 
						||
		copyCtx.NewRDBTableNames = append(copyCtx.NewRDBTableNames, newDoc.TableInfo.PhysicalTableName)
 | 
						||
	}
 | 
						||
 | 
						||
	docEntity, err := k.fromModelDocument(ctx, &newDoc)
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	fields, err := k.mapSearchFields(docEntity)
 | 
						||
	if err != nil {
 | 
						||
		logs.CtxErrorf(ctx, "map search fields failed, err: %v", err)
 | 
						||
		return errorx.New(errno.ErrKnowledgeSystemCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	indexingFields := getIndexingFields(fields)
 | 
						||
	collectionName := getCollectionName(newDoc.KnowledgeID)
 | 
						||
 | 
						||
	sliceIDs, err := k.sliceRepo.GetDocumentSliceIDs(ctx, []int64{doc.ID})
 | 
						||
	if err != nil {
 | 
						||
		return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	err = k.documentRepo.Create(ctx, &newDoc)
 | 
						||
	if err != nil {
 | 
						||
		return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
	}
 | 
						||
	defer func() {
 | 
						||
		status := int32(entity.DocumentStatusEnable)
 | 
						||
		msg := ""
 | 
						||
		if err != nil {
 | 
						||
			status = int32(entity.DocumentStatusFailed)
 | 
						||
			msg = err.Error()
 | 
						||
		}
 | 
						||
		updateErr := k.documentRepo.SetStatus(ctx, newDoc.ID, status, msg)
 | 
						||
		if updateErr != nil {
 | 
						||
			logs.CtxErrorf(ctx, "update document status failed, err: %v", updateErr)
 | 
						||
		}
 | 
						||
	}()
 | 
						||
	batchSize := 100
 | 
						||
	for i := 0; i < len(sliceIDs); i += batchSize {
 | 
						||
		end := i + batchSize
 | 
						||
		if end > len(sliceIDs) {
 | 
						||
			end = len(sliceIDs)
 | 
						||
		}
 | 
						||
		sliceInfo, err := k.sliceRepo.MGetSlices(ctx, sliceIDs[i:end])
 | 
						||
		if err != nil {
 | 
						||
			return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
		}
 | 
						||
		newSliceModels := make([]*model.KnowledgeDocumentSlice, 0)
 | 
						||
		newSliceIDs, err := k.genMultiIDs(ctx, len(sliceInfo))
 | 
						||
		if err != nil {
 | 
						||
			return errorx.New(errno.ErrKnowledgeIDGenCode, errorx.KV("msg", err.Error()))
 | 
						||
		}
 | 
						||
		old2NewIDMap := map[int64]int64{}
 | 
						||
		newMap := map[int64]*model.KnowledgeDocumentSlice{}
 | 
						||
		for t := range sliceInfo {
 | 
						||
			old2NewIDMap[sliceInfo[t].ID] = newSliceIDs[t]
 | 
						||
			newSliceModel := model.KnowledgeDocumentSlice{
 | 
						||
				ID:          old2NewIDMap[sliceInfo[t].ID],
 | 
						||
				KnowledgeID: copyCtx.CopyTask.TargetDataID,
 | 
						||
				DocumentID:  newDocID,
 | 
						||
				Content:     sliceInfo[t].Content,
 | 
						||
				Sequence:    sliceInfo[t].Sequence,
 | 
						||
				CreatedAt:   time.Now().UnixMilli(),
 | 
						||
				UpdatedAt:   time.Now().UnixMilli(),
 | 
						||
				CreatorID:   copyCtx.CopyTask.TargetUserID,
 | 
						||
				SpaceID:     copyCtx.CopyTask.TargetSpaceID,
 | 
						||
				Status:      int32(model.SliceStatusDone),
 | 
						||
				FailReason:  "",
 | 
						||
				Hit:         0,
 | 
						||
			}
 | 
						||
			newMap[newSliceIDs[t]] = &newSliceModel
 | 
						||
		}
 | 
						||
 | 
						||
		var sliceEntities []*entity.Slice
 | 
						||
		if doc.DocumentType == int32(knowledgeModel.DocumentTypeTable) {
 | 
						||
			sliceMap, err := k.selectTableData(ctx, doc.TableInfo, sliceInfo)
 | 
						||
			if err != nil {
 | 
						||
				logs.CtxErrorf(ctx, "select table data failed, err: %v", err)
 | 
						||
				return err
 | 
						||
			}
 | 
						||
			newSlices := make([]*entity.Slice, 0)
 | 
						||
			for id, info := range sliceMap {
 | 
						||
				info.DocumentID = newDocID
 | 
						||
				info.Hit = 0
 | 
						||
				info.DocumentName = doc.Name
 | 
						||
				info.ID = old2NewIDMap[id]
 | 
						||
				for t := range info.RawContent[0].Table.Columns {
 | 
						||
					info.RawContent[0].Table.Columns[t].ColumnID = columnMap[info.RawContent[0].Table.Columns[t].ColumnID]
 | 
						||
				}
 | 
						||
				newSlices = append(newSlices, info)
 | 
						||
			}
 | 
						||
			err = k.upsertDataToTable(ctx, newDoc.TableInfo, newSlices)
 | 
						||
			if err != nil {
 | 
						||
				logs.CtxErrorf(ctx, "upsert data to table failed, err: %v", err)
 | 
						||
				return err
 | 
						||
			}
 | 
						||
			sliceEntities = newSlices
 | 
						||
		}
 | 
						||
 | 
						||
		for _, v := range newMap {
 | 
						||
			cpSlice := v
 | 
						||
			newSliceModels = append(newSliceModels, cpSlice)
 | 
						||
			if doc.DocumentType != int32(knowledgeModel.DocumentTypeTable) {
 | 
						||
				sliceEntities = append(sliceEntities, k.fromModelSlice(ctx, cpSlice))
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		ssDocs, err := slices.TransformWithErrorCheck(sliceEntities, func(a *entity.Slice) (*schema.Document, error) {
 | 
						||
			return k.slice2Document(ctx, docEntity, a)
 | 
						||
		})
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
		for _, mgr := range k.searchStoreManagers {
 | 
						||
			ss, err := mgr.GetSearchStore(ctx, collectionName)
 | 
						||
			if err != nil {
 | 
						||
				return errorx.New(errno.ErrKnowledgeSearchStoreCode, errorx.KV("msg", err.Error()))
 | 
						||
			}
 | 
						||
 | 
						||
			if _, err = ss.Store(ctx, ssDocs,
 | 
						||
				searchstore.WithIndexerPartitionKey(fieldNameDocumentID),
 | 
						||
				searchstore.WithPartition(strconv.FormatInt(newDoc.ID, 10)),
 | 
						||
				searchstore.WithIndexingFields(indexingFields),
 | 
						||
			); err != nil {
 | 
						||
				return errorx.New(errno.ErrKnowledgeSearchStoreCode, errorx.KV("msg", err.Error()))
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		err = k.sliceRepo.BatchCreate(ctx, newSliceModels)
 | 
						||
		if err != nil {
 | 
						||
			return errorx.New(errno.ErrKnowledgeDBCode, errorx.KV("msg", err.Error()))
 | 
						||
		}
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
func (k *knowledgeSVC) createTable(ctx context.Context, doc *model.KnowledgeDocument) error {
 | 
						||
	// 表格型知识库,创建表
 | 
						||
	rdbColumns := []*rdbEntity.Column{}
 | 
						||
	tableColumns := doc.TableInfo.Columns
 | 
						||
	columnIDs, err := k.genMultiIDs(ctx, len(tableColumns)+1)
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	for i := range tableColumns {
 | 
						||
		tableColumns[i].ID = columnIDs[i]
 | 
						||
		rdbColumns = append(rdbColumns, &rdbEntity.Column{
 | 
						||
			Name:     convert.ColumnIDToRDBField(columnIDs[i]),
 | 
						||
			DataType: convert.ConvertColumnType(tableColumns[i].Type),
 | 
						||
			NotNull:  tableColumns[i].Indexing,
 | 
						||
		})
 | 
						||
	}
 | 
						||
	doc.TableInfo.Columns = append(doc.TableInfo.Columns, &entity.TableColumn{
 | 
						||
		ID:          columnIDs[len(columnIDs)-1],
 | 
						||
		Name:        consts.RDBFieldID,
 | 
						||
		Type:        document.TableColumnTypeInteger,
 | 
						||
		Description: "主键ID",
 | 
						||
		Indexing:    false,
 | 
						||
		Sequence:    -1,
 | 
						||
	})
 | 
						||
	// 为每个表格增加个主键ID
 | 
						||
	rdbColumns = append(rdbColumns, &rdbEntity.Column{
 | 
						||
		Name:     consts.RDBFieldID,
 | 
						||
		DataType: rdbEntity.TypeBigInt,
 | 
						||
		NotNull:  true,
 | 
						||
	})
 | 
						||
	// 创建一个数据表
 | 
						||
	resp, err := k.rdb.CreateTable(ctx, &rdb.CreateTableRequest{
 | 
						||
		Table: &rdbEntity.Table{
 | 
						||
			Columns: rdbColumns,
 | 
						||
			Indexes: []*rdbEntity.Index{
 | 
						||
				{
 | 
						||
					Name:    "pk",
 | 
						||
					Type:    rdbEntity.PrimaryKey,
 | 
						||
					Columns: []string{consts.RDBFieldID},
 | 
						||
				},
 | 
						||
			},
 | 
						||
		},
 | 
						||
	})
 | 
						||
	if err != nil {
 | 
						||
		logs.CtxErrorf(ctx, "create table failed, err: %v", err)
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	doc.TableInfo = &entity.TableInfo{
 | 
						||
		VirtualTableName:  doc.Name,
 | 
						||
		PhysicalTableName: resp.Table.Name,
 | 
						||
		TableDesc:         doc.TableInfo.TableDesc,
 | 
						||
		Columns:           doc.TableInfo.Columns,
 | 
						||
	}
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
type knowledgeCopyCtx struct {
 | 
						||
	OriginData       *model.Knowledge
 | 
						||
	CopyTask         *copyEntity.CopyDataTask
 | 
						||
	NewRDBTableNames []string
 | 
						||
}
 | 
						||
 | 
						||
func (k *knowledgeSVC) MoveKnowledgeToLibrary(ctx context.Context, request *MoveKnowledgeToLibraryRequest) error {
 | 
						||
	if request == nil || request.KnowledgeID == 0 {
 | 
						||
		return errors.New("invalid request")
 | 
						||
	}
 | 
						||
	kn, err := k.knowledgeRepo.GetByID(ctx, request.KnowledgeID)
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	if kn == nil || kn.ID == 0 {
 | 
						||
		return errors.New("knowledge not found")
 | 
						||
	}
 | 
						||
	kn.AppID = 0
 | 
						||
	err = k.knowledgeRepo.Update(ctx, kn)
 | 
						||
	return err
 | 
						||
}
 |