写点什么

基于 ElasticSearch 实现站内全文搜索 (1)

作者:Java高工P7
  • 2021 年 11 月 11 日
  • 本文字数:5650 字

    阅读完需:约 19 分钟


3 项目架构




  • 获取数据使用 ik 分词插件

  • 将数据存储在 es 引擎中

  • 通过 es 检索方式对存储的数据进行检索

  • 使用 es 的 java 客户端提供外部服务



4 实现效果



4.1 搜索页面

简单实现一个类似百度的搜索框即可。


4.2 搜索结果页面


点击第一个搜索结果是我个人的某一篇博文,为了避免数据版权问题,笔者在 es 引擎中存放的全是个人的博客数据。另外推荐:Java进阶视频资源



5 具体代码实现



5.1 全文检索的实现对象

按照博文的基本信息定义了如下实体类,主要需要知道每一个博文的 url,通过检索出来的文章具体查看要跳转到该 url。


package?com.lbh.es.entity;


import?com.fasterxml.jackson.annotation.JsonIgnore;


import?javax.persistence.*;


/**


*?PUT?articles


*?{


*?"mappings":


*?{"properties":{


*?"author":{"type":"text"},


*?"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},


*?"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},


*?"createDate":{"type":"date","format":"yyyy-MM-dd?HH:mm:ss||yyyy-MM-dd"},


*?"url":{"type":"text"}


*?}?},


*?"settings":{


*?????"index":{


*???????"number_of_shards":1,


*???????"number_of_replicas":2


*?????}


*???}


*?}


*?---------------------------------------------------------------------------------------------------------------------


*?Copyright(c)lbhbinhao@163.com


*?@author?liubinhao


*?@date?2021/3/3


*/


@Entity


@Table(name?=?"es_article")


public?class?ArticleEntity?{


@Id


@JsonIgnore


@GeneratedValue(strategy?=?GenerationType.IDENTITY)


private?long?id;


@Column(name?=?"author")


private?String?author;


@Column(name?=?"content",columnDefinition="TEXT")


private?String?content;


@Column(name?=?"title")


private?String?title;


@Column(name?=?"createDate")


private?String?createDate;


@Column(name?=?"url")


private?String?url;


public?String?getAuthor()?{


return?author;


}


public?void?setAuthor(String?author)?{


this.author?=?author;


}


public?String?getContent()?{


return?content;


}


public?void?setContent(String?content)?{


this.content?=?content;


}


public?String?getTitle()?{


return?title;


}


public?void?setTitle(String?title)?{


this.title?=?title;


}


public?String?getCreateDate()?{


return?createDate;


}


public?void?setCreateDate(String?createDate)?{


this.createDate?=?createDate;


}


public?String?getUrl()?{


return?url;


}


public?void?setUrl(String?url)?{


this.url?=?url;


}


}

5.2 客户端配置

通过 java 配置 es 的客户端。


package?com.lbh.es.config;


import?org.apache.http.HttpHost;


import?org.elasticsearch.client.RestClient;


import?org.elasticsearch.client.RestClientBuilder;


import?org.elasticsearch.client.RestHighLevelClient;


import?org.springframework.beans.factory.annotation.Value;


import?org.springframework.context.annotation.Bean;


import?org.springframework.context.annotation.Configuration;


import?java.util.ArrayList;


import?java.util.List;


/**


*?Copyright(c)lbhbinhao@163.com


*?@author?liubinhao


*?@date?2021/3/3


*/


@Configuration


public?class?EsConfig?{


@Value("${elasticsearch.schema}")


private?String?schema;


@Value("${elasticsearch.address}")


private?String?address;


@Value("${elasticsearch.connectTimeout}")


private?int?connectTimeout;


@Value("${elasticsearch.socketTimeout}")


private?int?socketTimeout;


@Value("${elasticsearch.connectionRequestTimeout}")


private?int?tryConnTimeout;


@Value("${elasticsearch.maxConnectNum}")


private?int?maxConnNum;


@Value("${elasticsearch.maxConnectPerRoute}")


private?int?maxConnectPerRoute;


@Bean


public?RestHighLevelClient?restHighLevelClient()?{


//?拆分地址


List<HttpHost>?hostLists?=?new?ArrayList<>();


String[]?hostList?=?address.split(",");


for?(String?addr?:?hostList)?{


String?host?=?addr.split(":")[0];


String?port?=?addr.split(":")[1];


hostLists.add(new?HttpHost(host,?Integer.parseInt(port),?schema));


}


//?转换成?HttpHost?数组


HttpHost[]?httpHost?=?hostLists.toArray(new?HttpHost[]{});


//?构建连接对象


RestClientBuilder?builder?=?RestClient.builder(httpHost);


//?异步连接延时配置


builder.setRequestConfigCallback(requestConfigBuilder?->?{


requestConfigBuilder.setConnectTimeout(connectTimeout);


requestConfigBuilder.setSocketTimeout(socketTimeout);


requestConfigBuilder.setConnectionRequestTimeout(tryConnTimeout);


return?requestConfigBuilder;


});


//?异步连接数配置


builder.setHttpClientConfigCallback(httpClientBuilder?->?{


httpClientBuilder.setMaxConnTotal(maxConnNum);


httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);


return?httpClientBuilder;


});


return?new?RestHighLevelClient(builder);


}


}

