SEO教程

SEO教程

Products

当前位置:首页 > SEO教程 >

国内个人开设购物网站是否违法?前十名网页设计公司有哪些?

96SEO 2026-02-19 16:38 14


数据层解决方案

的时候#xff0c;写过一次ES笔记#xff0c;可以结合一起看一下。

国内个人开设购物网站是否违法?前十名网页设计公司有哪些?

之前在SpringBoot里面写的相关ES笔记是基于Windows的#xff0c;现在我们是基于docker容器来使用#xff0c;需要你们提…实用篇-ES-环境搭建

ES是elasticsearch的简称。

我在SpringBoot学习

数据层解决方案

之前在SpringBoot里面写的相关ES笔记是基于Windows的现在我们是基于docker容器来使用需要你们提前准备好自己的docker容器以及掌握docker操作

1、Elasticsearch:

随着业务发展数据量越来越庞大传统的MySQL数据库难以满足我们的需求所以在微服务架构下一般都会用到一种分布式搜索的技术下面我们会学分布式搜索中最流行的一种也就是elasticsearch的用法。

包括学习elasticsearch的概念、安装、使用。

其中学习elasticsearch的使用的时候主要通过两个方面一方面是elasticsearch对于索引库(类似于数据库把数据导入进索引库导入的数据就是所谓的文档我们要实现文档的增删改查)的操作另一方面我们还会学习elasticsearch官方提供的Restful的API(也就是Java客户端)来更方便的操作elasticsearch

elasticsearch(读

nǎ)elasticsearch是一款非常强大的开源搜索引擎技术可以帮助我们从海量数据中快速找到需要的内容

stack的核心负责存储、搜索、分析数据。

我们主要学习这个elasticsearch底层实现是基于Lucene技术

2、Kibana是数据可视化的组件也就是展示搜索出来的数据。

elasticsearch的相关技术了解即可

3、Logstash、Beats是负责数据抓取的组件。

elasticsearch的相关技术了解即可

Lucene是一个Java语言的搜索引擎类库(其实就是一个jar包)是Apache公司的顶级项目由DougCutting于1999年研发

Lucene官网:

由于Lucene的缺点诞生出了elasticsearch与Lucene相比elasticsearch(基于Lucene且Compass是elasticsearch的前身)具有以下优点

2、提供Restful接口可被任何语言调用elasticsearch的核心技术是倒排索引下面会学

倒排索引

传统数据库(例如MySQL)采用正向索引例如给下表(tb_goods)中的id创建索引

elasticsearch采用倒排索引例如给下表(tb_goods)中的id创建索引

总结1、正向索引:

基于文档id来创建索引。

查询词条时必须先找到文档而后判断是否包含词条

2、倒排索引:

对文档内容进行分词对词条创建索引并记录词条所在文档的信息。

查询时先根据词条去查询文档id然后获取到文档

elasticsearch对比mysql

elasticsearch是面向文档存储的可以是数据库中的一条商品数据一个订单信息。

注意elasticsearch的文档是以json形式存储的也就是说我们把数据(也叫文档)存储进elasticsearch时这些文档数据就会自动被序列化为json格式然后才存储进elasticsearch

下面的表格是介绍elasticsearch中的各个概念以及含义看的时候重点看第二、三列第一列是为了让你更理解第二列的意思所以在第一列拿MySQL的概念来做匹配。

例如elasticsearch的Index表示索引也就是文档的集合就相当于MySQL的Table(也就是表)

MySQL

文档(Document)就是一条条的数据类似数据库中的行(Row)。

这里的文档都是JSON格式

Column

字段(Field)就是JSON文档中的字段类似数据库中的列(Column)

Schema

Mapping(映射)是索引中文档的约束例如字段类型约束。

类似数据库的表结构(Schema)

SQL

DSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD

我们下面会学习映射的创建以及文档的增删改查。

这些操作在MySQL里面是通过SQL语句实现但我们在elasticsearch中会使用的是DSL语句来操作。

在elasticsearch中当我们写好DSL语句要通过http请求发给elasticsearchelasticsearch才会响应原因是在elasticsearch对外暴露的是Restful接口

上面基本都是在讲elasticsearch那么是不是elasticsearch已经完全代码MySQL答案并不是两者擅长的事情不一样如下

1、MySQL:

擅长事务类型的操作可以确保数据的安全和一致性。

一般用于增删改2、Elasticsearch:

擅长海量数据的搜索、分析、计算。

一般用于查询两者是互补关系不是替代关系因此在业务系统架构中两者都会存在让用户在MySQL里面增删改数据然后MySQL把数据同步给elasticsearch用户要查询的时候就在elasticsearch里面进行查询

安装elasticsearch

chǐ)。

注意elasticsearch、kibana、IK分词器这三者通常是一起使用的

注意:

我们学习elasticsearch是基于docker容器来使用需要你们提前准备好自己的docker容器以及掌握docker操作。

elasticsearch一般都是搭配kibana(下节会学如何安装)来使用kibana的作用是让我们非常方便的去编写elasticsearch中的DSL语句从而去操作elasticsearch

第一步:

创建网络。

因为我们还需要部署kibana容器因此需要让es和kibana容器互联

systemctl

加载es镜像。

采用elasticsearch的7.12.1版本的镜像这个镜像体积有800多MB所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录

es.tar下载:

https://cowtransfer.com/s/c84ac851b9ba44

kibana.tar下载:

https://cowtransfer.com/s/a76d8339d7ba4d

第三步:

把在CentOS7的/root目录的es镜像导入到docker

docker

创建并运行es容器容器名称就叫es。

在docker(也叫Docker大容器、Docker主机、宿主机)根据es镜像来创建es容器

docker

es-data:/usr/share/elasticsearch/data

\-v

es-plugins:/usr/share/elasticsearch/plugins

\--privileged

cluster.namees-docker-cluster设置集群名称

●-e

discovery.typesingle-node运行模式例如非集群模式

●-v

es-data:/usr/share/elasticsearch/data挂载数据卷绑定es的数据

●-v

es-plugins:/usr/share/elasticsearch/plugins挂载数据卷绑定es的插件目录

●--network

9200:9200端口映射配置向外暴露的http请求端口用于用户访问

●-p

9300:9300端口映射配置是es容器各个节点之间互相访问的端口由于我们是单节点部署所以用不到

安装kibana

一起操作也就是说同一个实验。

注意elasticsearch、kibana、IK分词器这三者通常是一起使用的

kibana

让我们非常方便的去编写elasticsearch中的DSL语句从而去操作elasticsearch(读

chǐ)

加载kibana镜像。

这个镜像体积有1.04G所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录

es镜像:

https://cowtransfer.com/s/1c16f55edf2341

第三步:

把在CentOS7的/root目录的kibana镜像导入到docker

docker

创建并运行kibana容器容器名称就叫kibana。

在docker(也叫Docker大容器、Docker主机、宿主机)根据kibana镜像来创建kibana容器

docker

ELASTICSEARCH_HOSTShttp://es:9200

--networkes-net

加入一个名为es-net的网络中与elasticsearch在同一个网络中

ELASTICSEARCH_HOSTS:

由于kibana和es会被我们设置在同一个网络所以这里的kibana可以通过容器名直接访问eses的容器名我们在上一节设置的是es

ELASTICSEARCH_HOSTS:

设置elasticsearch的地址因为kibana已经与elasticsearch在一个网络因此可以用容器名直接访问elasticsearch

5601:5601:

https://github.com/medcl/elasticsearch-analysis-ik。

注意elasticsearch、kibana、IK分词器这三者通常是一起使用的

es在创建倒排索引时需要对文档进行分词。

在搜索时需要对用户输入的内容进行分词。

但默认的分词规则不支持中文处理默认是只支持对英文进行分词但是在正常业务中我们需要处理的文档大多是中文所以我们需要对中文进行分词所以就需要安装IK分词器

为了直观的体现es的分词规则不支持英文我们可以做下面的小演示如下

#测试分词器

上图就算分词器名称改成chinese或standard对于中文的分词也是一字一分。

解决:

第一步:

创建elasticsearch容器时指定了数据卷目录其中有个数据卷指定了自定义名称为es-plugins表示存放插件的数据卷

我们使用inspect命令把es-plugins数据卷的路径信息查询出来

docker

下载ik.zip压缩包到Windows下载后解压出来是ik文件夹

根据上面查询出来的es-plugins数据卷的路径把ik文件夹上传到CentOS7的

/var/lib/docker/volumes/es-plugins/_data

/var/lib/docker/volumes/es-plugins/_data

第三步:

创建elasticsearch容器时指定了自定义容器名称为es

重启elasticsearch容器

●ik_smart最少切分根据语义分词正常分词●ik_max_word最细切分也是根据语义分词分的词语更多更细

Ik分词器的分词底层是一个字典在字典里面会有各种各样的词语当ik分词器需要对分词文本进行分词时ik分词器就会拿着这个文本(乱拆成多个词或词语)一个个去字典里面匹配如果能匹配到证明某个词(乱拆成多个词或词语)是词就把这个证明后的词分出来作为一个词

第一个问题:

字典的分词效果是有限的只能对日常生活中常见的语义相关的词进行分词由于字典的词汇量少所以我们需要对字典进行扩展。

第二个问题:

字典的分词效果往往存在违禁词我们不希望IK分词器能匹配并成功把词典里的违禁词作为分词解决:

禁用某些敏感词条

1、要拓展或禁用ik分词器的词库只需要修改一个分词器中的IKAnalyzer.cfg.xml文件如下

/var/lib/docker/volumes/es-plugins/_data/ik/config

IKAnalyzer.cfg.xml

http://java.sun.com/dtd/properties.dtd

propertiescommentIK

扩展配置/comment!--用户可以在这里配置自己的扩展字典--entry

keyext_dictext.dic/entry!--用户可以在这里配置自己的扩展停止词字典

***

keyext_stopwordsstopword.dic/entry

/properties

2、在config目录新建myext.dic文件写入自己想要的特定词也就是扩展词。

新建mystopword.dic文件写入自己想要禁用的特定词也就是不参与分词的词

/var/lib/docker/volumes/es-plugins/_data/ik/config

touch

