【Elasticsearch】模仿淘寶,將搜索框的業務邏輯抽象成DSL語句

目標

僅提供一個搜索API,就能兼容前端的各種查詢需求

環境

  • ElasticSearch 5.6.8
  • kibana 5.6.8

需求

  • 界面根據用戶點擊,拼接用戶的查詢需求
input:
{
     1. 匹配查詢:
         keywords: 商品名 , 不傳入默認值爲"烤箱 家用小烤箱"
     2. 過濾查詢(布爾查詢):
         匹配
             category: 商品分類
             brand: 品牌
         範圍
             price: 價格
     	 規格查詢
     		 屏幕尺寸:53. 分頁查詢     	
	 4. 分組查詢	  		 
}
  • 後臺接收搜索參數,拼接成DSL。

需求的拆分與實現

【1】搜索框檢索 - 匹配查詢

匹配查詢 match-query 官方文檔

【1】 業務邏輯

在這裏插入圖片描述
用戶進入商品搜索頁面後,默認進行一次查詢。

  • 設定默認搜索詞可以避免觸發大量數據湧入前端
  • 默認搜索詞可以根據以後業務升級成動態數據,比如廣告業務

【1】 DSL

GET /_search
{
    "query": {
        "match" : {
            "name" : "華爲"
        }
    },
 	"from": 0,  // 分頁查詢 "from" 代表 page - 1
 	"size": 40  // 分頁查詢,每頁顯示數據量
}

【2】分類項顯示 - 聚合查詢 - 詞條查詢

聚合查詢-詞條查詢 aggs-terms 官方文檔

【2】業務邏輯

在這裏插入圖片描述

  • “搜索”被點擊後會顯示所有分類的一個表

【2】DSL

GET /_search
{
    "query": {
        "match" : {
            "name" : "烤箱 家用小烤箱"
        }
    },
    "aggs": {
        "skuCategoryGroup": { // skuCategoryGroup 表示自定義組名
      		"terms": {
       	 		"field": "categoryName",
        		"size": 500
      		}
 	 	}
	}
}

【2】值得注意

值得注意的是,聚合查詢的結果會放在ES response裏的,層級結構要關係,這個關乎java代碼的編寫
在這裏插入圖片描述

【3】單擊分類進行過濾 - 過濾查詢 - 布爾查詢

布爾查詢,不同的版本有差異,這裏選用後置布爾查詢

  • 2.x版本的 post_filter ,跟query ,aggs是同級的,用java代碼拼接起來可以解耦
  • post_filter 嵌套 bool 布爾查詢,語義合適
    Elasticsearch 中文指南 post_filter 後置過濾查詢
    bool 布爾查詢 官方文檔
    綜上,這個業務用了ES 2.x的語法post_filter,加上服務器版本5.6.8的ES文檔中的bool布爾查詢。
    PS: 語法上的組合使用,先去kibana驗證可行性才能繼續往後開發

【3】業務邏輯

在這裏插入圖片描述

  • 點擊“美的”品牌,只在匹配查詢的結果集中取"美的"品牌

【3】DSL

  "post_filter": {
    "bool": {
      "filter": [ // must 、filter 、should 、must_not。 filter 區別於must: 不做搜索排名處理(打分算法)
        {
          "term": {
            "brandName": {
              "value": "美的"
            }
          }
        }
      ]
    }
   }       

【4】單擊規格進行過濾 - 在【3】的實現下加入規格的參數

【4】業務邏輯

在這裏插入圖片描述

【4】獲得並顯示規格參數

規格參數是在match_query 的基礎上使用後置過濾器post_filter篩選的,跟品牌的思路是一致的,但是會遇到以下問題:

  1. 商品規格有多個,品牌對應只有一個,問題:商品表,規格表,兩張表的數據,如何把規格在一次DSL中抓取出來
  2. 品牌的傳參,問題:點擊"臥式", DSL告訴ES要搜{“款式” : “臥式”},建立一個字段叫"臥式",顯然不合理

解決思路:

  • 反三範式設計,商品表冗餘規格的字段spec,用json存起來
