ElasticSearch的了解使用

什么是ElasticSearch?
ElasticSearch是一个分布式的开源搜索与分析引擎,ElasticSearch着重于数据的检索与分析。但是众所周知MySQL等数据库也可以实现对于数据的分析与检索,但是闻道有先后,术业有专攻。MySQL主要用于数据的持久化管理以及存储,但用MySQL对海量数据进行检索与分析,ElasticSearch无疑更加在行。

Elastic 的底层是开源库Lucene。但是,你没法直接用Lucene,必须自己写代码去调用它的
接口。Elastic 是Lucene 的封装,提供了REST API 的操作接口,开箱即用。
REST API:天然的跨平台。
官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
官方中文:https://www.elastic.co/guide/cn/elasticsearch/guide/current/foreword_id.html

image-20220418223046575

image-20220418223232953

安装elasticSearch以及可视化工具Kibana

docker pull elasticsearch:7.4.2  //存储和检索数据
docker pull kibana:7.4.2 //可视化检索数据

创建实例

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml





chmod -R 777 /mydata/elasticsearch/ 保证权限
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.2

这样就说明es安装成功,尤其要注意文件夹的权限。

image-20220418223402793

安装kibana

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.56.10:9200 -p 5601:5601 \
-d kibana:7.4.2
http://192.168.56.10:9200 一定改为自己虚拟机的地址

image-20220418223504947

入门ES

1,_cat(查看es的信息)

GET /_cat/nodes:查看所有节点
GET /_cat/health:查看es 健康状况
GET /_cat/master:查看主节点
GET /_cat/indices:查看所有索引   相当于show databases;

image-20220418223655383

image-20220418223738060

image-20220418223758558

image-20220418223832944

image-20220418223850994

image-20220418223922285

image-20220418223947664

image-20220418224014070

image-20220418224042595

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RdU2sN4N-1650294289113)(https://s2.loli.net/2022/04/18/jaS3qrWZFtLiRMD.png)]

GET _search
{
  "query": {
    "match_all": {}
  }
}


bulk批量API

POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"giao"}
{"index":{"_id":"2"}}
{"name":"giao2"}


{
  "took" : 4,   //花费的
  "errors" : false,   //没有发生错误
  "items" : [
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "1",
        "_version" : 2,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 2,
        "_primary_term" : 1,
        "status" : 200
      }
    },
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "_version" : 2,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 3,
        "_primary_term" : 1,
        "status" : 200
      }
    }
  ]
}



导入整体数据
地址:https://gitee.com/xlh_blog/common_content/blob/master/es%E6%B5%8B%E8%AF%95%E6%95%B0%E6%8D%AE.json



POST /_bulk
{"delete": {"_index": "website","_type": "blog", "_id": "123"}}
{"create": {"_index": "website","_type": "blog", "_id": "123"}}
{"title": "My first blog post"}
{"index": {"_index": "website","_type": "blog"}}
{"title": "My second blog post"}
{"update": {"_index": "website","_type": "blog", "_id": "123"}}
{"doc": {"title": "My updated blog post"}}




GET bank/_search?q=*&sort=account_number:asc





语法介绍
GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "account_number": "asc"
    },
    {
      "balance": "desc"  
    }
  ]
}

GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "balance": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 5,
  "_source": ["balance","firstname"]
}


match全文检索
可以用来精确查询,也可以用来模糊匹配。最终会按照评分进行排序,会对检索条件进行分词匹配
GET bank/_search
{
  "query": {
    "match": {
      "address": "kings"
    }
  }
}
##全文搜索按照评分进行排序,会对检索条件进行分词匹配


match_phrase
对短语进行匹配,不会被分割
GET bank/_search
{
  "query":{
    "match_phrase": {
      "address": "mill lane"
    }
  }
}

multi_match

多字段匹配,会进行分词
GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill movico",
      "fields": ["address","city"]
    }
  }
}

