商城搜索 elasticSearch基础实战——排序,筛选价格区间·功效·品牌,聚合,分页等功能实现

一:在elasticSearch创建mapping(首先确保在LIUNX下安装成功elasticSearch)

PUT gemme
{
  "mappings": {
    "properties": {
      "productId": { //商品ID
        "type": "long"   
      },
      "price": {   //商品价格
        "type": "keyword"
      },
      "discountPrice": {  //商品折扣价格
        "type": "keyword"
      },
      "productName": { //商品名称
        "type": "text",
        "analyzer": "ik_smart"   //按照ik_smart进行分词
      },
      "productImg": {  //商品图片
        "type": "text",
        "index": false,   //不允许被查询
        "doc_values": false  //不允许被聚合
      },
      "brandId": {  //品牌ID
        "type": "long"
      },
      "brandName": { //品牌名称
        "type": "keyword"
      },
      "oneCategoryId": { //一级分类ID
        "type": "long"
      },
      "oneCategoryName": { //一级分类名称
        "type": "keyword"
      },
      "twoCategoryId": { //二级分类ID
        "type": "long"
      },
      "twoCategoryName": {  //一级分类名称
        "type": "keyword"
      },
      "lockCnt": {   //热销
        "type": "long"
      },
      "publishStatus": { //状态
        "type": "long"
      },
      "productEffect": {  //功效
        "type": "nested",   //防止在es库中被扁平处理
        "properties": {
          "productEffectId": {
            "type": "long"
          },
          "productEffectName": {
            "type": "keyword"
          }
        }
      },
      "skin": {  //肤质
        "type": "nested",
        "properties": {
          "skinId": {
            "type": "long"
          },
          "skinName": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

二: 将商品数据封装成elasticSearch库中需要的数据结构

2.1. 根据mapping规则创建实体类字段

@Data
public class ProductEsVo {
	//商品ID
	private Long productId;
	//商品名称
	private String productName;
	//商品价格
	private BigDecimal price;
	//商品打折后的价格
	private BigDecimal discountPrice;
	// 商品主图
	private String productImg;
	//品牌ID
	private Long brandId;
	//品牌名称
	private  String  brandName;
	//一级分类Id
	private Long oneCategoryId;
	//一级分类名称
	private String oneCategoryName;
	//二级分类Id
	private Long twoCategoryId;
	//二级分类名称
	private String twoCategoryName;
	//出售量
	private Long lockCnt;
	//商品状态
	private Long publishStatus;
	//商品功效
	private List<ProductEffect> productEffect;
	//肤质
	private  List<Skin> skin;

	@Data
	public static  class  ProductEffect{
		//功效ID
		private Long productEffectId;
		//功效名称
		private  String productEffectName;
	}

	@Data
	public static class Skin{
		//肤质ID
		private  Long skinId;
		//肤质名称
		private  String skinName;
	}

}

2.2. 将商品数据进行封装(这里根据自己的业务需求进行更改,不要直接复制)

List<ShopProductInfo> shopList=this.shopProductList();
List<ProductEsVo> esList=shopList.stream().map(pro->{
					ProductEsVo esvo=new ProductEsVo();
					esvo.setProductId(pro.getId());
					esvo.setProductName(pro.getProductName());
					esvo.setPrice(pro.getProductPrice());
					esvo.setBrandId(pro.getBrandId());
					esvo.setOneCategoryId(pro.getOneCategoryId());
					esvo.setTwoCategoryId(pro.getTwoCategoryId());
					esvo.setLockCnt((long) shopWarehouseProductService.getWarehouseProductLockCnt(pro.getId())!=0?(long) shopWarehouseProductService.getWarehouseProductLockCnt(pro.getId()):0);
					esvo.setBrandName(this.shopBrandString(pro.getBrandId()));
					esvo.setPublishStatus(Long.parseLong(publishStatus.toString()));
					esvo.setProductImg(shopProductPictureService.getPrcUrl(pro.getId()));
					esvo.setOneCategoryName(this.shopCategoryName(pro.getOneCategoryId()));
					esvo.setTwoCategoryName(this.shopCategoryName(pro.getTwoCategoryId()));
					String skuList = shopSkuService.skuList(pro.getId());
					if(skuList!=null && skuList.length()>0){
						List<ShopAttributeResponseVo> shopAttributeResponseVos = JSON.parseArray(skuList, ShopAttributeResponseVo.class);
						List<List<ShopAttributeResponseVo.AttributeChildren>> listChildren = shopAttributeResponseVos.stream().filter(item -> {
							return item.getKey().equals(shopAttributeService.getAttributeId(pro.getOneCategoryId()).toString());
						}).map(itemMap -> {
							return itemMap.getChildren();
						}).collect(Collectors.toList());
					if(listChildren.size()>0 && listChildren!=null){
						List<ProductEsVo.ProductEffect> effectList = listChildren.get(0).stream().map(childrenItem -> {
							ProductEsVo.ProductEffect effect = new ProductEsVo.ProductEffect();
							effect.setProductEffectId(Long.parseLong(childrenItem.getId()));
							effect.setProductEffectName(childrenItem.getAttributeName());
							return effect;
						}).collect(Collectors.toList());
						esvo.setProductEffect(effectList);
					}
					}
					return esvo;
				}
		).collect(Collectors.toList());
		return shopSearchFeign.saveSearch(esList); //调用elasticSearch服务的存放接口

2.3. 将封装的数据通过openfeign调用elasticSearch微服务中:

@FeignClient(contextId = "shopSearchFeign", value = "gemme-search”)
public interface ShopSearchFeign {
	@PostMapping("/search/info")
	public R saveSearch(@RequestBody List<ProductEsVo> list);
}

三. 创建elasticSearch微服务

3.1. elasticSearch服务目录结构

在这里插入图片描述

3.1. 在pom导入jar包

       <dependency>
			<groupId>org.elasticsearch.client</groupId>
			<artifactId>elasticsearch-rest-high-level-client</artifactId>
		</dependency>

首先注意:导入的elasticSearch版本号一定要和安装liunx中elasticSearch版本一致 。如果springboot自带elasticSearch版本号跟导入的jar版本号不一致,需要手动修改springBoot elasticSearch版本号

3.2. 在yml中配置elasticSearch路径

  spring.elasticsearch.rest.uris: ip:9200

3.3. 同样也可以在配置类(SearchConfig )进行配置

(这里是单个elasticSearch,可以根据自己业务配置集群)

@Configuration
public class SearchConfig {

	@Bean
	public RestHighLevelClient getRestHighLevelClient(){
		RestHighLevelClient client = new RestHighLevelClient(
				RestClient.builder(
						new HttpHost("ip", 9200, "http")));
		return client;
	}
}

3.4. 基础配置类(EsConstant)

public class EsConstant {
	public static final  String GEMME_INDEX = "gemme";  //elasticSearch名称
	public static final  int PASE_SIZE = 10;    
}

四. 将数据保存在elasticSearch中

4.1. 在SearchController中写saveSearch接口:

@RestController
@RequestMapping("/search")
public class SearchController {
	private final SearchService searchService;

	@PostMapping("/info")
	public R saveSearch(@RequestBody List<ProductEsVo> list) throws IOException {
		return R.ok(searchService.saveSearch(list));
	}
}

4.2. 在SearchServiceImpl中实现saveSearch接口

@Service
public class SearchServiceImpl implements SearchService {
	@Autowired
	private RestHighLevelClient getRestHighLevelClient;
	@Override  //这里是批量插入
	public boolean saveSearch(List<ProductEsVo> list) throws IOException {
		BulkRequest bulkRequest = new BulkRequest();
		bulkRequest.timeout("10s");
		for (ProductEsVo productEsVo : list) {
			IndexRequest indexRequest = new IndexRequest(EsConstant.GEMME_INDEX);
			indexRequest.id(productEsVo.getProductId().toString());
			indexRequest.source(JSON.toJSONString(productEsVo), XContentType.JSON);
			bulkRequest.add(indexRequest);
		}
		BulkResponse bulk = getRestHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
		return bulk.hasFailures();
	}
}

六. 搜索商品数据

6.1. 将搜索搜索条件封装成SearchVo 实体类

@Data
public class SearchVo {

	//搜索框内容
	private String keyWork;

	//品牌ID
	private Long  brandId;

	//商品分类二级ID
	private Long twoCategoryId;

	//商品价格
	private String price;   //接收数据结构: 1-100 1- -1

	/**
	 * 排序条件
	 * sort=price-asc/desc  按照价格进行排序
	 * sort=lockCnt-asc/desc 按照销售量进行排序
	 */
	private String sort;

	//功效筛选
	private String productEffectId;  //接收数据结构:功效ID1-功效ID2
	//页码
	private Integer pageNumber = 1;

6.2. 将渲染数据封装成

@Data
public class SearchRespon {
	//search服务中的数据
	private List<ProductEsVo> productEsVoList;

	/**
	 * 分页信息
	 */
	private Integer pageNnmber;//当前页码
	private Long total;//总记录数
	private Integer totalPages;//总页码

	//品牌
	private List<BrandVo> brandVoList;
	//功效
	private List<ResponseEffect> responseEffectList;

	@Data
	public static class  BrandVo{
		//品牌ID
		private Long brandId;
		//品牌名称
		private  String  brandName;
	}

	@Data
	public static  class  ResponseEffect{
		//功效ID
		private Long productEffectId;
		//功效名称
		private  String productEffectName;
	}

}

6.3. 在SearchController增加searchKeywork接口

	/**
	 * 查询数据
	 */
	@GetMapping("/searchKeywork")
	public R searchKeywork(SearchVo searchVo){
		return R.ok(searchService.searchKey(searchVo));
	}

6.4. 在SearchServiceImpl中实现searchKeywork接口

	@Override
	public SearchRespon searchKey(SearchVo searchVo) {
		SearchRespon request=null;
		SearchRequest searchRequest=querySearch(searchVo); 
		try {
			//获得返回的数据
			SearchResponse response = getRestHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
		  //返回封装数据格式
			request= querySearchResponse(response,searchVo);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return request;
	}

6.4.1. 传入搜索条件 querySearch

private SearchRequest querySearch(SearchVo searchVo) {
		//构建搜索条件
		SearchSourceBuilder searchSourceBuilder=new SearchSourceBuilder();
		//查询条件
		//根据搜索查询bool
		BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
		//按照搜索框信息查询
		if(!StringUtils.isEmpty(searchVo.getKeyWork())){
			boolQueryBuilder.must(QueryBuilders.matchQuery("productName",searchVo.getKeyWork()));
		}
		//按照品牌ID查询
		if(searchVo.getBrandId()!=null){
			boolQueryBuilder.filter(QueryBuilders.termQuery("brandId",searchVo.getBrandId()));
		}
		//按照二级分类进行查询
		if(searchVo.getTwoCategoryId()!=null){
			boolQueryBuilder.filter(QueryBuilders.termQuery("twoCategoryId",searchVo.getTwoCategoryId()));
		}
		//按照价格进行筛选    1-1000   1-   -1
		if(!StringUtils.isEmpty(searchVo.getPrice())){
			RangeQueryBuilder priceRange = QueryBuilders.rangeQuery("price");
			String[] priceSplit=searchVo.getPrice().split("-");
			if(priceSplit.length ==2){
				priceRange.gte(priceSplit[0]).lte(priceSplit[1]);
			}else if(priceSplit.length ==1){
				if(searchVo.getPrice().startsWith("-")){
					priceRange.lte(priceSplit[0]);
				}
				if (searchVo.getPrice().endsWith("-")){
					priceRange.gte(priceSplit[0]);
				}
			}
			boolQueryBuilder.filter(QueryBuilders.rangeQuery("price"));
		}
		//通过功效查询  1-2-3
		if(!StringUtils.isEmpty(searchVo.getProductEffectId())){
			String[] pro=searchVo.getProductEffectId().split("-");
			for (int i = 0; i < pro.length; i++) {
				BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
				boolQuery.must(QueryBuilders.termQuery("productEffect.productEffectId",pro[i]));
				// 扁平化处理
				NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("productEffect",boolQuery, ScoreMode.None);
				boolQueryBuilder.filter(nestedQueryBuilder);
			}

		}
		searchSourceBuilder.query(boolQueryBuilder);
		//排序	price-desc /asc      lockCnt-desc/asc
		if(!StringUtils.isEmpty(searchVo.getSort())){
			String[] stringSort=searchVo.getSort().split("-");
			SortOrder order=stringSort[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
			searchSourceBuilder.sort(stringSort[0],order);
		}
		//分页
		searchSourceBuilder.from((searchVo.getPageNumber()-1)*EsConstant.PASE_SIZE);
		searchSourceBuilder.size(EsConstant.PASE_SIZE);
		//构建聚合 (品牌,功效)
		TermsAggregationBuilder brand_agg= AggregationBuilders.terms("brand_agg");
		brand_agg.field("brandId").size(50);
		brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(50));
		searchSourceBuilder.aggregation(brand_agg);
		NestedAggregationBuilder effect_agg = AggregationBuilders.nested("effect_agg","productEffect");
		TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("product_effectId_agg").field("productEffect.productEffectId").size(20);
		termsAggregationBuilder.subAggregation(AggregationBuilders.terms("product_effectName_agg").field("productEffect.productEffectName").size(20));
		effect_agg.subAggregation(termsAggregationBuilder);
		searchSourceBuilder.aggregation(effect_agg);
		SearchRequest searchRequest=new SearchRequest(new String[]{EsConstant.GEMME_INDEX},searchSourceBuilder);
		return  searchRequest;
	}

6.4.2. 返回封装数据格式querySearchResponse

private SearchRespon querySearchResponse(SearchResponse response,SearchVo searchVo) {
		SearchRespon searchRespon = new SearchRespon();
		SearchHits hits = response.getHits();

		List<ProductEsVo> voList=new ArrayList<>();
		//获得商品所有基础信息
		if(hits.getHits() != null && hits.getHits().length>0){
			for (SearchHit hit : hits.getHits()) {
				ProductEsVo esVo = JSON.parseObject(hit.getSourceAsString(), ProductEsVo.class);
				voList.add(esVo);
			}
		}
		searchRespon.setProductEsVoList(voList);
		//获取品牌数据
		ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
		List<SearchRespon.BrandVo> brandVoList = brand_agg.getBuckets().stream().map(item -> {
			SearchRespon.BrandVo brandVo = new SearchRespon.BrandVo();
			brandVo.setBrandId(Long.parseLong(item.getKeyAsString()));
			ParsedStringTerms brand_name_agg = item.getAggregations().get("brand_name_agg");
			brandVo.setBrandName(brand_name_agg.getBuckets().get(0).getKeyAsString());
			return brandVo;
		}).collect(Collectors.toList());
		searchRespon.setBrandVoList(brandVoList);
		//获得功效数据
		ParsedNested effect_agg = response.getAggregations().get("effect_agg");
		ParsedLongTerms e_agg = effect_agg.getAggregations().get("product_effectId_agg");
		List<SearchRespon.ResponseEffect> effectList = e_agg.getBuckets().stream().map(item -> {
			SearchRespon.ResponseEffect responseEffect = new SearchRespon.ResponseEffect();
			responseEffect.setProductEffectId(((Terms.Bucket) item).getKeyAsNumber().longValue());
			ParsedStringTerms product_effectName_agg = item.getAggregations().get("product_effectName_agg");
			responseEffect.setProductEffectName(product_effectName_agg.getBuckets().get(0).getKeyAsString());
			return responseEffect;
		}).collect(Collectors.toList());
		searchRespon.setResponseEffectList(effectList);
		//当前页码
		searchRespon.setPageNnmber(searchVo.getPageNumber());
		//总记录数
		long total = hits.getTotalHits().value;
		searchRespon.setTotal(total);
		//总页码
		searchRespon.setTotalPages((int) (total%EsConstant.PASE_SIZE==0?total/EsConstant.PASE_SIZE:(total/EsConstant.PASE_SIZE+1)));
		return  searchRespon;
	}

七. 测试

7.1. PostMan测试接口数据 路径:localhost:端口号/search/searchKeywork

在这里插入图片描述

7.1.1 返回数据结构

{
    "code": 0,
    "msg": null,
    "data": {
        "productEsVoList": [
            {
                "productId": 1,
                "productName": "法国Dior/迪奥新款黑管瘾诱超模漆光唇釉口红唇膏740保湿持久正品",
                "price": 279.0,
                "discountPrice": null,
                "productImg": null,
                "brandId": 112359881173,
                "brandName": "Dior/迪奥",
                "oneCategoryId": 40,
                "oneCategoryName": "口红",
                "twoCategoryId": 42,
                "twoCategoryName": "迪奥",
                "lockCnt": 32,
                "publishStatus": 0,
                "productEffect": [
                    {
                        "productEffectId": 1278521450449358849,
                        "productEffectName": "美白"
                    },
                    {
                        "productEffectId": 1278521486050611201,
                        "productEffectName": "保湿"
                    }
                ],
                "skin": null
            },
            {
                "productId": 2,
                "productName": "正品YSL圣罗兰细管纯口红小金条持久哑光21复古红",
                "price": 123.0,
                "discountPrice": null,
                "productImg": null,
                "brandId": 112359881174,
                "brandName": "YSL/圣罗兰",
                "oneCategoryId": 40,
                "oneCategoryName": "口红",
                "twoCategoryId": 43,
                "twoCategoryName": "YSL",
                "lockCnt": 0,
                "publishStatus": 0,
                "productEffect": [
                    {
                        "productEffectId": 1278521450449358849,
                        "productEffectName": "美白"
                    },
                    {
                        "productEffectId": 1278521486050611201,
                        "productEffectName": "保湿"
                    }
                ],
                "skin": null
            }
        ],
        "pageNnmber": 1,
        "total": 2,
        "totalPages": 1,
        "brandVoList": [
            {
                "brandId": 112359881173,
                "brandName": "Dior/迪奥"
            },
            {
                "brandId": 112359881174,
                "brandName": "YSL/圣罗兰"
            }
        ],
        "responseEffectList": [
            {
                "productEffectId": 1278521450449358849,
                "productEffectName": "美白"
            },
            {
                "productEffectId": 1278521486050611201,
                "productEffectName": "保湿"
            }
        ]
    }
}

7.2. 测试搜索条件 querySearch是否有问题

	1.在querySearch中打印System.out.println(searchSourceBuilder.toString());,
	  将打印结构放入Kibana中进行测试

7.3. kibana测试查询数据结构:

GET gemme/_search
{
  "from": 0,
  "size": 10,
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "productName": {
              "query": "正品YSL圣罗兰",
              "operator": "OR",
              "prefix_length": 0,
              "max_expansions": 50,
              "fuzzy_transpositions": true,
              "lenient": false,
              "zero_terms_query": "NONE",
              "auto_generate_synonyms_phrase_query": true,
              "boost": 1
            }
          }
        }
      ],
      "filter": [
        {
          "nested": {
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "productEffect.productEffectId": {
                        "value": "1278521450449358849",
                        "boost": 1
                      }
                    }
                  }
                ],
                "adjust_pure_negative": true,
                "boost": 1
              }
            },
            "path": "productEffect",
            "ignore_unmapped": false,
            "score_mode": "none",
            "boost": 1
          }
        },
        {
          "nested": {
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "productEffect.productEffectId": {
                        "value": "1278521486050611201",
                        "boost": 1
                      }
                    }
                  }
                ],
                "adjust_pure_negative": true,
                "boost": 1
              }
            },
            "path": "productEffect",
            "ignore_unmapped": false,
            "score_mode": "none",
            "boost": 1
          }
        }
      ],
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "aggregations": {
    "brand_agg": {
      "terms": {
        "field": "brandId",
        "size": 50,
        "min_doc_count": 1,
        "shard_min_doc_count": 0,
        "show_term_doc_count_error": false,
        "order": [
          {
            "_count": "desc"
          },
          {
            "_key": "asc"
          }
        ]
      },
      "aggregations": {
        "brand_name_agg": {
          "terms": {
            "field": "brandName",
            "size": 50,
            "min_doc_count": 1,
            "shard_min_doc_count": 0,
            "show_term_doc_count_error": false,
            "order": [
              {
                "_count": "desc"
              },
              {
                "_key": "asc"
              }
            ]
          }
        }
      }
    },
    "effect_agg": {
      "nested": {
        "path": "productEffect"
      },
      "aggregations": {
        "product_effectId_agg": {
          "terms": {
            "field": "productEffect.productEffectId",
            "size": 20,
            "min_doc_count": 1,
            "shard_min_doc_count": 0,
            "show_term_doc_count_error": false,
            "order": [
              {
                "_count": "desc"
              },
              {
                "_key": "asc"
              }
            ]
          },
          "aggregations": {
            "product_effectName_agg": {
              "terms": {
                "field": "productEffect.productEffectName",
                "size": 20,
                "min_doc_count": 1,
                "shard_min_doc_count": 0,
                "show_term_doc_count_error": false,
                "order": [
                  {
                    "_count": "desc"
                  },
                  {
                    "_key": "asc"
                  }
                ]
              }
            }
          }
        }
      }
    }
  }
}