●ik_smart最少切分根据语义分词正常分词●ik_max_word最细切分也是根据语义分词分的词语更多更细

根据上图确实可以根据我们指定的扩展词进行分析违禁词也确实被禁用没有被分词

实用篇-ES-DSL操作文档

https://elastic.co/guide/en/elasticsearch/reference/current/index.html

下面的表格是介绍elasticsearch中的各个概念以及含义看的时候重点看第二、三列第一列是为了让你更理解第二列的意思所以在第一列拿MySQL的概念来做匹配。

例如elasticsearch的Index表示索引也就是文档的集合就相当于MySQL的Table(也就是表)

MySQL

文档(Document)就是一条条的数据类似数据库中的行(Row)。

这里的文档都是JSON格式

Column

字段(Field)就是JSON文档中的字段类似数据库中的列(Column)

Schema

Mapping(映射)是索引中文档的约束例如字段类型约束。

类似数据库的表结构(Schema)

SQL

DSL是elasticsearch提供的JSON风格的请求语句用来操作elasticsearch实现CRUD

mapping是对索引库中文档(es中的文档是json风格)的约束常见的mapping属性包括如下

●type:

text可分词的文本、keyword精确值例如品牌、国家、ip地址等不可分词的词语

○数值:

long、integer、short、byte、double、float

○布尔:

是否创建倒排索引默认为true(也就是可参与分词搜索)改成false的话别人就搜索不到你

●analyzer:

分词器当字段类型是text时必须指定分词器。

如果字段类型是keyword那么不需要指定分词器

●properties:

ES中通过Restful请求操作索引库、文档。

请求内容用DSL语句来表示。

创建索引库和mapping的DSL语法如下

PUT

//false表示这个字段不参与搜索该字段不会创建为倒排索引false不加双引号},字段名3:{properties:

{type:

//不参与搜索用户不能通过搜索搜到xxemail字段},name:

{type:

//参与搜索用户通过可搜索到firstName字段},lastName:

//子字段type:

索引库是无法被修改的但是可以添加新字段(不能和已有的重复否则报错)

PUT

1、新增文档的DSL语法其实就是告诉kibana我们要把文档添加到es的哪个索引库如果省略文档id的话es会默认随机生成一个建议自己指定文档id

POST

123huanfqc.cn,name:{firstName:张,lastName:三}

GET

然后开始下面的操作。

并且已经创建了名为huanfqc的索引库、文档id为1的文档

systemctl

这种操作是直接用新值覆盖掉旧的如果只put一个字段那么其它字段就没了所以你不想修改的字段也要原样写出来不然就没了

注意:

如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作

#修改文档

123huanfqc.cn,name:{firstName:修改你了-张,lastName:三,xxupdate:我还加了一个}

方式二:

如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作

#修改文档

ES官方提供了各种不同语言的客户端用来操作ES。

这些客户端的本质就是组装DSL语句通过http请求来发送给ES。

官方文档地址:

https://www.elastic.co/guide/en/elasticsearch/client/index.html

下面就使用java程序进行操作es不再像上面那样使用浏览器页面进行操作es

在下面会逐步完成一个案例:

下载提供的hotel-demo.zip压缩包解压后是hotel-demo文件夹是一个java项目工程文件按照条件创建索引库索引库名为hotelmapping属性根据数据库结构定义。

还要下载一个tb_hotel.sql文件作为数据库数据

hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043

tb_hotel.sql下载:

https://cowtransfer.com/s/716f049850a849

第一步:

打开database软件把tb_hotel.sql文件导入进你的数据库

create

把下载好的hotel-demo.zip压缩包解压得到hotel-demo文件夹在idea打开hotel-demo

第三步:

字段名、数据类型、是否参与搜索、是否分词、如果分词那么分词器是什么。

systemctl

我们刚刚在mysql导入了tb_hotel.sql里面有很多数据我们需要基于这些数据结构去分析并尝试编写对应的es的mapping映射

CREATE

根据mysql的数据类型等信息编写es(没有自己对着上面的sql写的)。

注意经纬度在es里面是geo_point类型且经纬度是写在一起的

PUT

操作主要是在idea的hotel-demo项目进行hotel-demo项目(不是springcloud项目只是springboot项目)是前面

第一步:

elasticsearch.version7.12.1/elasticsearch.version!--引入es的RestHighLevelClient版本要跟你Centos7里面部署的es版本一致--

dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.12.1/version

/dependency

在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelIndexTest类写入如下

package

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.junit.jupiter.api.AfterEach;

import

org.junit.jupiter.api.BeforeEach;

import

org.junit.jupiter.api.Test;import

class

xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void

setUp()

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.200.231:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void

tearDown()

{this.xxclient.close();}Test//现在才是测试代码对象已经在上面初始化并且有销毁的步骤了下面直接打印void

yytestInit()

{System.out.println(xxclient);}

第三步:

不是通过kibana的浏览器控制台通过DSL语句来进行操作es在es里面创建索引库

而是通过上一节初始化的RestClient对象在Java里面去操作es创建es的索引库。

根本不需要kibana做中间者

第一步:

在src/main/java/cn.itcast.hotel目录新建constants.HotelConstants类里面写DSL语句如下

hotel数据结构分析

cn.itcast.hotel.constants;public

class

在hotel-demo项目的HotelIndexTest类添加如下

Test

{//创建Request对象自定义索引库名称为gghotelCreateIndexRequest

request

CreateIndexRequest(gghotel);//准备请求的参数:

DSL语句request.source(xxMappingTemplate,

XContentType.JSON);//注意xxMappingTemplate是第一步定义的的静态常量导包别导错了//发送请求xxclient.indices().create(request,

第三步:

验证。

运行HotelIndexTest类的xxcreateHotelIndex测试方法

第五步:

如何更直观地验证es里面确实有刚刚创建的索引库(刚刚创建的索引库是叫gghotel)

那就不得不运行kibana了这样才能打开web浏览器页面进行查询

docker

然后开始下面的操作。

不需要浏览器操作es所以不需要启动kibana容器

systemctl

1、删除索引库。

在hotel-demo项目的HotelIndexTest类添加如下。

然后运行xxtestDeleteHotelIndex方法

//删除索引库

{//创建Request对象指定要删除哪个索引库DeleteIndexRequest

request

DeleteIndexRequest(gghotel);//发送请求xxclient.indices().delete(request,

2、判断索引库是否存在。

在hotel-demo项目的HotelIndexTest类添加如下。

然后运行xxtestDeleteHotelIndex方法

Testvoid

{//创建Request对象判断哪个索引库是否存在在esGetIndexRequest

request

GetIndexRequest(gghotel);//发送请求boolean

ffexists

xxclient.indices().exists(request,

RequestOptions.DEFAULT);//输出一下看是否存在System.out.println(ffexists

索引库已经存在

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

systemctl

去数据库查询酒店数据把查询到的结果导入到hotel索引库(上一节我们已经创建一个名为gghotel的索引库)实现酒店数据的增删改查

简单说就是先去数据查酒店数据把结果转换成索引库所需要的格式(新增文档的DSL语法)然后写到索引库然后在索引库对这些酒店数据进行增删改查

【必备操作】

你们拿到代码的时候这些操作已经做好不需要再去做我只是写出来方便后续复习

(1)、在pojo目录里面有一个Hotel类作用是指定根数据库交互的字段写入了如下

package

com.baomidou.mybatisplus.annotation.IdType;

import

com.baomidou.mybatisplus.annotation.TableId;

import

com.baomidou.mybatisplus.annotation.TableName;

import

(2)、在pojo目录里面有一个HotelDoc类作用是跟es的索引库交互的字段也就是跟我们索引库里面的字段类型联调写入了如下

package

hotel.getStarName();this.business

hotel.getBusiness();this.xxlocation

hotel.getLatitude()

(3)、在service新建了IHotelService接口作用是写mybatis-plus向数据库发送请求用于查询数据库的数据

package

com.baomidou.mybatisplus.extension.service.IService;public

interface

(4)、在service新建了impl里面有一个HotelService类是IHotelService接口的实现类

package

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import

org.springframework.stereotype.Service;Service

public

在hotel-demo项目的src/test/java/cn.itcast.hotel目录新建HotelDocumentTest类写入如下

package

cn.itcast.hotel.service.IHotelService;

import

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

import

org.elasticsearch.action.index.IndexRequest;

import

org.elasticsearch.client.RequestOptions;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.elasticsearch.client.indices.CreateIndexRequest;

import

org.elasticsearch.client.indices.GetIndexRequest;

import

org.elasticsearch.common.xcontent.XContentType;

import

org.junit.jupiter.api.AfterEach;

import

org.junit.jupiter.api.BeforeEach;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.boot.test.context.SpringBootTest;import

java.io.IOException;

cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;/***

author

xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void

setUp()

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void

tearDown()

{this.xxclient.close();}//-----------------------------上面是初始化下面是操作文档的测试-------------------------------------------Autowired//注入写好的IHotelService接口用于去数据库查询数据private

IHotelService

xxhotelService;Test//新增文档到gghotel索引库请保证你的es里面已经存在gghotel索引库void

testAddDocument()

{//去数据库查询数据我们简单查询一下id为61083的数据。

由于在实体类里面定义的id是Long类型所以要加L表示该数字是Long类型Hotel

xxdataExample

xxhotelService.getById(61083L);//把上一行数据库查询出来的字段类型转为es的索引库的文档类型才能往索引库里面新增文档HotelDoc

xxhotelDoc

HotelDoc(xxdataExample);//准备Request对象往哪个索引库添加文档文档的id需要自定义xxdataExample.getId().toString()表示文档id跟数据库的id一致IndexRequest

xxrequest

IndexRequest(gghotel).id(xxdataExample.getId().toString());//准备JSON文档.JSON.toJSONString()是com.alibaba.fastjson提供的API用于把JSON转为Stringxxrequest.source(JSON.toJSONString(xxhotelDoc),XContentType.JSON);//发送请求xxclient.index(xxrequest,RequestOptions.DEFAULT);}

第二步:

验证。

运行HotelDocumentTest类的testAddDocument方法

第三步:

如何更直观地验证es里面的gghotel索引库里面有刚刚我们新增的文档文档id就是数据里面的字段id

那就不得不运行kibana了这样才能打开web浏览器页面进行查询

docker

我们在刚刚为了直观地验证是否成功新增文档需要启动kibana然后去浏览器页面进行查询非常的麻烦下面就来学习通过Java代码进行查询文档

难点:

根据id查询到的文档数据类型是json需要反序列化为java对象

第一步:

{//准备Request对象要查询哪个索引库要查询的文档i我们上面指定的文档id是跟数据库字段的id一致上面新增的那条文档的id是61083GetRequest

yyrequest

RequestOptions.DEFAULT);//解析响应结果。

getSourceAsString方法的作用是把得到的JSON结果转为StringString

yyjson

yyresponse.getSourceAsString();//JSON.parseObject()是com.alibaba.fastjson提供的API作用是对上面那行的yyjson进行反序列化//第一个参数是你要对谁进行反序列化第二个参数是你想要的数据类型HotelDoc

yyhotelDoc

HotelDoc.class);//输出一下查询结果System.out.println(yyhotelDoc);}

第二步:

运行HotelDocumentTest类的xxtestGetDocumentById方法

修改文档

根据id修改酒店数据。

修改es的索引库的文档的数据有两种方式前面在学kibana操作文档的时候学过可前去

实用篇-ES-索引库和文档

这种操作是直接用新值覆盖掉旧的如果只put一个字段那么其它字段就没了所以你不想修改的字段也要原样写出来不然就没了。

如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作

2、增量修改(我们学习这种)。

修改指定字段的值。

如果你写的文档id或字段不存在的话本来是修改操作结果就变成新增操作

实用篇-ES-环境搭建

以及上面的五小节然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

systemctl

{//准备Request对象要修改哪个索引库要修改的文档idUpdateRequest

zzrequest

61083);//准备请求参数要修改哪些字段修改成什么zzrequest.doc(name,我修改了你3个文档字段,price,999,city,北京);//发送请求获取响应结果xxclient.update(zzrequest,RequestOptions.DEFAULT);

第二步:

先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)的数据。

运行HotelDocumentTest类的xxtestGetDocumentById方法

第三步:

运行HotelDocumentTest类的xxtestUpdateDocument方法作用是修改数据也就是我们第一步写的代码

第四步:

在去查一下文档的数据验证第三步是否修改成功。

运行HotelDocumentTest类的xxtestGetDocumentById方法

删除文档

以及上面的五小节然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

systemctl

{//准备Request对象要删除哪个索引库要删除的文档idDeleteRequest

wwrequest

61083);//发送请求xxclient.delete(wwrequest,RequestOptions.DEFAULT);

第二步:

先查一下原来的id为61083的文档(es中的文档就相当于mysql的一行)能不能查询到。

运行HotelDocumentTest类的xxtestGetDocumentById方法

第三步:

删除id为61083的文档(相当于删除mysql中id为某个数的那一行)。

运行HotelDocumentTest类的wwtestDeleteDocument方法

第四步:

验证。

再次执行第二步也就是运行HotelDocumentTest类的xxtestGetDocumentById方法

10.

在上面的6、7、8、9节中我们一直都是操作一条id为61083的文档(相当于数据库表的某一行)。

我们如何把mysql的更多数据导入进es的索引库(相当于mysql的表)呢下面就来学习批量把文档导入进索引库

思路:

1、利用mybatis-plus把MySQL中的酒店数据查询出来

3、利用RestClient中bulk批处理方法实现批量新增文档

实用篇-ES-环境搭建

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

systemctl

{//向数据库批量查询酒店数据list方法表示查询数据库的所有数据ListHotel

kkhotels

xxhotelService.list();//创建RequestBulkRequest

vvrequest

BulkRequest();//准备参数实际上就是添加多个新增的Requestfor

(Hotel

{//把遍历拿到的每个kkhotels转换为文档类型的数据HotelDoc

ffhotelDoc

HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类//往哪个索引库批量新增文档、新增后的文档id是什么文档类型是JSONvvrequest.add(new

IndexRequest(gghotel).id(ffhotelDoc.getId().toString())//JSON.parseObject()是com.alibaba.fastjson提供的API作用是对ffhotelDoc进行反序列化准换为json类型.source(JSON.toJSONString(ffhotelDoc),XContentType.JSON));}//发送请求xxclient.bulk(vvrequest,RequestOptions.DEFAULT);

第二步:

运行HotelDocumentTest类的testBulkRequest方法

第三步:

如何更直观地验证es里面的gghotel索引库里面有刚刚我们新增的文档。

那就不得不运行kibana了这样才能打开web浏览器页面进行查询

docker

上面我们导入了很多文档(相当于数据库的行很多行)下面我们将着重学习使用DSL对这些文档数据进行查询

实用篇-ES-DSL查询文档

https://elastic.co/guide/en/elasticsearch/reference/current/query-dsl.html#query-dsl。

DSL是用来查询文档的

Elasticsearch提供了基于JSON的DSL来定义查询简单说就是用json来描述查询条件然后发送给es服务最后es服务基于查询条件把结果返回给我们

1、查询所有:

根据精确的词条值去查找数据一般是查找keyword、数值、日期、boolean等类型的字段。

这些字段是不需要分词的但是依旧会建立倒排索引把字段的整体内容作为一个词条并存入倒排索引。

在查找的时候也就不需要分词直接把搜索的内容去跟倒排索引匹配即可

ids表示根据id进行精确匹配

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

浏览器访问

存在一个问题我们明明查询的是所有文档查询结果也显示查询出所有的文档了为什么上图右侧鼠标往下拉最多才只有10条文档数据呢

原因:

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

全文检索查询分为下面两种会对用户输入内容进行分词之后再进行匹配。

也就是利用分词器对用户输入内容进行分词然后去倒排索引库中匹配。

【第一种全文检索查询】

全文检索查询的一种会对用户输入的内容进行分词然后去倒排索引库检索

具体操作如下为了让大家知道gghotel索引库有哪些字段我把当初建立gghotel索引库的类先放出来

注意:

我要解释一下上面有个字段叫xxALL那个字段是当时自定义的不清楚的话可回去看

实用篇-ES-RestClient操作

xxALL的作用如下图相当于一个大的字段里面存放了几个小字段优点是我们可以在这个大的字段里面搜索到多个小字段的信息

然后我们就正式开始全文检索查询输入如下。

注意xxALL换成其它字段也没事例如换成name字段。

正常来说我们检索name字段就只在那么字段检索匹配的分词文档但是在XXALL字段里面检索时也会检索到name、brand、business字段原因如上面那个图的copy_to属性

第一步:

multi_match(也就是multi_match_query查询):

例如输入如下

输入如下DSL语句表示查询查询business、brand、name字段中包含如家的文档满足一个字段即可

精确查询

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

systemctl

#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器

精确查询一般是查找keyword、数值、日期、boolean等类型字段。

所以不会对搜索条件分词。

精确查询常见的有两种:

term:

输入如下DSL语句表示查找price字段满足200~300数值的文档注意字段类型不能是binary也就是price字段的类型不能是binary

第一种精确查询

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

systemctl

#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器

查询附近酒店、附近出租车、搜索附近的人。

使用方式有很多种介绍如下

geo_bounding_box:

查询geo_point值落在某个矩形范围的所有文档用两个点来围成的矩形范围

geo_distance:

具体操作如下但是为了让大家知道gghotel索引库有哪些字段我把当初建立gghotel索引库的类先放出来

上面的xxlocation字段类型必须是geo_point否则该字段不能用于地理查询

第一步:

输入如下DSL语句。

表示查找xxlocation字段在(31.25±15km,121.5±15km)范围内的文档

相关性算分

上面学的全文检索查询、精确查询、地理查询这三种查询在es当中都称为简单查询下面我们将学习复合查询。

复合查询可以其它简单查询组合起来实现更复杂的搜索逻辑其中就有

算分函数查询

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

systemctl

#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器

算分函数查询(function

词条时某个文档要是都能匹配这两个词条那么在所有被搜索出来的文档当中这个文档的位置就最靠前简单说就是越匹配就排名越靠前

GET

输入如下DSL语句表示在name字段哪个文档的匹配度高排名就靠前

GET

上面只是简单演了相关性打分中的函数算分查询文档与搜索关键字的相关度越高打分就越高排名就越靠前。

不过有的时候我们希望人为地去控制控制文档的排名例如某些文档我们就希望排名靠前一点算分高一点此时就需要使用函数算分查询下面就来学习

函数算分查询

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

systemctl

#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器

’函数算分查询(function

query)’可以在原始的相关性算分的基础上加以修改得到一个想要的算分从而去影响文档的排名语法如下

GET

布尔查询不会去修改算分而是把多个查询语句组合成一起形成新查询这些被组合的查询语句被称为子查询。

子查询的组合方式有如下四种

1、must必须匹配每个子查询类似与

#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器

实用篇-ES-环境搭建

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

GET

输入如下DSL语句表示搜索名字包含如家价格不高于400在坐标31.21,121.5周围10km范围内的文档

must表示匹配条件(注意写在must里面就会参与算分也就是查询出来的score值会更高)must_not表示取反filter表示过滤

GET

elasticsearch(称为es)支持对搜索的结果进行排序默认是根据

相关度

#由于DSL语句是需要kibana服务然后在浏览器进行所以这里要开启kibana容器

实用篇-ES-环境搭建

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

ASC(升序)、DESC(降序)

输入如下DSL语句表示对所有的文档根据评分(score)进行降序排序如果评分相同就根据价格(price)升序排序

GET

上图的_score算分为null是因为我们如果做了排序那么打分就没有意义了所以es就会放弃打分不再做相关性算分提高效率

【案例二】

输入如下DSL语句表示找到(121.66053,28.28811)周围的文档并按照距离进行升序排序

下面两种写法都是一样的注意第二种写法前面写的是纬度后面写的是经度

第一种写法

上图的_score算分为null是因为我们如果做了排序那么打分就没有意义了所以es就会放弃打分不再做相关性算分提高效率

搜索结果处理-分页

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作elasticsearch(称为es)默认情况下只返回前10

条数据。

而如果要查询更多数据就需要修改分页参数分页参数包括from和size语法如下

GET

输入如下DSL语句表示对所有的文档根据价格(price)进行升序排序每次分页显示20条数据看的是第六页size默认是10表示一页显示多少条文档。

from默认是0表示你要看的是第一页

GET

上面是基础的分页用法下面来详细了解es的分页。

es的底层使用的是倒排索引是不利于做分页的es采用的是逻辑上的分页就会导致当是分布式的时候就会产生下面的问题因此es限制结果集最多为10000

ES是分布式的所以会面临深度分页的问题。

例如按price排序后获取from990size10的数据如下图

GET

百度在这方面最多能查76页每页显示十条。

京东在这方面最多能查第100页所以深度分页我们不需要担心10000的限制足够了。

但是如果说一定要去解决深度分页问题的话ES提供了两种解决方案(两种分页方式)如下

官方文档:

https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html

1、search

分页时需要排序原理是从上一次的排序值开始查询下一页数据。

官方推荐使用的方式。

缺点:

只能向后翻页不能向前翻页

没有随机翻页需求的搜索例如手机向下滚动翻页。

虽然没有查询上限但是size不能超过10000

2、scroll:

创建了名为gghotel的索引库导入了批量文档。

然后开始下面的操作

GET

要一致。

如果我们写的是不一致的话就需要修改为falsepre_tags:

em,

用来标记高亮字段的前置标签es会帮我们把标签加在关键字上。

默认是empost_tags:

/em

用来标记高亮字段的后置标签es会帮我们把标签加在关键字上。

默认是/em}}}

具体操作如下

上面的查询文档都是依赖kibana在浏览器页面使用DSL语句去查询es如何用java去查询es里面的文档(数据)呢

我们通过match_all查询来演示基本的API注意下面演示的是

match_all查询也叫基础查询

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

systemctl

在src/test/java/cn.itcast.hotel目录新建HotelSearchTest类写入如下

package

org.elasticsearch.action.search.SearchRequest;

import

org.elasticsearch.action.search.SearchResponse;

import

org.elasticsearch.client.RequestOptions;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.junit.jupiter.api.AfterEach;

import

org.junit.jupiter.api.BeforeEach;

import

org.junit.jupiter.api.Test;import

class

xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void

setUp()

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void

tearDown()

{this.xxclient.close();}//-----------------------------上面是初始化下面是查询文档-快速入门的测试-------------------------------------------Testvoid

xxtestMatchAll()

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchAllQuery());//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//在控制台输出结果System.out.println(xxresponse);}

第二步:

把控制台里面我们需要的数据解析出来。

返回的数据很多我们主要是解析hits里面的数据就行了

把HotelSearchTest类修改为如下主要的修改是sout之前做了一次解析拿到我们想要的数据

package

org.elasticsearch.action.search.SearchRequest;

import

org.elasticsearch.action.search.SearchResponse;

import

org.elasticsearch.client.RequestOptions;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.junit.jupiter.api.AfterEach;

import

org.junit.jupiter.api.BeforeEach;

import

org.junit.jupiter.api.Test;import

class

xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void

setUp()

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void

tearDown()

{this.xxclient.close();}//-----------------------------上面是初始化下面是查询文档-快速入门的测试-------------------------------------------Testvoid

xxtestMatchAll()

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchAllQuery());//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}}

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

match

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchAllQuery());//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.matchQuery(name,如家));//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.multiMatchQuery(如家,name,business));//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}}

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

我们发现对于

查询的解析部分的代码都是相同的所以我们可以对解析部分的代码进行抽取(ctrlaltm)如下

private

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

精确查询一般是查找keyword、数值、日期、boolean等类型字段。

所以不会对搜索条件分词。

精确查询常见的有两种:

term:

java代码和DSL语句的对应关系如下图。

gt表示大于gte表示大于等于lt表示小于lte表示小于等于

Test

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.termQuery(city,上海));//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//准备DSL语句source方法可以调用很多API。

QueryBuilders是RestClient提供的工具可以调用很多查询类型xxrequest.source().query(QueryBuilders.rangeQuery(price).gte(100).lte(150));//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

Test

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//创建布尔查询BoolQueryBuilder

xxboolQuery

QueryBuilders.boolQuery();//添加must条件xxboolQuery.must(QueryBuilders.termQuery(city,上海));//添加filter条件xxboolQuery.filter(QueryBuilders.rangeQuery(price).lte(200));//把上面的布尔对象传进来就可以生效了xxrequest.source().query(xxboolQuery);//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

【geo_distance查询】在HotelSearchTest类添加如下

Test

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//创建一个地理位置查询构造器指定了要查询字段的是xxlocationGeoDistanceQueryBuilder

xxgeoQuery

QueryBuilders.geoDistanceQuery(xxlocation);xxgeoQuery.point(31.25,

31.25

DistanceUnit.KILOMETERS);//设置查询的半径距离和单位这里的

即表示

将查询条件添加到查询构造器对象searchSourceBuilder.query(xxgeoQuery);//

将查询构造器的对象添加到查询请求对象xxrequest中就可以生效了xxrequest.source(searchSourceBuilder);//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

上面是各种查询的学习当我们把文档查询出来的时候接下来就是对文档的处理也就是你要把查询结果怎么展示出来。

API以及对应的DSL语句如下图

实用篇-ES-环境搭建

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

Test

{//页面、每页大小。

如果你要翻第二页就把下面的xxpage改成2int

xxpage

5;//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//查询全部xxrequest.source().query(QueryBuilders.matchAllQuery());//sort排序asc升序desc降序xxrequest.source().sort(price,

SortOrder.ASC);//from、size分页。

例如查第一页每页显示5条文档(数据)。

from表示当前页我们使用公式动态设定xxrequest.source().from((xxpage-1)*xxsize).size(5);//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

高亮API包括请求DSL构建和结果解析两部分API和对应的DSL语句如下图下图只是构建再下面还有解析高亮必须由构建解析才能实现

解析如下图

创建了名为gghotel的索引库然后开始下面的操作。

如果需要浏览器操作es那就不需要启动kibana容器

Test

{//准备Request对象要查询哪个索引库SearchRequest

xxrequest

SearchRequest(gghotel);//【构建】//查询name字段的文档xxrequest.source().query(QueryBuilders.matchQuery(name,上海));//对查询出来的文档的特定字段进行高亮显示xxrequest.source().highlighter(new

HighlightBuilder().field(name).requireFieldMatch(true).preTags(em).postTags(/em));//发送请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析获取到杂乱JSON数据SearchHits

xxsearchHits

xxresponse.getHits();//获取总条数long

xxtotal

xxsearchHits.getTotalHits().value;System.out.println(共搜索到xxtotal条文档(数据));//获取hits数组SearchHit[]

xxhits

xxsearchHits.getHits();//遍历数组把hits数组的每个source取出来for

(SearchHit

xxhit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

xxhotelDoc

HotelDoc.class);//【解析】获取高亮结果MapString,

HighlightField

xxhit.getHighlightFields();//使用CollectionUtils工具类进行判空避免空指针if

(!CollectionUtils.isEmpty(xxhighlightFields)){//根据字段名获取高亮结果HighlightField

xxhighlightField

xxhighlightFields.get(name);//判断name不为空if

(xxhighlightField

xxhighlightField.getFragments()[0].string();//覆盖非高亮结果xxhotelDoc.setName(xxname);}}//最终输出System.out.println(每个HotelDoc对象

xxhotelDoc);}

这个案例我做了两遍才做出来了第一遍排了一上午的错所以很有必要进行环境准备下面我将带领你对一下我的环境全网最详细的自创笔记

环境准备-docker

企业部署一般都是采用Linux操作系统而其中又数CentOS发行版占比最多因此我们接下来会在CentOS下安装Docker

https://cowtransfer.com/s/56423adc78374f

https://cowtransfer.com/s/b4c8fcb5c15244

ideajdk下载

https://cowtransfer.com/s/7dcb0c66154d45

mysql下载

https://cowtransfer.com/s/567413055c9a4f

第一步:

在VMware虚拟机安装CentOS7系统安装完成之后使用finalshell远程软件进行远程连接然后安装yum工具执行如下

yum

\https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposed

s/download.docker.com/mirrors.aliyun.com\/docker-ce/g

/etc/yum.repos.d/docker-ce.repoyum

makecache

执行如下安装docker稍等片刻docker即可安装成功。

docker-ce为社区免费版本

yum

由于Docker应用需要用到各种端口逐一去修改防火墙设置会非常麻烦所以学习期间直接关闭防火墙即可

systemctl

配置docker镜像仓库设置为国内的镜像仓库以后在docker里面下载东西的时候速度会更快。

分别执行如下命令

sudo

[https://93we6x1g.mirror.aliyuncs.com]

EOF

在刚刚创建的文件夹里面新建daemon.json文件并写入花括号里面的数据

sudo

加载es镜像。

采用elasticsearch的7.12.1版本的镜像这个镜像体积有800多MB所以需要在Windows上下载链接安装包下载下来是一个es的镜像tar包然后传到CentOS7的/root目录

es.tar下载:

https://cowtransfer.com/s/c84ac851b9ba44

kibana.tar下载:

https://cowtransfer.com/s/a76d8339d7ba4d

第三步:

把在CentOS7的/root目录的es镜像导入到docker

docker

创建并运行es容器容器名称就叫es。

在docker(也叫Docker大容器、Docker主机、宿主机)根据es镜像来创建es容器

docker

es-data:/usr/share/elasticsearch/data

\-v

es-plugins:/usr/share/elasticsearch/plugins

\--privileged

打开database软件把tb_hotel.sql文件导入进你的数据库

tb_hotel.sql下载:

https://cowtransfer.com/s/68c94a66d17248

create

把下载好的hotel-demo.zip压缩包解压得到hotel-demo文件夹在idea打开hotel-demo

hotel-demo.zip下载:https://cowtransfer.com/s/36ac0a9f9d9043

第二步:

xmlnshttp://maven.apache.org/POM/4.0.0

xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0

https://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersionparentgroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-parent/artifactIdversion2.3.10.RELEASE/versionrelativePath/

!--

--/parentgroupIdcn.itcast.demo/groupIdartifactIdhotel-demo/artifactIdversion0.0.1-SNAPSHOT/versionnamehotel-demo/namedescriptionDemo

project

Boot/descriptionpropertiesjava.version1.8/java.versionelasticsearch.version7.12.1/elasticsearch.version/propertiesdependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependency!--引入es的RestHighLevelClient版本要跟你Centos7里面部署的es版本一致--dependencygroupIdorg.elasticsearch.client/groupIdartifactIdelasticsearch-rest-high-level-client/artifactIdversion7.12.1/version/dependencydependencygroupIdcom.baomidou/groupIdartifactIdmybatis-plus-boot-starter/artifactIdversion3.4.2/version/dependencydependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdscoperuntime/scope/dependencydependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scopeexclusionsexclusiongroupIdorg.junit.vintage/groupIdartifactIdjunit-vintage-engine/artifactId/exclusion/exclusions/dependency!--FastJson--dependencygroupIdcom.alibaba/groupIdartifactIdfastjson/artifactIdversion1.2.71/version/dependencydependencygroupIdorg.apache.commons/groupIdartifactIdcommons-lang3/artifactId/dependency/dependenciesbuildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexcludesexcludegroupIdorg.projectlombok/groupIdartifactIdlombok/artifactId/exclude/excludes/configuration/plugin/plugins/build/project

环境准备-同步数据

把mysql的数据导入进es我们需要使用前面学的es提供的RestClient就可以通过java代码创建索引库并往这个索引库导入文档(文档就是数据的意思)

第一步:

写完就运行xxcreateHotelIndex方法把索引库创建出来

package

org.elasticsearch.client.RequestOptions;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.elasticsearch.client.indices.CreateIndexRequest;

import

org.elasticsearch.common.xcontent.XContentType;

import

org.junit.jupiter.api.AfterEach;

import

org.junit.jupiter.api.BeforeEach;

import

cn.itcast.hotel.constants.HotelConstants.xxMappingTemplate;public

class

xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void

setUp()

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void

tearDown()

{this.xxclient.close();}//删除索引库(如果下面创建hotel索引库的时候出现已存在那么就执行这里的删除操作把hotel索引库删掉再创建)Testvoid

throws

{//创建Request对象指定要删除哪个索引库DeleteIndexRequest

gghotel

DeleteIndexRequest(hotel);//发送请求xxclient.indices().delete(gghotel,

RequestOptions.DEFAULT);}//使用xxclient对象向es创建索引库Testvoid

xxcreateHotelIndex()

{//创建Request对象自定义索引库名称为hotelCreateIndexRequest

gghotel

CreateIndexRequest(hotel);//准备请求的参数:

DSL语句gghotel.source(xxMappingTemplate,

XContentType.JSON);//发送请求xxclient.indices().create(gghotel,

第二步:

constants.HotelConstants类为es准备数据写入如下

package

cn.itcast.hotel.constants;public

class

src/main/java/cn.itcast.hotel/pojo

package

com.baomidou.mybatisplus.annotation.IdType;

import

com.baomidou.mybatisplus.annotation.TableId;

import

com.baomidou.mybatisplus.annotation.TableName;

import

hotel.getStarName();this.business

hotel.getBusiness();this.location

hotel.getLatitude()

HotelDocumentTest类用于把mysql的数据批量导入进es写入如下

写完就运行testBulkRequest方法把数据往索引库里面批量导入

package

cn.itcast.hotel.service.IHotelService;

import

org.elasticsearch.action.bulk.BulkRequest;

import

org.elasticsearch.action.index.IndexRequest;

import

org.elasticsearch.client.RequestOptions;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.elasticsearch.common.xcontent.XContentType;

import

org.junit.jupiter.api.AfterEach;

import

org.junit.jupiter.api.BeforeEach;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.boot.test.context.SpringBootTest;import

java.io.IOException;

xxclient;BeforeEach//该注解表示一开始就完成RestHighLevelClient对象的初始化void

setUp()

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}AfterEach//该注解表示销毁当对象运行完之后就销毁这个对象void

tearDown()

{this.xxclient.close();}Autowired//注入写好的IHotelService接口用于去数据库查询数据private

IHotelService

{//向数据库批量查询酒店数据list方法表示查询数据库的所有数据ListHotel

kkhotels

xxhotelService.list();//创建RequestBulkRequest

vvrequest

BulkRequest();//准备参数实际上就是添加多个新增的Requestfor

(Hotel

{//把遍历拿到的每个kkhotels转换为文档类型的数据HotelDoc

ffhotelDoc

HotelDoc(kkhotel);//HotelDoc是我们写的一个实体类//往哪个索引库批量新增文档、新增后的文档id是什么文档类型是JSONvvrequest.add(new

IndexRequest(hotel).id(ffhotelDoc.getId().toString())//JSON.parseObject()是com.alibaba.fastjson提供的API作用是对ffhotelDoc进行反序列化准换为json类型.source(JSON.toJSONString(ffhotelDoc),XContentType.JSON));}//发送请求xxclient.bulk(vvrequest,RequestOptions.DEFAULT);}}

搜索、分页

二、定义controller接口接收页面请求调用IHotelService的search方法

package

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

com.baomidou.mybatisplus.extension.service.IService;public

interface

(3)、把HotelDemoApplication启动类修改为如下

package

com.baomidou.mybatisplus.annotation.DbType;

import

com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;

import

com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.mybatis.spring.annotation.MapperScan;

import

org.springframework.boot.SpringApplication;

import

org.springframework.boot.autoconfigure.SpringBootApplication;

import

org.springframework.context.annotation.Bean;MapperScan(cn.itcast.hotel.mapper)

public

{SpringApplication.run(HotelDemoApplication.class,

args);}Bean//注入es提供的RestHighLevelClient类public

RestHighLevelClient

RestHighLevelClient(RestClient.builder(//指定你Centos7部署的es的主机地址HttpHost.create(http://192.168.127.180:9200)));}}

(4)、在src/main/java/cn.itcast.hotel目录新建web.HotelController类写入如下

package

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.web.bind.annotation.PostMapping;

import

org.springframework.web.bind.annotation.RequestBody;

import

org.springframework.web.bind.annotation.RequestMapping;

import

org.springframework.web.bind.annotation.RestController;RestController

public

{//注入项目准备好的IHotelService接口Autowiredprivate

IHotelService

hotelService;PostMapping(/list)//使用RequestBody注解接收前端的请求。

PageResult、RequestParams是我们刚刚定义的实体类public

PageResult

三、定义IHotelService中的search方法利用match查询实现根据关键字搜索酒店信息

package

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

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.index.query.QueryBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.stereotype.Service;

import

{Autowired//注入在引导类声明好的Beanprivate

RestHighLevelClient

{//准备Request对象要查询哪个索引库SearchRequest

request

SearchRequest(hotel);//【关键字搜索功能】String

key

params.getKey();//前端传过来的搜索关键字//判断前端传的key是否为空避免空指针if

(key

{//matchAllQuery方法表示查es的全部文档不需要条件request.source().query(QueryBuilders.matchAllQuery());}

else

{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空request.source().query(QueryBuilders.matchQuery(all,

key));}//【分页功能】int

params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int

size

params.getSize();//前端传过来的每页大小。

其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page

size).size(size);//发送请求。

下面那行的search报红线我们不能抛出要捕获一下SearchResponse

response

RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。

并把解析作为结果返回return

catch

RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private

PageResult

searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]

hits

searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc

hotels

hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

hotelDoc

HotelDoc.class);hotels.add(hotelDoc);}//封装返回return

new

四、运行HotelDemoApplication引导类浏览器访问

分析:

1、修改RequestParams类添加brand、city、startName、minPrice、maxPrice等参数

2、修改HotelService类的search方法的实现在关键字搜索时如果brand等参数存在就需要对其做过滤

3、注意多个条件之间是AND关系组合多条件用BooleanQuery

5、city精确匹配brand精确匹配startName精确匹配price范围过滤

第一步:

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

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.index.query.BoolQueryBuilder;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.stereotype.Service;

import

{Autowired//注入在引导类声明好的Beanprivate

RestHighLevelClient

{//准备Request对象要查询哪个索引库SearchRequest

request

SearchRequest(hotel);//【构建BooleanQuery】BoolQueryBuilder

boolQuery

QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String

key

params.getKey();//前端传过来的搜索关键字//用的是must精确查找if

(key

{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}

else

{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,

key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()

null

!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()

null

!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()

null

!params.getStarName().equals()){//注意下面那行的是starName不要写成startNameboolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。

gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()

null

null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//这一步必须有request.source().query(boolQuery);//【分页功能】int

page

params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int

size

params.getSize();//前端传过来的每页大小。

其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page

size).size(size);//发送请求。

下面那行的search报红线我们不能抛出要捕获一下SearchResponse

response

RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。

并把解析作为结果返回return

catch

RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private

PageResult

searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]

hits

searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc

hotels

hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

hotelDoc

HotelDoc.class);hotels.add(hotelDoc);}//封装返回return

new

实现前端页面点击定位后会将你所在的位置发送给后台前端的请求信息如下会向后端发送location参数。

分析:

1、修改RequestParams参数接收来自前端的location字段

2、修改HotelService类的search方法的业务逻辑如果location有值就添加根据geo_distance排序的功能

第一步:

maxPrice;//地理位置查询的字段前端会把location传给我们private

String

pic;//地理位置查询相关的字段。

distance字段用于保存解析后的距离值private

Object

hotel.getStarName();this.business

hotel.getBusiness();this.location

hotel.getLatitude()

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

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.geo.GeoPoint;

import

org.elasticsearch.common.unit.DistanceUnit;

import

org.elasticsearch.index.query.BoolQueryBuilder;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.elasticsearch.search.sort.SortBuilders;

import

org.elasticsearch.search.sort.SortOrder;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.stereotype.Service;

import

{Autowired//注入在引导类声明好的Beanprivate

RestHighLevelClient

{//准备Request对象要查询哪个索引库SearchRequest

request

SearchRequest(hotel);//【构建BooleanQuery】BoolQueryBuilder

boolQuery

QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String

key

params.getKey();//前端传过来的搜索关键字//用的是must精确查找if

(key

{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}

else

{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,

key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()

null

!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()

null

!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()

null

!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。

gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()

null

null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//这一步必须有request.source().query(boolQuery);//【分页功能】int

page

params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int

size

params.getSize();//前端传过来的每页大小。

其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page

size).size(size);//【地理排序功能】String

location

params.getLocation();//对前端传的location进行判断是否为空if

(location

!location.equals()){//sort排序指定是geoDistanceSort地理坐标排序要排序的字段是location中心点是new

GeoPoint(location)request.source().sort(SortBuilders.geoDistanceSort(location,new

GeoPoint(location)).order(SortOrder.ASC)

//升序排序.unit(DistanceUnit.KILOMETERS)

//地理坐标的单位);}//发送请求。

下面那行的search报红线我们不能抛出要捕获一下SearchResponse

response

RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。

并把解析作为结果返回return

catch

RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private

PageResult

searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]

hits

searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc

hotels

hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

hotelDoc

HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]

sortValues

sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}hotels.add(hotelDoc);}//封装返回return

new

重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。

点击定位按钮查看是否能查询出距离自己最近的酒店并显示米数

广告置顶

让指定的酒店在搜索结果中排名置顶。

我们给需要置顶的酒店文档添加一个标记。

然后利用function

分析:

2、挑选几个你喜欢的酒店给它的文档数据添加isAD字段值为true

3、修改HotelService类的search方法添加function

Function

Score查询可以控制文档的相关性算分java代码以及对应DSL语句如下图

第一步:

pic;//地理位置查询相关的字段。

distance字段用于保存解析后的距离值private

Object

hotel.getStarName();this.business

hotel.getBusiness();this.location

hotel.getLatitude()

使用DSL语句为索引库增加字段由于使用DSL语句需要在浏览器使用kibana所以我们把docker里面的kibana容器运行一下

docker

DSL语句表示给某个id文档添加新字段。

id不一定要跟我一样随便去mysql数据库找几个id就行

POST

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

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.geo.GeoPoint;

import

org.elasticsearch.common.unit.DistanceUnit;

import

org.elasticsearch.index.query.BoolQueryBuilder;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;

import

org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.elasticsearch.search.sort.SortBuilders;

import

org.elasticsearch.search.sort.SortOrder;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.stereotype.Service;

import

{Autowired//注入在引导类声明好的Beanprivate

RestHighLevelClient

{//准备Request对象要查询哪个索引库SearchRequest

request

SearchRequest(hotel);//【构建BooleanQuery下面那行的boolQuery是原始查询】BoolQueryBuilder

boolQuery

QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String

key

params.getKey();//前端传过来的搜索关键字//用的是must精确查找if

(key

{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}

else

{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,

key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()

null

!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()

null

!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()

null

!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。

gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()

null

null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//【构建functionScoreQuery实现算分查询。

对应的是广告置顶功能】FunctionScoreQueryBuilder

functionScoreQuery

//原始查询需要进行相关性算分的查询QueryBuilders.functionScoreQuery(boolQuery,//function

scorenew

FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function

scorenew

FunctionScoreQueryBuilder.FilterFunctionBuilder(//过滤简单说就是满足isAD字段为true的文档就会参与算分QueryBuilders.termQuery(isAD,true),//要使用什么算分函数下面那行使用的是weightFactorFunction加权算分最终score分数越大排名就越前ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10)

});//这一步必须有request.source().query(functionScoreQuery);//【分页功能】int

page

params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int

size

params.getSize();//前端传过来的每页大小。

其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page

size).size(size);//【地理排序功能】String

location

params.getLocation();//对前端传的location进行判断是否为空if

(location

!location.equals()){//sort排序指定是geoDistanceSort地理坐标排序要排序的字段是location中心点是new

GeoPoint(location)request.source().sort(SortBuilders.geoDistanceSort(location,new

GeoPoint(location)).order(SortOrder.ASC)

//升序排序.unit(DistanceUnit.KILOMETERS)

//地理坐标的单位);}//发送请求。

下面那行的search报红线我们不能抛出要捕获一下SearchResponse

response

RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。

并把解析作为结果返回return

catch

RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private

PageResult

searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]

hits

searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc

hotels

hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

hotelDoc

HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]

sortValues

sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}hotels.add(hotelDoc);}//封装返回return

new

重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。

查看我们指定的那两个酒店是否置顶

10.

高亮API包括请求DSL构建和结果解析两部分API和对应的DSL语句如下图下图只是构建再下面还有解析高亮必须由构建解析才能实现

解析如下图

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.core.toolkit.CollectionUtils;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

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.geo.GeoPoint;

import

org.elasticsearch.common.unit.DistanceUnit;

import

org.elasticsearch.index.query.BoolQueryBuilder;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;

import

org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;

import

org.elasticsearch.search.fetch.subphase.highlight.HighlightField;

import

org.elasticsearch.search.sort.SortBuilders;

import

org.elasticsearch.search.sort.SortOrder;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.stereotype.Service;

import

{Autowired//注入在引导类声明好的Beanprivate

RestHighLevelClient

{//准备Request对象要查询哪个索引库SearchRequest

request

SearchRequest(hotel);//【构建BooleanQuery下面那行的boolQuery是原始查询】BoolQueryBuilder

boolQuery

QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String

key

params.getKey();//前端传过来的搜索关键字//用的是must精确查找if

(key

{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}

else

{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,

key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()

null

!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()

null

!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()

null

!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,params.getStarName()));}//价格range范围过滤注意判空。

gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()

null

null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//【构建functionScoreQuery实现算分查询。

对应的是广告置顶功能】FunctionScoreQueryBuilder

functionScoreQuery

//原始查询需要进行相关性算分的查询QueryBuilders.functionScoreQuery(boolQuery,//function

scorenew

FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function

scorenew

FunctionScoreQueryBuilder.FilterFunctionBuilder(//过滤简单说就是满足isAD字段为true的文档就会参与算分QueryBuilders.termQuery(isAD,true),//要使用什么算分函数下面那行使用的是weightFactorFunction加权算分最终score分数越大排名就越前ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10)});//【高亮显示】对查询出来的文档的特定字段进行高亮显示request.source().highlighter(new

HighlightBuilder().field(all).requireFieldMatch(true).preTags(em).postTags(/em));//这一步必须有request.source().query(functionScoreQuery);//【分页功能】int

page

params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int

size

params.getSize();//前端传过来的每页大小。

其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page

size).size(size);//【地理排序功能】String

location

params.getLocation();//对前端传的location进行判断是否为空if

(location

!location.equals()){//sort排序指定是geoDistanceSort地理坐标排序要排序的字段是location中心点是new

GeoPoint(location)request.source().sort(SortBuilders.geoDistanceSort(location,new

GeoPoint(location)).order(SortOrder.ASC)

//升序排序.unit(DistanceUnit.KILOMETERS)

//地理坐标的单位);}//发送请求。

下面那行的search报红线我们不能抛出要捕获一下SearchResponse

response

RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。

并把解析作为结果返回return

catch

RuntimeException(e);}}//这个方法就是我们抽取出来的负责解析的private

PageResult

searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]

hits

searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc

hotels

hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

hotelDoc

HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]

sortValues

sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}//【解析】获取高亮结果MapString,

HighlightField

hit.getHighlightFields();//使用CollectionUtils工具类进行判空避免空指针if

(!CollectionUtils.isEmpty(xxhighlightFields)){//根据字段名获取高亮结果HighlightField

xxhighlightField

xxhighlightFields.get(all);//判断name不为空if

(xxhighlightField

xxhighlightField.getFragments()[0].string();//覆盖非高亮结果hotelDoc.setName(xxname);}}hotels.add(hotelDoc);}//封装返回return

new

重启HotelDemoApplication引导类浏览器访问http://localhost:8089/。

查看是否能将搜索词高亮显示

文件下载-奶牛快传

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

聚合:

可以实现对文档数据的统计、分析、运算。

聚合常见的有如下三类。

注意聚合的字段必然是不分词的原因:

(Bucket)

按照文档字段值分组。

(我们下面会演示这个按照品牌进行分桶)○Date

Histogram聚合:

同时求max、min、avg、sum。

(我们下面会演示这个按照品牌进行求评分最值和平均值)

●管道

现在我们要统计所有数据中的酒店品牌有多少种此时我们可以根据酒店品牌的名称做聚合由于品牌是字段也就是要对字段值做分组采用的是TermAggregation聚合类型为term类型DSL示例如下

浏览器访问

具体操作浏览器输入如下表示对不同的品牌进行聚合也就是不同的品牌为不同的桶相同的品牌放进一个桶里面

第二步:

如何修改默认的排序规则我们不希望是按照找出来的文档总条数降序排序。

默认情况下Bucket会统计Bucket内的文档数量记为_count并且按照count降序排序。

我们如果要修改结果排序方式的话只需要加一个order属性如下

第三步:

我们上面是对整个索引库的数据做聚合搜索如果索引库本身有庞大数据的话对整个索引库的聚合搜索是对内存消耗非常大我们希望自定义聚合的搜索范围也就是限定要聚合的文档范围只需要添加query条件即可如下

DSL实现Metrics聚合

Metrics聚合也就是度量聚合。

例如我们要求获取每个品牌的用户评分的min最小值、max最大值、avg平均值。

注意不是整个索引库的所有酒店(文档)进行求值所以要结合上一节的Bucket聚合一起使用

浏览器访问

具体操作浏览器输入如下表示对品牌(父聚合)的评分(子聚合)进行求值

第二步:

如果我们还需要对结果按照评分的平均值再去做个排序看一下哪个酒店评价最高注意我们是在同里面做排序也就是排序要写在terms里面

RestClient实现聚合

如何在java代码使用RestClient来实现聚合。

java代码以及对应的DSL语句如下图

具体操作:

SearchRequest(hotel);//准备DSL。

设置sizexxrequest.source().size(0);//准备DSL。

聚合语句xxrequest.source().aggregation(AggregationBuilders.terms(BrandAggMyName)

//自定义聚合名称为BrandAggMyName.field(brand).size(10));//发出请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//【解析】//解析聚合结果Aggregations

xxaggregations

xxresponse.getAggregations();//根据聚合名称获取聚合结果Terms

xxbrandTerms

xxaggregations.get(BrandAggMyName);//获取桶(buckets)获取的是一个集合List?

extends

xxbrandTerms.getBuckets();//遍历集合取出每一个bucketfor

(Terms.Bucket

xxbucket.getKeyAsString();System.out.println(key);}

第二步:

中搜索页面的品牌、城市等信息不应该是在页面写死而是通过聚合索引库中的酒店数据得来的

第一步:

//在给出来的案例图中左侧加深的字是key右侧浅灰的字是value并且右侧的value有多个值。

所以我们使用Map集合并且value是list集合

MapString,

SearchRequest(hotel);//准备DSL。

设置sizexxrequest.source().size(0);//准备DSL。

聚合语句。

对多个字段进行聚合buildAggregation(xxrequest);//发出请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//【解析】//解析聚合结果MapString,

ListString

xxresponse.getAggregations();//1、根据名称获取品牌的结果ListString

xxbrandList

getAggByName(xxaggregations,BrandAggMyName);//把品牌的结果信息放入mapyyresult.put(品牌,xxbrandList);//2、根据名称获取城市的结果ListString

xxcityList

getAggByName(xxaggregations,cityAggMyName);//把城市的结果信息放入mapyyresult.put(城市,xxcityList);//3、根据名称获取星级的结果ListString

xxstarList

getAggByName(xxaggregations,starAggMyName);//把星级的结果信息放入mapyyresult.put(星级,xxstarList);//返回yyresultreturn

yyresult;}

xxaggregations.get(kkaggName);//获取桶(buckets)获取的是一个集合List?

extends

xxbrandTerms.getBuckets();//遍历xxbuckets集合取出每一个key。

把取到的key放到xxbrandList集合ListString

xxbrandList

xxbucket.getKeyAsString();xxbrandList.add(xxkey);}return

xxbrandList;

{xxrequest.source().aggregation(AggregationBuilders.terms(BrandAggMyName)

//自定义聚合名称为BrandAggMyName.field(brand).size(100)//聚合结果限制);xxrequest.source().aggregation(AggregationBuilders.terms(cityAggMyName).field(city).size(100));xxrequest.source().aggregation(AggregationBuilders.terms(starAggMyName).field(starName).size(100));

抽取代码成为方法

在HotelDemoApplicationTests添加如下。

并运行contextLoads方法

Autowired

hotelService.xxfilters();System.out.println(filters);

多条件聚合

1、可以看到请求参数与之前search时的RequestParam完全一致这是在限定聚合时的文档范围。

用户搜索“外滩”价格在300~600那聚合必须是在这个搜索条件基础上完成

3、修改IUserService#getFilters()方法添加RequestParam参数

3、修改getFilters方法的业务聚合时添加query条件

具体操作如下

把IHotelService接口修改为如下原本xxfilters方法是没有参数的现在我们要给xxfilters方法添加参数

package

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

com.baomidou.mybatisplus.extension.service.IService;

import

希尔顿]}*///在给出来的案例图中左侧加深的字是key右侧浅灰的字是value并且右侧的value有多个值。

所以我们使用Map集合并且value是list集合MapString,

ListString

cn.itcast.hotel.service.impl;import

cn.itcast.hotel.mapper.HotelMapper;

import

cn.itcast.hotel.pojo.PageResult;

import

cn.itcast.hotel.pojo.RequestParams;

import

cn.itcast.hotel.service.IHotelService;

import

com.baomidou.mybatisplus.core.toolkit.CollectionUtils;

import

com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import

org.elasticsearch.action.search.SearchRequest;

import

org.elasticsearch.action.search.SearchResponse;

import

org.elasticsearch.client.RequestOptions;

import

org.elasticsearch.client.RestClient;

import

org.elasticsearch.client.RestHighLevelClient;

import

org.elasticsearch.common.geo.GeoPoint;

import

org.elasticsearch.common.unit.DistanceUnit;

import

org.elasticsearch.index.query.BoolQueryBuilder;

import

org.elasticsearch.index.query.QueryBuilders;

import

org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;

import

org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;

import

org.elasticsearch.search.SearchHit;

import

org.elasticsearch.search.SearchHits;

import

org.elasticsearch.search.aggregations.AggregationBuilders;

import

org.elasticsearch.search.aggregations.Aggregations;

import

org.elasticsearch.search.aggregations.bucket.terms.Terms;

import

org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;

import

org.elasticsearch.search.fetch.subphase.highlight.HighlightField;

import

org.elasticsearch.search.sort.SortBuilders;

import

org.elasticsearch.search.sort.SortOrder;

import

org.springframework.beans.factory.annotation.Autowired;

import

org.springframework.stereotype.Service;

import

{Autowired//注入在引导类声明好的Beanprivate

RestHighLevelClient

{//准备Request对象要查询哪个索引库SearchRequest

request

SearchRequest(hotel);buildBasicQuery(params,

request);//【分页功能】int

params.getPage();//前端传过来的当前页面值注意为了参与运算我们将原来的Integer类型拆箱为int类型不用包装类int

size

params.getSize();//前端传过来的每页大小。

其实拆箱就是把默认的包装类类型改成基本类型request.source().from((page

size).size(size);//【地理排序功能】String

location

params.getLocation();//对前端传的location进行判断是否为空if

(location

!location.equals()){//sort排序指定是geoDistanceSort地理坐标排序要排序的字段是location中心点是new

GeoPoint(location)request.source().sort(SortBuilders.geoDistanceSort(location,new

GeoPoint(location)).order(SortOrder.ASC)

//升序排序.unit(DistanceUnit.KILOMETERS)

//地理坐标的单位);}//发送请求。

下面那行的search报红线我们不能抛出要捕获一下SearchResponse

response

RequestOptions.DEFAULT);//调用下面抽取后的方法我们是把解析的代码抽取出去了。

并把解析作为结果返回return

catch

{//【构建BooleanQuery下面那行的boolQuery是原始查询】BoolQueryBuilder

boolQuery

QueryBuilders.boolQuery();//【关键字搜索】//判断前端传的key是否为空避免空指针String

key

params.getKey();//前端传过来的搜索关键字//用的是must精确查找if

(key

{//matchAllQuery方法表示查es的全部文档不需要条件boolQuery.must(QueryBuilders.matchAllQuery());}

else

{//matchQuery表示按照分词查询es的文档需要条件这个条件我们进行了判空boolQuery.must(QueryBuilders.matchQuery(all,

key));}//【条件过滤】//城市term精确查找注意判空if(params.getCity()

null

!params.getCity().equals()){boolQuery.filter(QueryBuilders.termQuery(city,

params.getCity()));}//品牌term精确查找注意判空if(params.getBrand()

null

!params.getBrand().equals()){boolQuery.filter(QueryBuilders.termQuery(brand,

params.getBrand()));}//星级term精确查找注意判空if(params.getStarName()

null

!params.getStarName().equals()){boolQuery.filter(QueryBuilders.termQuery(starName,

params.getStarName()));}//价格range范围过滤注意判空。

gt表示大于gte表示大于等于lt表示小于lte表示小于等于if(params.getMinPrice()

null

null){boolQuery.filter(QueryBuilders.rangeQuery(price).gte(params.getMinPrice()).lte(params.getMaxPrice()));}//【构建functionScoreQuery实现算分查询。

对应的是广告置顶功能】FunctionScoreQueryBuilder

functionScoreQuery

//原始查询需要进行相关性算分的查询QueryBuilders.functionScoreQuery(boolQuery,//function

scorenew

FunctionScoreQueryBuilder.FilterFunctionBuilder[]{//一个具体的function

scorenew

FunctionScoreQueryBuilder.FilterFunctionBuilder(//过滤简单说就是满足isAD字段为true的文档就会参与算分QueryBuilders.termQuery(isAD,true),//要使用什么算分函数下面那行使用的是weightFactorFunction加权算分最终score分数越大排名就越前ScoreFunctionBuilders.weightFactorFunction(10)//算出来的最终score就会被乘10)});//【高亮显示】对查询出来的文档的特定字段进行高亮显示request.source().highlighter(new

HighlightBuilder().field(all).requireFieldMatch(true).preTags(em).postTags(/em));//这一步必须有request.source().query(functionScoreQuery);return

request;}//这个方法就是我们抽取出来的负责解析的private

PageResult

searchHits.getTotalHits().value;System.out.println(共搜索到total条文档(数据));//获取hits数组SearchHit[]

hits

searchHits.getHits();//遍历数组把hits数组的每个source取出来把遍历到的每条数据添加到lisi集合ListHotelDoc

hotels

hit.getSourceAsString();//此时可以直接打印也可以使用fastjson工具类进行反序列化从而转为HotelDoc类型HotelDoc类是我们写的实体类HotelDoc

hotelDoc

HotelDoc.class);//【地理坐标查询的解析】,通过getSortValues方法来获取排序值得到的是多个值也就是数组我们只需要一个值Object[]

sortValues

sortValues[0];//把拿到的sortValue返回到页面也就是需要把sortValue值放到HoteDochotelDoc.setDistance(sortValue);}//【解析】获取高亮结果MapString,

HighlightField

hit.getHighlightFields();//使用CollectionUtils工具类进行判空避免空指针if

(!CollectionUtils.isEmpty(xxhighlightFields)){//根据字段名获取高亮结果HighlightField

xxhighlightField

xxhighlightFields.get(all);//判断name不为空if

(xxhighlightField

xxhighlightField.getFragments()[0].string();//覆盖非高亮结果hotelDoc.setName(xxname);}}hotels.add(hotelDoc);}//封装返回return

new

PageResult(total,hotels);}//--------------------------------------------下面是多条件聚合-----------------------------------------Overridepublic

MapString,

SearchRequest(hotel);//添加query查询信息也就是限定聚合的范围buildBasicQuery(params,

xxrequest);//准备DSL。

设置sizexxrequest.source().size(0);//准备DSL。

聚合语句。

对多个字段进行聚合buildAggregation(xxrequest);//发出请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//【解析】//解析聚合结果MapString,

ListString

xxresponse.getAggregations();//1、根据名称获取品牌的结果ListString

xxbrandList

getAggByName(xxaggregations,BrandAggMyName);//把品牌的结果信息放入mapyyresult.put(brand,xxbrandList);//2、根据名称获取城市的结果ListString

xxcityList

getAggByName(xxaggregations,cityAggMyName);//把城市的结果信息放入mapyyresult.put(city,xxcityList);//3、根据名称获取星级的结果ListString

xxstarList

getAggByName(xxaggregations,starAggMyName);//把星级的结果信息放入mapyyresult.put(starName,xxstarList);//返回yyresultreturn

yyresult;}

xxaggregations.get(kkaggName);//获取桶(buckets)获取的是一个集合List?

extends

xxbrandTerms.getBuckets();//遍历xxbuckets集合取出每一个key。

把取到的key放到xxbrandList集合ListString

xxbrandList

xxbucket.getKeyAsString();xxbrandList.add(xxkey);}return

xxbrandList;}//把聚合的代码抽取出来private

void

{xxrequest.source().aggregation(AggregationBuilders.terms(BrandAggMyName)

//自定义聚合名称为BrandAggMyName.field(brand).size(100)//聚合结果限制);xxrequest.source().aggregation(AggregationBuilders.terms(cityAggMyName).field(city).size(100));xxrequest.source().aggregation(AggregationBuilders.terms(starAggMyName).field(starName).size(100));}//--------------------------------------------上面是多条件聚合-----------------------------------------}

第四步:

重新运行HotelDemoApplication引导类浏览器查看是否功能正常

实用篇-ES-自动补全

下载下来是py.zip压缩包解压之后得到一个py文件夹把这个文件夹上传到你CentOS7的

/var/lib/docker/volumes/es-plugins/_data

第二步:

#启动kibana容器。

由于在网页使用DSL去操作es所以就需要kibana

浏览器访问

输入如下DSL语句会把文本的每个中文分成对应的拼音也会把整段文本的拼音首字母拼在一起

POST

刚才我们看到了拼音分词器的效果但是并不能用于生产环境还存在一些问题

Elasticsearch中的分词器(analyzer)的组成包含三部分

●character

在tokenizer之前对文本进行处理。

例如删除字符、替换字符

●tokenizer:

将tokenizer输出的词条做进一步处理。

例如大小写转换、同义词处理、拼音处理等

拼音分词器不会分词那么就先用ik分词器分好词后再交给拼音分词器

解决如下注意必须在创建索引库的时候做例如创建一个名为test的索引库

PUT

过滤器类型这里是pinyinkeep_full_pinyin:

false,

//不允许单个字来分拼音keep_joined_full_pinyin:

true,

//全拼给什么字或词就把什么写成拼音keep_original:

true,

//保留中文解决了拼音分词器分词后全是拼音的问题limit_first_letter_length:

true,none_chinese_pinyin_tokenize:

false}}}}

具体操作如下注意自定义分词器只对当前索引库有效因为我们是写在settings属性里面

systemctl

#启动kibana容器因为我们需要在浏览器执行DSL语句去操作es

第一步:

输入如下DSL语句表示创建名为test的索引库实现自定义分词器也就是对分词器做一些限制、开放一些限制

PUT

true,limit_first_letter_length:

true,none_chinese_pinyin_tokenize:

false}}}},mappings:

