前言

前面我已经搭建好了ElasticSearch服务,并完成了MySQL到ElasticSearch的数据迁移;

使用ES专门做搜索功能,打破MySQL搜索瓶颈;

ElasticSearch的应用场景

  • 数据库字段太多,查询太慢,索引没有办法再做优化;
  • 数据库1个count查询就拖死全表;
  • MySQL的limit翻到几十几百万页后实在是太慢;
  • 数据库like实在太慢,每次like整个服务器cpu内存飙高,拖慢整个线上服务;
  • 想要对外/内提供db里的数据的全文检索服务;
  • 提供日志(程序运行)查询功能;

本文将使用ElasticSearch的倒排索引取代MySQL的索引,进行大数据查询,提升查询效率;

 

一、精确查询(termQuery)

termQuery不会对查询条件进行分词 但这并不以为着查询的字段没有进行分词存储

1.使用Kibana的DevTools查询

term精确查询并不会对查询条件进行分词,类似于MySQL中 select * from table where 字段='xx值';

GET hotel/_search
{
  "query": {
    "term": {
      "brand": {
        "value": "万豪"
      }
    }
  },
  "from": 0,
  "size": 20
}

2.JavaAPI查询

将以上在Kibana输入的DSM转换成Java代码;

 //按照品牌精确查询
    @Override
    public Map<String, Object> brandTermQuery(int current, int size, Map<String, Object> searchParam) {
        //按品牌精确查询实现
        //1.获取前端参数
        String brand = (String) searchParam.get("brand");
        //响应前端的Map
        Map<String, Object> resultMap = new HashMap<>();
        //2.构建查询条件
        //查询请求
        SearchRequest hotelSearchRequest = new SearchRequest("hotel");
        //请求体
        SearchSourceBuilder hotelSearchSourceBuilder = new SearchSourceBuilder();
        //如果查询条件为空就查询所有
        if (StringUtils.hasText(brand)) {
            //请求体-查询部分
            TermQueryBuilder hotelTermQueryBuilder = QueryBuilders.termQuery("brand", brand);
            hotelSearchSourceBuilder.query(hotelTermQueryBuilder);
        }
        //请求体-分页部分
        hotelSearchSourceBuilder.from((current - 1) * size);
        hotelSearchSourceBuilder.size(size);
        //查询请求-封装请求体
        hotelSearchRequest.source(hotelSearchSourceBuilder);

        //3.去查询
        try {
            SearchResponse hotelSearchResponse = restHighLevelClient.search(hotelSearchRequest, RequestOptions.DEFAULT);
            //4.处理查询结果集
            SearchHits hotelSearchResponseHits = hotelSearchResponse.getHits();
            //获取命中总条目
            Long totalHotelHits = hotelSearchResponseHits.getTotalHits().value;
            //获取命中的每1个条
            SearchHit[] hoteHits = hotelSearchResponseHits.getHits();
            //前端
            ArrayList<HotelEntity> hotelEntitieList = new ArrayList<>();
            if (hoteHits != null || hoteHits.length > 0) {
                for (SearchHit hoteHit : hoteHits) {
                    String sourceAsString = hoteHit.getSourceAsString();
                    //字符串转换成Java对象
                    HotelEntity hotelEntity = JSON.parseObject(sourceAsString, HotelEntity.class);
                    hotelEntitieList.add(hotelEntity);
                }
            }
            //前端展示
            resultMap.put("list", hotelEntitieList);
            resultMap.put("totalResultSize", totalHotelHits);
            //设置分页相关
            resultMap.put("current", current);
            resultMap.put("totalPage", (totalHotelHits + size - 1) / size);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return resultMap;
    }
HotelServiceImpl.java

 

二、中文分词器

如果设置了字段的type为keyword,就可以对该字段使用term精确查询;

如果设置了字段的type为text,当添加文档时,对于该域的值会进行分词,形成若干term(词条)存储在倒排索引中。

当用户进行term查询时,ES会将当前查询条件当做1个term(词条),和当前倒排索引中term(词条)进行匹配?

匹配成功则会查询到数据,如果倒排索引中不存在该term(词条)则查询不到数据。

那我们如何对text类型的字段进行term查询呢?

这就需要利用中文分词器对文档中的内容进行中文分词, 重构ES的倒排索引的结构,把整个文档分词成为若干中文term(词条)

1.ElasticSearch内置分词器

在ElasticSearch默认内置了多种分词器:

  • Standard Analyzer - 默认分词器,按英文空格切分
  •  Simple Analyzer - 按照非字母切分(符号被过滤)
  •  Stop Analyzer - 小写处理,停用词过滤(the,a,is)
  •  Whitespace Analyzer - 按照空格切分,不转小写
  • Keyword Analyzer - 不分词,直接将输入当作输出
  • Patter Analyzer - 正则表达式,默认\W+(非字符分割)

 

2.默认分词无法对中文分词

看看ES是默认使用Standard Analyzer分词器对文档内容进行分词;

GET _analyze
{
"text": "北京市东城区万豪酒店" }

此时可以发现Standard Analyzer分词器把每1个汉字形成了一个词,这显然无法满足汉语搜索习惯;

 

3.安装IK分词器

IK分词器是国人开发的1款智能中文分词器,基于Java语言开发、具有60万字/秒的高速处理能力。

并且用户可以按需求,设置停用词与扩展词。

3.1.上传IK安装包

#因为启动es时候 已经做好的目录挂载
容器内部:/usr/share/elasticsearch/plugins 
宿主机:/mydata/elasticsearch/plugins

所以只需要将文件复制到/mydata/elasticsearch/plugins 目录下即可

3.2.重启容器

docker restart elasticsearch

 3.3.测试

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": "北京市东城区万豪酒店"
}

 

