SpringBoot實戰電商項目mall(30k+star)地址:github.com/macrozheng/…html
上次寫了一篇《Elasticsearch快速入門,掌握這些剛恰好!》,帶你們學習了下Elasticsearch的基本用法,此次咱們來篇實戰教程,以mall
項目中的商品搜索爲例,把Elasticsearch用起來!java
因爲商品搜索會涉及中文搜索,Elasticsearch須要安裝插件才能夠支持,咱們先來了解下中文分詞器,這裏使用的是IKAnalyzer。在《Elasticsearch快速入門,掌握這些剛恰好!》中已經講過其安裝方式,這裏直接講解它的用法。git
GET /pms/_analyze
{
"text": "小米手機性價比很高",
"tokenizer": "standard"
}
複製代碼
GET /pms/_analyze
{
"text": "小米手機性價比很高",
"tokenizer": "ik_max_word"
}
複製代碼
在SpringBoot中使用Elasticsearch本文再也不贅述,直接參考《mall整合Elasticsearch實現商品搜索》便可。這裏須要提一下,對於須要進行中文分詞的字段,咱們直接使用@Field註解將analyzer屬性設置爲ik_max_word
便可。github
/** * 搜索中的商品信息 * 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;
//省略若干代碼......
}
複製代碼
咱們先來實現一個最簡單的商品搜索,搜索商品名稱、副標題、關鍵詞中包含指定關鍵字的商品。spring
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);
}
}
複製代碼
接下來咱們來實現一個複雜的商品搜索,涉及到過濾、不一樣字段匹配權重不一樣以及能夠進行排序。bash
這裏咱們有一點特殊的需求,好比商品名稱匹配關鍵字的的商品咱們認爲與搜索條件更匹配,其次是副標題和關鍵字,這時就須要用到function_score
查詢了;elasticsearch
在Elasticsearch中搜索到文檔的相關性由_score
字段來表示的,文檔的_score
字段值越高,表示與搜索條件越匹配,而function_score
查詢能夠經過設置權重來影響_score
字段值,使用它咱們就能夠實現上面的需求了;ide
使用Query DSL調用Elasticsearch的Restful API實現,能夠發現商品名稱權重設置爲了10,商品副標題權重設置爲了5,商品關鍵字設置爲了2;學習
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來簡單實現下。ui
這裏咱們的實現原理是這樣的:首先根據ID獲取指定商品信息,而後以指定商品的名稱、品牌和分類來搜索商品,而且要過濾掉當前商品,調整搜索條件中的權重以獲取最好的匹配度;
使用Query DSL調用Elasticsearch的Restful API實現;
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來簡單實現下。
這裏咱們可使用Elasticsearch的聚合來實現,搜索出相關商品,聚合出商品的品牌、商品的分類以及商品的屬性,只要出現次數最多的前十個便可;
使用Query DSL調用Elasticsearch的Restful API實現;
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的具體使用能夠參考官方文檔。
mall項目全套學習教程連載中,關注公衆號第一時間獲取。