5.3 业务代码编写

包括一些检索文章的信息,可以从文章标题,文章内容以及作者信息这些维度来查看相关信息。另外推荐:Java进阶视频资源


package?com.lbh.es.service;


import?com.google.gson.Gson;


import?com.lbh.es.entity.ArticleEntity;


import?com.lbh.es.repository.ArticleRepository;


import?org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;


import?org.elasticsearch.action.get.GetRequest;


import?org.elasticsearch.action.get.GetResponse;


import?org.elasticsearch.action.index.IndexRequest;


import?org.elasticsearch.action.index.IndexResponse;


import?org.el


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


asticsearch.action.search.SearchRequest;


import?org.elasticsearch.action.search.SearchResponse;


import?org.elasticsearch.action.support.master.AcknowledgedResponse;


import?org.elasticsearch.client.RequestOptions;


import?org.elasticsearch.client.RestHighLevelClient;


import?org.elasticsearch.client.indices.CreateIndexRequest;


import?org.elasticsearch.client.indices.CreateIndexResponse;


import?org.elasticsearch.common.settings.Settings;


import?org.elasticsearch.common.xcontent.XContentType;


import?org.elasticsearch.index.query.QueryBuilders;


import?org.elasticsearch.search.SearchHit;


import?org.elasticsearch.search.builder.SearchSourceBuilder;


import?org.springframework.stereotype.Service;


import?javax.annotation.Resource;


import?java.io.IOException;


import?java.util.*;


/**


*?Copyright(c)lbhbinhao@163.com


*?@author?liubinhao


*?@date?2021/3/3


*/


@Service


public?class?ArticleService?{


private?static?final?String?ARTICLE_INDEX?=?"article";


@Resource


private?RestHighLevelClient?client;


@Resource


private?ArticleRepository?articleRepository;


public?boolean?createIndexOfArticle(){


Settings?settings?=?Settings.builder()


.put("index.number_of_shards",?1)


.put("index.number_of_replicas",?1)


.build();


//?{"properties":{"author":{"type":"text"},


//?"content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}


//?,"title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"},


//?,"createDate":{"type":"date","format":"yyyy-MM-dd?HH:mm:ss||yyyy-MM-dd"}


//?}


String?mapping?=?"{"properties":{"author":{"type":"text"},\n"?+


""content":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}\n"?+


","title":{"type":"text","analyzer":"ik_max_word","search_analyzer":"ik_smart"}\n"?+


","createDate":{"type":"date","format":"yyyy-MM-dd?HH:mm:ss||yyyy-MM-dd"}\n"?+


"},"url":{"type":"text"}\n"?+


"}";


CreateIndexRequest?indexRequest?=?new?CreateIndexRequest(ARTICLE_INDEX)


.settings(settings).mapping(mapping,XContentType.JSON);


CreateIndexResponse?response?=?null;


try?{


response?=?client.indices().create(indexRequest,?RequestOptions.DEFAULT);


}?catch?(IOException?e)?{


e.printStackTrace();


}


if?(response!=null)?{


System.err.println(response.isAcknowledged()???"success"?:?"default");


return?response.isAcknowledged();


}?else?{


return?false;


}


}


public?boolean?deleteArticle(){


DeleteIndexRequest?request?=?new?DeleteIndexRequest(ARTICLE_INDEX);


try?{


AcknowledgedResponse?response?=?client.indices().delete(request,?RequestOptions.DEFAULT);


return?response.isAcknowledged();


}?catch?(IOException?e)?{


e.printStackTrace();


}


return?false;


}


public?IndexResponse?addArticle(ArticleEntity?article){


Gson?gson?=?new?Gson();


String?s?=?gson.toJson(article);


//创建索引创建对象


IndexRequest?indexRequest?=?new?IndexRequest(ARTICLE_INDEX);


//文档内容


indexRequest.source(s,XContentType.JSON);


//通过 client 进行 http 的请求


IndexResponse?re?=?null;


try?{


re?=?client.index(indexRequest,?RequestOptions.DEFAULT);


}?catch?(IOException?e)?{


e.printStackTrace();


}


return?re;


}


public?void?transferFromMysql(){


articleRepository.findAll().forEach(this::addArticle);


}


public?List<ArticleEntity>?queryByKey(String?keyword){


SearchRequest?request?=?new?SearchRequest();


/*


*?创建??搜索内容参数设置对象:SearchSourceBuilder


*?相对于 matchQuery,multiMatchQuery 针对的是多个 fi eld,也就是说,当 multiMatchQuery 中,fieldNames 参数只有一个时,其作用与 matchQuery 相当;


*?而当 fieldNames 有多个参数时,如 field1 和 field2,那查询的结果中,要么 field1 中包含 text,要么 field2 中包含 text。


*/


SearchSourceBuilder?searchSourceBuilder?=?new?SearchSourceBuilder();


searchSourceBuilder.query(QueryBuilders


.multiMatchQuery(keyword,?"author","content","title"));


request.source(searchSourceBuilder);


List<ArticleEntity>?result?=?new?ArrayList<>();


try?{


SearchResponse?search?=?client.search(request,?RequestOptions.DEFAULT);


for?(SearchHit?hit:search.getHits()){


Map<String,?Object>?map?=?hit.getSourceAsMap();


ArticleEntity?item?=?new?ArticleEntity();


item.setAuthor((String)?map.get("author"));


item.setContent((String)?map.get("content"));


item.setTitle((String)?map.get("title"));


item.setUrl((String)?map.get("url"));


result.add(item);


}


return?result;


}?catch?(IOException?e)?{


e.printStackTrace();


}


return?null;


}


public?ArticleEntity?queryById(String?indexId){


GetRequest?request?=?new?GetRequest(ARTICLE_INDEX,?indexId);


GetResponse?response?=?null;


try?{


response?=?client.get(request,?RequestOptions.DEFAULT);


}?catch?(IOException?e)?{


e.printStackTrace();


}


if?(response!=null&&response.isExists()){


Gson?gson?=?new?Gson();


return?gson.fromJson(response.getSourceAsString(),ArticleEntity.class);


}


return?null;


}


}