4.使用IK分词器

4.1.分词模式

IK分词器有两种分词模式it_max_word和ik_smart模式
  • ik_max_word:细粒度分词,存储的时候分词可以细粒度一些;
  • ik_smart:粗粒度分词,搜索的时候可以对搜索条件分词粗粒度一些;

4.2.配置自定义词库

IK分词器虽然非常智能。但是中华语言博大精深,其并不能完全识别所有的词。

如在文档存储时,无法对专业术语、店名、网络词语等等。所以IK提供了扩展词库,用户可以按需求添加自定义分词数据。

4.2.1.定义扩展词

4.2.2.定义停用词

4.3.修改IK分词器的配置文件

vim IKAnalyzer.cfg.xml
#修改配置文件 注意这个地方 不要把搞乱码了!!! 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
    <comment>IK Analyzer 扩展配置</comment>
    <!--用户可以在这里配置自己的扩展字典 -->
    <entry key="ext_dict">my.dic</entry>
     <!--用户可以在这里配置自己的扩展停止词字典-->
    <entry key="ext_stopwords">extra_stopword.dic</entry>
    <!--用户可以在这里配置远程扩展字典 -->
    <entry key="remote_ext_dict">http://106.75.109.43:28888/remote.dic</entry>
    <!--用户可以在这里配置远程扩展停止词字典-->
    <!-- <entry key="remote_ext_stopwords">http://ip地址:端口号/词典文件</entry> -->
</properties>

 

4.4.重建索引指定分词器

把数据从原索引(表)中迁移到目标索引(表);

如果是线上环境,在重建索引时,一定要选择异步构建和平滑构建; 

PUT hotel_2
{
  "mappings": {
    "properties": {
      "name":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "address":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "brand":{
        "type": "keyword"
      },
      "type":{
        "type": "keyword"
      },
       "price":{
        "type": "integer"
      },
      "specs":{
        "type": "keyword"
      },
       "salesVolume":{
        "type": "integer"
      },
      "area":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "imageUrl":{
        "type": "text"
      },
      "synopsis":{
        "type": "text",
        "analyzer": "ik_max_word",
        "search_analyzer": "ik_smart"
      },
      "createTime":{
        "type": "date",
        "format": "yyyy-MM-dd"
      },
      "isAd":{
        "type":"integer"
      }
    }
  }
}


