当前位置: 首页 > 编程笔记 >

Spring Boot2.0整合ES5实现文章内容搜索实战

曹旭
2023-03-14
本文向大家介绍Spring Boot2.0整合ES5实现文章内容搜索实战,包括了Spring Boot2.0整合ES5实现文章内容搜索实战的使用技巧和注意事项,需要的朋友参考一下

一、文章内容搜索思路

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

  1. 基于「短语匹配」并设置最小匹配权重值
  2. 哪来的短语,利用 IK 分词器分词
  3. 基于 Fiter 实现筛选
  4. 基于 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)即可。

六、小结

这个思路比较简单。希望对大家的学习有所帮助,也希望大家多多支持小牛知识库。

 类似资料:
  • Rails 实现全文搜索 ElasticSearch 初次使用小结,一起学习进步哈~ MongoDB + Rails 有什么好的全文搜索的办法吗? Sunspot 学习笔记 做了一个脚本,方便大家用 Sunpot 做中文全文索引 How search and index works (Ruby 语言描述)

  • 本文向大家介绍使用Lucene.NET实现站内搜索,包括了使用Lucene.NET实现站内搜索的使用技巧和注意事项,需要的朋友参考一下 导入Lucene.NET 开发包 Lucene 是apache软件基金会一个开放源代码的全文检索引擎工具包,是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中

  • 本文向大家介绍探索Vue.js component内容实现,包括了探索Vue.js component内容实现的使用技巧和注意事项,需要的朋友参考一下 现在来系统地学习一下Vue(参考vue.js官方文档): Vue.js是一个构建数据驱动的web界面的库,其目标是实现响应的数据绑定和组合的试图组件。 Vue.js拥抱数据驱动的视图概念,这意味着我们能在普通的HTML模板中使用特殊的用法将DOM“

  • 本文向大家介绍python实现的用于搜索文件并进行内容替换的类实例,包括了python实现的用于搜索文件并进行内容替换的类实例的使用技巧和注意事项,需要的朋友参考一下 本文实例讲述了python实现的用于搜索文件并进行内容替换的类。分享给大家供大家参考。具体实现方法如下: 希望本文所述对大家的Python程序设计有所帮助。

  • 本文向大家介绍SpringBoot2.0 整合 Dubbo框架实现RPC服务远程调用方法,包括了SpringBoot2.0 整合 Dubbo框架实现RPC服务远程调用方法的使用技巧和注意事项,需要的朋友参考一下 一、Dubbo框架简介 1、框架依赖 图例说明: 1)图中小方块 Protocol, Cluster, Proxy, Service, Container, Registry, Monit

  • 本文向大家介绍edittext + listview 实现搜索listview中的内容方法(推荐),包括了edittext + listview 实现搜索listview中的内容方法(推荐)的使用技巧和注意事项,需要的朋友参考一下 主要原理:是在主界面有两个空间,一个是EditText,一个是ListView,ListView是放在EditText下面的,然后自定义建立一个adapter适配器,