基于 ElasticSearch 实现站内全文搜索 (1)
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
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;
评论