#重建索引 异步构建和平滑构建 
POST _reindex?wait_for_completion=false&requests_per_second=2000
{
  "source": {
    "index": "原始索引名字"
  },
  "dest": {
    "index": "目标索引名字"
  }
}

#查看任务完成情况
GET _tasks/任务id

#重建别名关联关系
#断开原来的关系
POST _aliases
{
  "actions": [
    {
      "remove": {
        "index": "hotel_1",
        "alias": "hotel"
      }
    }
  ]
}
#删除原来的索引表
DELETE hotel_1

#新建hotel_2的关系
POST _aliases
{
  "actions": [
    {
      "add": {
        "index": "hotel_2",
        "alias": "hotel"
      }
    }
  ]
}
dms

 

4.5.测试文档是否被分词

此时文档在存储时已经被中文分词器进行了中文分词并存储,我们就可以使用termQuery精确查询进行分词结果测试了;

由于termQuery精确查询,不会对查询条件进行分词,所依我根据分词结果进行查询,如果分词成功,就会查询到text字段的结果;

 

三、分词查询(mathQuery)

上述的term精确查询必须要根据分词之后的结果进行精确查询;

可是用户不知道你的文档是怎么分词的,所以我们需要对用户的查询条件也进行分词;

1.Kibana分词查询

GET hotel/_search
{
  "query": {
    "match": {
     "name":"北京市东城区瑞麟湾"
    }
  }
}

matchQuery会对查询条件进行分词,并拿分词后的结果,去ES中进行逐一匹配,默认取结果并集。

2.JavaAPI分词查询

 //根据酒店名称匹配查询
    @Override
    public Map<String, Object> nameMatchQuery(Integer current, Integer size, Map<String, Object> searchParam) {
        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        Map<String, Object> map = new HashMap<>();
        //获取name参数
        String name = (String) searchParam.get("name");
        if (StringUtils.hasText(name)) {
            //组装查询对象
            MatchQueryBuilder nameMatchQueryBuilder = QueryBuilders.matchQuery("name", name);
            searchSourceBuilder.query(nameMatchQueryBuilder);
        }
        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        searchRequest.source(searchSourceBuilder);

        //处理查询结果
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();

            long totalHits = hits.getTotalHits().value;

            SearchHit[] searchHits = hits.getHits();

            List<HotelEntity> list = new ArrayList<>();

            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
            }
            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return map;
    }
HotelServiceImpl.java

 

四、模糊搜索(wildcardQuery)

当在搜索框进行搜索时,展示出所有品牌以美开头的酒店。

wildcardQuery:会对查询条件进行分词,还可以使用通配符 ?(任意单个字符) 和 * (0个或多个字符)

1.Kibana分词查询

GET hotel/_search
{
  "query": {
    "wildcard": {
      "brand": {
        "value": "美*"
      }
    }
  }
}

2.JavaAPI分词查询

 //根据酒店品牌模糊查询
    @Override
    public Map<String, Object> nameWildcardQuery(Integer current, Integer size, Map<String, Object> searchParam) {
        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //根据酒店名称模糊查询
        //1.获取前端参数
        String name = (String) searchParam.get("name");
        //2.组装查询对象
        if (StringUtils.hasText(name)) {
            WildcardQueryBuilder brandWildcardQuery = QueryBuilders.wildcardQuery("brand", name+"*");
            searchSourceBuilder.query(brandWildcardQuery);
        }

        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        searchRequest.source(searchSourceBuilder);
        Map<String, Object> map = new HashMap<>();

        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();
            long totalHits = hits.getTotalHits().value;
            SearchHit[] searchHits = hits.getHits();
            List<HotelEntity> list = new ArrayList<>();
            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
            }
            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return map;
    }
