Spring Boot2.0整合ES5實(shí)現(xiàn)文章內(nèi)容搜索實(shí)戰(zhàn)
一、文章內(nèi)容搜索思路
上一篇講了在怎么在 Spring Boot 2.0 上整合 ES 5 ,這一篇聊聊具體實(shí)戰(zhàn)。簡(jiǎn)單講下如何實(shí)現(xiàn)文章、問答這些內(nèi)容搜索的具體實(shí)現(xiàn)。實(shí)現(xiàn)思路很簡(jiǎn)單:
- 基于「短語匹配」并設(shè)置最小匹配權(quán)重值
- 哪來的短語,利用 IK 分詞器分詞
- 基于 Fiter 實(shí)現(xiàn)篩選
- 基于 Pageable 實(shí)現(xiàn)分頁排序
這里直接調(diào)用搜索的話,容易搜出不盡人意的東西。因?yàn)閮?nèi)容搜索關(guān)注內(nèi)容的連接性。所以這里處理方法比較 low ,希望多交流一起實(shí)現(xiàn)更好的搜索方法。就是通過分詞得到很多短語,然后利用短語進(jìn)行短語精準(zhǔn)匹配。
ES 安裝 IK 分詞器插件很簡(jiǎn)單。第一步,在下載對(duì)應(yīng)版本 https://github.com/medcl/elasticsearch-analysis-ik/releases。第二步,在 elasticsearch-5.5.3/plugins 目錄下,新建一個(gè)文件夾 ik,把 elasticsearch-analysis-ik-5.5.3.zip 解壓后的文件拷貝到 elasticsearch-5.1.1/plugins/ik 目錄下。最后重啟 ES 即可。
二、搜索內(nèi)容分詞
安裝好 IK ,如何調(diào)用呢?
第一步,我這邊搜搜內(nèi)容會(huì)以 逗號(hào) 拼接傳入。所以會(huì)先將逗號(hào)分割
第二步,在搜索詞中加入自己本身,因?yàn)橛行┰~經(jīng)過 ik 分詞后就沒了... 這是個(gè) bug
第三步,利用 AnalyzeRequestBuilder 對(duì)象獲取 IK 分詞后的返回值對(duì)象列表
第四步,優(yōu)化分詞結(jié)果,比如都為詞,則保留全部;有詞有字,則保留詞;只有字,則保留字
核心實(shí)現(xiàn)代碼如下:
/** * 搜索內(nèi)容分詞 */ protected List<String> handlingSearchContent(String searchContent) { List<String> searchTermResultList = new ArrayList<>(); // 按逗號(hào)分割,獲取搜索詞列表 List<String> searchTermList = Arrays.asList(searchContent.split(SearchConstant.STRING_TOKEN_SPLIT)); // 如果搜索詞大于 1 個(gè)字,則經(jīng)過 IK 分詞器獲取分詞結(jié)果列表 searchTermList.forEach(searchTerm -> { // 搜索詞 TAG 本身加入搜索詞列表,并解決 will 這種問題 searchTermResultList.add(searchTerm); // 獲取搜索詞 IK 分詞列表 searchTermResultList.addAll(getIkAnalyzeSearchTerms(searchTerm)); }); return searchTermResultList; } /** * 調(diào)用 ES 獲取 IK 分詞后結(jié)果 */ 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(); // 循環(huán)賦值 List<String> searchTermList = new ArrayList<>(); ikTokenList.forEach(ikToken -> { searchTermList.add(ikToken.getTerm()); }); return handlingIkResultTerms(searchTermList); } /** * 如果分詞結(jié)果:洗發(fā)水(洗發(fā)、發(fā)水、洗、發(fā)、水) * - 均為詞,保留 * - 詞 + 字,只保留詞 * - 均為字,保留字 */ 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; }
三、搜索查詢語句
構(gòu)造內(nèi)容枚舉對(duì)象,羅列需要搜索的字段,ContentSearchTermEnum 代碼如下:
import lombok.AllArgsConstructor; @AllArgsConstructor public enum ContentSearchTermEnum { // 標(biāo)題 TITLE("title"), // 內(nèi)容 CONTENT("content"); /** * 搜索字段 */ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
循環(huán)進(jìn)行「短語搜索匹配」搜索字段,然后并設(shè)置最低權(quán)重值為 1。核心代碼如下:
/** * 構(gòu)造查詢條件 */ 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); }
四、篩選條件
搜到東西不止,有時(shí)候需求是這樣的。需要在某個(gè)品類下搜索,比如電商需要在某個(gè) 品牌 下搜索商品。那么需要構(gòu)造一些 fitler 進(jìn)行篩選。對(duì)應(yīng) SQL 語句的 Where 下的 OR 和 AND 兩種語句。在 ES 中使用 filter 方法添加過濾。代碼如下:
/** * 構(gòu)建篩選條件 */ private void buildFilterQuery(BoolQueryBuilder boolQueryBuilder, Integer type, String category) { // 內(nèi)容類型篩選 if (type != null) { BoolQueryBuilder typeFilterBuilder = QueryBuilders.boolQuery(); typeFilterBuilder.should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME, type).lenient(true)); boolQueryBuilder.filter(typeFilterBuilder); } // 內(nèi)容類別篩選 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 中搜索呢?具體實(shí)現(xiàn)代碼很簡(jiǎn)單:
typeFilterBuilder .should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME, 1) .should(QueryBuilders.matchQuery(SearchConstant.TYPE_NAME, 2) .lenient(true));
通過鏈?zhǔn)奖磉_(dá)式,兩個(gè) should 實(shí)現(xiàn)或,即 SQL 對(duì)應(yīng)的 OR 語句。通過兩個(gè) BoolQueryBuilder 實(shí)現(xiàn)與,即 SQL 對(duì)應(yīng)的 AND 語句。
五、分頁、排序條件
分頁排序代碼就很簡(jiǎn)單了:
@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); // 構(gòu)建搜索短語 String searchContent = contentSearchBean.getSearchContent(); List<String> searchTermList = handlingSearchContent(searchContent); // 構(gòu)建查詢條件 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); buildMatchQuery(boolQueryBuilder, searchTermList); // 構(gòu)建篩選條件 buildFilterQuery(boolQueryBuilder, contentSearchBean.getType(), contentSearchBean.getCategory()); // 構(gòu)建分頁、排序條件 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 對(duì)象,構(gòu)造分頁參數(shù)以及指定對(duì)應(yīng)的 排序字段、排序順序(DESC ASC)即可。
六、小結(jié)
這個(gè)思路比較簡(jiǎn)單。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
spring validation多層對(duì)象校驗(yàn)教程
這篇文章主要介紹了spring validation多層對(duì)象校驗(yàn)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10基于JAVA代碼 獲取手機(jī)基本信息(本機(jī)號(hào)碼,SDK版本,系統(tǒng)版本,手機(jī)型號(hào))
本文給大家介紹基于java代碼獲取手機(jī)基本信息,包括獲取電話管理對(duì)象、獲取手機(jī)號(hào)碼、獲取手機(jī)型號(hào)、獲取SDK版本、獲取系統(tǒng)版本等相關(guān)信息,對(duì)本文感興趣的朋友一起學(xué)習(xí)吧2015-12-12

SpringCloud?微服務(wù)數(shù)據(jù)權(quán)限控制的實(shí)現(xiàn)

Java 實(shí)現(xiàn)RSA非對(duì)稱加密算法

Java中ByteBuffer的allocate方法 和allocateDirect方法的區(qū)別和選用原則解析

Servlet實(shí)現(xiàn)統(tǒng)計(jì)頁面訪問次數(shù)功能