5.4 对外接口

和使用 springboot 开发 web 程序相同。


package?com.lbh.es.controller;


import?com.lbh.es.entity.ArticleEntity;


import?com.lbh.es.service.ArticleService;


import?org.elasticsearch.action.index.IndexResponse;


import?org.springframework.web.bind.annotation.*;


import?javax.annotation.Resource;


import?java.util.List;


/**


*?Copyright(c)lbhbinhao@163.com


*?@author?liubinhao


*?@date?2021/3/3


*/


@RestController


@RequestMapping("article")


public?class?ArticleController?{


@Resource


private?ArticleService?articleService;


@GetMapping("/create")


public?boolean?create(){


return?articleService.createIndexOfArticle();


}


@GetMapping("/delete")


public?boolean?delete()?{


return?articleService.deleteArticle();


}


@PostMapping("/add")


public?IndexResponse?add(@RequestBody?ArticleEntity?article){


return?articleService.addArticle(article);


}


@GetMapping("/fransfer")


public?String?transfer(){


articleService.transferFromMysql();


return?"successful";


}


@GetMapping("/query")


public?List<ArticleEntity>?query(String?keyword){


return?articleService.queryByKey(keyword);


}


}

5.5 页面

此处页面使用 thymeleaf,主要原因是笔者真滴不会前端,只懂一丢丢简单的 h5,就随便做了一个可以展示的页面。

搜索页面

<!DOCTYPE?html>


<html?lang="en"?xmlns:th="http://www.thymeleaf.org">


<head>


<meta?charset="UTF-8"?/>


<meta?name="viewport"?content="width=device-width,?initial-scale=1.0"?/>


<title>YiyiDu</title>


<!--


input:focus 设定当输入框被点击时,出现蓝色外边框


text-indent:?11px;和 padding-left:?11px;设定输入的字符的起始位置与左边框的距离


-->


<style>


input:focus?{


border:?2px?solid?rgb(62,?88,?206);


}


input?{


text-indent:?11px;


padding-left:?11px;


font-size:?16px;


}


</style>


<style?class="input/css">


.input?{


width:?33%;


height:?45px;


vertical-align:?top;


box-sizing:?border-box;


border:?2px?solid?rgb(207,?205,?205);


border-right:?2px?solid?rgb(62,?88,?206);


border-bottom-left-radius:?10px;


border-top-left-radius:?10px;


outline:?none;


margin:?0;


display:?inline-block;


background:?url(/static/img/camera.jpg)?no-repeat?0?0;


background-position:?565px?7px;


background-size:?28px;


padding-right:?49px;

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
基于 ElasticSearch 实现站内全文搜索(1)