HotelServiceImpl.java

 

五、多域(字段)查询(query_string

当用户在搜索框输入查询条件时,为了给用户展示更多的数据,该条件不应该仅仅作用于某1个域(字段),而要让其作用于多个域进行搜索,从而搜索出更多的查询结果。

类似于MySQL数据中的 select * from table 字段1=条件  or 字段2=条件.....;

简而言之就是使用1个条件去多个字段中查询;

当前需要将用户的搜索条件,作用于:酒店名称(name)、酒店描述(synopsis)、酒店地区(area)、酒店地址(address);

1.Kibana查询

GET hotel/_search
{
  "query": {
    "query_string": {
      "fields": ["name","brand","address","synopsis"],
      "query": "万豪 OR 北京 OR 上海"
    }
  }
}

2.JavaAPI查询

    //根据name,synopsis,area,address进行多域(字段)查询
    @Override
    public Map<String, Object> searchQueryStringQuery(Integer current, Integer size, Map<String, Object> searchParam) {
        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        Map<String, Object> map = new HashMap<>();
        //根据name,synopsis,area,address进行多域查询
        String condition = (String) searchParam.get("condition");
        //组装查询对象
        if (StringUtils.hasText(condition)) {
            QueryStringQueryBuilder queryStringQueryBuilder = QueryBuilders.queryStringQuery(condition)
                    .field("name")
                    .field("address")
                    .field("synopsis")
                    .field("area")
                    .defaultOperator(Operator.OR);
            searchSourceBuilder.query(queryStringQueryBuilder);
        }

        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        searchRequest.source(searchSourceBuilder);
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();

            long totalHits = hits.getTotalHits().value;

            SearchHit[] searchHits = hits.getHits();

            List<HotelEntity> list = new ArrayList<>();

            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
            }


            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return map;
    }
HotelServiceImpl

 

六、排序查询(sort)

当用户进行搜索时,有时会关注该商品的销量、评论数等信息,对某些域(字段)进行进行排序,搜索出销量最高或评论数最多的商品

1.Kibana查询

使用match_all查询所有数据,对price和salesVolume域(字段)进行排序;

#排序查询:支持多字段排序
GET hotel/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    },
     {
      "salesVolume": {
        "order": "asc"
      }
    }
  ]
}

2.JavaAPI查询

 //根据销量排序查询
    @Override
    public Map<String, Object> salesSortQuery(Integer current, Integer size, Map<String, Object> searchParam) {
        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //设置按销量排序
        String sortWay = (String) searchParam.get("sortWay");
        //组装sort部分
        if (StringUtils.hasText(sortWay)) {
            if ("asc".equals(sortWay)) {
                searchSourceBuilder.sort("price", SortOrder.ASC).sort("salesVolume", SortOrder.ASC);
            } else {
                searchSourceBuilder.sort("price", SortOrder.DESC).sort("salesVolume", SortOrder.DESC);
            }
        }

        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        searchRequest.source(searchSourceBuilder);
        Map<String, Object> map = new HashMap<>();
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();

            long totalHits = hits.getTotalHits().value;

            SearchHit[] searchHits = hits.getHits();

            List<HotelEntity> list = new ArrayList<>();

            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
            }


            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);
            map.put("sortWay", searchParam.get("sortWay"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return map;
    }
HotelServiceImpl.java

 

七、范围查询(range)

当用户要搜索商品时,有时会对某一个特定的价格区间进行查询,搜索出符合心理预期价格的商品;

1.Kibana查询

区间范围查询:查询价格在100-500元范围之间的商品;

GET hotel/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 100,
        "lte": 500
      }
    }
  }
}

