» Go:使用ElasticSearch构建全文检索API » 3. 搜索文档 » 3.1 发送 Search 请求

发送 Search 请求

更新 domain/gateway/book_manager.go:

@@ -12,4 +12,5 @@ import (
 // BookManager manages all books
 type BookManager interface {
        IndexBook(ctx context.Context, b *model.Book) (string, error)
+       SearchBooks(ctx context.Context, query string) ([]*model.Book, error)
 }

更新 infrastructure/search/es.go:

@@ -5,8 +5,11 @@ package search
 
 import (
        "context"
+       "encoding/json"
 
        "github.com/elastic/go-elasticsearch/v8"
+       "github.com/elastic/go-elasticsearch/v8/typedapi/core/search"
+       "github.com/elastic/go-elasticsearch/v8/typedapi/types"
 
        "literank.com/fulltext-books/domain/model"
 )
@@ -42,3 +45,29 @@ func (s *ElasticSearchEngine) IndexBook(ctx context.Context, b *model.Book) (str
        }
        return resp.Id_, nil
 }
+
+// SearchBooks search from ES and return a list of books
+func (s *ElasticSearchEngine) SearchBooks(ctx context.Context, query string) ([]*model.Book, error) {
+       resp, err := s.client.Search().Index(INDEX_BOOK).
+               Request(&search.Request{
+                       Query: &types.Query{
+                               MultiMatch: &types.MultiMatchQuery{
+                                       Query:  query,
+                                       Fields: []string{"title", "author", "content"},
+                               },
+                       },
+               }).
+               Do(ctx)
+       if err != nil {
+               return nil, err
+       }
+       books := make([]*model.Book, 0)
+       for _, hit := range resp.Hits.Hits {
+               var b model.Book
+               if err := json.Unmarshal(hit.Source_, &b); err != nil {
+                       return nil, err
+               }
+               books = append(books, &b)
+       }
+       return books, nil
+}

此处我们使用 MultiMatch 来进行多字段查询:“title”,“author”和“content”。

更新 application/executor/book_operator.go

@@ -24,3 +24,7 @@ func NewBookOperator(b gateway.BookManager) *BookOperator {
 func (o *BookOperator) CreateBook(ctx context.Context, b *model.Book) (string, error) {
        return o.bookManager.IndexBook(ctx, b)
 }
+
+func (o *BookOperator) SearchBooks(ctx context.Context, query string) ([]*model.Book, error) {
+       return o.bookManager.SearchBooks(ctx, query)
+}

更新 adapter/router.go

@@ -14,6 +14,8 @@ import (
        "literank.com/fulltext-books/domain/model"
 )
 
+const fieldQuery = "q"
+
 // RestHandler handles all restful requests
 type RestHandler struct {
        bookOperator *executor.BookOperator
@@ -31,6 +33,7 @@ func MakeRouter(wireHelper *application.WireHelper) (*gin.Engine, error) {
        // Create a new Gin router
        r := gin.Default()
 
+       r.GET("/books", rest.searchBooks)
        r.POST("/books", rest.createBook)
        return r, nil
 }
@@ -51,3 +54,13 @@ func (r *RestHandler) createBook(c *gin.Context) {
        }
        c.JSON(http.StatusCreated, gin.H{"id": bookID})
 }
+
+func (r *RestHandler) searchBooks(c *gin.Context) {
+       books, err := r.bookOperator.SearchBooks(c, c.Query(fieldQuery))
+       if err != nil {
+               fmt.Printf("Failed to search books: %v\n", err)
+               c.JSON(http.StatusNotFound, gin.H{"error": "failed to search books"})
+               return
+       }
+       c.JSON(http.StatusOK, books)
+}

重启后再次使用 curl 测试:

curl 'http://localhost:8080/books?q=katniss+hunger'

样例响应:

[
  {
    "title": "The Hunger Games",
    "author": "Suzanne Collins",
    "published_at": "2008-09-14",
    "content": "In a dystopian future, teenagers are forced to participate in a televised death match called the Hunger Games. Katniss Everdeen volunteers to take her sister‘s place and becomes a symbol of rebellion."
  }
]

PostmanInsomnia 之类工具中尝试:

http://localhost:8080/books?q=new%20york%20circus%20girl

在 URL 编码中,%20+ 都可以表示空格,但是它们的使用场景稍有区别。

Insomnia Screenshot

赞!你刚刚完成了一个全文检索。

注意:
全文检索需要语言分词器。默认的标准分词器对中、日、韩文等语言处理效果不理想。 可以使用语言特定的分词器以获取更佳效果。