bool复合查询(极其重要)
合并多个查询条件,这些条件都必须被满足
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "gender": "M"
          }
        },
        {
          "match": {
            "address": "mill"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "age": "18"
          }
        }
      ],
      "should": [
        {
          "match": {
            "lastname": "Wallace"
          }
        }
      ], 
      ## must以及should被满足会获得相关性得分,must_not会被当成过滤器,这就引出了过滤器,过滤器不会提供相关性得分。
      "filter": {
        "range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }
      }
    }
  }
}  
##filter结果过滤
不会计算相关性得分



GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "range": {
            "age": {
              "gte": 18,
              "lte": 30
            }
          }
        }
      ]
    }
  }
}



GET bank/_search
{
  "query": {
    "bool": {
      "filter": {
        "range": {
          "age": {
            "gte": 18,
            "lte": 30
          }
        }
      }
    }
  }
}


term(非常重要!)
和match 一样。匹配某个属性的值。全文检索字段用match,其他非text 字段匹配用term。
数字——>term
文本——>match


GET bank/_search
{
  "query": {
    "term": {
        "balance": "32838"
    }
  }
}

##精确匹配
GET bank/_search
{
  "query": {
    "match": {
      "address.keyword": "789 Madison" 
    }
  }
}


GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "789 Madison"
    }
  }
}
##搜索address中包含mill的所有人的年龄分布以及平均年龄


GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 10
      }
    },
    "ageAvg":{
      "avg": {
        "field": "age"
      }
    },
    "balanceAvg":{
      "avg": {
        "field": "balance"
      }
    }
  },
  "size": 0
}
##按照年龄聚合,并且请求这些年龄的这些人的平均薪资
aggregations(执行聚合)
查数据的时候就可以获取到聚合信息。

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 100
      },
      "aggs": {
        "aggAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}
##查出所有年龄分布,并且这些年龄段中M的平均工资和F的平均工资以及这个年龄段的总平均工资

GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageAgg": {
      "terms": {
        "field": "age",
        "size": 10
      },
      "aggs": {
        "genderAgg": {
          "terms": {
            "field": "gender.keyword",
            "size": 10
          },
          "aggs": {
            "balanceAvg": {
              "avg": {
                "field": "balance"
              }
                },
                "ageBalanceAvg":{
                  "avg": {
                    "field": "balance"
                  }
                }
              }
            }
          }
        }
      }
    }

GET bank/_mapping
##查看所有映射

es在6之后移除了类型,直接将数据存储到索引中去了。第一次存数据的时候,es会自动猜测存储类型。而在保存数据前,我们可以指定映射,从而确保文档类型是我们想要的。注意:映射是不允许更新的,需要改的话,需要使用数据迁移(下节),创建新索引,重新保存。
创建映射
创建索引并指定映射 

PUT /my_index1
{
  "mappings": {
    "properties": {
      "age":{"type":"integer"},
      "email":{"type":"keyword"},
      "name":{"type": "text"}
    }
  }
}


##添加新的字段映射
PUT /my_index1/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false
    }
  }
}


对于已存在的映射字段,不能更新。更新必须创建新的索引进行数据迁移
##创建新的索引

PUT /newbank
{
  "mappings": {
    "properties": {
      "account_number": {
        "type": "long"
      },
      "address": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "age": {
            "type": "integer"
          },
          "balance": {
            "type": "long"
          },
          "city": {
            "type": "keyword",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "email": {
            "type": "keyword"
          },
          "employer": {
            "type": "keyword"
          },
          "firstname": {
            "type": "keyword"
          },
          "gender": {
            "type": "keyword"
          },
          "lastname": {
            "type": "text",
            "fields": {
              "keyword": {
                "type": "keyword",
                "ignore_above": 256
              }
            }
          },
          "state": {
            "type": "keyword"
          }
        }
      }
    }
  }
}
##数据迁移

POST _reindex
{
  "source": {
    "index": "bank",
    "type": "account"
  },
  "dest": {
    "index": "newbank"
  }
}

##不用type,老的数据可以迁移过来

SpringBoot整合high-level-client

1.导入依赖(注意ESclient的版本需要和elasticSearch的版本一致)
      <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>
2.编写配置类,给容器中注入RestHighLevelClient(虚拟机ip改成自己的)
@Configuration
public class ElasticSearchConfig {