2.JavaAPI查询

 //处理前端参数
    public Long transferToLong(Object param){
        if(param==null || "".equals(param)){
            return null;
        }else{
           return Long.parseLong((String)param);
        }
    }
    //根据价格范围查询
    @Override
    public Map<String, Object> priceRangeQuery(Integer current, Integer size, Map<String, Object> searchParam) {

        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //根据价格范围查询
        //1.获取参数
        Long maxPrice = transferToLong(searchParam.get("maxPrice"));
        Long minPrice = transferToLong(searchParam.get("minPrice"));
        //2.组装搜索条件
        if (maxPrice != null || maxPrice != null) {
            RangeQueryBuilder priceRangeQueryBuilder = QueryBuilders.rangeQuery("price");
            if (maxPrice != null) {
                priceRangeQueryBuilder.lte(maxPrice);
            }
            if (minPrice != null) {
                priceRangeQueryBuilder.gte(maxPrice);
            }
        }
        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        searchRequest.source(searchSourceBuilder);

        //处理查询结果
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();

            long totalHits = hits.getTotalHits().value;

            SearchHit[] searchHits = hits.getHits();

            List<HotelEntity> list = new ArrayList<>();

            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
            }

            Map<String, Object> map = new HashMap<>();
            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);

            map.put("minPrice", searchParam.get("minPrice"));
            map.put("maxPrice", searchParam.get("maxPrice"));

            return map;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
HotelServiceImpl.java

 

八、多条件查询(boolQuery

多字段查询类似于没MySQL中的以下SQL

select * from table where 字段1='条件1' or 字段2='条件2' ;

当用户进行搜索时,不会仅仅只输入一个搜索条件,有时会传递多个条件,将符合多条件的商品搜索出来。

boolQuery可以对多个查询条件连接。

连接方式有

  • must(and):       条件必须成立
  • must_not(not): 条件必须不成立
  • should(or):       1个条件可以成立即可
  • filter:                条件必须成立,性能比must高

1.Kibana查询

查询北京市的万豪价格区间在500-2000,最好是五星级;

GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "brand": {
              "value": "万豪"
            }
          }
        },
        {
          "term": {
            "area": {
              "value": "北京市"
            }
          }
        }
      ],
      "should": [
        {
          "term": {
            "specs": {
              "value": "五星级"
            }
          }
        }
      ], 
      "filter": [
        {
          "range": {
            "price": {
              "gte": 500,
              "lte": 2000
            }
          }
        }
      ]
    }
  }
}

2.JavaAPI查询

