1. 背景
Lucene 是一套信息检索工具包(jar包),不包含搜索引擎系统。包含:索引结构,排序,搜索规则…
ElasticSearch 基于 Lucene库 做了一些封装和增强,是一个分布式全文搜索引擎(全文搜索、结构化搜索、分析),可以通过简单的RESTful API来隐藏Lucene的复杂性,从而让全局搜索变得更简单。
2.ElasticSearch安装
- JDK1.8,最低要求
bin 启动文件
config 配置文件
log4j2.properties 日志配置文件
jvm.options java 虚拟机相关配置
elasticsearch.yml elasticsearch 的配置文件!默认9200端口
- ES默认端口9200,API调用默认9300
- Logstash默认端口5044
- Kibana默认端口5601
head当作数据展示的工具,所有的查询都在Kibana里做
ELK的关系
3. ES核心概念
- 索引
- 类型(mapping)
- 文档(documents)
Relational DB | ElasticSearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | 类型(types) |
行(rows) | 文档(documents) |
字段(columns) | fields |
- 物理设计:ES在后台把每个索引划分成多个分片,每个分片可以在集群中的不同服务器间迁移
- 逻辑设计:索引一篇文档时,通过顺序找到它:索引 👉 类型 👉 文档ID
- 分片:倒排索引(inverted index)
- 常规索引是文档到关键词的映射:文档 ——> 关键词
- 倒排索引是关键词到文档的映射:关键词 ——> 文档
这样只要有关键词,立马就能找到她在那个文档里出现过,剩下的事就是把它揪出来了
4. ElasticSearch启动
应用 | 打开方式 |
---|---|
ElasticSearch | 打开elasticsearch.bat |
head插件 | 在E:/EssentialSoftware/Elasticsearch-Head下执行grunt server |
Logstash | 在E:/EssentialSoftware/Logstash-7.7.0/bin下执行logstash -f logstash.conf |
Kibana | 打开kibana.bat |
- 开发工具使用Kibana,http://localhost:5601
5. ik分词器
5.1 ik_max_word 和 ik_smart
-
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
-
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase Query。
5.2 查看ES下安装的插件
5.3 添加自己的字典
-
在 E:\EssentialSoftware\Elasticsearch-7.7.0\plugins\ik\config 路径下新建mydir.dic
-
在mydir.dic里写入自己的词语
-
打开路径:E:\EssentialSoftware\Elasticsearch-7.7.0\plugins\ik\config\IKAnalyzer.cfg.xml ,在指定位置写入mydir.dic
- 重启ES
6. Rest风格
一种软件架构风格,不是硬性的标准。它主要用于客户端和服务器端交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
- 基本的Rest命令:
method | url | description |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost:9200/索引名称/类型名称/文档id | 查询文档 |
POST | localhost:9200/索引名称/类型名称/文档id/_search | 查询文档中的所有数据 |
6.1 关于文档的操作
6.1 创建一个文档 PUT
PUT /test1/type1/doc1
{
"name": "大海的对面都是敌人",
"age": 3
}
6.2 查询文档 GET
- 如果自己的文档字段没有指定,那么 ES 就会配置默认的字段类型
通过GET _cat/命令,可获得 ES 的很多信息
GET _cat/health
GET _cat/indices
6.3 修改文档 POST
POST /test1/type1/doc1/_update
{
"doc": {
"name": "但是我也要去"
}
}
6.4 查询文档
GET test1/type1/_search?q=name:一舫
6.5 花式查询文档
6.5.1 查询
- 查询文档中带“但是我”的字段
GET test1/type1/_search
{
"query": {
"match": {
"name": "但是我"
}
}
}
6.5.2 过滤字段
- 过滤掉"age"这个字段
GET test1/type1/_search
{
"query": {
"match": {
"name": "舫"
}
},
"_source": ["age"]
}
6.5.3 排序字段
- 对"age"字段,用降序方式进行排序
GET test1/type1/_search
{
"query": {
"match": {
"name": "舫"
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
6.5.4 分页查询
- "from"从第几个数据开始,"size"返回多少条数据(单页面的数据)
GET test1/type1/_search
{
"query": {
"match": {
"name": "舫"
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from": 0,
"size": 2
}
6.5.5 布尔值查询
- must(相当于and)
GET test1/type1/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"name": "舫"
}
},
{
"match": {
"age": 23
}
}
]
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from": 0,
"size": 2
}
- should(相当于or)
GET test1/type1/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"name": "舫"
}
},
{
"match": {
"age": 23
}
}
]
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from": 0,
"size": 2
}
- must_not(相等于not)
GET test1/type1/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"name": "舫"
}
}
]
}
},
"sort": [
{
"age": {
"order": "desc"
}
}
],
"from": 0,
"size": 2
}
6.5.6 高级过滤字段
GET test1/type1/_search
{
"query": {
"bool": {
"must_not": [
{
"match": {
"name": "舫"
}
}
],
"filter": {
"range": {
"age": {
"gte": 10,
"lte": 30
}
}
}
}
}
}
6.5.7 短语查询
GET /test1/_search
{
"query": {
"match": {
"hobby": "自由 跑步"
}
}
}
6.5.8 精确查询
6.2 常见的数据类型
6.3 关于索引的操作
6.3.1 创建一个索引
PUT /test2
{
"mappings": {
"properties": {
"name": {
"type": "text"
},
"age": {
"type": "long"
},
"birthday": {
"type": "date"
}
}
}
}
7. ES 在 Spring Boot 中创建索引与文档
7.1 官方文档
7.2 配置基本项目依赖
- 新建项目,选择导入的依赖
- 配置ES的依赖
<properties>
<java.version>1.8</java.version>
<!-- 这里SpringBoot默认配置的版本不匹配,我们需要自己配置版本!我这里使用7.7.0 -->
<elasticsearch.version>7.7.0</elasticsearch.version>
</properties>
- 我们编写一个配置类,提供这个bean来进行操作
package com.example.esdemo.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticSearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
return client;
}
}
7.3 常用方法
所需要的头文件和包
package com.example.esdemo;
import com.alibaba.fastjson.JSON;
import com.example.esdemo.pojo.User;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
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.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
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.client.indices.GetIndexRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class EsDemoApplicationTests {
@Autowired
@Qualifier("restHighLevelClient")
RestHighLevelClient client;
}
7.3.1 创建索引
@Test
void testGreateIndex() throws IOException {
// 1、创建索引请求
CreateIndexRequest request = new CreateIndexRequest("yifang_index");
// 2、客户端执行请求 IndicesClient,请求后获得响应
CreateIndexResponse createIndexResponse =
client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
7.3.2 获取索引
@Test
void testExistIndex() throws IOException {
GetIndexRequest request = new GetIndexRequest("yifang_index");
boolean exists =
client.indices().exists(request, RequestOptions.DEFAULT);
System.out.println(exists);
}
7.3.3 删除索引
@Test
void testDeleteIndex() throws IOException {
DeleteIndexRequest request = new DeleteIndexRequest("yifang_index");
AcknowledgedResponse delete =
client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
在POJO下创建User类
package com.example.esdemo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@Component
@NoArgsConstructor
public class User {
private String name;
private int age;
}
7.3.4 添加文档
@Test
void testAddDocment() throws IOException {
// 创建对象
User user = new User("xuyifang", 22);
// 创建请求
IndexRequest request = new IndexRequest("yifang_index");
// 规则
request.id("1");
request.timeout("1s");
// 将我们的数据放入请求 json
request.source(JSON.toJSONString(user), XContentType.JSON);
// 客户端发送请求
IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString());
System.out.println(indexResponse.status());
}
7.3.5 获取文档信息,判断文档是否存在
@Test
void testIsExist() throws IOException {
GetRequest getRequest = new GetRequest("yifang_index", "1");
// 不获取返回的 _source 的上下文
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
// 判断文档是否存在
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists);
}
7.3.6 更新文档信息
@Test
void testUpdateDocment() throws IOException {
UpdateRequest updateRequest = new UpdateRequest("yifang_index", "1");
updateRequest.timeout("1s");
User user = new User("xuyifang", 22);
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
7.3.7 删除文档信息
@Test
void testDeleteDocment() throws IOException {
DeleteRequest deleteRequest = new DeleteRequest("yifang_index", "1"); // index, id
deleteRequest.timeout("1s");
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
7.3.8 批量插入文档信息
@Test
void testBulkDocment() throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> userArrayList = new ArrayList<>();
userArrayList.add(new User("Java 为什么搜不出来", 123));
userArrayList.add(new User("Java 我真的搞不懂", 23));
userArrayList.add(new User("Java 我真的搞不懂啊啊啊啊", 233));
userArrayList.add(new User("Java 终于能搜出来了", 253));
// 批处理请求,生成指定的id
// for (int i = 0; i < userArrayList.size(); i++) {
// bulkRequest.add(
// new IndexRequest("yifang_index")
// .id("" + (i+1))
// .source(JSON.toJSONString(userArrayList.get(i)), XContentType.JSON)
// );
// }
// 不指定id会生成随机的id
for (int i = 0; i < userArrayList.size(); i++) {
bulkRequest.add(
new IndexRequest("yifang_index")
.source(JSON.toJSONString(userArrayList.get(i)), XContentType.JSON)
);
}
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures()); // 返回false,代表成功
}
7.3.9 查询文档
@Test
void testSearchDocment() throws IOException {
SearchRequest searchRequest = new SearchRequest("yifang_index");
// 构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 精准匹配
TermQueryBuilder queryBuilder = QueryBuilders.termQuery("name", "java");
searchSourceBuilder.query(queryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 多项精准匹配
// TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", "java", "ava");
// 匹配所有
// MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
// 匹配一部分
// MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "java");
// 模糊匹配
// FuzzyQueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("name", "java");
// 模糊匹配(不知有何区别)
// WildcardQueryBuilder queryBuilder = QueryBuilders.wildcardQuery("name", "*av*");
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
System.out.println("=======================================================");
for (SearchHit documentFields: searchResponse.getHits().getHits()){
System.out.println(JSON.toJSONString(documentFields.getSourceAsMap()));
}
}
8. 项目实战
8.1 初始化项目
- 启动 ES 和 ES head 服务,见第4章
- 使用springboot快速构建服务,比之前多个Thymeleaf
- 修改依赖
pom.xml
<properties>
<java.version>1.8</java.version>
<!-- 修改elasticsearch的版本 -->
<elasticsearch.version>7.7.0</elasticsearch.version>
</properties>
<dependencies>
<!-- jSoup解析网页 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.38</version>
</dependency>
<dependencies>
- 配置
application.properties
server.port=9090
# 关闭 thymeleaf 的缓存
spring.thymeleaf.cache=false
- 导入前端素材
<html xmlns:th="http://www.thymeleaf.org">
- 编写IndexController进行跳转测试
package com.example.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
// http://localhost:9090/ 与 http://localhost:9090/index 都能访问
@GetMapping({"/", "/index"})
public String index() {
return "index";
}
}
8.2 Jsoup
- 在
pom.xml
中导入 Jsoup依赖
<!-- jSoup解析网页 -->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
- 编写一个工具类
HtmlParseUtil.java
package com.example.utils;
import com.example.pojo.Content;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
@Component
public class HtmlParseUtil {
public ArrayList<Content> parseJD(String keywords) throws IOException {
String key = URLEncoder.encode(keywords, "utf-8");
String url = "https://search.jd.com/Search?keyword=" + key;
Document document = Jsoup.parse(new URL(url), 30000);
Element element = document.getElementById("J_goodsList");
Elements elements = element.getElementsByTag("li");
ArrayList<Content> goodsList = new ArrayList<>();
for (Element el: elements) {
String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
String price = el.getElementsByClass("p-price").eq(0).text();
String title = el.getElementsByClass("p-name").eq(0).text();
Content content = new Content();
content.setImg(img);
content.setPrice(price);
content.setTitle(title);
goodsList.add(content);
}
return goodsList;
}
}
- 封装一个实体类
Content.java
保存爬取下来的数据
package com.example.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Content {
private String img;
private String price;
private String title;
// 可以自己添加属性
}
8.3 业务编写
- 导入
ElasticsearchClientConfig.java
配置类
package com.example.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ElasticSearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient() {
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
return client;
}
}
- 编写业务
ContentService.java
package com.example.service;
import com.alibaba.fastjson.JSON;
import com.example.pojo.Content;
import com.example.utils.HtmlParseUtil;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class ContentService {
@Autowired
private RestHighLevelClient restHighLevelClient;
// 1、解析数据放入 es 索引中
public Boolean parseContent(String keywords) throws IOException {
List<Content> contents = new HtmlParseUtil().parseJD(keywords);
// 把查询到的数据放入 es 中
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("2m");
for (int i = 0; i < contents.size(); i++) {
bulkRequest.add(
new IndexRequest("jd_goods")
.source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
);
}
BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures();
}
// 2、获取这些数据实现搜索功能
public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException {
if (pageNo <= 1) {
pageNo = 1;
}
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 分页
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
// 精准匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 执行搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit documentFields: searchResponse.getHits().getHits()) {
list.add(documentFields.getSourceAsMap());
}
return list;
}
}
Controller.java
package com.example.controller;
import com.example.service.ContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
import java.util.Map;
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
// http://localhost:9090/parse/java
@GetMapping("/parse/{keyword}")
public boolean parse (@PathVariable("keyword") String keyword) throws IOException {
return contentService.parseContent(keyword);
}
// http://localhost:9090/search/java/1/10
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize) throws IOException {
return contentService.searchPage(keyword, pageNo, pageSize);
}
}
8.4 前端逻辑
index.html
- 导入vue和axios的依赖,这里导入后无法识别,重启IDEA就好了
<script th:src="@{/js/axios.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
- 初始化Vue对象,给外层div绑定app对象!
<script>
new Vue({
el:'#app',
data:{
keyword: '', // 搜索的关键字
results: [] // 搜索的结果
}
})
</script>
- 绑定搜索框及相关事件
- 编写方法,获取后端传递的数据
<script>
new Vue({
el:'#app',
data:{
keyword: '', // 搜索的关键字
results: [] // 搜索的结果
},
methods:{
searchKey() {
var keyword = this.keyword;
console.log(keyword);
// 对接后端的接口
axios.get('search/' + keyword + "/1/50").then(response=>{
console.log(response);
this.results = response.data; // 绑定数据!
})
}
}
})
</script>
- 渲染解析回来的数据!
8.5 搜索高亮
- 编写业务类,处理高亮字段
ContentService.java
// 3、获取这些数据实现搜索高亮功能
public List<Map<String, Object>> searchHighlightPage(String keyword, int pageNo, int pageSize) throws IOException {
if (pageNo <= 1) {
pageNo = 1;
}
// 条件搜索
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 分页
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
// 精准匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
searchSourceBuilder.query(termQueryBuilder);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
searchSourceBuilder.highlighter(highlightBuilder);
// 执行搜索
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
ArrayList<Map<String, Object>> list = new ArrayList<>();
for (SearchHit hit: searchResponse.getHits().getHits()) {
// 解析高亮的字段
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
HighlightField title = highlightFields.get("title");
Map<String, Object> sourceAsMap = hit.getSourceAsMap(); // 原来的结果
// 将原来的字段替换成我们的高亮字段即可
if (title != null) {
Text[] fragments = title.fragments();
String newTitle = "";
for (Text text : fragments) {
newTitle += text;
}
sourceAsMap.put("title", newTitle); // 原来的字段替换成我们的高亮字段
}
list.add(sourceAsMap);
}
return list;
}
- controller
ContentController.java
@GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
@PathVariable("pageNo") int pageNo,
@PathVariable("pageSize") int pageSize) throws IOException {
return contentService.searchHighlightPage(keyword, pageNo, pageSize);
}
- vue解析html
index.html
<!--标题-->
<p class="productTitle">
<a v-html="result.title"></a>
</p>
- 最终效果
感谢狂神说ElasticSearch!!!
狂神说ElasticSearch