测试自定义分词器(也就是ik分词器拼音分词器)。

第一种测试方法

POST

测试自定义分词器(也就是ik分词器拼音分词器)。

第二种测试方法通过往test库插入文档、然后查询插入的文档的方式来测试

POST

其实还是有个问题如下图在搜索中文时却搜索出了同音字我们不想搜索出同音字解决在下一节会学

因此字段在创建倒排索引时应该用自定义分词器(my_analyzer);

字段在搜索时应该使用is_smart分词器。

对应的DSL语句如下

PUT

//在mappings里面指定两个分词器properties:

{name:

//analyzer表示创建索引时使用这个my_analyzer分词器search_analyzer:

ik_smart

//search_analyzer表示在搜索时使用这个search_analyzer分词器}}}

具体操作如下

#启动kibana容器因为我们需要在浏览器执行DSL语句去操作es

第一步:

输入如下DSL语句重新创建test索引库这次我们在mappings属性里面多添加了一个分词器search_analyzer:

ik_smart

true,limit_first_letter_length:

true,none_chinese_pinyin_tokenize:

false}}}},mappings:

测试。

同样是往test库创建两个文档然后使用中文去匹配如果不出现同音字就表示验证通过

POST

Suggester查询来实现自动补全功能。

这个查询会匹配以用户输入内容开头的词条并返回。

