赞
踩
根据DSL语句构建检索条件
- GET gulimall_product/_search
- {
- "query": {
- "bool": {
- "must": [
- {
- "match": {
- "skuTitle": "华为"
- }
- }
- ],
- "filter": [
- {
- "term": {
- "catalogId": "1111133"
- }
- },
- {
- "terms": {
- "brandId": [
- "3",
- "5"
- ]
- }
- },
- {
- "term": {
- "hasStock": true
- }
- },
- {
- "nested": {
- "path": "attrs",
- "query": {
- "bool": {
- "must": [
- {
- "term": {
- "attrs.attrId": {
- "value": "15"
- }
- }
- },
- {
- "terms": {
- "attrs.attrValue": [
- "aaa",
- "白色",
- "OCE-AN10"
- ]
- }
- }
- ]
- }
- }
- }
- }
- ]
- }
- },
- "sort": [
- {
- "skuPrice": {
- "order": "desc"
- }
- }
- ],
- "aggs": {
- "brand_agg": {
- "terms": {
- "field": "brandId",
- "size": 10
- },
- "aggs": {
- "brand_name_agg": {
- "terms": {
- "field": "brandName",
- "size": 10
- }
- },
- "brand_img_agg":{
- "terms": {
- "field": "brandImg",
- "size": 10
- }
- }
- }
- },
- "catalog_agg":{
- "terms": {
- "field": "catalogId",
- "size": 10
- },
- "aggs": {
- "catalog_name_agg": {
- "terms": {
- "field": "catalogName",
- "size": 10
- }
- }
- }
- },
- "attr_agg":{
- "nested": {
- "path": "attrs"
- },
- "aggs": {
- "attr_id": {
- "terms": {
- "field": "attrs.attrId",
- "size": 10
- },
- "aggs": {
- "attr_name_id": {
- "terms": {
- "field": "attrs.attrName",
- "size": 10
- }
- }
- }
- }
- }
- }
- },
- "from": 0,
- "size": 2,
- "highlight": {
- "fields": {"skuTitle": {}},
- "pre_tags": "<b style='color:red'>",
- "post_tags": "</b>"
- }
- }
在java中使用es检索,比较重要的一个对象是SearchSourceBuilder
- import com.bjc.gulimall.search.config.GulimallElasticSearchConfig;
- import com.bjc.gulimall.search.constant.EsConstant;
- import com.bjc.gulimall.search.service.MallSearchService;
- import com.bjc.gulimall.search.vo.SearchParam;
- import com.bjc.gulimall.search.vo.SearchResult;
- import lombok.extern.slf4j.Slf4j;
- import org.apache.commons.lang3.StringUtils;
- import org.apache.lucene.search.join.ScoreMode;
- import org.elasticsearch.action.search.SearchRequest;
- import org.elasticsearch.action.search.SearchResponse;
- import org.elasticsearch.client.RestHighLevelClient;
- import org.elasticsearch.index.query.*;
- import org.elasticsearch.search.builder.SearchSourceBuilder;
- import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
- import org.elasticsearch.search.sort.SortOrder;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Service;
- import org.springframework.util.CollectionUtils;
-
-
- @Service
- @Slf4j
- public class MallSearchServiceImpl implements MallSearchService {
-
- @Autowired
- private RestHighLevelClient client;
-
- /*
- * 根据检索参数返回检索结果
- * SearchResult 包含页面需要的所有信息
- * */
- @Override
- public SearchResult search(SearchParam searchParam) {
-
- SearchResult result = null;
-
- /*
- * 1. 动态构建出查询需要的DSL语句
- * */
- // 1.1 创建检索请求对象
- // SearchRequest searchRequest = new SearchRequest();
- SearchRequest searchRequest = buildSearchRequest(searchParam);
- try {
-
- /* 2. 执行检索请求 */
- SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
-
- /* 3. 分析响应数据,封装成指定的数据格式 */
- result = buildSearchResult(response);
- } catch (Exception e) {
- log.error("动态构建出查询需要的DSL语句出错,原因:",e);
- }
- return null;
- }
-
- /* 根据响应构建返回结果对象 */
- private SearchResult buildSearchResult(SearchResponse response) {
- return null;
- }
-
- /* 准备检索请求
- * 关键字模糊匹配、过滤(按照属性、分类、品牌、价格区间),排序,分页,高亮,聚合分析
- * */
- private SearchRequest buildSearchRequest(SearchParam searchParam) {
-
- // 用于构建DSL语句
- SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
-
- /*
- * 1. 查询部分DSL构建
- * */
- // 1.1 构建queryBuilder对象。复杂的query是通过bool组合检索的,因此需要构建BoolQueryBuilder对象
- BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
-
- // 1.2 构建全文检索条件must,关键字模糊匹配. 如果页面有关键字搜索,才进行全文模糊检索
- if(StringUtils.isNotEmpty(searchParam.getKeyword())){
- // 将按照skuTitle全文检索的条件封装到boolQuery中
- boolQuery.must(QueryBuilders.matchQuery("skuTitle",searchParam.getKeyword()));
- }
-
- // 1.3 构建过滤filter条件
- // 1.3.1 按照三级分类id过滤
- if(null != searchParam.getCatalog3Id()){
- // 三级分类id是精确匹配,用term
- boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatalog3Id()));
- }
- // 1.3.2 按照品牌ID过滤(支持多选)
- if(!CollectionUtils.isEmpty(searchParam.getBrandId())){
- boolQuery.filter(QueryBuilders.termsQuery("brandId",searchParam.getBrandId()));
- }
-
-
- // 1.3.3 按照所有指定的属性进行查询
- /*
- * {
- "nested": {
- "path": "attrs",
- "query": {
- "bool": {
- "must": [
- {
- "term": {
- "attrs.attrId": {
- "value": "15"
- }
- }
- },
- {
- "terms": {
- "attrs.attrValue": [
- "aaa",
- "白色",
- "OCE-AN10"
- ]
- }
- }
- ]
- }
- }
- }
- }
- * */
- if(!CollectionUtils.isEmpty(searchParam.getAttrs())){
- searchParam.getAttrs().forEach(attrStr -> {
- BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
- // attrs=1_5寸:8寸&attrs=2_8G:16G
- String[] s = attrStr.split("_");
- // 检索的属性ID
- String attrId = s[0];
- // 检索的属性ID对应的属性值的数组
- String[] attrValues = s[1].split(":");
- nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
- nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
-
- // 每一个属性都需要生成一个嵌入式查询
- // ScoreMode.None 表示不参与评分
- NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
- boolQuery.filter(nestedQueryBuilder);
- });
- }
- // 1.3.4 按照库存是否有进行查询
- boolQuery.filter(QueryBuilders.termQuery("hasStock",searchParam.getHasStock() == 1));
- // 1.3.5 按照价格区间进行查询
- if(StringUtils.isNotEmpty(searchParam.getSkuPrice())){
- // 1_500/_500/500_
- /*
- * "range": {
- "skuPrice": {
- "gte": 0.0,
- "lte": 20000.0
- }
- }
- * */
- int g = 0;
- int l = 0;
- // 1)构建rangeQueryBuilder对象
- RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
- // 2)解析查询参数
- String[] s = searchParam.getSkuPrice().split("_");
- if(s.length == 2){
- rangeQueryBuilder.gte(s[0]).lte(s[1]);
- } else if(s.length == 1){ // 否则就是单值
- if(searchParam.getSkuPrice().startsWith("_")){
- rangeQueryBuilder.lte(s[0]);
- } else {
- rangeQueryBuilder.gte(s[0]);
- }
- }
- boolQuery.filter(rangeQueryBuilder);
- }
-
- // 将所有的查询条件封装到sourceBuilder
- sourceBuilder.query(boolQuery);
-
- /*
- * 2. 排序、分页、高亮
- * */
- // 2.1 排序
- if(StringUtils.isNotEmpty(searchParam.getSort())){
- String sort = searchParam.getSort(); // &sort = hotScore_asc/desc
- String[] s = sort.split("_");
- // SortOrder sortOrder = s[1].equalsIgnoreCase("asc")?SortOrder.ASC:SortOrder.DESC;
- sourceBuilder.sort(s[0], SortOrder.fromString(s[1]));
- }
- // 2.2 分页 from = (当前页-1) * 每页显示条数
- sourceBuilder.from((searchParam.getPageNum()-1) * EsConstant.PRODUCT_PAGESIZE) // 第几页
- .size(EsConstant.PRODUCT_PAGESIZE); // 每页显示记录数
- // 2.3 高亮 只有关键字查询才高亮
- if(StringUtils.isNotEmpty(searchParam.getKeyword())){
- HighlightBuilder highlight = new HighlightBuilder();
- // 指定需要高亮的字段是哪个
- highlight.field("skuTitle");
- // 指定高亮前置标签
- highlight.preTags("<b style='color:red'>");
- // 指定后缀标签
- highlight.postTags("</b>");
- sourceBuilder.highlighter(highlight);
- }
-
- /*
- * 3. 聚合分析
- * */
- System.out.println("构建的DSL:" + sourceBuilder.toString());
-
- SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, sourceBuilder);
- return searchRequest;
- }
- }
测试:
1)带keyword
控制台结果:
将该结果复制到Kibana上检索,如图:
2)添加属性和分类查询条件
- /*
- * 3. 聚合分析
- * */
- // 3.1 品牌聚合
- AggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg") // 参数为聚合的名称
- .field("brandId") // 要聚合的字段
- .size(50); // 查询并显示多少条记录
- // 3.1.1 子聚合
- brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
- .field("brandName").size(1));
- // 3.1.2 子聚合
- brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
- .field("brandImg").size(1));
- // 3.1.3 添加聚合条件到sourceBuilder
- sourceBuilder.aggregation(brand_agg);
-
- // 3.2 分类聚合
- AggregationBuilder catalog_agg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(50);
- catalog_agg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(1));
- sourceBuilder.aggregation(catalog_agg);
-
- // 3.3 属性聚合(嵌入式聚合)
- AggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs");
-
- // 3.3.1 聚合出当前所有的AttrId
- AggregationBuilder attrIdAggregation = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(100);
- // 3.3.2 聚合分析出当前attrId对应的名称
- attrIdAggregation.subAggregation(AggregationBuilders.terms("attrNameIdAgg").field("attrs.attrName").size(1));
- // 3.3.3 聚合分析出当前attrId对应的所有可能的属性值attrValue
- attrIdAggregation.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(50));
- // 3.3.4 将attrId子聚合添加到嵌入式聚合attrAgg中
- attrAgg.subAggregation(attrIdAggregation);
- // 3.3.4 将嵌入式聚合attrAgg聚合条件添加到sourceBuilder中
- sourceBuilder.aggregation(attrAgg);
- /* 根据响应构建返回结果对象 */
- private SearchResult buildSearchResult(SearchResponse response,SearchParam searchParam) {
- SearchResult result = new SearchResult();
-
- // 获取命中结果
- SearchHits hits = response.getHits();
- // 从名字结果中获取命中记录
- SearchHit[] resultHits = hits.getHits();
-
-
- // 1. 封装所有查询到的商品
- List<SkuEsModel> esModels = new ArrayList<>();
- if(ArrayUtils.isNotEmpty(resultHits)){
- for(SearchHit hit : resultHits){
- String sourceAsString = hit.getSourceAsString();
- SkuEsModel esModel = JSONObject.parseObject(sourceAsString, SkuEsModel.class);
- // 设置高亮
- Map<String, HighlightField> highlightFields = hit.getHighlightFields();
- if(!CollectionUtils.isEmpty(highlightFields)){
- HighlightField skuTitle = highlightFields.get("skuTitle");
- String title = skuTitle.getFragments()[0].string();
- esModel.setSkuTitle(title);
- }
- esModels.add(esModel);
- }
- }
- result.setProducts(esModels);
-
- // 获取所有的聚合信息
- Aggregations aggregations = response.getAggregations();
- // 2. 封装当前所有商品涉及到的所有属性信息
- List<SearchResult.AttrVo> attrs = new ArrayList<>();
- // 获取嵌套聚合
- ParsedNested parsedNested = aggregations.get("attrAgg");
- // 获取属性id聚合
- ParsedLongTerms attrIdAgg = parsedNested.getAggregations().get("attrIdAgg");
- // 遍历属性id聚合,得到每一个属性下的属性值
- attrIdAgg.getBuckets().forEach(item -> {
- SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
- // 获取属性id
- String key = item.getKeyAsString();
- attrVo.setAttrId(Long.parseLong(key));
-
- // 获取属性名称
- ParsedStringTerms attrNameTerms = item.getAggregations().get("attrNameIdAgg");
- String name = attrNameTerms.getBuckets().get(0).getKeyAsString();
- attrVo.setAttrName(name);
-
- // 获取属性值
- ParsedStringTerms attrValueTerms = item.getAggregations().get("attrValueAgg");
- List<String> attrValues = attrValueTerms.getBuckets().stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
- attrVo.setAttrValue(attrValues);
-
- // 将每一个属性对象添加到属性集合
- attrs.add(attrVo);
- });
- result.setAttrs(attrs);
-
- // 3. 封装当前商品所涉及到的品牌信息
- List<SearchResult.BrandVo> brandVos = new ArrayList<>();
- ParsedLongTerms brandAgg = aggregations.get("brand_agg");
- brandAgg.getBuckets().forEach(bucket -> {
- SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
- // 获取品牌id
- String key = bucket.getKeyAsString();
- brandVo.setBrandId(Long.parseLong(key));
-
- // 获取品牌名称
- ParsedStringTerms stringTerms = bucket.getAggregations().get("brand_name_agg");
- String name = stringTerms.getBuckets().get(0).getKeyAsString();
- brandVo.setBrandName(name);
-
- // 获取品牌图片
- ParsedStringTerms imgTerms = bucket.getAggregations().get("brand_img_agg");
- String img = imgTerms.getBuckets().get(0).getKeyAsString();
- brandVo.setBrandImg(img);
-
- brandVos.add(brandVo);
- });
- result.setBrandVos(brandVos);
-
- // 4. 封装当前商品所属的分类
- List<SearchResult.CatalogVo> catalogs = new ArrayList<>();
- // 获取分类聚合信息(因为分类id是long类型的,所以用Aggregations接口的实现类ParsedLongTerms)
- ParsedLongTerms catalogAgg = aggregations.get("catalogAgg");
- List<? extends Terms.Bucket> buckets = catalogAgg.getBuckets();
- buckets.forEach(bucket -> {
- SearchResult.CatalogVo cVo = new SearchResult.CatalogVo();
- // bucket的key就是分类的id
- String key = bucket.getKeyAsString();
- cVo.setCatalogId(Long.parseLong(key));
-
- // 获取分类名称 分类的名称是分类id的子聚合
- ParsedStringTerms nameAgg = bucket.getAggregations().get("catalogNameAgg");
- String name = nameAgg.getBuckets().get(0).getKeyAsString();
- cVo.setCatalogName(name);
-
- catalogs.add(cVo);
- });
- result.setCatalogs(catalogs);
-
- // 5. 设置分页信息
- long total = hits.getTotalHits().value;
- // 5.1 页码
- result.setPageNum(searchParam.getPageNum());
- // 5.2 总记录数
- result.setTotal(total);
- // 5.3 总页码
- int pages = (int)(total/EsConstant.PRODUCT_PAGESIZE + (total%EsConstant.PRODUCT_PAGESIZE > 0 ? 1 : 0));
- result.setTotalPages(pages);
-
- return result;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。