Spring Boot 2.0 整合 ES 5 文章内容搜索实战
本章内容
文章内容搜索思路
搜索内容分词
搜索查询语句
筛选条件
分页、排序条件
小结
一、文章内容搜索思路
上一篇讲了在怎么在 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

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
Redis 通信协议-了解 Redis 客户端实现原理
简介 几乎所有的主流编程语言都有Redis的客户端(http://redis.io/clients),不考虑Redis非常流行的原因,如果站在技术的角度看原因还有两个: 客户端与服务端之间的通信协议是在 TCP 协议之上构建的。 客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。 客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。 Redis制定了 RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。 发送命令 RESP 在 Redis 1.2 版本中引入, 并最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式。 在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。 RESP 的规定一条命令的格式如下: *<参数数量>CRLF $<参数1的字节数量>CRLF <参数1的数据>CRLF ... $<参数N的字节数量...
-
下一篇
Zabbix 监控部署
zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案。 zabbix能监视各种网络参数,保证服务器系统的安全运营;并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。 zabbix由2部分构成,zabbix server与可选组件zabbix agent。 zabbix server可以通过SNMP,zabbix agent,ping,端口监视等方法提供对远程服务器/网络状态的监视,数据收集等功能,它可以运行在Linux, Solaris, HP-UX, AIX, Free BSD, Open BSD, OS X等平台上。 zabbix agent需要安装在被监视的目标服务器上,它主要完成对硬件信息或与操作系统有关的内存,CPU等信息的收集。zabbix agent可以运行在Linux,Solaris,HP-UX,AIX,Free BSD,Open BSD, OS X, Tru64/OSF1, Windows等系统之上。 zabbix server可以单独监视远程服务器的服务状态;同时也可以与zabbix agent配合,可以轮询zab...
相关文章
文章评论
共有0条评论来说两句吧...