您现在的位置是:首页 > 文章详情

Spring Boot 2.0 整合 ES 5 文章内容搜索实战

日期:2018-11-28点击:265

本章内容

  1. 文章内容搜索思路

  2. 搜索内容分词

  3. 搜索查询语句

  4. 筛选条件

  5. 分页、排序条件

  6. 小结


一、文章内容搜索思路

上一篇讲了在怎么在 Spring Boot 2.0 上整合 ES 5 ,这一篇聊聊具体实战。简单讲下如何实现文章、问答这些内容搜索的具体实现。实现思路很简单:


  • 基于「短语匹配」并设置最小匹配权重值

  • 哪来的短语,利用 IK 分词器分词

  • 基于 Fiter 实现筛选

  • 基于 Pageable 实现分页排序

这里直接调用搜索的话,容易搜出不尽人意的东西。因为内容搜索关注内容的连接性。所以这里处理方法比较 low ,希望多交流一起实现更好的搜索方法。就是通过分词得到很多短语,然后利用短语进行短语精准匹配。

 

ES 安装 IK 分词器插件很简单。第一步,在下载对应版本 https://github.com/medcl/elasticsearch-analysis-ik/releases。第二步,在 elasticsearch-5.5.3/plugins 目录下,新建一个文件夹 ik,把 elasticsearch-analysis-ik-5.5.3.zip 解压后的文件拷贝到 elasticsearch-5.1.1/plugins/ik 目录下。最后重启 ES 即可。

 

二、搜索内容分词

 

安装好 IK ,如何调用呢?

 

第一步,我这边搜搜内容会以 逗号 拼接传入。所以会先将逗号分割

 

第二步,在搜索词中加入自己本身,因为有些词经过 ik 分词后就没了... 这是个 bug

 

第三步,利用 AnalyzeRequestBuilder 对象获取 IK 分词后的返回值对象列表

 

第四步,优化分词结果,比如都为词,则保留全部;有词有字,则保留词;只有字,则保留字

 

核心实现代码如下:

   /**      * 搜索内容分词      */     protected List<String>      handlingSearchContent(String searchContent) {                List<String> searchTermResultList = new ArrayList<>();              // 按逗号分割,获取搜索词列表              List<String> searchTermList = Arrays.asList(searchContent.split(SearchConstant.STRING_TOKEN_SPLIT));                // 如果搜索词大于 1 个字,则经过 IK 分词器获取分词结果列表              searchTermList.forEach(searchTerm -> {                  // 搜索词 TAG 本身加入搜索词列表,并解决 will 这种问题                  searchTermResultList.add(searchTerm);                  // 获取搜索词 IK 分词列表                  searchTermResultList.addAll(getIkAnalyzeSearchTerms(searchTerm));              });                return searchTermResultList;     }       /**      * 调用 ES 获取 IK 分词后结果      */     protected List<String>      getIkAnalyzeSearchTerms(String searchContent) {              AnalyzeRequestBuilder ikRequest = new AnalyzeRequestBuilder(elasticsearchTemplate.getClient(),                      AnalyzeAction.INSTANCE, SearchConstant.INDEX_NAME,      searchContent);              ikRequest.setTokenizer(SearchConstant.TOKENIZER_IK_MAX);              List<AnalyzeResponse.AnalyzeToken> ikTokenList =      ikRequest.execute().actionGet().getTokens();                // 循环赋值              List<String> searchTermList = new ArrayList<>();              ikTokenList.forEach(ikToken -> {                  searchTermList.add(ikToken.getTerm());              });                return handlingIkResultTerms(searchTermList);     }       /**      * 如果分词结果:洗发水(洗发、发水、洗、发、水)      * - 均为词,保留      * - 词 + 字,只保留词      * - 均为字,保留字      */     private List<String>      handlingIkResultTerms(List<String> searchTermList) {              Boolean isPhrase = false;              Boolean isWord = false;              for (String term : searchTermList) {                  if (term.length() > SearchConstant.SEARCH_TERM_LENGTH)      {                      isPhrase = true;                  } else {                      isWord = true;                  }              }                if (isWord & isPhrase) {                  List<String> phraseList = new ArrayList<>();                  searchTermList.forEach(term -> {                      if (term.length() > SearchConstant.SEARCH_TERM_LENGTH)      {                          phraseList.add(term);                      }                  });                  return phraseList;              }                return searchTermList;     }

三、搜索查询语句

 

构造内容枚举对象,罗列需要搜索的字段,ContentSearchTermEnum 代码如下:

import lombok.AllArgsConstructor;   @AllArgsConstructor public enum ContentSearchTermEnum {       // 标题     TITLE("title"),     // 内容     CONTENT("content");       /**      * 搜索字段      */     private String      name;       public String      getName() {              return name;     }       public void      setName(String name) {              this.name = name;     }   }

循环进行「短语搜索匹配」搜索字段,然后并设置最低权重值为 1。核心代码如下:

   /**      * 构造查询条件      */     private void      buildMatchQuery(BoolQueryBuilder queryBuilder, List<String>      searchTermList) {              for (String searchTerm : searchTermList) {                  for (ContentSearchTermEnum searchTermEnum : ContentSearchTermEnum.values())      {                      queryBuilder.should(QueryBuilders.matchPhraseQuery(searchTermEnum.getName(),      searchTerm));                  }              }              queryBuilder.minimumShouldMatch(SearchConstant.MINIMUM_SHOULD_MATCH);     }

四、筛选条件

搜到东西不止,有时候需求是这样的。需要在某个品类下搜索,比如电商需要在某个 品牌 下搜索商品。那么需要构造一些 fitler 进行筛选。对应 SQL 语句的 Where 下的 OR AND 两种语句。在 ES 中使用 filter 方法添加过滤。代码如下:

   /**      * 构建筛选条件      */     private void      buildFilterQuery(BoolQueryBuilder boolQueryBuilder, Integer type, String      category) {              // 内容类型筛选              if (type != null) {                  BoolQueryBuilder typeFilterBuilder = QueryBuilders.boolQuery();                  typeFilterBuilder.should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME,      type).lenient(true));                  boolQueryBuilder.filter(typeFilterBuilder);              }                // 内容类别筛选              if (!StringUtils.isEmpty(category)) {                  BoolQueryBuilder categoryFilterBuilder = QueryBuilders.boolQuery();                  categoryFilterBuilder.should(QueryBuilders.matchQuery(SearchConstant.CATEGORY_NAME,      category).lenient(true));                  boolQueryBuilder.filter(categoryFilterBuilder);              }     }

type 是大类,category 是小类,这样就可以支持 大小类 筛选。但是如果需要在 type = 1 或者 type = 2 中搜索呢?具体实现代码很简单:

typeFilterBuilder     .should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME,      1)     .should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME,      2)     .lenient(true));