为了提高补全查询的效率对于文档中字段的类型有一些约束

查询语法如下

//不是query而是suggest表示自动补全title_suggest:

title,

要自动补全查询的字段注意该字段必须是completion类型skip_duplicates:

true,

具体操作如下注意自定义分词器只对当前索引库有效因为我们是写在settings属性里面

systemctl

#启动kibana容器因为我们需要在浏览器执行DSL语句去操作es

第一步:

输入如下DSL语句表示重新创建test索引库并为索引库添加3条文档数据

PUT

我们接下来要把自动补全功能应用在前面学习的黑马旅游案例中这节我们就先学习如下几点

3、索引库添加一个新字段suggestion类型为completion类型使用自定义的分词器

第一步:

输入如下DSL语句。

重新创建一个hotel索引库只是创建一个空的hotel索引库

PUT

true,limit_first_letter_length:

true,none_chinese_pinyin_tokenize:

false}}}},mappings:

由于某个文档(酒店)可能包括多个商圈如下图所以在下一步我们会在HotelDoc类进行切割后存入suggestion

第五步:

在上面的DSL语句中我们还多创建了一个suggestion字段所以对应的我们需要在黑马旅游案例的java代码中修改实体类

package

lombok.NoArgsConstructor;import

java.util.ArrayList;