//多条件查询
//搜索框多域、品牌精确、城市精确、星级精确、价格范围、销量排序
@Override
public Map<String, Object> searchBoolQuery(Integer current, Integer size, Map<String, Object> searchParam) {

    //设置查询
    SearchRequest searchRequest = new SearchRequest("hotel");

    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();


    //todo 多条件查询 :多域、品牌精确、城市精确、星级精确、价格范围、销量排序
    //设置查询方式
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

    //多域
    if (StringUtils.hasText(searchParam.get("condition"))) {
        QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
            .field("name")
            .field("synopsis")
            .field("area")
            .field("address")
            .defaultOperator(Operator.OR);
        boolQueryBuilder.must(queryBuilder);
    }

    //品牌精确
    if (StringUtils.hasText(searchParam.get("brand"))) {
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("brand", searchParam.get("brand"));
        boolQueryBuilder.filter(termQueryBuilder);
    }

    //城市精确
    if (StringUtils.hasText(searchParam.get("area"))) {
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("area", searchParam.get("area"));
        boolQueryBuilder.filter(termQueryBuilder);
    }

    //星级精确
    if (StringUtils.hasText(searchParam.get("specs"))) {
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("specs", searchParam.get("specs"));
        boolQueryBuilder.filter(termQueryBuilder);
    }

    //价格范围
    //1.获取参数
    Long maxPrice = transferToLong(searchParam.get("maxPrice"));
    Long minPrice = transferToLong(searchParam.get("minPrice"));
    //2.根据情况组装条件
    if (maxPrice!=null||minPrice!=null){
        RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("price");

        //gte great than and equal 大于等于
        //lte less than and equal 小于等于

        if (maxPrice!=null){
            rangeQueryBuilder.lte(maxPrice);
        }
        if (minPrice!=null){
            rangeQueryBuilder.gte(minPrice);
        }

        boolQueryBuilder.must(rangeQueryBuilder);
    }

    //销量排序
    if (StringUtils.hasText(searchParam.get("sortWay"))) {
        if ("desc".equalsIgnoreCase(searchParam.get("sortWay").toString())) {
            searchSourceBuilder.sort("salesVolume", SortOrder.DESC);
        } else {
            searchSourceBuilder.sort("salesVolume", SortOrder.ASC);
        }
    }

    searchSourceBuilder.query(boolQueryBuilder);


    //设置分页
    searchSourceBuilder.from((current - 1) * size);
    searchSourceBuilder.size(size);

    searchRequest.source(searchSourceBuilder);

    //处理查询结果
    try {
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        SearchHits hits = searchResponse.getHits();

        long totalHits = hits.getTotalHits().value;

        SearchHit[] searchHits = hits.getHits();

        List<HotelEntity> list = new ArrayList<>();

        for (SearchHit searchHit : searchHits) {
            String sourceAsString = searchHit.getSourceAsString();
            list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
        }

        Map<String, Object> map = new HashMap<>();
        map.put("list", list);
        map.put("totalResultSize", totalHits);
        map.put("current", current);
        //设置总页数
        map.put("totalPage", (totalHits + size - 1) / size);

        map.put("brand", searchParam.get("brand"));
        map.put("area", searchParam.get("area"));
        map.put("specs", searchParam.get("specs"));
        map.put("sortWay", searchParam.get("sortWay"));
        map.put("minPrice", searchParam.get("minPrice"));
        map.put("maxPrice", searchParam.get("maxPrice"));

        return map;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
HotelServiceImpl.java

 

九、纠错查询(fuzzy)

用户在进行搜索时,有可能打错字输入了错误的信息。 那么后台在处理时,应该智能的识别错误信息,更加友好的展示相关数据。

fuzzyQuery

自动纠错查询,会自动尝试将搜索条件进行纠错,然后去跟term进行匹配。

  • fuzziness:      查询允许的最大编辑距离,默认为0,允许搜索条件出现几个错别字

  • prefix_length:设置前几个字符不允许编辑

在未经处理的情况下,一旦输入信息有误,对应不到分词的话,则不会有任何结果返回;

1.Kibana查询

自动纠错查询

  • fuzzines:允许搜索条件出现x个错别字
  • prefix_length:不允许搜索条件的第1字出错

用户输入搜索条件为北漂去,也可以搜索area为北京的酒店;

GET hotel/_search
{
  "query": {
    "fuzzy": {
      "area": {
        "value": "北漂去",
        "fuzziness": 2,
        "prefix_length": 1
        
      }
    }
  }
}

2.JavaAPI查询

//地址纠错查询
    @Override
    public Map<String, Object> searchFuzzyQuery(Integer current, Integer size, Map<String, Object> searchParam) {

        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //设置查询方式
        String area=(String) searchParam.get("area");
        //设置城市为纠错查询
        if(StringUtils.hasText(area)){
            FuzzyQueryBuilder fuzzyQueryBuilder  = QueryBuilders.fuzzyQuery("area", area)
                    .fuzziness(Fuzziness.TWO)  //允许搜索条件出现2个错别字
                    .prefixLength(1);  //不允许搜索条件的第1字出错
            searchSourceBuilder.query(fuzzyQueryBuilder);
        }
        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        searchRequest.source(searchSourceBuilder);

        // 处理查询结果
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();

            long totalHits = hits.getTotalHits().value;

            SearchHit[] searchHits = hits.getHits();

            List<HotelEntity> list = new ArrayList<>();

            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                list.add(JSON.parseObject(sourceAsString, HotelEntity.class));
            }

            Map<String, Object> map = new HashMap<>();
            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);

            map.put("brand", searchParam.get("brand"));
            map.put("area", searchParam.get("area"));
            map.put("specs", searchParam.get("specs"));
            map.put("sortWay", searchParam.get("sortWay"));
            map.put("minPrice", searchParam.get("minPrice"));
            map.put("maxPrice", searchParam.get("maxPrice"));
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);

            return map;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