通过链式表达式,两个 should 实现或,即 SQL 对应的 OR 语句。通过两个 BoolQueryBuilder 实现与,即 SQL 对应的 AND 语句。

 

五、分页、排序条件

 

分页排序代码就很简单了:

  @Override     public PageBean      searchContent(ContentSearchBean contentSearchBean) {                Integer pageNumber = contentSearchBean.getPageNumber();              Integer pageSize = contentSearchBean.getPageSize();                PageBean<ContentEntity> resultPageBean = new PageBean<>();              resultPageBean.setPageNumber(pageNumber);              resultPageBean.setPageSize(pageSize);                // 构建搜索短语              String searchContent = contentSearchBean.getSearchContent();              List<String> searchTermList =      handlingSearchContent(searchContent);                // 构建查询条件              BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();              buildMatchQuery(boolQueryBuilder, searchTermList);                // 构建筛选条件              buildFilterQuery(boolQueryBuilder, contentSearchBean.getType(),      contentSearchBean.getCategory());                // 构建分页、排序条件              Pageable pageable = PageRequest.of(pageNumber, pageSize);              if (!StringUtils.isEmpty(contentSearchBean.getOrderName())) {                  pageable = PageRequest.of(pageNumber, pageSize, Sort.Direction.DESC,      contentSearchBean.getOrderName());              }              SearchQuery searchQuery = new NativeSearchQueryBuilder().withPageable(pageable)                      .withQuery(boolQueryBuilder).build();                // 搜索              LOGGER.info("\n ContentServiceImpl.searchContent() [" +      searchContent                      + "] \n DSL  = \n " +      searchQuery.getQuery().toString());              Page<ContentEntity> contentPage =      contentRepository.search(searchQuery);                resultPageBean.setResult(contentPage.getContent());              resultPageBean.setTotalCount((int) contentPage.getTotalElements());              resultPageBean.setTotalPage((int) contentPage.getTotalElements() /      resultPageBean.getPageSize() + 1);              return resultPageBean;     }

利用 Pageable 对象,构造分页参数以及指定对应的 排序字段、排序顺序(DESC ASC)即可。


springboot视频教程:http://www.roncoo.com/course/list.html?courseName=spring+boot


原文链接:https://blog.roncoo.com/article/132392
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章