pic;//地理位置查询相关的字段。

distance字段用于保存解析后的距离值private

Object

isAD;//用于自动补全的类型该类型在es中必须是completion类型该类型在java中就要写成数组(我们写成集合)private

ListString

hotel.getStarName();this.business

hotel.getBusiness();this.location

hotel.getLatitude()

hotel.getPic();if(this.business.contains(/)){//如果同一文档的business商圈有多个为了自动补全的分词效果更明显我们需要分割一下String[]

arr

this.business.split(/);this.suggestion

new

ArrayList();this.suggestion.add(this.brand);//Collections是工具类可以把数组数据逐个放进suggestion集合Collections.addAll(this.suggestion,arr);}else{//自动补全我们可以让品牌商圈来做自动补全用户触发自动补全时补全到的就是品牌商圈字段的文档this.suggestion

Arrays.asList(this.brand,this.business);}}

打开hotel-demo项目(继续做前面学的黑马旅游案例)把HotelDoc类修改为如下

package

lombok.NoArgsConstructor;import

java.util.ArrayList;

pic;//地理位置查询相关的字段。

distance字段用于保存解析后的距离值private

Object

isAD;//用于自动补全的类型该类型在es中必须是completion类型该类型在java中就要写成数组(我们写成集合)private

ListString

hotel.getStarName();this.business

