目標
僅提供一個搜索API,就能兼容前端的各種查詢需求
環境
- ElasticSearch 5.6.8
- kibana 5.6.8
需求
- 界面根據用戶點擊,拼接用戶的查詢需求
input:
{
1. 匹配查詢:
keywords: 商品名 , 不傳入默認值爲"烤箱 家用小烤箱"
2. 過濾查詢(布爾查詢):
匹配
category: 商品分類
brand: 品牌
範圍
price: 價格
規格查詢
屏幕尺寸:5寸
3. 分頁查詢
4. 分組查詢
}
- 後臺接收搜索參數,拼接成DSL。
需求的拆分與實現
【1】搜索框檢索 - 匹配查詢
【1】 業務邏輯
用戶進入商品搜索頁面後,默認進行一次查詢。
- 設定默認搜索詞可以避免觸發大量數據湧入前端
- 默認搜索詞可以根據以後業務升級成動態數據,比如廣告業務
【1】 DSL
GET /_search
{
"query": {
"match" : {
"name" : "華爲"
}
},
"from": 0, // 分頁查詢 "from" 代表 page - 1
"size": 40 // 分頁查詢,每頁顯示數據量
}
【2】分類項顯示 - 聚合查詢 - 詞條查詢
【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
篩選的,跟品牌的思路是一致的,但是會遇到以下問題:
- 商品規格有多個,品牌對應只有一個,問題:商品表,規格表,兩張表的數據,如何把規格在一次DSL中抓取出來
- 品牌的傳參,問題:點擊"臥式", 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
即可。