SpringBoot實戰電商項目mall(30k+star)地址: https://github.com/macrozheng/mall
上次寫了一篇《Elasticsearch快速入門,掌握這些剛恰好!》,帶你們學習了下Elasticsearch的基本用法,此次咱們來篇實戰教程,以mall
項目中的商品搜索爲例,把Elasticsearch用起來!html
因爲商品搜索會涉及中文搜索,Elasticsearch須要安裝插件才能夠支持,咱們先來了解下中文分詞器,這裏使用的是IKAnalyzer。在 《Elasticsearch快速入門,掌握這些剛恰好!》中已經講過其安裝方式,這裏直接講解它的用法。
GET /pms/_analyze { "text": "小米手機性價比很高", "tokenizer": "standard" }
GET /pms/_analyze { "text": "小米手機性價比很高", "tokenizer": "ik_max_word" }
在SpringBoot中使用Elasticsearch本文再也不贅述,直接參考《mall整合Elasticsearch實現商品搜索》便可。這裏須要提一下,對於須要進行中文分詞的字段,咱們直接使用@Field註解將analyzer屬性設置爲ik_max_word
便可。java
/** * 搜索中的商品信息 * Created by macro on 2018/6/19. */ @Document(indexName = "pms", type = "product",shards = 1,replicas = 0) public class EsProduct implements Serializable { private static final long serialVersionUID = -1L; @Id private Long id; @Field(analyzer = "ik_max_word",type = FieldType.Text) private String name; @Field(analyzer = "ik_max_word",type = FieldType.Text) private String subTitle; @Field(analyzer = "ik_max_word",type = FieldType.Text) private String keywords; //省略若干代碼...... }
咱們先來實現一個最簡單的商品搜索,搜索商品名稱、副標題、關鍵詞中包含指定關鍵字的商品。
POST /pms/product/_search { "from": 0, "size": 2, "query": { "multi_match": { "query": "小米", "fields": [ "name", "subTitle", "keywords" ] } } }
/** * 商品搜索管理Service實現類 * Created by macro on 2018/6/19. */ @Service public class EsProductServiceImpl implements EsProductService { @Override public Page<EsProduct> search(String keyword, Integer pageNum, Integer pageSize) { Pageable pageable = PageRequest.of(pageNum, pageSize); return productRepository.findByNameOrSubTitleOrKeywords(keyword, keyword, keyword, pageable); } }
接下來咱們來實現一個複雜的商品搜索,涉及到過濾、不一樣字段匹配權重不一樣以及能夠進行排序。
function_score
查詢了;_score
字段來表示的,文檔的_score
字段值越高,表示與搜索條件越匹配,而function_score
查詢能夠經過設置權重來影響_score
字段值,使用它咱們就能夠實現上面的需求了;POST /pms/product/_search { "query": { "function_score": { "query": { "bool": { "must": [ { "match_all": {} } ], "filter": { "bool": { "must": [ { "term": { "brandId": 6 } }, { "term": { "productCategoryId": 19 } } ] } } } }, "functions": [ { "filter": { "match": { "name": "小米" } }, "weight": 10 }, { "filter": { "match": { "subTitle": "小米" } }, "weight": 5 }, { "filter": { "match": { "keywords": "小米" } }, "weight": 2 } ], "score_mode": "sum", "min_score": 2 } }, "sort": [ { "_score": { "order": "desc" } } ] }
/** * 商品搜索管理Service實現類 * Created by macro on 2018/6/19. */ @Service public class EsProductServiceImpl implements EsProductService { @Override public Page<EsProduct> search(String keyword, Long brandId, Long productCategoryId, Integer pageNum, Integer pageSize,Integer sort) { Pageable pageable = PageRequest.of(pageNum, pageSize); NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder(); //分頁 nativeSearchQueryBuilder.withPageable(pageable); //過濾 if (brandId != null || productCategoryId != null) { BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); if (brandId != null) { boolQueryBuilder.must(QueryBuilders.termQuery("brandId", brandId)); } if (productCategoryId != null) { boolQueryBuilder.must(QueryBuilders.termQuery("productCategoryId", productCategoryId)); } nativeSearchQueryBuilder.withFilter(boolQueryBuilder); } //搜索 if (StringUtils.isEmpty(keyword)) { nativeSearchQueryBuilder.withQuery(QueryBuilders.matchAllQuery()); } else { List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>(); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword), ScoreFunctionBuilders.weightFactorFunction(10))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword), ScoreFunctionBuilders.weightFactorFunction(5))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword), ScoreFunctionBuilders.weightFactorFunction(2))); FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]; filterFunctionBuilders.toArray(builders); FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM) .setMinScore(2); nativeSearchQueryBuilder.withQuery(functionScoreQueryBuilder); } //排序 if(sort==1){ //按新品重新到舊 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("id").order(SortOrder.DESC)); }else if(sort==2){ //按銷量從高到低 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("sale").order(SortOrder.DESC)); }else if(sort==3){ //按價格從低到高 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)); }else if(sort==4){ //按價格從高到低 nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC)); }else{ //按相關度 nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); } nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC)); NativeSearchQuery searchQuery = nativeSearchQueryBuilder.build(); LOGGER.info("DSL:{}", searchQuery.getQuery().toString()); return productRepository.search(searchQuery); } }
當咱們查看相關商品的時候,通常底部會有一些商品推薦,這裏使用Elasticsearch來簡單實現下。
POST /pms/product/_search { "query": { "function_score": { "query": { "bool": { "must": [ { "match_all": {} } ], "filter": { "bool": { "must_not": { "term": { "id": 28 } } } } } }, "functions": [ { "filter": { "match": { "name": "紅米5A" } }, "weight": 8 }, { "filter": { "match": { "subTitle": "紅米5A" } }, "weight": 2 }, { "filter": { "match": { "keywords": "紅米5A" } }, "weight": 2 }, { "filter": { "term": { "brandId": 6 } }, "weight": 5 }, { "filter": { "term": { "productCategoryId": 19 } }, "weight": 3 } ], "score_mode": "sum", "min_score": 2 } } }
/** * 商品搜索管理Service實現類 * Created by macro on 2018/6/19. */ @Service public class EsProductServiceImpl implements EsProductService { @Override public Page<EsProduct> recommend(Long id, Integer pageNum, Integer pageSize) { Pageable pageable = PageRequest.of(pageNum, pageSize); List<EsProduct> esProductList = productDao.getAllEsProductList(id); if (esProductList.size() > 0) { EsProduct esProduct = esProductList.get(0); String keyword = esProduct.getName(); Long brandId = esProduct.getBrandId(); Long productCategoryId = esProduct.getProductCategoryId(); //根據商品標題、品牌、分類進行搜索 List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctionBuilders = new ArrayList<>(); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("name", keyword), ScoreFunctionBuilders.weightFactorFunction(8))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("subTitle", keyword), ScoreFunctionBuilders.weightFactorFunction(2))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("keywords", keyword), ScoreFunctionBuilders.weightFactorFunction(2))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("brandId", brandId), ScoreFunctionBuilders.weightFactorFunction(5))); filterFunctionBuilders.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.matchQuery("productCategoryId", productCategoryId), ScoreFunctionBuilders.weightFactorFunction(3))); FunctionScoreQueryBuilder.FilterFunctionBuilder[] builders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[filterFunctionBuilders.size()]; filterFunctionBuilders.toArray(builders); FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(builders) .scoreMode(FunctionScoreQuery.ScoreMode.SUM) .setMinScore(2); //用於過濾掉相同的商品 BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder(); boolQueryBuilder.mustNot(QueryBuilders.termQuery("id",id)); //構建查詢條件 NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); builder.withQuery(functionScoreQueryBuilder); builder.withFilter(boolQueryBuilder); builder.withPageable(pageable); NativeSearchQuery searchQuery = builder.build(); LOGGER.info("DSL:{}", searchQuery.getQuery().toString()); return productRepository.search(searchQuery); } return new PageImpl<>(null); } }
在搜索商品時,常常會有一個篩選界面來幫助咱們找到想要的商品,這裏使用Elasticsearch來簡單實現下。
POST /pms/product/_search { "query": { "multi_match": { "query": "小米", "fields": [ "name", "subTitle", "keywords" ] } }, "size": 0, "aggs": { "brandNames": { "terms": { "field": "brandName", "size": 10 } }, "productCategoryNames": { "terms": { "field": "productCategoryName", "size": 10 } }, "allAttrValues": { "nested": { "path": "attrValueList" }, "aggs": { "productAttrs": { "filter": { "term": { "attrValueList.type": 1 } }, "aggs": { "attrIds": { "terms": { "field": "attrValueList.productAttributeId", "size": 10 }, "aggs": { "attrValues": { "terms": { "field": "attrValueList.value", "size": 10 } }, "attrNames": { "terms": { "field": "attrValueList.name", "size": 10 } } } } } } } } } }
小米
這個關鍵字的時候,聚合出了下面的分類和品牌信息;屏幕尺寸
爲5.0
和5.8
的篩選屬性信息;/** * 商品搜索管理Service實現類 * Created by macro on 2018/6/19. */ @Service public class EsProductServiceImpl implements EsProductService { @Override public EsProductRelatedInfo searchRelatedInfo(String keyword) { NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); //搜索條件 if(StringUtils.isEmpty(keyword)){ builder.withQuery(QueryBuilders.matchAllQuery()); }else{ builder.withQuery(QueryBuilders.multiMatchQuery(keyword,"name","subTitle","keywords")); } //聚合搜索品牌名稱 builder.addAggregation(AggregationBuilders.terms("brandNames").field("brandName")); //集合搜索分類名稱 builder.addAggregation(AggregationBuilders.terms("productCategoryNames").field("productCategoryName")); //聚合搜索商品屬性,去除type=1的屬性 AbstractAggregationBuilder aggregationBuilder = AggregationBuilders.nested("allAttrValues","attrValueList") .subAggregation(AggregationBuilders.filter("productAttrs",QueryBuilders.termQuery("attrValueList.type",1)) .subAggregation(AggregationBuilders.terms("attrIds") .field("attrValueList.productAttributeId") .subAggregation(AggregationBuilders.terms("attrValues") .field("attrValueList.value")) .subAggregation(AggregationBuilders.terms("attrNames") .field("attrValueList.name")))); builder.addAggregation(aggregationBuilder); NativeSearchQuery searchQuery = builder.build(); return elasticsearchTemplate.query(searchQuery, response -> { LOGGER.info("DSL:{}",searchQuery.getQuery().toString()); return convertProductRelatedInfo(response); }); } /** * 將返回結果轉換爲對象 */ private EsProductRelatedInfo convertProductRelatedInfo(SearchResponse response) { EsProductRelatedInfo productRelatedInfo = new EsProductRelatedInfo(); Map<String, Aggregation> aggregationMap = response.getAggregations().getAsMap(); //設置品牌 Aggregation brandNames = aggregationMap.get("brandNames"); List<String> brandNameList = new ArrayList<>(); for(int i = 0; i<((Terms) brandNames).getBuckets().size(); i++){ brandNameList.add(((Terms) brandNames).getBuckets().get(i).getKeyAsString()); } productRelatedInfo.setBrandNames(brandNameList); //設置分類 Aggregation productCategoryNames = aggregationMap.get("productCategoryNames"); List<String> productCategoryNameList = new ArrayList<>(); for(int i=0;i<((Terms) productCategoryNames).getBuckets().size();i++){ productCategoryNameList.add(((Terms) productCategoryNames).getBuckets().get(i).getKeyAsString()); } productRelatedInfo.setProductCategoryNames(productCategoryNameList); //設置參數 Aggregation productAttrs = aggregationMap.get("allAttrValues"); List<LongTerms.Bucket> attrIds = ((LongTerms) ((InternalFilter) ((InternalNested) productAttrs).getProperty("productAttrs")).getProperty("attrIds")).getBuckets(); List<EsProductRelatedInfo.ProductAttr> attrList = new ArrayList<>(); for (Terms.Bucket attrId : attrIds) { EsProductRelatedInfo.ProductAttr attr = new EsProductRelatedInfo.ProductAttr(); attr.setAttrId((Long) attrId.getKey()); List<String> attrValueList = new ArrayList<>(); List<StringTerms.Bucket> attrValues = ((StringTerms) attrId.getAggregations().get("attrValues")).getBuckets(); List<StringTerms.Bucket> attrNames = ((StringTerms) attrId.getAggregations().get("attrNames")).getBuckets(); for (Terms.Bucket attrValue : attrValues) { attrValueList.add(attrValue.getKeyAsString()); } attr.setAttrValues(attrValueList); if(!CollectionUtils.isEmpty(attrNames)){ String attrName = attrNames.get(0).getKeyAsString(); attr.setAttrName(attrName); } attrList.add(attr); } productRelatedInfo.setProductAttrs(attrList); return productRelatedInfo; } }
關於Spring Data Elasticsearch的具體使用能夠參考官方文檔。
https://docs.spring.io/spring...git
https://github.com/macrozheng/mallgithub
mall項目全套學習教程連載中,關注公衆號第一時間獲取。spring