hotel.getBusiness();this.location

hotel.getLatitude()

hotel.getPic();if(this.business.contains(/)){//如果同一文档的business商圈有多个为了自动补全的分词效果更明显我们需要分割一下String[]

arr

this.business.split(/);this.suggestion

new

ArrayList();this.suggestion.add(this.brand);//Collections是工具类可以把数组数据逐个放进suggestion集合Collections.addAll(this.suggestion,arr);}else{//自动补全我们可以让品牌商圈来做自动补全用户触发自动补全时补全到的就是品牌商圈字段的文档this.suggestion

Arrays.asList(this.brand,this.business);}}

第六步:

运行HotelDocumentTest类的testBulkRequest方法重新把mysql的数据导入到es的hotel索引库

第七步:

在HotelSearchTest类添加如下并运行xxtestSuggest方法

Test

{//准备Request查的是哪个索引库SearchRequest

xxrequest

SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new

SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(h)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析结果System.out.println(xxresponse);

第二步:

结果解析。

把HotelSearchTest类的xxtestSuggest方法修改为如下并运行xxtestSuggest方法

Test

{//准备Request查的是哪个索引库SearchRequest

xxrequest

SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new

SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,

//为这个自定义补全查询起一个名字//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(h)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析结果Suggest

xxsuggest

xxresponse.getSuggest();//根据补全查询名称获取补全结果CompletionSuggestion

xxsuggestions

xxsuggest.getSuggestion(zidingyizidongbuquanmingzi);//获取xxsuggestionsListCompletionSuggestion.Entry.Option

xxoptions

xxsuggestions.getOptions();//遍历for

(CompletionSuggestion.Entry.Option

xxoption

xxoption.getText().toString();System.out.println(text);}

hm-搜索框自动补全查询

我们刚刚使用RestAPI实现了自动补全查询现在我们要把这个功能应用在

黑马旅游案例

{//准备Request查的是哪个索引库SearchRequest

xxrequest

SearchRequest(hotel);//准备DSL自动补全查询也就是suggestion查询xxrequest.source().suggest(new

SuggestBuilder().addSuggestion(zidingyizidongbuquanmingzi,

//为这个自定义补全查询起一个名字//要自动补全哪个字段SuggestBuilders.completionSuggestion(suggestion)//模拟用户在搜索框输入查找.prefix(xxprefix)//跳过重复不把重复的结果展示.skipDuplicates(true)//查多少条数据.size(10)));//发起请求SearchResponse

xxresponse

RequestOptions.DEFAULT);//解析结果Suggest

xxsuggest

xxresponse.getSuggest();//根据补全查询名称获取补全结果CompletionSuggestion

xxsuggestions

xxsuggest.getSuggestion(zidingyizidongbuquanmingzi);//获取xxsuggestionsListCompletionSuggestion.Entry.Option

xxoptions

xxsuggestions.getOptions();//准备好一个集合当自动补全时相关词就会在这个集合并返回给前端ListString

xxlist

ArrayList(xxoptions.size());//遍历for

(CompletionSuggestion.Entry.Option

xxoption

xxoption.getText().toString();//把自动补全时查到的相关词放到我们准备好的集合里面xxlist.add(text);}return

xxlist;}

xxgetSuggestions(RequestParam(key)

String

hotelService.xxgetSuggestions(xxprefix);

第四步:

测试。

查看自动补全查询是否在搜索框生效访问http://localhost:8089/

实用篇-ES-数据同步



SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback