This commit is contained in:
2026-03-14 10:02:49 +08:00
parent 03b50ef904
commit 830f75a334
75 changed files with 10677 additions and 2 deletions

197
service/dataset_service.go Normal file
View File

@@ -0,0 +1,197 @@
package service
import (
"context"
"customer-server/dao"
"customer-server/model/entity"
"fmt"
"gitea.com/red-future/common/ragflow"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
// EnsureTenantDataset 确保租户知识库存在
// 逻辑:
// 1. 用租户ID查找MongoDB中的知识库ID
// 2. 测试知识库ID是否在RAGFlow中可用
// 3. 如果不可用就直接根据名字在RAGFlow中查找
// 4. 找到了就把知识库ID存到MongoDB
// 5. 如果名字也找不到,就创建新的
// 6. 把新的ID和名字存到MongoDB
func EnsureTenantDataset(ctx context.Context, tenantId string) (datasetId string, err error) {
ragflowClient := ragflow.GetGlobalClient()
datasetName := fmt.Sprintf("dataset_tenant_%s", tenantId)
// 1. 用租户ID查找MongoDB中的知识库ID
datasetId, err = dao.RAGFlowConfig.FindDatasetIdByTenant(ctx, tenantId)
if err != nil && err.Error() != "mongo: no documents in result" {
return "", gerror.Wrap(err, "查询租户知识库ID失败")
}
// 2. 测试知识库ID是否在RAGFlow中可用
if datasetId != "" {
g.Log().Infof(ctx, "租户%s已有知识库ID: %s测试是否可用", tenantId, datasetId)
listReq := &ragflow.ListDatasetsReq{Page: 1, PageSize: 100}
listRes, listErr := ragflowClient.ListDatasets(ctx, listReq)
if listErr == nil && listRes != nil {
g.Log().Infof(ctx, "ListDatasets返回%d个知识库", len(listRes.Data))
for i, ds := range listRes.Data {
g.Log().Infof(ctx, "知识库[%d]: id=%s, name=%s", i, ds.Id, ds.Name)
}
// 测试ID是否可用
for _, ds := range listRes.Data {
if ds.Id == datasetId {
g.Log().Infof(ctx, "知识库ID可用: %s", datasetId)
return datasetId, nil
}
}
// 3. ID不可用直接根据名字查找
g.Log().Warningf(ctx, "知识库ID不可用根据名字查找: %s", datasetName)
for _, ds := range listRes.Data {
if ds.Name == datasetName {
datasetId = ds.Id
g.Log().Infof(ctx, "找到同名知识库: name=%s, id=%s", datasetName, datasetId)
// 4. 把知识库ID存到MongoDB
if updateErr := updateDatasetIdInMongo(ctx, tenantId, datasetId); updateErr != nil {
g.Log().Errorf(ctx, "更新MongoDB失败: %v", updateErr)
}
return datasetId, nil
}
}
g.Log().Warningf(ctx, "未找到同名知识库: %s", datasetName)
} else {
g.Log().Errorf(ctx, "ListDatasets失败: %v", listErr)
}
}
// 5. 名字也找不到,创建新的
g.Log().Infof(ctx, "创建新知识库: name=%s", datasetName)
// 从config读取embedding模型如果未配置则使用默认值
embeddingModel := g.Cfg().MustGet(ctx, "ragflow.embedding_model", "text-embedding-v4").String()
createReq := &ragflow.CreateDatasetReq{
Name: datasetName,
EmbeddingModel: embeddingModel,
ChunkMethod: "naive",
}
dataset, err := ragflowClient.CreateDataset(ctx, createReq)
// 不依赖CreateDataset的返回值因为RAGFlow可能返回code=0但data=null
// 立即用ListDatasets查找新创建的知识库来确认
g.Log().Infof(ctx, "创建请求已发送,立即查找确认: name=%s", datasetName)
listReq2 := &ragflow.ListDatasetsReq{Page: 1, PageSize: 100}
listRes2, listErr2 := ragflowClient.ListDatasets(ctx, listReq2)
if listErr2 == nil && listRes2 != nil {
for _, ds := range listRes2.Data {
if ds.Name == datasetName {
datasetId = ds.Id
g.Log().Infof(ctx, "确认知识库已创建: id=%s, name=%s", datasetId, datasetName)
goto saveRecord
}
}
}
// 如果ListDatasets也找不到说明创建真的失败了
if err != nil {
return "", gerror.Wrapf(err, "创建知识库失败且无法确认")
}
if dataset == nil || dataset.Id == "" {
return "", gerror.Newf("创建知识库失败无法通过ListDatasets确认: name=%s", datasetName)
}
// 使用CreateDataset返回的ID兜底
datasetId = dataset.Id
g.Log().Infof(ctx, "知识库创建成功: id=%s, name=%s", datasetId, datasetName)
saveRecord:
// 6. 把新的ID和名字存到MongoDB
if err := saveSystemDatasetRecord(ctx, tenantId, datasetId, datasetName); err != nil {
g.Log().Errorf(ctx, "保存到MongoDB失败: %v", err)
return "", gerror.Wrap(err, "保存到MongoDB失败")
}
return datasetId, nil
}
// uploadPlaceholderDocument 上传占位文档
func uploadPlaceholderDocument(ctx context.Context, datasetId, tenantId string) error {
ragflowClient := ragflow.GetGlobalClient()
placeholderContent := "欢迎使用智能客服系统。这是一个占位文档,实际产品和话术会在后续上传。"
placeholderFilename := fmt.Sprintf("placeholder_tenant_%s.txt", tenantId)
documentId, err := ragflowClient.UploadDocumentFromText(ctx, datasetId, placeholderContent, placeholderFilename)
if err != nil {
return gerror.Wrap(err, "上传占位文档失败")
}
g.Log().Infof(ctx, "占位文档上传成功: document_id=%s", documentId)
if parseErr := ragflowClient.ParseDocuments(ctx, datasetId, []string{documentId}); parseErr != nil {
g.Log().Warningf(ctx, "解析占位文档失败: %v", parseErr)
return parseErr
}
g.Log().Infof(ctx, "占位文档解析已启动(后台异步进行)")
return nil
}
// saveSystemDatasetRecord 保存系统占位记录到MongoDB
func saveSystemDatasetRecord(ctx context.Context, tenantId, datasetId, datasetName string) error {
// 先检查是否已有记录
existing, err := dao.RAGFlowConfig.FindDatasetIdByTenant(ctx, tenantId)
if err == nil && existing != "" {
g.Log().Infof(ctx, "租户%s已有知识库记录无需创建系统占位记录", tenantId)
return nil
}
// 创建系统占位记录
systemAccountName := fmt.Sprintf("_system_tenant_%s", tenantId)
config := &entity.RAGFlowConfig{
AccountName: systemAccountName,
Platform: "system",
DatasetId: datasetId,
DatasetIds: []string{datasetId},
DatasetName: datasetName,
ChatId: "", // 系统记录不需要Chat
Prompt: "",
DocumentIds: []string{},
SimilarityThreshold: 0.2,
KeywordsSimilarityWeight: 0.7,
TopN: 8,
EmptyResponse: "抱歉,我暂时无法回答这个问题。",
SyncStatus: "synced",
}
// 统一使用string类型存储tenantId到MongoDB
config.TenantId = tenantId
config.IsDeleted = false
if err := dao.RAGFlowConfig.Insert(ctx, config); err != nil {
return gerror.Wrap(err, "插入系统占位记录失败")
}
g.Log().Infof(ctx, "系统占位记录已保存: tenant_id=%s, dataset_id=%s", tenantId, datasetId)
return nil
}
// updateDatasetIdInMongo 更新MongoDB中租户的所有datasetId记录
func updateDatasetIdInMongo(ctx context.Context, tenantId, newDatasetId string) error {
// 更新该租户所有ragflow_config记录的datasetId
if err := dao.RAGFlowConfig.UpdateDatasetIdByTenant(ctx, tenantId, newDatasetId); err != nil {
return gerror.Wrap(err, "更新租户datasetId失败")
}
g.Log().Infof(ctx, "已更新租户%s的所有记录的datasetId: %s", tenantId, newDatasetId)
return nil
}