skuid name brand(品牌) spec(規格) spec_template(對應規格的模板)
1 美的家用烤箱 可解凍 美的 {“款式”:“臥式”,“附加功能”:“解凍”} 廚具

主要目的 :減少表連接,並且把規格名和規格內容的關係也保留的起來

  • java程序處理spec(規格)字段的數據
    把spec的字段全部放進set集合裏面做第一輪去重 ——》 規格字段都是獨一無二的,可以使用ES的aggs去重
    {“款式”:“臥式”,“附加功能”:“解凍”}
    {“款式”:“臥式”,“附加功能”:“定時”}
    {“款式”:“立式”,“附加功能”:“解凍”}
    第二輪去重,key去重,並設定key對應的取值,粒度太小,適合把第一輪去重的結果交給java代碼去重
    “款式” : "臥式 立式"
    "附加功能" : “解凍 定時”
    至此,界面就可以顯示這樣的篩選添加了

新的問題{"款式" : "臥式"} ,前端這麼傳字符串,後端不可能寫死一個key 爲 “款式”的解析代碼。

  • 解決思路1:後端維護一個字典把諸如 “款式”、“附加功能” 作爲產品參數的解析規則。
  • 解決思路2:前端加個spec_前綴或者導入數據時加個spec_前綴,後端解析成本小
    這裏使用思路二。

新的問題{“附加功能”:“定時”} 的語義是在附加功能中,使用關鍵詞:定時不分詞得查詢
這裏引用一個實踐

       	// 把spec 字段轉換成一個map
        for (SkuInfo skuInfo : skuInfos) {
            String spec = skuInfo.getSpec();
            Map map = JSON.parseObject(spec, Map.class);
            skuInfo.setSpecMap(map);
        }

        //存進ES的時候,map的數據類型如何映射爲ES的Field
        skuEsMapper.saveAll(skuInfos);

使用kibana能查詢到對應的文檔字段被映射爲
在這裏插入圖片描述

【4】DSL

把規格參數的查詢,加入bool布爾查詢的代碼塊中

  "post_filter": {
    "bool": {
      "filter": [ // must 、filter 、should 、must_not。 filter 區別於must: 不做搜索排名處理(打分算法)
        {
          "term": {
            "brandName": {
              "value": "美的"
            }
          }
        },
         {
          "term": {
            "specMap.款式.keyword": {
              "value": "臥式"
            }
          }
        },
      ]
    }
   }       
       

【5】整體DSL語句

{
  "from": 0, // 分頁
  "size": 40,
  "query": {
    "match": {
      "name": "烤箱 家用小烤箱"
    }
  },
  "post_filter": {
    "bool": {
      "filter": [
        {
          "term": {
            "brandName": { // 品牌篩選
              "value": "美的"
            }
          }
        },
        {
          "range": {  // 價格區間
            "price": {
              "from": "0",
              "to": "500",
              "include_lower": true,
              "include_upper": true
            }
          }
        },
        {
          "term": { // 參數篩選
            "specMap.款式.keyword": {
              "value": "臥式"
            }
          }
        }
      ]
    }
  },
  "aggregations": {
    "skuCategoryGroup": { 
      "terms": {
        "field": "categoryName",
        "size": 500
      }
    },
    "skuBrandGroup": { // 品牌分組查詢,用於顯示
      "terms": {
        "field": "brandName",
        "size": 5000
      }
    },
    "skuSpecGroup": { // Spec第一輪去重,DSL過濾即可
      "terms": {
        "field": "spec.keyword",
        "size": 500000
      }
    }
  },
  "highlight": {  // 關鍵詞高亮配置
    "pre_tags": [
      """<em style="color:red">""" 
    ],
    "post_tags": [
      "</em>"
    ],
    "fields": {
      "name": {}
    }
  }
}

SpringBoot 操作 ElasticSerach

版本由起步依賴決定,直接導入pom即可

	<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
    </dependencies>

application.yml文件配置一下

spring:
  data:
    elasticsearch:
      cluster-name: es
      cluster-nodes: 127.0.0.1:9300

將DSL做成動態拼接的,使用ElasticSerachTemplate即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章