HotelServiceImpl.java

 

十、高亮展示搜索结果

当用户在搜索框输入搜索条件后,对于查询结果的展示,应将搜索条件以特殊的样式展示,这种查询就称为高亮结果查询;

 

1.Kibana查询

#高亮查询结果
#告诉前端为什么匹配中了
GET hotel/_search
{
  "query": {
    "match": {
      "name": "北京市"
    }
  },
  "highlight": {
    "fields": {
      "name": {
        "pre_tags": "<font color='red'>",
        "post_tags": "</font>"
      }
    }
  }
}

2.JavaAPI查询

 //按名称高亮查询
    @Override
    public Map<String, Object> searchHighLight(Integer current, Integer size, Map<String, Object> searchParam) {

        //设置查询
        SearchRequest searchRequest = new SearchRequest("hotel");

        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        //设置查询方式
        if (!StringUtils.isEmpty(searchParam.get("condition"))) {
            QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(searchParam.get("condition").toString())
                    .field("name")
                    .field("synopsis")
                    .field("area")
                    .field("address")
                    .defaultOperator(Operator.OR);
            searchSourceBuilder.query(queryBuilder);
        }
        //设置分页
        searchSourceBuilder.from((current - 1) * size);
        searchSourceBuilder.size(size);

        //查询高亮设置
        HighlightBuilder highlightBuider = new HighlightBuilder();
        highlightBuider.field("name");
        highlightBuider.preTags("<font color='red'>");
        highlightBuider.postTags("</font>");
        searchSourceBuilder.highlighter(highlightBuider);

        searchRequest.source(searchSourceBuilder);
        try {
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            SearchHits hits = searchResponse.getHits();

            long totalHits = hits.getTotalHits().value;

            SearchHit[] searchHits = hits.getHits();

            List<HotelEntity> list = new ArrayList<>();

            for (SearchHit searchHit : searchHits) {
                String sourceAsString = searchHit.getSourceAsString();
                HotelEntity hotelEntity = JSON.parseObject(sourceAsString, HotelEntity.class);

                //处理高亮结果
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField!=null){
                    Text[] fragments = highlightField.fragments();
                    hotelEntity.setName(fragments[0].toString());
                }

                list.add(hotelEntity);
            }

            Map<String, Object> map = new HashMap<>();
            map.put("list", list);
            map.put("totalResultSize", totalHits);
            map.put("current", current);
            //设置总页数
            map.put("totalPage", (totalHits + size - 1) / size);

            return map;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
HotelServiceImpl.java

 

十一、短语查询(match_phrase)

在搜索条件部分词的前提下,使用短语进行查询;

1.kibana查询

#磁盘空间写满
GET k8s-2023.02.13/_search
{
    "query": {
      "bool": {
      "must": [
        {  "match_phrase": {
      "message": "app-workspace-373-1675214627414-podgroup-s4l6-5qr7g"
    }},
    {  "match_phrase": {
      "message": "Evicted"
    }}
        
        ]}
    },
    "sort": [
        {
            "@timestamp": {
                "order": "desc"
            }
        }],
    "from": 0,
    "_source": { 
    "includes":["@timestamp","host_ip","log.file.path","message"]
  }
}

 

参考

 

posted on 2022-07-03 22:02  Martin8866  阅读(1024)  评论(0编辑  收藏  举报