Files
backend/internal/search/document_search.go
T
2026-07-15 22:42:31 +08:00

215 lines
5.6 KiB
Go

package search
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/XingfenD/yoresee_doc/internal/config"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/XingfenD/yoresee_doc/pkg/storage"
)
const defaultSearchIndexPrefix = "yoresee_doc"
type DocumentSearchRequest struct {
Keyword string
UserID *int64
KnowledgeID *int64
ListOwnDoc bool
DocType *string
Tags []string
CreateTimeRangeStart *string
CreateTimeRangeEnd *string
UpdateTimeRangeStart *string
UpdateTimeRangeEnd *string
Size int
}
func SearchDocumentIDs(ctx context.Context, req DocumentSearchRequest) ([]int64, error) {
if storage.ES == nil {
return nil, fmt.Errorf("elasticsearch client is nil")
}
return storage.ES.SearchIDs(ctx, DocumentIndexName(), BuildDocumentSearchBody(req))
}
func UpsertDocument(ctx context.Context, doc *model.Document) error {
if doc == nil {
return fmt.Errorf("document is nil")
}
if storage.ES == nil {
return fmt.Errorf("elasticsearch client is nil")
}
return storage.ES.UpsertDocument(ctx, DocumentIndexName(), strconv.FormatInt(doc.ID, 10), BuildDocumentIndexBody(doc))
}
func DocumentIndexName() string {
prefix := defaultSearchIndexPrefix
if config.GlobalConfig != nil {
configPrefix := strings.TrimSpace(config.GlobalConfig.Elasticsearch.IndexPrefix)
if configPrefix != "" {
prefix = configPrefix
}
}
return key.KeyESDocumentIndex(prefix)
}
func BuildDocumentSearchBody(req DocumentSearchRequest) map[string]interface{} {
size := req.Size
if size <= 0 {
size = 5000
}
if size > 10000 {
size = 10000
}
must := []map[string]interface{}{}
if strings.TrimSpace(req.Keyword) != "" {
must = append(must, map[string]interface{}{
"multi_match": map[string]interface{}{
"query": strings.TrimSpace(req.Keyword),
"fields": []string{"title^3", "summary^2", "content"},
"type": "best_fields",
"operator": "and",
"fuzziness": "AUTO",
},
})
}
filter := []map[string]interface{}{}
mustNot := []map[string]interface{}{}
if req.UserID != nil {
filter = append(filter, map[string]interface{}{
"term": map[string]interface{}{"user_id": *req.UserID},
})
}
if req.ListOwnDoc {
mustNot = append(mustNot, map[string]interface{}{
"exists": map[string]interface{}{"field": "knowledge_id"},
})
}
if req.KnowledgeID != nil {
filter = append(filter, map[string]interface{}{
"term": map[string]interface{}{"knowledge_id": *req.KnowledgeID},
})
}
if req.DocType != nil && strings.TrimSpace(*req.DocType) != "" {
filter = append(filter, map[string]interface{}{
"term": map[string]interface{}{"type": strings.TrimSpace(*req.DocType)},
})
}
for _, tag := range req.Tags {
tag = strings.TrimSpace(tag)
if tag == "" {
continue
}
filter = append(filter, map[string]interface{}{
"term": map[string]interface{}{"tags.keyword": tag},
})
}
rangeCreated := map[string]interface{}{}
if req.CreateTimeRangeStart != nil && strings.TrimSpace(*req.CreateTimeRangeStart) != "" {
rangeCreated["gte"] = strings.TrimSpace(*req.CreateTimeRangeStart)
}
if req.CreateTimeRangeEnd != nil && strings.TrimSpace(*req.CreateTimeRangeEnd) != "" {
rangeCreated["lte"] = strings.TrimSpace(*req.CreateTimeRangeEnd)
}
if len(rangeCreated) > 0 {
filter = append(filter, map[string]interface{}{
"range": map[string]interface{}{"created_at": rangeCreated},
})
}
rangeUpdated := map[string]interface{}{}
if req.UpdateTimeRangeStart != nil && strings.TrimSpace(*req.UpdateTimeRangeStart) != "" {
rangeUpdated["gte"] = strings.TrimSpace(*req.UpdateTimeRangeStart)
}
if req.UpdateTimeRangeEnd != nil && strings.TrimSpace(*req.UpdateTimeRangeEnd) != "" {
rangeUpdated["lte"] = strings.TrimSpace(*req.UpdateTimeRangeEnd)
}
if len(rangeUpdated) > 0 {
filter = append(filter, map[string]interface{}{
"range": map[string]interface{}{"updated_at": rangeUpdated},
})
}
return map[string]interface{}{
"size": size,
"_source": []string{
"id",
},
"query": map[string]interface{}{
"bool": map[string]interface{}{
"must": must,
"filter": filter,
"must_not": mustNot,
},
},
}
}
func BuildDocumentIndexBody(doc *model.Document) map[string]interface{} {
payload := map[string]interface{}{
"id": doc.ID,
"external_id": doc.ExternalID,
"title": doc.Title,
"summary": doc.Summary,
"content": doc.Content,
"type": doc.Type,
"user_id": doc.UserID,
"tags": doc.Tags,
"created_at": doc.CreatedAt.Format(time.RFC3339),
"updated_at": doc.UpdatedAt.Format(time.RFC3339),
}
if doc.KnowledgeID != nil {
payload["knowledge_id"] = *doc.KnowledgeID
}
return payload
}
func BuildDocumentIndexMapping() map[string]interface{} {
return map[string]interface{}{
"mappings": map[string]interface{}{
"properties": map[string]interface{}{
"id": map[string]interface{}{
"type": "long",
},
"external_id": map[string]interface{}{
"type": "keyword",
},
"title": map[string]interface{}{
"type": "text",
},
"summary": map[string]interface{}{
"type": "text",
},
"content": map[string]interface{}{
"type": "text",
},
"type": map[string]interface{}{
"type": "keyword",
},
"user_id": map[string]interface{}{
"type": "long",
},
"knowledge_id": map[string]interface{}{
"type": "long",
},
"tags": map[string]interface{}{
"type": "keyword",
},
"created_at": map[string]interface{}{
"type": "date",
},
"updated_at": map[string]interface{}{
"type": "date",
},
},
},
}
}