    public static final RequestOptions COMMON_OPTIONS;

    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
        COMMON_OPTIONS = builder.build();
    }

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

3.编写测试类

GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": {
    "age_agg": {
      "terms": {
        "field": "age",
        "size": 10
      }
    },
    "age_avg": {
      "avg": {
        "field": "age"
      }
    },
    "balance_avg": {
      "avg": {
        "field": "balance"
      }
    }
  },
  "size": 0
}

    1
    
    @Test
    public void searchData() throws IOException {
        //1. 创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //1.1)指定索引
        searchRequest.indices("bank");
        //1.2)构造检索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery("address", "Mill"));
        //1.2.1)按照年龄分布进行聚合
        TermsAggregationBuilder ageAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        sourceBuilder.aggregation(ageAgg);
        //1.2.2)计算平均年龄
        AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");
        sourceBuilder.aggregation(ageAvg);
        //1.2.3)计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        sourceBuilder.aggregation(balanceAvg);
        System.out.println("检索条件:" + sourceBuilder);
        searchRequest.source(sourceBuilder);
        //2. 执行检索
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println("检索结果:" + searchResponse);
        //3. 将检索结果封装为Bean
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            String sourceAsString = searchHit.getSourceAsString();
            Account account = JSON.parseObject(sourceAsString, Account.class);
            System.out.println(account);
        }
        //4. 获取聚合信息
        Aggregations aggregations = searchResponse.getAggregations();
        Terms ageAgg1 = aggregations.get("ageAgg");
        for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄:" + keyAsString + " ==> " + bucket.getDocCount());
        }
        Avg ageAvg1 = aggregations.get("ageAvg");
        System.out.println("平均年龄:" + ageAvg1.getValue());
        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("平均薪资:" + balanceAvg1.getValue());
    }

2


@Resource
    private RestHighLevelClient client;

    @ToString
    @Data
    static class Account {
        private int account_number;

        private int balance;

        private String firstname;

        private String lastname;

        private int age;

        private String gender;

        private String address;

        private String employer;

        private String email;

        private String city;

        private String state;
    }


    @Test
    public void searchData() throws IOException {
        //1,创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //指定索引
        searchRequest.indices("bank");
        //指定DSL,检索条件
        //SearchSourceBuilder sourceBuilder

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchRequest.source(searchSourceBuilder);

        //1.1)构造检索条件
//        searchSourceBuilder.query();
//        searchSourceBuilder.from();
//        searchSourceBuilder.size();
//        searchSourceBuilder.aggregation();

        searchSourceBuilder.query(QueryBuilders.matchQuery("address", "mill"));
        //1.2),按照年龄的值分布进行聚合
        TermsAggregationBuilder aggAgg = AggregationBuilders.terms("ageAgg").field("age").size(10);
        searchSourceBuilder.aggregation(aggAgg);
        //1.3)计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        searchSourceBuilder.aggregation(balanceAvg);

        System.out.println("检索条件" + searchSourceBuilder.toString());
        searchRequest.source(searchSourceBuilder);

        //2,执行检索
        SearchResponse searchResponse = client.search(searchRequest, MallElasticSearchConfig.COMMON_OPTIONS);

        //3,分析结果 searchResponse
        System.out.println(searchResponse.toString());

//        Map map = JSON.parseObject(searchResponse.toString(), Map.class);

        //3.1获取所有查到的数据
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit hit : searchHits) {
            /**
             * "_index":"bank",
             * "_type":"account",
             * "_id":"345",
             * "_score":"5.4032025",
             * "_source":
             */
//            hit.getIndex();
//            hit.getType();
//            hit.getId();
//            hit.getScore();
            String string = hit.getSourceAsString();
            Account account = JSON.parseObject(string, Account.class);
            System.out.println("account:"+account);
        }

        //3.2)获取这次检索的分析信息
        Aggregations aggregations = searchResponse.getAggregations();
//        for (Aggregation aggregation : aggregations.asList()) {
//            System.out.println("当前聚合"+aggregation.getName());
//            aggregation.get
//        }
        Terms ageAgg = aggregations.get("ageAgg");
        for (Terms.Bucket bucket : ageAgg.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄"+keyAsString);
        }
        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("平均薪资"+balanceAvg1.getValue());

