用到京東的對其搜索應該不會陌生,其搜索也是使用elasticsearch完成的,下圖爲一個搜索效果圖:
搜索篩選條件會根據查詢返回的結果動態變化,要實現這個功能就要用到elasticsearch的聚合功能,先看下商品索引對應的映射:
{
"mapping": {
"es-product": {
"dynamic_templates": [
{
"strings_as_keyword": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
],
"properties": {
"aggProperties": {
"type": "nested",
"properties": {
"key": {
"type": "keyword"
},
"value": {
"type": "keyword"
}
}
},
"brandName": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"level1Category": {
"type": "keyword"
},
"level2Category": {
"type": "keyword"
},
"level3Category": {
"type": "keyword"
},
"mainTitle": {
"type": "text",
"analyzer": "ik_max_word"
},
"searchProperties": {
"properties": {
"光澤度": {
"type": "keyword"
},
"光源個數": {
"type": "keyword"
},
"光源類型": {
"type": "keyword"
},
"功率": {
"type": "keyword"
},
"包裝體積": {
"type": "keyword"
},
"型號": {
"type": "keyword"
},
"密度": {
"type": "keyword"
},
"導熱性": {
"type": "keyword"
},
"導電性": {
"type": "keyword"
},
"延展性": {
"type": "keyword"
},
"抗氧化性": {
"type": "keyword"
},
"是否帶護欄": {
"type": "keyword"
},
"是否帶軟靠": {
"type": "keyword"
},
"是否帶遙控器": {
"type": "keyword"
},
"照射面積": {
"type": "keyword"
},
"熔點": {
"type": "keyword"
},
"磁性": {
"type": "keyword"
},
"表面機理": {
"type": "keyword"
},
"適用人數": {
"type": "keyword"
},
"適用空間": {
"type": "keyword"
},
"風格": {
"type": "keyword"
}
}
},
"subTitle": {
"type": "keyword"
}
}
}
}
}
searchProperties部分爲動態屬性,使用elasticsearch的dynamic template配置,aggProperties部分爲動態聚合所用,通過aggProperties下面的值動態聚合滿足條件的搜索結果所具有的所有屬性,比如光澤度、熔點等,而searchProperties是爲搜索使用,先說下屬性動態聚合的實現,下面是elasticsearch的查詢腳本:
{
"from" : 0, "size" : 100,
"query": {
"bool":{
"must":[{
"match" : {
"mainTitle" : "拉手"
}
}
]
}
},
"aggs": {
"dynamicproperty": {
"nested": {
"path": "aggProperties"
},
"aggs": {
"name": {
"terms": {
"field": "aggProperties.key"
},
"aggs": {
"value": {
"terms": {
"field": "aggProperties.value"
}
}
}
}
}
}
}
}
通過上面的搜索得到下面結果:
{
"took": 13,
"timed_out": false,
"_shards": {
"total": 3,
"successful": 3,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 8,
"max_score": 1.3591974,
"hits": [
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157c3c61c0089",
"_score": 1.3591974,
"_source": {
"id": "2c95ae137157829c017157c3c61c0089",
"mainTitle": "富達銘黑色隱形拉手免打孔鋁合金拉手一字型暗拉手櫥櫃衣櫃門長把手隱藏拉手長拉手 咖啡150mm",
"subTitle": "",
"brandName": "三葉",
"aggProperties": [
{
"key": "密度",
"value": "2000克/cm³"
},
{
"key": "熔點",
"value": "2000℃"
},
{
"key": "導熱性",
"value": "很好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
},
{
"key": "導電性",
"value": "是"
},
{
"key": "磁性",
"value": "順磁材料"
},
{
"key": "光澤度",
"value": "啞光"
},
{
"key": "表面機理",
"value": "拉絲"
},
{
"key": "抗氧化性",
"value": "中"
}
],
"searchProperties": {
"表面機理": "拉絲",
"抗氧化性": "中",
"導熱性": "很好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"磁性": "順磁材料",
"導電性": "是",
"密度": "2000克/cm³",
"熔點": "2000℃",
"光澤度": "啞光"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157ad040c004d",
"_score": 1.2214447,
"_source": {
"id": "2c95ae137157829c017157ad040c004d",
"mainTitle": "致諾 老式鐵皮拉手 不鏽鋼拉手大木門蟹殼拉手 明裝四孔鐵皮門輕把手簡易拉手老式通用型防盜門把手 大號2個價格",
"subTitle": "",
"brandName": "三葉",
"aggProperties": [
{
"key": "密度",
"value": "1000克/cm³"
},
{
"key": "熔點",
"value": "1000℃"
},
{
"key": "導熱性",
"value": "較好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
}
],
"searchProperties": {
"導熱性": "較好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"密度": "1000克/cm³",
"熔點": "1000℃"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157a991b60011",
"_score": 1.1077276,
"_source": {
"id": "2c95ae137157829c017157a991b60011",
"mainTitle": "卡貝拉手衣櫃門拉手抽屜櫥櫃衣櫃鞋櫃高檔門把手現代簡約五金配件",
"subTitle": "",
"brandName": "宗藝石材",
"aggProperties": [
{
"key": "密度",
"value": "1000克/cm³"
},
{
"key": "熔點",
"value": "2000℃"
},
{
"key": "導熱性",
"value": "較好"
},
{
"key": "導電性",
"value": "否"
},
{
"key": "磁性",
"value": "順磁材料"
},
{
"key": "光澤度",
"value": "啞光"
},
{
"key": "表面機理",
"value": "拉絲"
},
{
"key": "抗氧化性",
"value": "中"
}
],
"searchProperties": {
"表面機理": "拉絲",
"抗氧化性": "中",
"導熱性": "較好",
"磁性": "順磁材料",
"導電性": "否",
"密度": "1000克/cm³",
"熔點": "2000℃",
"光澤度": "啞光"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157c5c98100a7",
"_score": 0.77200073,
"_source": {
"id": "2c95ae137157829c017157c5c98100a7",
"mainTitle": "CD超代歐式簡約陶瓷衣櫃櫃門抽屜把手小拉手 田園大理石陶瓷酒櫃拉手 單隻價格 A7-96孔距-長120mm",
"subTitle": "",
"brandName": "三葉",
"aggProperties": [
{
"key": "密度",
"value": "1500克/cm³"
},
{
"key": "熔點",
"value": "1600℃"
},
{
"key": "導熱性",
"value": "較好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
},
{
"key": "導電性",
"value": "是"
},
{
"key": "磁性",
"value": "順磁材料"
},
{
"key": "光澤度",
"value": "啞光"
},
{
"key": "表面機理",
"value": "拉絲"
},
{
"key": "抗氧化性",
"value": "高"
}
],
"searchProperties": {
"表面機理": "拉絲",
"抗氧化性": "高",
"導熱性": "較好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"磁性": "順磁材料",
"導電性": "是",
"密度": "1500克/cm³",
"熔點": "1600℃",
"光澤度": "啞光"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157aaa03d0022",
"_score": 0.7351246,
"_source": {
"id": "2c95ae137157829c017157aaa03d0022",
"mainTitle": "適用於步陽羣升新多星月神春天進戶大門防盜門把手 拉手面板雙快加厚實",
"subTitle": "",
"brandName": "西門子",
"aggProperties": [
{
"key": "密度",
"value": "1000克/cm³"
},
{
"key": "熔點",
"value": "1500℃"
},
{
"key": "導熱性",
"value": "較好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
}
],
"searchProperties": {
"導熱性": "較好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"密度": "1000克/cm³",
"熔點": "1500℃"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157ac51f9003f",
"_score": 0.6896249,
"_source": {
"id": "2c95ae137157829c017157ac51f9003f",
"mainTitle": "防盜門把手拉手進戶門拉手雙活雙快執手手柄加厚大門鎖具配件 加厚鋁合金門把手 >55mm 通用型 不帶鑰匙",
"subTitle": "",
"brandName": "西門子",
"aggProperties": [
{
"key": "密度",
"value": "1500克/cm³"
},
{
"key": "熔點",
"value": "2000℃"
},
{
"key": "導熱性",
"value": "較好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
},
{
"key": "導電性",
"value": "是"
},
{
"key": "磁性",
"value": "順磁材料"
},
{
"key": "光澤度",
"value": "高光"
},
{
"key": "表面機理",
"value": "拉絲"
},
{
"key": "抗氧化性",
"value": "中"
}
],
"searchProperties": {
"表面機理": "拉絲",
"抗氧化性": "中",
"導熱性": "較好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"磁性": "順磁材料",
"導電性": "是",
"密度": "1500克/cm³",
"熔點": "2000℃",
"光澤度": "高光"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157add2c8005c",
"_score": 0.4982656,
"_source": {
"id": "2c95ae137157829c017157add2c8005c",
"mainTitle": "匯樂斯 防盜門把手拉手執手大門把手門把手門鎖把手鎖體鎖帽 面板把手一對/配件+外固定把手 左開",
"subTitle": "",
"brandName": "三棵樹",
"aggProperties": [
{
"key": "密度",
"value": "1000克/cm³"
},
{
"key": "熔點",
"value": "1000℃"
},
{
"key": "導熱性",
"value": "很好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
}
],
"searchProperties": {
"導熱性": "很好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"密度": "1000克/cm³",
"熔點": "1000℃"
}
}
},
{
"_index": "smp-product",
"_type": "es-product",
"_id": "2c95ae137157829c017157c4cb880098",
"_score": 0.48578173,
"_source": {
"id": "2c95ae137157829c017157c4cb880098",
"mainTitle": "防盜門把手拉手雙活雙快執手手柄大門鎖配件鋁合金門把手 加厚白金帶點固定款(13*13) >55mm 通用型 不帶鑰匙",
"subTitle": "",
"brandName": "三葉",
"aggProperties": [
{
"key": "密度",
"value": "1500克/cm³"
},
{
"key": "熔點",
"value": "1500℃"
},
{
"key": "導熱性",
"value": "較好"
},
{
"key": "延展性",
"value": "易鍛物質,不需退火可錘鍊可壓延"
},
{
"key": "導電性",
"value": "否"
},
{
"key": "磁性",
"value": "鐵磁材料"
},
{
"key": "光澤度",
"value": "高光"
},
{
"key": "表面機理",
"value": "拉絲"
},
{
"key": "抗氧化性",
"value": "中"
}
],
"searchProperties": {
"表面機理": "拉絲",
"抗氧化性": "中",
"導熱性": "較好",
"延展性": "易鍛物質,不需退火可錘鍊可壓延",
"磁性": "鐵磁材料",
"導電性": "否",
"密度": "1500克/cm³",
"熔點": "1500℃",
"光澤度": "高光"
}
}
}
]
},
"aggregations": {
"dynamicproperty": {
"doc_count": 56,
"name": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "密度",
"doc_count": 8,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "1000克/cm³",
"doc_count": 4
},
{
"key": "1500克/cm³",
"doc_count": 3
},
{
"key": "2000克/cm³",
"doc_count": 1
}
]
}
},
{
"key": "導熱性",
"doc_count": 8,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "較好",
"doc_count": 6
},
{
"key": "很好",
"doc_count": 2
}
]
}
},
{
"key": "熔點",
"doc_count": 8,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "2000℃",
"doc_count": 3
},
{
"key": "1000℃",
"doc_count": 2
},
{
"key": "1500℃",
"doc_count": 2
},
{
"key": "1600℃",
"doc_count": 1
}
]
}
},
{
"key": "延展性",
"doc_count": 7,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "易鍛物質,不需退火可錘鍊可壓延",
"doc_count": 7
}
]
}
},
{
"key": "光澤度",
"doc_count": 5,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "啞光",
"doc_count": 3
},
{
"key": "高光",
"doc_count": 2
}
]
}
},
{
"key": "導電性",
"doc_count": 5,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "是",
"doc_count": 3
},
{
"key": "否",
"doc_count": 2
}
]
}
},
{
"key": "抗氧化性",
"doc_count": 5,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "中",
"doc_count": 4
},
{
"key": "高",
"doc_count": 1
}
]
}
},
{
"key": "磁性",
"doc_count": 5,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "順磁材料",
"doc_count": 4
},
{
"key": "鐵磁材料",
"doc_count": 1
}
]
}
},
{
"key": "表面機理",
"doc_count": 5,
"value": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "拉絲",
"doc_count": 5
}
]
}
}
]
}
}
}
}
這樣就在返回搜索結果的同時返回了滿足條件的所有索引的動態屬性,把aggregations中的數據處理後返回給前端就可以實現類似京東的商品搜索效果。
接下來給出基於RestHighLevelClient的客戶端搜索實現:
@Test
public void aggregate2() throws IOException {
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.must(QueryBuilders.matchQuery("mainTitle","拉手"));
BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery();
boolQueryBuilder1.must(QueryBuilders.matchQuery("props.key","密度"));
boolQueryBuilder1.must(QueryBuilders.matchQuery("props.value","2000克/cm³"));
NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("props",boolQueryBuilder1, ScoreMode.Total);
searchSourceBuilder.query(nestedQueryBuilder);
NestedAggregationBuilder level1Aggs = AggregationBuilders.nested("level1Agg","props");
TermsAggregationBuilder propKeyAgg = AggregationBuilders.terms("keyAggs").field("props.key");
level1Aggs.subAggregation(propKeyAgg);
TermsAggregationBuilder propValAgg = AggregationBuilders.terms("valAggs").field("props.value");
propKeyAgg.subAggregation(propValAgg);
searchSourceBuilder.aggregation(level1Aggs);
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices("smp-product");
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
if(RestStatus.OK == searchResponse.status()){
//查詢結果
SearchHits hits = searchResponse.getHits();
log.info("hits={}",hits.getTotalHits());
SearchHit[] arrs = hits.getHits();
List<ESProduct> prods = new ArrayList<>(arrs.length);
for(int i=0,len=arrs.length;i<len;i++){
SearchHit hit = arrs[i];
//ESProduct爲商品類
ESProduct prod = JSON.parseObject(hit.getSourceAsString(),ESProduct.class);
prods.add(prod);
}
Aggregations aggregations = searchResponse.getAggregations();
//聚合結果處理
ParsedNested level1Agg = aggregations.get("level1Agg");
Aggregations keyAggs = level1Agg.getAggregations();
Terms byAgeAggregation = keyAggs.get("keyAggs");
List<FilterParamsDto> params = new ArrayList<>();
for(Terms.Bucket buck : byAgeAggregation.getBuckets()) {
Aggregations valAggs = buck.getAggregations();
Terms byValAggs = valAggs.get("valAggs");
FilterParamsDto param = new FilterParamsDto();
param.setName(buck.getKeyAsString());
List<String> vals = new ArrayList<>();
param.setFilters(vals);
for(Terms.Bucket bk : byValAggs.getBuckets()){
log.info("key: " + bk.getKeyAsString());
vals.add(bk.getKeyAsString());
}
params.add(param);
}
}
log.info("finish");
}
上面程序對應的ESProduct類定義如下:
@Getter
@Setter
@Document(indexName="smp-product",type="es-product",shards=3,replicas = 0)
@Mapping(mappingPath="mapping/es-product.json")
public class ESProduct extends BaseEntityMethods {
@Field
String id;
/**
* 商品名字
*/
@Field(name="mainTitle")
String mainTitle;
@Field(name="subTitle")
String subTitle;
/**
* 商品品牌
*/
@Field(name="brandName")
String brandName;
/**
* 一級類目名稱
*/
@JsonIgnore
@Field(name="level1Category")
String level1Category;
/**
* 二級類目名稱
*/
@JsonIgnore
@Field(name="level2Category")
String level2Category;
/**
* 三級類目名稱
*/
@JsonIgnore
@Field(name="level3Category")
String level3Category;
/**
* 聚合關鍵詞使用
*/
@Field(name="aggProperties")
List<SearchProperty> aggProperties;
/**
* 檢索使用
*/
@Field(name="searchProperties")
Map<String,String> searchProperties;
}
PS:
1、本文使用的elasticsearch爲6.8版本