如果有问题请私信我,对自己有收获请给我点个赞。谢谢!

Elasticsearch 简介 ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。elasticSearch 的使用场景 1、在海量数据前提下,对数据进行检索。比如:京东,淘宝等电商项目课程目标: 1. 了解企业级搜索引擎2. 安装elasticsearch 课程目录: 01 课程介绍02 elasticsearch 简介03 elasticsearch 使用场景04 安装elasticsearch 之前先安装jdk05 安装elasticsearch06 测试elasticsearch是否安装成功 07 安装kibana08 elasticsearch 基本认识 以及添加索引和删除索引09 elasticsearch 添加查询数据10 elasticsearch 修改删除数据11 elasticsearch 有条件的查询12 分词子属性fuzzy查询13 elasticsearch 过滤使用14 elasticsearch 排序分页15 elasticsearch 如何查询指定的字段16 elasticsearch 高亮显示17 elasticsearch 聚合18 elasticsearch mapping 概念19 elasticsearch 的中文词库20 elasticsearch 中文词库安装测试21 elasticsearch 中文词库的使用案例22 elasticsearch 自定义词库配置23 安装nginx 配置中文词库24 测试elasticsearch 自定义中文词库25 搭建项目父工程26 搭建项目bean-interface-common27 搭建search 的service web 项目28 测试项目是否能与elasticsearch联通29 创建数据库并搭建首页30 数据上传功能实现类完成31 数据上传控制器完成32 dubbo 介绍以及安装zookeeper33 将数据从mysql 上传到elasticsearch 中34 elasticsearch查询功能分析35 编写业务需求的dsl 语句36 编写输入参数返回结果集的实体类37 实现类编写38 编写实现类中dsl 语句39 返回集结果转换40 结果测试41 测试通过输入查询条件并将数据显示到页面
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值