feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
126
backend/domain/search/service/eventbus.go
Normal file
126
backend/domain/search/service/eventbus.go
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* 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"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
type eventbusImpl struct {
|
||||
producer eventbus.Producer
|
||||
}
|
||||
|
||||
func NewProjectEventBus(p eventbus.Producer) ProjectEventBus {
|
||||
return &eventbusImpl{
|
||||
producer: p,
|
||||
}
|
||||
}
|
||||
|
||||
func NewResourceEventBus(p eventbus.Producer) ResourceEventBus {
|
||||
return &eventbusImpl{
|
||||
producer: p,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *eventbusImpl) PublishResources(ctx context.Context, event *entity.ResourceDomainEvent) error {
|
||||
if event.Meta == nil {
|
||||
event.Meta = &entity.EventMeta{}
|
||||
}
|
||||
|
||||
now := time.Now().UnixMilli()
|
||||
event.Meta.SendTimeMs = time.Now().UnixMilli()
|
||||
|
||||
if event.OpType == entity.Created &&
|
||||
event.Resource != nil &&
|
||||
(event.Resource.CreateTimeMS == nil || *event.Resource.CreateTimeMS == 0) {
|
||||
event.Resource.CreateTimeMS = ptr.Of(now)
|
||||
}
|
||||
|
||||
if (event.OpType == entity.Created || event.OpType == entity.Updated) &&
|
||||
event.Resource != nil &&
|
||||
(event.Resource.UpdateTimeMS == nil || *event.Resource.UpdateTimeMS == 0) {
|
||||
event.Resource.UpdateTimeMS = ptr.Of(now)
|
||||
}
|
||||
|
||||
if defaultResourceHandler != nil {
|
||||
err := defaultResourceHandler.indexResources(ctx, event)
|
||||
if err == nil {
|
||||
json, _ := sonic.Marshal(event)
|
||||
logs.CtxInfof(ctx, "Sync PublishResources success: %s", string(json))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
logs.CtxWarnf(ctx, "Sync PublishResources indexResources error: %s", err.Error())
|
||||
}
|
||||
|
||||
bytes, err := sonic.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logs.Infof("PublishResources success: %s", string(bytes))
|
||||
return d.producer.Send(ctx, bytes)
|
||||
}
|
||||
|
||||
func (d *eventbusImpl) PublishProject(ctx context.Context, event *entity.ProjectDomainEvent) error {
|
||||
if event.Meta == nil {
|
||||
event.Meta = &entity.EventMeta{}
|
||||
}
|
||||
|
||||
event.Meta.SendTimeMs = time.Now().UnixMilli()
|
||||
now := time.Now().UnixMilli()
|
||||
event.Meta.SendTimeMs = time.Now().UnixMilli()
|
||||
|
||||
if event.OpType == entity.Created &&
|
||||
event.Project != nil &&
|
||||
(event.Project.CreateTimeMS == nil || *event.Project.CreateTimeMS == 0) {
|
||||
event.Project.CreateTimeMS = ptr.Of(now)
|
||||
}
|
||||
|
||||
if (event.OpType == entity.Created || event.OpType == entity.Updated) &&
|
||||
event.Project != nil &&
|
||||
(event.Project.UpdateTimeMS == nil || *event.Project.UpdateTimeMS == 0) {
|
||||
event.Project.UpdateTimeMS = ptr.Of(now)
|
||||
}
|
||||
|
||||
if defaultProjectHandle != nil {
|
||||
err := defaultProjectHandle.indexProject(ctx, event)
|
||||
if err == nil {
|
||||
json, _ := sonic.Marshal(event)
|
||||
logs.CtxInfof(ctx, "Sync PublishProject success: %s", string(json))
|
||||
return nil
|
||||
}
|
||||
logs.CtxWarnf(ctx, "Sync PublishProject indexProject error: %s", err.Error())
|
||||
}
|
||||
|
||||
bytes, err := sonic.Marshal(event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logs.Infof("PublishProject success: %s", string(bytes))
|
||||
return d.producer.Send(ctx, bytes)
|
||||
}
|
||||
89
backend/domain/search/service/handler_project.go
Normal file
89
backend/domain/search/service/handler_project.go
Normal file
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
const projectIndexName = "project_draft"
|
||||
|
||||
type projectHandlerImpl struct {
|
||||
esClient es.Client
|
||||
}
|
||||
|
||||
type ConsumerHandler = eventbus.ConsumerHandler
|
||||
|
||||
var defaultProjectHandle *projectHandlerImpl // deprecate
|
||||
|
||||
func NewProjectHandler(ctx context.Context, e es.Client) ConsumerHandler {
|
||||
handler := &projectHandlerImpl{
|
||||
esClient: e,
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (s *projectHandlerImpl) HandleMessage(ctx context.Context, msg *eventbus.Message) error {
|
||||
ev := &entity.ProjectDomainEvent{}
|
||||
|
||||
logs.CtxInfof(ctx, "Project Handler receive: %s", string(msg.Body))
|
||||
err := sonic.Unmarshal(msg.Body, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.indexProject(ctx, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *projectHandlerImpl) indexProject(ctx context.Context, ev *entity.ProjectDomainEvent) error {
|
||||
if ev.Project == nil {
|
||||
return fmt.Errorf("project is nil")
|
||||
}
|
||||
|
||||
if ev.Meta == nil {
|
||||
ev.Meta = &entity.EventMeta{}
|
||||
}
|
||||
|
||||
ev.Meta.ReceiveTimeMs = time.Now().UnixMilli()
|
||||
|
||||
switch ev.OpType {
|
||||
case entity.Created:
|
||||
return s.esClient.Create(ctx, projectIndexName, conv.Int64ToStr(ev.Project.ID), ev.Project)
|
||||
case entity.Updated:
|
||||
return s.esClient.Update(ctx, projectIndexName, conv.Int64ToStr(ev.Project.ID), ev.Project)
|
||||
case entity.Deleted:
|
||||
return s.esClient.Delete(ctx, projectIndexName, conv.Int64ToStr(ev.Project.ID))
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected op type: %v", ev.OpType)
|
||||
}
|
||||
88
backend/domain/search/service/handler_resource.go
Normal file
88
backend/domain/search/service/handler_resource.go
Normal file
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/eventbus"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
const resourceIndexName = "coze_resource"
|
||||
|
||||
type resourceHandlerImpl struct {
|
||||
esClient es.Client
|
||||
}
|
||||
|
||||
var defaultResourceHandler *resourceHandlerImpl // deprecate
|
||||
|
||||
func NewResourceHandler(ctx context.Context, e es.Client) ConsumerHandler {
|
||||
handler := &resourceHandlerImpl{
|
||||
esClient: e,
|
||||
}
|
||||
|
||||
return handler
|
||||
}
|
||||
|
||||
func (s *resourceHandlerImpl) HandleMessage(ctx context.Context, msg *eventbus.Message) error {
|
||||
ev := &entity.ResourceDomainEvent{}
|
||||
|
||||
logs.Infof("Resource Handler receive: %s", string(msg.Body))
|
||||
|
||||
err := sonic.Unmarshal(msg.Body, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.indexResources(ctx, ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceHandlerImpl) indexResources(ctx context.Context, ev *entity.ResourceDomainEvent) error {
|
||||
if ev.Meta == nil {
|
||||
ev.Meta = &entity.EventMeta{}
|
||||
}
|
||||
|
||||
ev.Meta.ReceiveTimeMs = time.Now().UnixMilli()
|
||||
|
||||
return s.indexResource(ctx, ev.OpType, ev.Resource)
|
||||
}
|
||||
|
||||
func (s *resourceHandlerImpl) indexResource(ctx context.Context, opType entity.OpType, r *entity.ResourceDocument) error {
|
||||
switch opType {
|
||||
case entity.Created:
|
||||
return s.esClient.Create(ctx, resourceIndexName, conv.Int64ToStr(r.ResID), r)
|
||||
case entity.Updated:
|
||||
return s.esClient.Update(ctx, resourceIndexName, conv.Int64ToStr(r.ResID), r)
|
||||
case entity.Deleted:
|
||||
return s.esClient.Delete(ctx, resourceIndexName, conv.Int64ToStr(r.ResID))
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected op type: %v", opType)
|
||||
}
|
||||
377
backend/domain/search/service/search.go
Normal file
377
backend/domain/search/service/search.go
Normal file
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* 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"
|
||||
"strconv"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
|
||||
model "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/search"
|
||||
searchEntity "github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
"github.com/coze-dev/coze-studio/backend/infra/contract/es"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/conv"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/lang/ptr"
|
||||
"github.com/coze-dev/coze-studio/backend/pkg/logs"
|
||||
)
|
||||
|
||||
var searchInstance *searchImpl
|
||||
|
||||
func NewDomainService(ctx context.Context, e es.Client) Search {
|
||||
return &searchImpl{
|
||||
esClient: e,
|
||||
}
|
||||
}
|
||||
|
||||
type searchImpl struct {
|
||||
esClient es.Client
|
||||
}
|
||||
|
||||
type fieldName string
|
||||
|
||||
const (
|
||||
fieldOfSpaceID = "space_id"
|
||||
fieldOfOwnerID = "owner_id"
|
||||
fieldOfID = "id"
|
||||
fieldOfAPPID = "app_id"
|
||||
fieldOfName = "name"
|
||||
fieldOfNameRaw = "name.raw"
|
||||
fieldOfHasPublished = "has_published"
|
||||
fieldOfStatus = "status"
|
||||
fieldOfType = "type"
|
||||
fieldOfIsFav = "is_fav"
|
||||
fieldOfIsRecentlyOpen = "is_recently_open"
|
||||
|
||||
resTypeSearchAll = -1
|
||||
)
|
||||
|
||||
func (s *searchImpl) SearchProjects(ctx context.Context, req *searchEntity.SearchProjectsRequest) (resp *searchEntity.SearchProjectsResponse, err error) {
|
||||
logs.CtxDebugf(ctx, "[SearchProjects] search : %s", conv.DebugJsonToStr(req))
|
||||
searchReq := &es.Request{
|
||||
Query: &es.Query{
|
||||
Bool: &es.BoolQuery{},
|
||||
},
|
||||
}
|
||||
|
||||
if req.ProjectID != 0 { // 精确搜索
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfID, conv.Int64ToStr(req.ProjectID)))
|
||||
} else {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfSpaceID, conv.Int64ToStr(req.SpaceID)))
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewContainsQuery(fieldOfNameRaw, req.Name))
|
||||
}
|
||||
|
||||
if req.IsPublished {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfHasPublished, conv.BoolToInt(req.IsPublished)))
|
||||
}
|
||||
|
||||
if req.IsFav {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfIsFav, conv.BoolToInt(req.IsFav)))
|
||||
}
|
||||
|
||||
if req.IsRecentlyOpen {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfIsRecentlyOpen, conv.BoolToInt(req.IsRecentlyOpen)))
|
||||
}
|
||||
|
||||
if req.OwnerID > 0 {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfOwnerID, conv.Int64ToStr(req.OwnerID)))
|
||||
}
|
||||
|
||||
if len(req.Status) > 0 {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewInQuery(fieldOfStatus, req.Status))
|
||||
}
|
||||
|
||||
if len(req.Types) > 0 {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewInQuery(fieldOfType, req.Types))
|
||||
}
|
||||
|
||||
reqLimit := 100
|
||||
if req.Limit > 0 {
|
||||
reqLimit = int(req.Limit)
|
||||
}
|
||||
|
||||
realLimit := reqLimit + 1
|
||||
searchReq.Size = &realLimit
|
||||
|
||||
if req.OrderFiledName == "" {
|
||||
req.OrderFiledName = model.FieldOfUpdateTime
|
||||
}
|
||||
|
||||
searchReq.Sort = []es.SortFiled{
|
||||
{
|
||||
Field: req.OrderFiledName,
|
||||
Asc: req.OrderAsc,
|
||||
},
|
||||
}
|
||||
|
||||
if req.Cursor != "" && req.Cursor != "0" {
|
||||
searchReq.SearchAfter = []any{
|
||||
fieldValueCaster(req.OrderFiledName, req.Cursor),
|
||||
}
|
||||
}
|
||||
|
||||
result, err := s.esClient.Search(ctx, projectIndexName, searchReq)
|
||||
if err != nil {
|
||||
logs.CtxDebugf(ctx, "[Serarch.DO] err : %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hits := result.Hits.Hits
|
||||
|
||||
hasMore := func() bool {
|
||||
if len(hits) > reqLimit {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
if hasMore {
|
||||
hits = hits[:reqLimit]
|
||||
}
|
||||
|
||||
docs := make([]*searchEntity.ProjectDocument, 0, len(hits))
|
||||
for _, hit := range hits {
|
||||
doc, err := hit2AppDocument(hit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
|
||||
nextCursor := ""
|
||||
if len(docs) > 0 {
|
||||
nextCursor = formatProjectNextCursor(req.OrderFiledName, docs[len(docs)-1])
|
||||
}
|
||||
if nextCursor == "" {
|
||||
hasMore = false
|
||||
}
|
||||
|
||||
resp = &searchEntity.SearchProjectsResponse{
|
||||
Data: docs,
|
||||
HasMore: hasMore,
|
||||
NextCursor: nextCursor,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func hit2AppDocument(hit es.Hit) (*searchEntity.ProjectDocument, error) {
|
||||
doc := &searchEntity.ProjectDocument{}
|
||||
|
||||
if err := sonic.Unmarshal(hit.Source_, doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return doc, nil
|
||||
}
|
||||
|
||||
func fieldValueCaster(fieldName, cursor string) any {
|
||||
switch fieldName {
|
||||
case model.FieldOfCreateTime,
|
||||
model.FieldOfUpdateTime,
|
||||
model.FieldOfPublishTime,
|
||||
model.FieldOfFavTime,
|
||||
model.FieldOfRecentlyOpenTime:
|
||||
cursorInt, err := strconv.ParseInt(cursor, 10, 64)
|
||||
if err != nil {
|
||||
cursorInt = 0
|
||||
}
|
||||
|
||||
return cursorInt
|
||||
default:
|
||||
return cursor
|
||||
}
|
||||
}
|
||||
|
||||
func formatProjectNextCursor(ob string, val *searchEntity.ProjectDocument) string {
|
||||
fieldName2Cursor := map[string]string{
|
||||
model.FieldOfCreateTime: conv.Int64ToStr(val.GetCreateTime()),
|
||||
model.FieldOfUpdateTime: conv.Int64ToStr(val.GetUpdateTime()),
|
||||
model.FieldOfPublishTime: conv.Int64ToStr(val.GetPublishTime()),
|
||||
model.FieldOfFavTime: conv.Int64ToStr(val.GetFavTime()),
|
||||
model.FieldOfRecentlyOpenTime: conv.Int64ToStr(val.GetRecentlyOpenTime()),
|
||||
}
|
||||
|
||||
res, ok := fieldName2Cursor[ob]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func formatResourceNextCursor(ob string, val *searchEntity.ResourceDocument) string {
|
||||
fieldName2Cursor := map[string]string{
|
||||
model.FieldOfCreateTime: conv.Int64ToStr(val.GetCreateTime()),
|
||||
model.FieldOfUpdateTime: conv.Int64ToStr(val.GetUpdateTime()),
|
||||
model.FieldOfPublishTime: conv.Int64ToStr(val.GetPublishTime()),
|
||||
}
|
||||
|
||||
res, ok := fieldName2Cursor[ob]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *searchImpl) SearchResources(ctx context.Context, req *searchEntity.SearchResourcesRequest) (resp *searchEntity.SearchResourcesResponse, err error) {
|
||||
logs.CtxDebugf(ctx, "[SearchResources] search : %s", conv.DebugJsonToStr(req))
|
||||
searchReq := &es.Request{
|
||||
Query: &es.Query{
|
||||
Bool: &es.BoolQuery{},
|
||||
},
|
||||
}
|
||||
|
||||
if req.APPID > 0 {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfAPPID, conv.Int64ToStr(req.APPID)))
|
||||
} else {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfSpaceID, conv.Int64ToStr(req.SpaceID)))
|
||||
searchReq.Query.Bool.Should = append(searchReq.Query.Bool.Should,
|
||||
es.NewNotExistsQuery(fieldOfAPPID))
|
||||
searchReq.Query.Bool.Should = append(searchReq.Query.Bool.Should,
|
||||
es.NewEqualQuery(fieldOfAPPID, "0"))
|
||||
|
||||
searchReq.Query.Bool.MinimumShouldMatch = ptr.Of(1)
|
||||
}
|
||||
|
||||
if req.Name != "" {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewContainsQuery(fieldOfNameRaw, req.Name))
|
||||
}
|
||||
|
||||
if req.OwnerID > 0 {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(fieldOfOwnerID, conv.Int64ToStr(req.OwnerID)))
|
||||
}
|
||||
|
||||
if len(req.ResTypeFilter) == 1 && int(req.ResTypeFilter[0]) != resTypeSearchAll {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(searchEntity.FieldOfResType, req.ResTypeFilter[0]))
|
||||
}
|
||||
|
||||
if len(req.ResTypeFilter) == 2 {
|
||||
resType := req.ResTypeFilter[0]
|
||||
resSubType := int(req.ResTypeFilter[1])
|
||||
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(searchEntity.FieldOfResType, resType))
|
||||
|
||||
if resSubType != resTypeSearchAll {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(searchEntity.FieldOfResSubType, int(resSubType)))
|
||||
}
|
||||
}
|
||||
|
||||
if req.PublishStatusFilter != 0 {
|
||||
searchReq.Query.Bool.Must = append(searchReq.Query.Bool.Must,
|
||||
es.NewEqualQuery(searchEntity.FieldOfPublishStatus, req.PublishStatusFilter))
|
||||
}
|
||||
|
||||
if req.OrderFiledName == "" {
|
||||
req.OrderFiledName = model.FieldOfUpdateTime
|
||||
}
|
||||
|
||||
searchReq.Sort = []es.SortFiled{
|
||||
{
|
||||
Field: req.OrderFiledName,
|
||||
Asc: req.OrderAsc,
|
||||
},
|
||||
}
|
||||
|
||||
reqLimit := 100
|
||||
if req.Limit > 0 {
|
||||
reqLimit = int(req.Limit)
|
||||
}
|
||||
realLimit := reqLimit + 1
|
||||
searchReq.Size = &realLimit
|
||||
|
||||
if req.Page != nil {
|
||||
page := *req.Page
|
||||
if page <= 0 {
|
||||
page = 1
|
||||
}
|
||||
searchReq.From = ptr.Of(int(page-1) * reqLimit)
|
||||
} else if req.Cursor != "" && req.Cursor != "0" {
|
||||
searchReq.SearchAfter = []any{
|
||||
req.Cursor,
|
||||
}
|
||||
}
|
||||
|
||||
result, err := s.esClient.Search(ctx, resourceIndexName, searchReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hits := result.Hits.Hits
|
||||
|
||||
hasMore := func() bool {
|
||||
if len(hits) > reqLimit {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
if hasMore {
|
||||
hits = hits[:reqLimit]
|
||||
}
|
||||
|
||||
docs := make([]*searchEntity.ResourceDocument, 0, len(hits))
|
||||
for _, hit := range hits {
|
||||
doc := &searchEntity.ResourceDocument{}
|
||||
if err = sonic.Unmarshal(hit.Source_, doc); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docs = append(docs, doc)
|
||||
}
|
||||
|
||||
nextCursor := ""
|
||||
if len(docs) > 0 {
|
||||
nextCursor = formatResourceNextCursor(req.OrderFiledName, docs[len(docs)-1])
|
||||
}
|
||||
if nextCursor == "" {
|
||||
hasMore = false
|
||||
}
|
||||
|
||||
var total *int64
|
||||
if result.Hits.Total != nil {
|
||||
total = ptr.Of(result.Hits.Total.Value)
|
||||
}
|
||||
|
||||
resp = &searchEntity.SearchResourcesResponse{
|
||||
Data: docs,
|
||||
TotalHits: total,
|
||||
HasMore: hasMore,
|
||||
NextCursor: nextCursor,
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
36
backend/domain/search/service/service.go
Normal file
36
backend/domain/search/service/service.go
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
"github.com/coze-dev/coze-studio/backend/domain/search/entity"
|
||||
)
|
||||
|
||||
type ProjectEventBus interface {
|
||||
PublishProject(ctx context.Context, event *entity.ProjectDomainEvent) error
|
||||
}
|
||||
|
||||
type ResourceEventBus interface {
|
||||
PublishResources(ctx context.Context, event *entity.ResourceDomainEvent) error
|
||||
}
|
||||
|
||||
type Search interface {
|
||||
SearchProjects(ctx context.Context, req *entity.SearchProjectsRequest) (resp *entity.SearchProjectsResponse, err error)
|
||||
SearchResources(ctx context.Context, req *entity.SearchResourcesRequest) (resp *entity.SearchResourcesResponse, err error)
|
||||
}
|
||||
Reference in New Issue
Block a user