//        Aggregation balanceAvg2 = aggregations.get("balanceAvg");
    }
​```

SearchRequest的构建-检索

1.首先对传入参数实体类进行封装(检索条件)

/**
 * 封装页面所有可能传递过来的参数
 */
@Data
public class SearchParam {

    /**
     * 页面传递过来的全文匹配关键字
     * &keyword=小米
     */
    private String keyword;

    /**
     * 品牌id,可以多选
     *
     */
    private List<Long> brandId;

    /**
     * 三级分类id
     * &catalog3Id=255
     */
    private Long catalog3Id;

    /**
     * 排序条件:sort=price/salecount/hotscore_desc/asc
     */
    private String sort;

    /**
     * 是否显示有货
     * &hasStock=0/1
     */
    private Integer hasStock;

    /**
     * 价格区间查询
     * &skuPrice=1_500/_500/500_
     */
    private String skuPrice;

    /**
     * 按照属性进行筛选
     * attrs=1_其他:安卓&attrs=2_5寸:6寸
     */
    private List<String> attrs;

    /**
     * 页码
     */
    private Integer pageNum = 1;

    /**
     * 原生的所有查询条件
     */
    private String _queryString;


}


2.封装结果返回实体类

@Data
public class SearchResult {

    /**
     * 查询到的所有商品信息
     */
    private List<SkuEsModel> product;


    /**
     * 当前页码
     */
    private Integer pageNum;

    /**
     * 总记录数
     */
    private Long total;

    /**
     * 总页码
     */
    private Integer totalPages;

    private List<Integer> pageNavs;

    /**
     * 当前查询到的结果,所有涉及到的品牌
     */
    private List<BrandVo> brands;

    /**
     * 当前查询到的结果,所有涉及到的所有属性
     */
    private List<AttrVo> attrs;

    /**
     * 当前查询到的结果,所有涉及到的所有分类
     */
    private List<CatalogVo> catalogs;


    //===========================以上是返回给页面的所有信息============================//


    /* 面包屑导航数据 */
    private List<NavVo> navs;

    @Data
    public static class NavVo {
        private String navName;
        private String navValue;
        private String link;
    }


    @Data
    public static class BrandVo {

        private Long brandId;

        private String brandName;

        private String brandImg;
    }


    @Data
    public static class AttrVo {

        private Long attrId;

        private String attrName;

        private List<String> attrValue;
    }


    @Data
    public static class CatalogVo {

        private Long catalogId;

        private String catalogName;
    }
}

3.编写DSL语句

查询过滤

GET gulimall_product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": "225"
          }
        },
        {
          "terms": {
            "brandId": [
              "1",
              "2"
            ]
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "8"
                      }
                    }
                  },
                  {
                    "terms": {
                      "attrs.attrValue": [
                        "4G",
                        "5G"
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        {
          "term": {
            "hasStock": {
              "value": "true"
            }
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 2000,
              "lte": 2500
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2,
  "highlight": {
    "fields": {"skuTitle": {}}, 
    "pre_tags":"<b style='color:red'>",
    "post_tags": "</b>"
  }
}

在这里插入图片描述

聚合分析

GET gulimall_product/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brandId",
        "size": 2
      },
      "aggs": {
        "barndNameAgg": {
          "terms": {
            "field": "brandName",
            "size": 10
          }
        },
        "brandImgAgg": {
          "terms": {
            "field": "brandImg",
            "size": 10
          }
        }
      }
    },
    "catalogAgg": {
      "terms": {
        "field": "catalogId",
        "size": 10
      },
      "aggs": {
        "catalogNameAgg": {
          "terms": {
            "field": "catalogName",
            "size": 10
          }
        }
      }
    },
    "attrsAgg": {
      "nested": {
        "path": "attrs"
      },
      "aggs": {
        "attrIdAgg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 10
          },
          "aggs": {
            "attrNameAgg": {
              "terms": {
                "field": "attrs.attrName",
                "size": 10
              }
            },
            "attrValueAgg": {
              "terms": {
                "field": "attrs.attrValue",
                "size": 10
              }
            }
          }
        }
      }
    }
  }
}

4.根据DSL语句编写检索逻辑的业务逻辑代码

  1. 由于这里前端页面使用的是thymeleaf模板。前后端分离的话可以做出相应修改。

        @GetMapping("/list.html")
        public String listPage(SearchParam param, Model model) {
            SearchResult search = mallSearchService.search(param);
            model.addAttribute("result",search);
            return "list";
        }
    
    
  2. 具体serviceImpl实现

    • 准备检索请求
    • 执行检索请求
    • 分析响应数据,封装成我们需要的格式
    public SearchResult search(SearchParam param) {
        //1、动态构建出查询需要的DSL语句
        SearchResult result = null;
        //1、准备检索请求
        SearchRequest searchRequest = buildSearchRequest(param);
        try {
            //2、执行检索请求
            SearchResponse response = esRestClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
            //3、分析响应数据,封装成我们需要的格式
            result = buildSearchResult(response,param);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

由于业务逻辑复杂,所以就不在该方法中实现,接下来我们单独对准备检索请求,以及构建响应数据进行实现。

  • 首先实现对检索请求进行封装,这部分需要完成三部分,首先是第一部分模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)

            //构建DSL语句
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            /**
             * 模糊匹配,过滤(按照属性,分类,品牌,价格区间,库存)
             */
            //1. 构建bool-query
            BoolQueryBuilder boolQueryBuilder=new BoolQueryBuilder();
            //1.1 bool-must
            if(!StringUtils.isEmpty(param.getKeyword())){
                boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
            }
            //1.2 bool-fiter
            //1.2.1 catelogId
            if(null != param.getCatalog3Id()){
                boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
            }
            //1.2.2 brandId
            if(null != param.getBrandId() && param.getBrandId().size() >0){
                boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId",param.getBrandId()));
            }
            //1.2.3 attrs
            if(param.getAttrs() != null && param.getAttrs().size() > 0){
                param.getAttrs().forEach(item -> {
                    //attrs=1_5寸:8寸&2_16G:8G
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    //attrs=1_5寸:8寸
                    String[] s = item.split("_");
                    String attrId=s[0];
                    String[] attrValues = s[1].split(":");//这个属性检索用的值
                    boolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                    boolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                    //每一个必须都得生成一个嵌入式查询
                    NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
                    boolQueryBuilder.filter(nestedQueryBuilder);
                });
            }
            //1.2.4 hasStock 按照有无库存进行检索
            if(null != param.getHasStock()){
                boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock",param.getHasStock() == 1));
            }
            //1.2.5 skuPrice 按照价格区间进行检索
            if(!StringUtils.isEmpty(param.getSkuPrice())){
                //skuPrice形式为:1_500或_500或500_
                RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");
                String[] price = param.getSkuPrice().split("_");
                if(price.length==2){
                    rangeQueryBuilder.gte(price[0]).lte(price[1]);
                }else if(price.length == 1){
                    if(param.getSkuPrice().startsWith("_")){
                        rangeQueryBuilder.lte(price[1]);
                    }
                    if(param.getSkuPrice().endsWith("_")){
                        rangeQueryBuilder.gte(price[0]);
                    }
                }
                boolQueryBuilder.filter(rangeQueryBuilder);
            }
            //封装所有的询条件
            searchSourceBuilder.query(boolQueryBuilder);
    
    
  • 第二部分就是实现排序,分页,高亮

            /**
             * 排序,分页,高亮
             */
            //排序
            //形式为sort=hotScore_asc/desc
            if(!StringUtils.isEmpty(param.getSort())){
                String sort = param.getSort();
                String[] sortFileds = sort.split("_");
                SortOrder sortOrder="asc".equalsIgnoreCase(sortFileds[1])?SortOrder.ASC:SortOrder.DESC;
                searchSourceBuilder.sort(sortFileds[0],sortOrder);
            }
            //分页
            searchSourceBuilder.from((param.getPageNum()-1)*EsConstant.PRODUCT_PAGESIZE);
            searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
            //高亮
            if(!StringUtils.isEmpty(param.getKeyword())){
                HighlightBuilder highlightBuilder = new HighlightBuilder();
                highlightBuilder.field("skuTitle");
                highlightBuilder.preTags("<b style='color:red'>");
                highlightBuilder.postTags("</b>");
                searchSourceBuilder.highlighter(highlightBuilder);
            }
    
    
  • 聚合分析

            /**
             * 聚合分析
             */
            //1. 按照品牌进行聚合
            TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
            brand_agg.field("brandId").size(50);
            //1.1 品牌的子聚合-品牌名聚合
            brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg")
                    .field("brandName").size(1));
            //1.2 品牌的子聚合-品牌图片聚合
            brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg")
                    .field("brandImg").size(1));
            searchSourceBuilder.aggregation(brand_agg);
            //2. 按照分类信息进行聚合
            TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
            catalog_agg.field("catalogId").size(20);
            catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catelogName").size(1));
            searchSourceBuilder.aggregation(catalog_agg);
            //2. 按照属性信息进行聚合
            NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
            //2.1 按照属性ID进行聚合
            TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
            attr_agg.subAggregation(attr_id_agg);
            //2.1.1 在每个属性ID下,按照属性名进行聚合
            attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
            //2.1.1 在每个属性ID下,按照属性值进行聚合
    		attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
            searchSourceBuilder.aggregation(attr_agg);
            System.out.println("构建的DSL语句:"+searchSourceBuilder.toString());
            SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},searchSourceBuilder);
            return searchRequest;
    
    

此时通过postman测试构建的DSL语句是可以输出的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V4yi8N9B-1650294289115)(https://s2.loli.net/2022/04/18/OwNctozBd2Q67nX.png)]

SearchResult的分析与封装

我们需要返回一下数据

1,返回所有查询到的商品

在这里插入图片描述

        SearchResult result = new SearchResult();

        //1、返回的所有查询到的商品
        SearchHits hits = response.getHits();

        List<SkuEsModel> esModels = new ArrayList<>();
        //遍历所有商品信息
        if (hits.getHits() != null && hits.getHits().length > 0) {
            for (SearchHit hit : hits.getHits()) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);

                //判断是否按关键字检索,若是就显示高亮,否则不显示
                if (!StringUtils.isEmpty(param.getKeyword())) {
                    //拿到高亮信息显示标题
                    HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                    String skuTitleValue = skuTitle.getFragments()[0].string();
                    esModel.setSkuTitle(skuTitleValue);
                }
                esModels.add(esModel);
            }
        }
        result.setProduct(esModels);

2,当前商品所涉及到的属性信息

在这里插入图片描述

        //2、当前商品涉及到的所有属性信息
        List<SearchResult.AttrVo> attrVos = new ArrayList<>();
        //获取属性信息的聚合
        ParsedNested attrsAgg = response.getAggregations().get("attrAgg");
        ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
        for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
            SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
            //1、得到属性的id
            long attrId = bucket.getKeyAsNumber().longValue();
            attrVo.setAttrId(attrId);

            //2、得到属性的名字
            ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
            String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
            attrVo.setAttrName(attrName);

            //3、得到属性的所有值
            ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
            List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());
            attrVo.setAttrValue(attrValues);

            attrVos.add(attrVo);
        }

        result.setAttrs(attrVos);

3当前商品所涉及到的品牌信息

在这里插入图片描述

        //3、当前商品涉及到的所有品牌信息
        List<SearchResult.BrandVo> brandVos = new ArrayList<>();
        //获取到品牌的聚合
        ParsedLongTerms brandAgg = response.getAggregations().get("brandAgg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResult.BrandVo brandVo = new SearchResult.BrandVo();

            //1、得到品牌的id
            long brandId = bucket.getKeyAsNumber().longValue();
            brandVo.setBrandId(brandId);

            //2、得到品牌的名字
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brandNameAgg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandName(brandName);

            //3、得到品牌的图片
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brandImgAgg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
            brandVo.setBrandImg(brandImg);

            brandVos.add(brandVo);
        }
        result.setBrands(brandVos);

4当前商品所涉及到的分类信息

在这里插入图片描述

        //4、当前商品涉及到的所有分类信息
        //获取到分类的聚合
        List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
        ParsedLongTerms catalogAgg = response.getAggregations().get("catalogAgg");
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
            //得到分类id
            String keyAsString = bucket.getKeyAsString();
            catalogVo.setCatalogId(Long.parseLong(keyAsString));

            //得到分类名
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalogNameAgg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
            catalogVo.setCatalogName(catalogName);
            catalogVos.add(catalogVo);
        }

        result.setCatalogs(catalogVos);

5 分页信息-页码,总记录数

        //5、分页信息-页码
        result.setPageNum(param.getPageNum());
        //5、1分页信息、总记录数
        long total = hits.getTotalHits().value;
        result.setTotal(total);

        //5、2分页信息-总页码-计算
        int totalPages = (int)total % EsConstant.PRODUCT_PAGESIZE == 0 ?
                (int)total / EsConstant.PRODUCT_PAGESIZE : ((int)total / EsConstant.PRODUCT_PAGESIZE + 1);
        result.setTotalPages(totalPages);

到此为止,ES的后端就结束了,通过postman测试,构建的DSL语句如下所示:

GET gulimall_product/_search
{
  "from": 0,
  "size": 2,
  "query": {
    "bool": {
      "adjust_pure_negative": true,
      "boost": 1
    }
  },
  "aggregations": {
    "brandAgg": {
      "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": {
        "brandNameAgg": {
          "terms": {
            "field": "brandName",
            "size": 1,
            "min_doc_count": 1,
            "shard_min_doc_count": 0,
            "show_term_doc_count_error": false,
            "order": [
              {
                "_count": "desc"
              },
              {
                "_key": "asc"
              }
            ]
          }
        },
        "brandImgAgg": {
          "terms": {
            "field": "brandImg",
            "size": 1,
            "min_doc_count": 1,
            "shard_min_doc_count": 0,
            "show_term_doc_count_error": false,
            "order": [
              {
                "_count": "desc"
              },
              {
                "_key": "asc"
              }
            ]
          }
        }
      }
    },
    "catalogAgg": {
      "terms": {
        "field": "catalogId",
        "size": 20,
        "min_doc_count": 1,
        "shard_min_doc_count": 0,
        "show_term_doc_count_error": false,
        "order": [
          {
            "_count": "desc"
          },
          {
            "_key": "asc"
          }
        ]
      },
      "aggregations": {
        "catalogNameAgg": {
          "terms": {
            "field": "catalogName",
            "size": 1,
            "min_doc_count": 1,
            "shard_min_doc_count": 0,
            "show_term_doc_count_error": false,
            "order": [
              {
                "_count": "desc"
              },
              {
                "_key": "asc"
              }
            ]
          }
        }
      }
    },
    "attrAgg": {
      "nested": {
        "path": "attrs"
      },
      "aggregations": {
        "attrIdAgg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 10,
            "min_doc_count": 1,
            "shard_min_doc_count": 0,
            "show_term_doc_count_error": false,
            "order": [
              {
                "_count": "desc"
              },
              {
                "_key": "asc"
              }
            ]
          },
          "aggregations": {
            "attrNameAgg": {
              "terms": {
                "field": "attrs.attrName",
                "size": 1,
                "min_doc_count": 1,
                "shard_min_doc_count": 0,
                "show_term_doc_count_error": false,
                "order": [
                  {
                    "_count": "desc"
                  },
                  {
                    "_key": "asc"
                  }
                ]
              }
            },
            "attrValueAgg": {
              "terms": {
                "field": "attrs.attrValue",
                "size": 50,
                "min_doc_count": 1,
                "shard_min_doc_count": 0,
                "show_term_doc_count_error": false,
                "order": [
                  {
                    "_count": "desc"
                  },
                  {
                    "_key": "asc"
                  }
                ]
              }
            }
          }
        }
      }
    }
  }
}
清空所有数据
POST /mall_product/_delete_by_query?pretty
{
  "query":{
    "match_all":{}
  }
}

总结初次体验确实很煎熬,看着他这个语法一阵头大,多练就会熟练了,也就那样了,加油冲冲冲

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值