Elasticsearch安装
声明:JDK1.8, 最低要求 , Elasticsearch客户端,界面工具!
Java开发,elasticsearch的版本和我们之后对应的Java的核心jar包! 版本对应! JDK环境是正常的
这里一定要保证
下载
一定要在服务器上面搭建
下载地址:https://www.elastic.co/cn/downloads/elasticsearch
官网下载巨慢,翻墙,网盘中下载即可
华为云: https://mirrors.huaweicloud.com/elasticsearch/7.6.2/
我们学习的话Window和Linux都可以学习 我们这里现在window下学习
ELK三剑客,解压即用!(web项目! 前端环境! npm 下载依赖)
Node.js python2
window下安装!
elasticSearch
直接解压即可,访问端口:9200
elasticSearch Head:
解压后在config/elasticsearch.yml中添加解决跨域问题:
http.cors.enabled: true
http.cors.allow-origin: “*”
在安装目录下cmd:安装并启动
cnpm install
npm run start
访问端口:9100
索引-新增索引-添加:把es当作数据库(可以建立索引(库),文档(库中的数据!))
这个head把它当作数据展示工具,后面靠kibana做查询操作
kibana
直接解压
访问端口:5601
之后的所有操作都在这操作
汉化
重启
ES核心概念理解
1、索引
2、字段类型(mapping)
3、文档(document)
概述
在前面的学习中,我们掌握了es是什么,同时也把es的服务已经安装启动,那么es是如何去存储数据,数据结构是什么,又是如何实现搜索的呢?我们先来聊聊Elasticsearch的相关概念吧!
集群,节点,索引,类型,文档,分片,映射是什么
elasticsearch是面向文档的,关系行数据库和elasticsearch客观的对比! 一切都是json
RelationalDB | Elasticsearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | 类型(types)(7版本以及之后会被抛弃,默认_doc) |
行(rows) | 文档(documents) |
字段(columns) | fields |
面向文档 面向文档 面向文档 ~
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型先又包含多个文档(行),每个文档中又包含多个字段(列).
物理设计
elasticsearch在后台吧每个索引划分成多个分片,每个分片可以在集群中的不同服务器间迁移
一个人就是一个集群,默认的集群名就是elasticSearch
逻辑设计
一个索引类型中,包含多个文档,比如所文档1,文档2.当我们索引一篇文章时,可以通过这样的一各顺序找到它:索引>类型
文档id,通过这个组合我们就能索引到某个具体的文档. 注意: id不必是整数,实际上他是一个字符串.
user | name | age |
---|---|---|
1 | zhasna | 18 |
2 | kaugshen | 23 |
3 |
之前说elasticsearch是面向文档的,name就也为这索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:
-
自我包含,一篇文档同时包含字段和对应值,也就是同时包含key:value!
-
可以是层次型的,一个文档中包含文档,复杂的逻辑实体就是这么来的!
-
灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在elasticsearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段.
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整型.因为elasticsearch会保存字段和类型之间的映射及其他的设置.这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型.
类型
类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器.类型中对于字段的定义称为映射,比如name映射为字符串类型.我们说文档是无模式的,他们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么elasticsearch是怎么做的呢?
elasticsearch会自动的将新的字段加入映射,但是这个字段的不确定它是什么类型,elasticsearch就开始猜,如果这个值是18,那么elasticsearch会认为他是整型.但是elasticsearch也可能猜不对,所有最安全的方式就是提前定义好所需要的映射,这点跟关系型数据库殊途同归了,先定义好字段,然后在使用,别整什么幺蛾子.
索引
就是数据库
索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合.索引存储了映射类型字段和其他设置,然后他们呗存储到了各个分片上了.我们来研究下分片是如何工作的.
物理设计: 节点和分片 如何工作
一个集群至少要有一个节点,儿一个节点就是一个elasticsearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有5个分片(primary shard,又称主分片) 构成的,每个主分片会有一个副本(replica shard,又称复制分片)
倒排索引
elasticsearch使用的是一种称为倒排索引的结构,采用Lucene倒排索引作为底层.这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表.例如,现在有两个文档,每个文档包含如下内容.
为了创建倒排索引,我们首先要将每个文档拆分成独立的词(或称为词条或者tokens),然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:
两个文档都匹配,但是第一个文档比第二个匹配程度更高.如果没有别的条件,现在,这两个包含关键字的文档都将返回.
term | doc_1 | doc_2 |
---|---|---|
to | √ | × |
forever | √ | √ |
total | 2 | 1 |
再来看一个示例,比如我们通过博客标签来搜索博客文章.那么倒排索引列表就是这样的一个结构:
如果要搜索含有python标签的文章,那相对查找所有原始数据而言,查找倒排索引后的数据将会快的多.只需要查看标签这一栏,然后获取相关文章id即可.
IK分词器插件
什么是分词器
如果使用中文,建议使用ik分词器
IK体用了两个分词算法:ik_smart和,其中ik_smart为最少(ˉ▽ ̄~) 切~~分,ik_max_word为最细粒度划分! 一会我们测试
安装
1、https://github.com/medcl/elasticsearch-analysis-ik,版本需要和ElasticSearch版本对应
2、下载完毕之后,放入到我们的elasticsearch插件即可
2.1、重启观察ES
2.2、elasticsearch-plugin 可以通过这个命令来查看加载的插件
2.3、使用kibana测试!
查看不同的分词器效果
ik分词器增加我们自己的配置
这种自己需要的词,需要自己加到我们的分词器字典中!
重启ES,看细节
再次测试一下狂神说,
以后的话我们需要自己配置自己的词,只需要在自定义的dic文件中进行配置即可!
Rest风格说明
一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制.
基本Rest风格命令说明
method | url地址 | 描述 |
---|---|---|
PUT | localhost:9200/索引名称/类型名称/文档id | 创建文档(指定id) |
POST | localhost:9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost:9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost:9200/索引名称/类型名称/文档id | 删除索引 |
GET | localhost:9200/索引名称/类型名称/文档id | 查询文档通过文档id |
POST | localhost:9200/索引名称/类型名称/_search | 查询所有数据 |
基础测试
关于索引的基本操作
索引indices
映射mapping
那么name这个字段用不用指定类型呢,毕竟我们关系型数据库 是需要指定类型映射的啊
1、指定字段的类型
创建规则
获得这个规则! 可通过GET请求获取具体的信息
2、不给索引设置mapping的话,ElasticSearch会默认匹配;如果自己的文档字段没有指定,那么ES就会给我们配置字段类型
关于文档的基本操作
https://www.bilibili.com/video/BV17a4y1x7zq?p=10
基本操作
添加数据
获取数据
更新数据 PUT,更新时是覆盖更新,没有更新的字段会被覆盖为空!
Post _update,推荐使用这种更新方式!
简单地搜索!
简单的条件查询,可以根据默认的映射规则,产生基本的查询
复杂操作搜索
select(排序,分页,高亮,模糊查询,精准查询! )
输出结果不想要那么多,select *
现在是select name,age
可以指定字段
结果的过滤
我们之后使用Java操作ES,所有的方法和对象就是这里面的key!
排序
分页查询
数据下标还是从0开始,和之前所学的数据结构都是一样的!
布尔值查询
must(and) ,所有条件都要符合 where id=1 and name=xxx
should (or) ,所有条件都要符合 where id=1 or name = xxx
must not (not)
过滤器 filter
- gt 大于
gte 大于等于
lt 小于
lte 小于等于
匹配多个条件
用空格分隔也行
精确查询
term 查询时直接通过倒排索引指定的词条进程精确查找的!
关于分词:
term,直接查询精确的
match,会使用分词器解析! (先分析分档,然后再通过分析的分档进行查询! )
两个类型 text keyword
多个值匹配精确查询
高亮查询!
搜索的高亮条件,会在HTML里面自动的加上标签
集成SpringBoot
找文档!
https://proxies.app.aidoru.net/-----https://www.elastic.co/guide/index.html
1、找到原生的依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.6.2</version>
</dependency>
2、找对象
3 、分析这个类中的方法即可
配置基本的项目
问题:一定要保证我们导入的依赖和我们的ES版本一致
按照官网的操作我们要构建一个对象
分析源码
狂神的Spring步骤:
1、找对象
2、放到spring中待用
3、如果是springboot,那就先分析源码
xxxAutoConfiguration,xxxProperties
源码中提供的对象
虽然这里导入了3个类,静态内部类,核心类就一个
关于索引的API操作详解
具体的Api测试!
restHighLevelClient.indices().xxx()
1、创建索引
//测试索引的创建 Request PUT axiang_index
@Test
void testCreateIndex() throws IOException {
//1、创建索引请求
CreateIndexRequest request = new CreateIndexRequest("axiang_index");
//2、客户端执行请求 IndicesClient,请求后获得相应
CreateIndexResponse createIndexResponse = client.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
2、判断索引是否存在
//测试获取索引
@Test
void testExistIndex() throws IOException{
GetIndexRequest request = new GetIndexRequest("axiang_index");
boolean exists = client.indices().exists(request,RequestOptions.DEFAULT);
System.out.println(exists);
}
3、删除索引
//测试删除索引
@Test
void testDeleteIndex() throws IOException{
DeleteIndexRequest request = new DeleteIndexRequest("axiang_index");
AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
关于文档的API操作详解
1、创建文档
//测试添加文档
@Test
void testAddDocument() throws IOException{
//创建对象
User user = new User("阿翔", 21);
//创建请求
IndexRequest request = new IndexRequest("axiang_index");
//规则 put/axiang_index/doc_1
request.id("1");
request.timeout(TimeValue.timeValueSeconds(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());
}
2、获取文档
//获取文档,判断是否存在 get/index/_doc/1
@Test
void testExists() throws IOException{
GetRequest getRequest = new GetRequest("axiang_index", "1");
//不获取返回的_source的上下文
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
boolean exists = client.exists(getRequest,RequestOptions.DEFAULT);
System.out.println(exists);
}
3、获取文档信息
//获取文档信息
@Test
void testGetDocument() throws IOException{
GetRequest getRequest = new GetRequest("axiang_index", "1");
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString());//打印文档内容
System.out.println(getResponse);
}
4、更新文档信息
//更新文档信息
//POST /test/_doc/1/update
@Test
void testUpdateRequest() throws IOException{
UpdateRequest updateRequest = new UpdateRequest("axiang_index", "1");
updateRequest.timeout("1s");
User user = new User("咩咩哦", 18);
updateRequest.doc(JSON.toJSONString(user),XContentType.JSON);
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.status());
}
5、删除文档记录
//删除文档记录
@Test
void testDeleteRequest() throws IOException{
DeleteRequest request = new DeleteRequest("axiang_index", "1");
request.timeout("1s");
DeleteResponse deleteResponse = client.delete(request, RequestOptions.DEFAULT);
System.out.println(deleteResponse.status());
}
6、批量插入数据
//批量插入数据
@Test
void testBulkRequest() throws IOException{
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("10s");
ArrayList<User> userList = new ArrayList<>();
userList.add(new User("aa",3));
userList.add(new User("bb",4));
userList.add(new User("cc",5));
userList.add(new User("dd",6));
userList.add(new User("ee",7));
userList.add(new User("ff",8));
//批处理请求
for (int i = 0; i < userList.size();i++){
//批量更新和批量删除,在此处修改对应的请求就可以了
bulkRequest.add(
new IndexRequest("axiang_index")
.id(""+(i+1))//不设置的话,会生成随机id
.source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
}
BulkResponse bulkResponse = client.bulk(bulkRequest,RequestOptions.DEFAULT);
System.out.println(bulkResponse.hasFailures());//是否执行成功 返回false代表成功
}
7、花式查询
//查询
//SearchRequest构造搜索请求
//SearchSourceBuilder构造搜索条件
//HighLightBuilder构建高亮
//TermQueryBuilder精确查询
//MatchAllQueryBuilder查询所有
// xxx QueryBuilder对应下面的命令
@Test
void testSearch() throws IOException {
//构造搜索请求
// SearchRequest searchRequest = new SearchRequest(ESconst.ES_INDEX);//去utils中调用
SearchRequest searchRequest = new SearchRequest("axiang_index");
//构造搜索条件
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//查询条件,用QueryBuilders工具实现
//精确查询:QueryBuilders.termQuery
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "aa");
//查询所有:QueryBuilders.matchAllQuery
// MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchRequest.source(sourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits()));
for (SearchHit documentFields : searchResponse.getHits().getHits()){
System.out.println(documentFields.getSourceAsMap());
}
}
实战
爬取数据
1、导入静态资源
2、导入所需的依赖
<dependencies>
<!-- 解析网页jsoup-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.10.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
3、编写控制类使页面跳转到index.html:IndexController.java
@Controller
public class IndexController {
@GetMapping({"/","index"})
public String index(){
return "index";
}
}
4、封装实体类:Content.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Content {
private String title;
private String img;
private String price;
}
5、封装爬取数据工具类:HtmlParseUtil.java
@Component
public class HtmlParseUtil {
public List<Content> parseJD(String keywords) throws Exception {
//获取请求
String url = "https://search.jd.com/Search?keyword="+keywords;
//解析网页
Document document = Jsoup.parse(new URL(url), 30000);
Element element = document.getElementById("J_goodsList");
//获取所有的li元素
Elements elements = element.getElementsByTag("li");
ArrayList<Content> goodsList = new ArrayList<>();
//获取元素中的内容,这里el,就是每一个li标签
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;
}
}
业务编写
编写的配置类:ElasticSearchClientConfig.java
@Configuration
public class ElasticSearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1",9200,"http")
)
);
return client;
}
}
业务编写:ContentService.java
//业务编写
@Service
public class ContentService {
@Autowired
private RestHighLevelClient restHighLevelClient;
//1、解析数据放入es索引中
public Boolean parseContent(String keywords) throws Exception{
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 sourceBuilder = new SearchSourceBuilder();
//分页
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
//精确匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//执行搜索
searchRequest.source(sourceBuilder);
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;
}
}
请求编写:ContentController.java
//请求编写
@RestController
public class ContentController {
@Autowired
private ContentService contentService;
@GetMapping("/parse/{keyword}")
public Boolean parse(@PathVariable("keyword") String keyword) throws Exception{
return contentService.parseContent(keyword);
}
@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);
}
}
前后端交互
1、在桌面随便建个文件夹(文件名不能是中文)
2、进入文件夹中cmd,输入命令:1、npm init 2、npm install vue 3、npm install axios
3、进入文件夹
C:\Users\Administrator\Desktop\a\node_modules\vue\dist和
C:\Users\Administrator\Desktop\a\node_modules\axios\dist
拷贝
vue.min.js和axios.min.js
到项目的
src/main/resources/static/js中
4、删除index.html的jquery的js
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EvwpWNfB-1622082347932)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210320114214136.png)]
5、导入axios和vue的js
<!--前端使用vue,实现前后端分离-->
<script th:src="@{/js/axios.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
6、对index.html实现前后端互动
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>狂神说Java-ES仿京东实战</title>
<link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>
<body class="pg">
<div class="page" id="app">
<div id="mallPage" class=" mallist tmall- page-not-market ">
<!-- 头部搜索 -->
<div id="header" class=" header-list-app">
<div class="headerLayout">
<div class="headerCon ">
<!-- Logo-->
<h1 id="mallLogo">
<img th:src="@{/images/jdlogo.png}" alt="">
</h1>
<div class="header-extra">
<!--搜索-->
<div id="mallSearch" class="mall-search">
<form name="searchTop" class="mallSearch-form clearfix">
<fieldset>
<legend>天猫搜索</legend>
<div class="mallSearch-input clearfix">
<div class="s-combobox" id="s-combobox-685">
<div class="s-combobox-input-wrap">
<input v-model="keyword" type="text" autocomplete="off" value="dd" id="mq"
class="s-combobox-input" aria-haspopup="true">
</div>
</div>
<button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
</div>
</fieldset>
</form>
<ul class="relKeyTop">
<li><a>狂神说Java</a></li>
<li><a>狂神说前端</a></li>
<li><a>狂神说Linux</a></li>
<li><a>狂神说大数据</a></li>
<li><a>狂神聊理财</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- 商品详情页面 -->
<div id="content">
<div class="main">
<!-- 品牌分类 -->
<form class="navAttrsForm">
<div class="attrs j_NavAttrs" style="display:block">
<div class="brandAttr j_nav_brand">
<div class="j_Brand attr">
<div class="attrKey">
品牌
</div>
<div class="attrValues">
<ul class="av-collapse row-2">
<li><a href="#"> 狂神说 </a></li>
<li><a href="#"> Java </a></li>
</ul>
</div>
</div>
</div>
</div>
</form>
<!-- 排序规则 -->
<div class="filter clearfix">
<a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
<a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
<a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
<a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
<a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
</div>
<!-- 商品详情 -->
<div class="view grid-nosku">
<div class="product" v-for="result in results">
<div class="product-iWrap">
<!--商品封面-->
<div class="productImg-wrap">
<a class="productImg">
<img :src="result.img">
</a>
</div>
<!--价格-->
<p class="productPrice">
<em>{{result.price}}</em>
</p>
<!--标题-->
<p class="productTitle">
<a>{{result.title}}</a>
</p>
<!-- 店铺名 -->
<div class="productShop">
<span>店铺: 狂神说Java </span>
</div>
<!-- 成交信息 -->
<p class="productStatus">
<span>月成交<em>999笔</em></span>
<span>评价 <a>3</a></span>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!--前端使用vue,实现前后端分离-->
<script th:src="@{/js/axios.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
<script>
new Vue({
el:'#app',
data:{
keyword:'',//搜索的关键字
results:[] //搜索的结果
},
methods:{
searchKey(){
var keyword = this.keyword;
console.log(keyword);
//对接后端的接口
axios.get('search/'+keyword+"/1/20").then(response=>{
console.log(response);
this.results = response.data;//绑定数据
})
}
}
})
</script>
</body>
</html>
实现高亮
在ContentService.java编写实现高亮的方法
//3、获取这些数据实现搜索高亮功能
public List<Map<String,Object>> searchPageHighlightBuilder(String keyword,int pageNo,int pageSize) throws IOException {
if (pageNo<=1){
pageNo = 1;
}
//条件搜索(构造搜索请求,构造搜索条件)
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//分页
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
//精确匹配
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
sourceBuilder.query(termQueryBuilder);
sourceBuilder.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>");
sourceBuilder.highlighter(highlightBuilder);
//执行搜索
searchRequest.source(sourceBuilder);
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 n_title = "";
for (Text text :fragments){
n_title += text;
}
sourceAsMap.put("title",n_title);//高亮字段替换掉原来的内容即可
}
list.add(sourceAsMap);
}
return list;
}
在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.searchPageHighlightBuilder(keyword,pageNo,pageSize);
}
最终结果: