Elasticsearch是一个开源的分布式、RESTful 风格的搜索和数据分析引擎,它的底层是开源库Apache Lucene。
Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库——无论是开源还是私有,但它也仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理,因为Lucene 非常复杂。
为了解决Lucene使用时的繁复性,于是Elasticsearch便应运而生。它使用 Java 编写,内部采用 Lucene 做索引与搜索,但是它的目标是使全文检索变得更简单,简单来说,就是对Lucene 做了一层封装,它提供了一套简单一致的 RESTful API 来帮助我们实现存储和检索。
当然,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确地形容:
谁在使用:
ELK技术,elasticsearch+logstash+kibana
Elasticsearch是一个实时分布式搜索和分析引擎。它让你以前所未有的速度处理大数据成为可能。
它用于全文搜索、结构化搜索、分析
以及将这三者混合使用:
全文搜索并高亮关键字
,以及输入实时搜索(search-asyou-type)和搜索纠错(did-you-mean)等搜索建议功能
。用户日志和社交网络数据
提供给他们的编辑以实时的反馈
,以便及时了解公众对新发表的文章的回应。全文搜索与地理位置查询
,以及more-like-this功能来找到相关的问题和答案。检索1300亿行
的代码。但是Elasticsearch不仅用于大型企业,它还让像DataDog以及klout这样的创业公司将最初的想法变成可扩展的解决方案。
Elasticsearch可以在你的笔记本上运行,也可以在数以百计的服务器上处理PB级别的数据
。
Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎
。无论在开源还是专有领域,Lucene
可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库
。
但是,Lucene只是一个库
。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中
,更糟糕的是,Lucene非常复杂
,需要深入了解检索的相关知识来理解它是如何工作的
。
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能
,但是它的目的是通过简单的 RESTful API 来隐藏Lucene的复杂性
,从而让全文搜索变得简单
。
Solr是Apache 下的一个顶级开源项目,采用Java开发
,它是基于Lucene的全文搜索服务器
。Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展,并对索引、搜索性能进行了优化
Solr可以独立运行,运行在Jetty、Tomcat
等这些Servlet容器中,Solr 索引的实现方法很简单,用POST 方法向 Solr 服务器发送一个描述 Field 及其内容的XML 文档,Solr根据xml文档添加、删除、更新索引
。Solr搜索只需要发送 HTTP GET 请求,然后对 Solr返回xml、json等格式的查询结果进行解析 ,组织页面布局。Soir不提供构建UI的功能,Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
solr是基于lucene开发企业级搜索服务器,实际上就是封装了lucene。
Solr是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口
。用户可以通过http请求,向搜索引擎服务器提交一定格式的文件,生成索引;也可以通过提出查找请求,并得到返回结果。
Lucene是apache软件基金会4 jiakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检素引擎的架构,提供了完整的查询引擎和索引引擎 ,部分文本分析引擎(英文与德文两种西方语言)。
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其本身而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库
。人们经常提到信息检索程序库,虽然与搜索引擎有关,但不应该将信息检索程序库与搜索引擎相混淆。
Lucene是一个全文检索引擎的架构。那什么是全文搜索引擎?
全文搜索引擎是名副其实的搜索引擎,国外具代表性的有Google、Fast/AllTheweb、AltaVista、Inktomi、Teoma、WiseNut等,国内著名的有百度(Baidu)。它们都是通过从互联网上提取的各个网站的信息(以网页文字为主)而建立的数据库中,检索与用户查询条件匹配的相关记录,然后按一定的排列顺序将结果返回给用户 ,因此他们是真正的搜素引擎。
从搜素结果来源的角度,全文搜素引擎又可细分为两种,一种是拥有自己的检索程序(Indexer),俗称"蜘蛛”( Spider )程序或’机器人〞(Robot)程序,并自建网页数据库,搜索结果直接从自身的数据库中调用,如上面提到的7家引擎;另一种则是租用其他引擎的数据库,并按自定的格式排列搜索结果,如Lycos引擎.
即实时性查询快
,用于facebook新浪等搜索ELK是Elasticsearch、Logstash、kibana三大开源框架首字母大写简称。市面上也被成为Elastic stack。
其中Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用Elasticsearch作为底层支持框架,可见Elasticsearch提供的搜索能力确实强大,市面上很多时候我们简称Elasticsearch为es。
Logstash是ELK的中央数据流引擎,用于从不同目标( 文件/数据存储/MQ)收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。
kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch素引中的数据。使用Kibana可以通过各种图表进行高级数据分析及展示。Kibana止海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示Elasticsearch查询动态。设置Kibana非常简单。无需编码或者额外的基础架构 ,几分钟内就可以完成Kibana安装并启动Elasticsearch素乌监测。kibana可以将elasticsearch的数据通过友好的页面展示出来,提供实时分析的功能。
市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。
总结一下就是:收集清洗数据——>建立索引,储存——>Kibana分析
目录介绍:
bin 启动文件
config 配置文件
log4j2 日志配置文件
jvm. options java 虚拟机相关配置
elasticsearch.yml
lib 相关jar包
logs 日志
modules 功能模块
plugins 插件
从Docker Hub拉取ElasticSearch:
test-MBP:~ test$ docker search elasticsearch
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
elasticsearch Elasticsearch is a powerful open source sear… 5347 [OK]
nshou/elasticsearch-kibana Elasticsearch-7.15.1 Kibana-7.15.1 134 [OK]
mobz/elasticsearch-head elasticsearch-head front-end and standalone … 82
elastichq/elasticsearch-hq Official Docker image for ElasticHQ: Elastic… 77 [OK]
itzg/elasticsearch Provides an easily configurable Elasticsearc… 71 [OK]
elastic/elasticsearch The Elasticsearch Docker image maintained by… 57
taskrabbit/elasticsearch-dump Import and export tools for elasticsearch 27 [OK]
lmenezes/elasticsearch-kopf elasticsearch kopf 18 [OK]
barnybug/elasticsearch Latest Elasticsearch 1.7.2 and previous rele… 17 [OK]
justwatch/elasticsearch_exporter Elasticsearch stats exporter for Prometheus 17
blacktop/elasticsearch Alpine Linux based Elasticsearch Docker Image 16 [OK]
esystemstech/elasticsearch Debian based Elasticsearch packing for Lifer… 15
monsantoco/elasticsearch ElasticSearch Docker image 11 [OK]
mesoscloud/elasticsearch [UNMAINTAINED] Elasticsearch 9 [OK]
arcseldon/elasticsearch-kibana-marvel-sense ElasticSearch-2.3.5 Kibana-4.5.3 Marvel Sense 8
centerforopenscience/elasticsearch Elasticsearch 4 [OK]
dtagdevsec/elasticsearch elasticsearch 4 [OK]
barchart/elasticsearch-aws Elasticsearch AWS node 3
axway/elasticsearch-docker-beat "Beat" extension to read logs of containers … 1 [OK]
phenompeople/elasticsearch Elasticsearch is a powerful open source sear… 1 [OK]
kuzzleio/elasticsearch Elasticsearch container based on Alpine Linu… 1 [OK]
dsteinkopf/elasticsearch-ingest-attachment elasticsearch + ingest-attachment to be used… 1 [OK]
jetstack/elasticsearch-pet An elasticsearch image for kubernetes PetSets 1 [OK]
wreulicke/elasticsearch elasticsearch 0 [OK]
travix/elasticsearch-kubernetes To run ElasticSearch in kubernetes and expor… 0 [OK]
test-MBP:~ test$ docker pull elasticsearch:7.8.0
7.8.0: Pulling from library/elasticsearch
524b0c1e57f8: Downloading
7a096b8f20be: Download complete
9dd8117fbfec: Download complete
335891dbdd0e: Download complete
dfce820717b4: Download complete
82d3459719f7: Download complete
2e79822fece3: Download complete
2f80b981dd6a: Download complete
05f8a08da0ba: Download complete
7.8.0: Pulling from library/elasticsearch
524b0c1e57f8: Pull complete
7a096b8f20be: Pull complete
9dd8117fbfec: Pull complete
335891dbdd0e: Pull complete
dfce820717b4: Pull complete
82d3459719f7: Pull complete
2e79822fece3: Pull complete
2f80b981dd6a: Pull complete
05f8a08da0ba: Pull complete
Digest: sha256:945f80960f2ad1bd4b88bd07a9ba160d22d4285bbc8a720d052379006d5d57a6
Status: Downloaded newer image for elasticsearch:7.8.0
docker.io/library/elasticsearch:7.8.0
test-MBP:~ test$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
elasticsearch 7.8.0 121454ddad72 17 months ago 810MB
test-MBP:~ test$
新建网络,如果需要安装kibana、logstash等其他,需要创建一个网络,名字任意取,让它们在同一个网络,使得es和kibana、logstash通信。
docker network create -d bridge 网络名称
-d:参数指定 Docker 网络类型,有 bridge、overlay。其中 overlay 网络类型用于 Swarm mode。
test-MBP:~ test$ docker network create -d bridge es-net
1a3cb60832111771b6eda580c4bf5c03608b34db0a482213ca0691a45c2f0762
test-MBP:~ test$ docker network ls
NETWORK ID NAME DRIVER SCOPE
0ed40a7e5ecd bridge bridge local
1a3cb6083211 es-net bridge local
65bb643181ef host host local
d1584fea4569 none null local
test-MBP:~ test$
在宿主机上创建存储elasticsearch配置文件、数据以及plugins的数据卷:
test-MBP:~ test$ cd /Users/test/Documents/Development-Configuration
test-MBP:Development-Configuration test$ sudo mkdir -p /myelasticsearchdata/config
test-MBP:Development-Configuration test$ sudo mkdir -p /myelasticsearchdata/data
test-MBP:Development-Configuration test$ sudo mkdir -p /myelasticsearchdata/plugins
在宿主机上配置elasticsearch的elasticsearch.yml配置文件:
test-MBP:Development-Configuration test$ cd /Users/test/Documents/Development-Configuration/myelasticsearchdata/config
test-MBP:config test$ echo "network.host: 0.0.0.0" >> elasticsearch.yml
test-MBP:config test$ chmod -R 777 /Users/test/Documents/Development-Configuration/myelasticsearchdata/config/elasticsearch.yml
创建elasticsearch容器并运行容器:
test-MBP:~ test$ docker run --name elasticsearch7.8.0 --net es-net -p 9200:9200 -p 9300:9300 -e ES_JAVA_OPTS="-Xms200m -Xmx200m" -e "discovery.type=single-node" -v /Users/test/Documents/Development-Configuration/myelasticsearchdata/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /Users/test/Documents/Development-Configuration/myelasticsearchdata/data:/usr/share/elasticsearch/data -v /Users/test/Documents/Development-Configuration/myelasticsearchdata/plugins:/usr/share/elasticsearch/plugins -it elasticsearch:7.8.0
72ac331854e39ffd67d225b68e894b9bf7af8aebfa45202a6ef4570cb1e40e3f
test-MBP:~ test$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
72ac331854e3 elasticsearch:7.8.0 "/tini -- /usr/local…" 4 minutes ago Up 4 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch7.8.0
test-MBP:~ test$
----------------------------------------------
命令说明:
test-MBP:~ test$ docker run --name elasticsearch7.8.0 # 给容器取一个别名
--net es-net # 指定容器的网络连接
-p 9200:9200 -p 9300:9300 # 暴露宿主机上的9200、9300 端口映射到容器的 9200、9300 端口上,9200:ES节点和外部通讯使用,9300:ES节点之间的通讯使用
-e ES_JAVA_OPTS="-Xms200m -Xmx200m" # 指定JVM 内存占用 200m,可根据实际情况调整
-e "discovery.type=single-node" # 指定为单节点模式
-v /Users/test/Documents/Development-Configuration/myelasticsearchdata/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml # 创建数据卷,将宿主机上myelasticsearchdata/config/elasticsearch.yml 映射到容器里的 elasticsearch.yml 上
-v /Users/test/Documents/Development-Configuration/myelasticsearchdata/data:/usr/share/elasticsearch/data
-v /Users/test/Documents/Development-Configuration/myelasticsearchdata/plugins:/usr/share/elasticsearch/plugins
-di elasticsearch:7.8.0 /bin/bash # -d: 后台运行容器,并返回容器ID;i: 以交互模式运行容器,通常与 -t 同时使用;-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;后面的/bin/bash的作用是表示载入容器后运行bash ,docker中必须要保持一个进程的运行,要不然整个容器启动后就会马上kill itself,这样当你使用docker ps 查看启动的容器时,就会发现你刚刚创建的那个容器并不在已启动的容器队列中。这个/bin/bash就表示启动容器后启动bash。
进入elasticsearch7.8.0容器进行配置elasticsearch.yml文件:
test-MBP:~ test$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
72ac331854e3 elasticsearch:7.8.0 "/tini -- /usr/local…" 3 hours ago Up 3 hours 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch7.8.0
test-MBP:~ test$ docker exec -it 72ac331854e3 /bin/bash
[root@72ac331854e3 elasticsearch]# pwd
/usr/share/elasticsearch
[root@72ac331854e3 elasticsearch]# ls
LICENSE.txt NOTICE.txt README.asciidoc bin config data jdk lib logs modules plugins
[root@72ac331854e3 elasticsearch]# cd config
[root@72ac331854e3 config]# ls
elasticsearch.yml jvm.options jvm.options.d log4j2.properties role_mapping.yml roles.yml users users_roles
[root@72ac331854e3 config]# cat elasticsearch.yml
http.host: 0.0.0.0
[root@72ac331854e3 config]# vi elasticsearch.yml
[root@72ac331854e3 config]# exit
elasticsearch.yml文件:
# 集群名称,es服务会通过广播方式自动连接在同一网段下的es服务,通过多播方式进行通信,同一网段下可以有多个集群,通过集群名称这个属性来区分不同的集群
cluster.name: elasticsearch
# 当前配置所在机器的节点名,你不设置就默认随机指定一个name列表中名字,该name列表在es的jar包中config文件夹里name.txt文件中,其中有很多作者添加的有趣名字。
node.name: "node-1"
# 指定该节点是否有资格被选举成为node(注意这里只是设置成有资格, 不代表该node一定就是master),默认是true,es是默认集群中的第一台机器为master,如果这台机挂了就会重新选举master。
node.master: true
# 指定该节点是否存储索引数据,默认为true。
node.data: true
# 设置默认索引分片个数,默认为5片。
#index.number_of_shards: 5
# 设置默认索引副本个数,默认为1个副本。如果采用默认设置,而你集群只配置了一台机器,那么集群的健康度为yellow,也就是所有的数据都是可用的,但是某些复制没有被分配。
#index.number_of_replicas: 1
# 设置配置文件的存储路径,默认是es根目录下的config文件夹。
#path.conf: /path/to/conf
# 设置索引数据的存储路径,默认是es根目录下的data文件夹,可以设置多个存储路径,用逗号隔开,例:path.data: /path/to/data1,/path/to/data2
#path.data: /path/to/data
# 设置临时文件的存储路径,默认是es根目录下的work文件夹。
#path.work: /path/to/work
# 设置日志文件的存储路径,默认是es根目录下的logs文件夹
#path.logs: /path/to/logs
# 设置插件的存放路径,默认是es根目录下的plugins文件夹, 插件在es里面普遍使用,用来增强原系统核心功能。
#path.plugins: /path/to/plugins
# 设置为true来锁住内存不进行swapping。因为当jvm开始swapping时es的效率 会降低,所以要保证它不swap,可以把ES_MIN_MEM和ES_MAX_MEM两个环境变量设置成同一个值,并且保证机器有足够的内存分配给es。 同时也要允许elasticsearch的进程可以锁住内存,linux下启动es之前可以通过`ulimit -l unlimited`命令设置。
#bootstrap.mlockall: true
# 设置绑定的ip地址,可以是ipv4或ipv6的,默认为127.0.0.1。
#network.bind_host: 192.168.0.1
# 设置其它节点和该节点交互的ip地址,如果不设置它会自动判断,值必须是个真实的ip地址。
#network.publish_host: 192.168.0.1
# 这个参数是用来同时设置bind_host和publish_host上面两个参数。设置为0.0.0.0以后就可以让任何计算机节点访问到了
network.host: 0.0.0.0
# 支持跨域,跨域配置是为了kibana,head连接
http.cors.enabled: true
http.cors.allow-origin: "*"
# 设置为true锁住内存,当服务混合部署了多个组件及服务时,应开启此操作,允许es占用足够多的内存。
bootstrap.memory_lock: false
# es优化,是否支持过滤掉系统调用
bootstrap.system_call_filter: false
# 设置节点之间交互的tcp端口,默认是9300。
transport.tcp.port: 9300
# 设置是否压缩tcp传输时的数据,默认为false,不压缩。
transport.tcp.compress: true
# 设置对外服务的http端口,默认为9200。
http.port: 9200
# 设置内容的最大容量,默认100mb
http.max_content_length: 100mb
# 是否使用http协议对外提供服务,默认为true,开启。
#http.enabled: true
# gateway的类型,默认为local即为本地文件系统,可以设置为本地文件系统,分布式文件系统,hadoop的HDFS,和amazon的s3服务器等。
#gateway.type: local
# 设置集群中N个节点启动时进行数据恢复,默认为1。
gateway.recover_after_nodes: 1
# 设置初始化数据恢复进程的超时时间,默认是5分钟。
gateway.recover_after_time: 5m
# 设置这个集群中节点的数量,默认为2,一旦这N个节点启动,就会立即进行数据恢复。
gateway.expected_nodes: 2
# 初始化数据恢复时,并发恢复线程的个数,默认为4。
cluster.routing.allocation.node_initial_primaries_recoveries: 4
# 添加删除节点或负载均衡时并发恢复线程的个数,默认为4。
cluster.routing.allocation.node_concurrent_recoveries: 4
# 设置数据恢复时限制的带宽,如入100mb,默认为0,即无限制。
#indices.recovery.max_size_per_sec: 0
# 设置这个参数来限制从其它分片恢复数据时最大同时打开并发流的个数,默认为5。
#indices.recovery.concurrent_streams: 5
# 设置这个参数来保证集群中的节点可以知道其它N个有master资格的节点。默认为1,对于大的集群来说,可以设置大一点的值(2-4)
discovery.zen.minimum_master_nodes: 1
# 设置集群中自动发现其它节点时ping连接超时时间,默认为3秒,对于比较差的网络环境可以高点的值来防止自动发现时出错。
#discovery.zen.ping.timeout: 3s
# 设置是否打开多播发现节点,默认是true。
#discovery.zen.ping.multicast.enabled: false
# 设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。
discovery.zen.ping.unicast.hosts: ["host1", "host2:port", "host3[portX-portY]"]
访问 http://localhost:9200
能看到 ElasticSearch 信息就运行成功了:
elasticsearch-analysis-ik
版本要和 elasticsearch
版本对应,下载地址elasticsearch-analysis-ik-7.8.0.zip
上传到Linux服务器,在Linux服务器中拷贝到 Docker
里 elasticsearch
的 容器中,具体路径 /usr/share/elasticsearch/plugins
目录下,最后重启 elasticsearch
容器测试即可。test-MBP:~ test$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d330c700ac4a elasticsearch:7.8.0 "/tini -- /usr/local…" About an hour ago Exited (143) 44 minutes ago elasticsearch7.8.0
test-MBP:~ test$ docker exec -it d330c700ac4a /bin/bash
Error response from daemon: Container d330c700ac4abff139a68d9ccf9fb9afffc4e5edfe1ef9073b024f089189d2f4 is not running
test-MBP:~ test$ docker exec -it d330c700ac4a /bin/bash
[root@d330c700ac4a elasticsearch]# ls
LICENSE.txt NOTICE.txt README.asciidoc bin config data jdk lib logs modules plugins
[root@d330c700ac4a elasticsearch]# cd plugins/
[root@d330c700ac4a plugins]# ls
elasticsearch-analysis-ik-7.8.0
从Docker Hub拉取elasticsearch-head:
test-MBP:~ test$ docker pull mobz/elasticsearch-head:5
5: Pulling from mobz/elasticsearch-head
75a822cd7888: Pull complete
57de64c72267: Pull complete
4306be1e8943: Pull complete
871436ab7225: Pull complete
0110c26a367a: Pull complete
1f04fe713f1b: Pull complete
723bac39028e: Pull complete
7d8cb47f1c60: Pull complete
7328dcf65c42: Pull complete
b451f2ccfb9a: Pull complete
304d5c28a4cf: Pull complete
4cf804850db1: Pull complete
Digest: sha256:55a3c82dd4ba776e304b09308411edd85de0dc9719f9d97a2f33baa320223f34
Status: Downloaded newer image for mobz/elasticsearch-head:5
docker.io/mobz/elasticsearch-head:5
test-MBP:~ test$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
elasticsearch 7.8.0 121454ddad72 17 months ago 810MB
mobz/elasticsearch-head 5 b19a5c98e43b 4 years ago 824MB
test-MBP:~ test$
创建elasticsearch-head容器并运行容器:
test-MBP:~ test$ docker run --restart=always --name elasticsearch-head -it -p 9100:9100 mobz/elasticsearch-head:5
Running "connect:server" (connect) task
Waiting forever...
Started connect web server on http://localhost:9100
----------------------------------------------
命令说明:
docker run --restart=always # 当 Docker 重启时,容器能自动启动。–restart具体参数值详细信息:no - 容器退出时,不重启容器;on-failure - 只有在非0状态退出时才从新启动容器;always - 无论退出状态是如何,都重启容器;
--name elasticsearch-head # 给容器取一个别名
-it # i: 以交互模式运行容器,通常与 -t 同时使用;-t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
-p 9100:9100 mobz/elasticsearch-head:5 # 暴露宿主机上的 9100 端口映射到容器的 9100 端口上
访问 http://localhost:9100
可见如下图就成功了:
elasticsearch-head 数据浏览无显示解决办法点击此处
这个 Kibana
版本也是要和 elasticsearch
版本对应,从Docker Hub拉取elasticsearch-head:
test-MBP:~ test$ docker pull kibana:7.8.0
7.8.0: Pulling from library/kibana
524b0c1e57f8: Already exists
6c3c85c9f549: Pull complete
75826be8d25e: Pull complete
f4951867bd56: Pull complete
e0af11a7b96b: Pull complete
1f81b8aaeaf3: Pull complete
1c52e407927e: Pull complete
6ca195712ee9: Pull complete
94650bdbb5bf: Pull complete
c978c1a82f00: Pull complete
50fe1f68e816: Pull complete
Digest: sha256:326ac27cbc363e515e1b660d3117bbad3c5cd140db358ade251195c094db6fda
Status: Downloaded newer image for kibana:7.8.0
docker.io/library/kibana:7.8.0
test-MBP:~ test$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
kibana 7.8.0 df0a0da46dd1 17 months ago 1.29GB
elasticsearch 7.8.0 121454ddad72 17 months ago 810MB
mobz/elasticsearch-head 5 b19a5c98e43b 4 years ago 824MB
test-MBP:~ test$
在宿主机上创建存储kibana配置文件作为数据卷持久化:
test-MBP:~ test$ cd /Users/test/Documents/Development-Configuration
test-MBP:Development-Configuration test$ sudo mkdir -p mykibanadata
Password:
test-MBP:Development-Configuration test$ sudo chmod 777 mykibanadata
test-MBP:Development-Configuration test$ sudo echo "server.host: 0.0.0.0" >> /Users/leiliang/Documents/Development-Configuration/mykibanadata/config/kibana.yml
test-MBP:Development-Configuration test$ sudo echo "elasticsearch.hosts: http://192.168.124.2:9200" >> /Users/test/Documents/Development-Configuration/mykibanadata/kibana.yml
test-MBP:Development-Configuration test$ cat /Users/test/Documents/Development-Configuration/mykibanadata/config/kibana.yml
server.host: 0.0.0.0
elasticsearch.hosts: http://192.168.124.2:9200
test-MBP:Development-Configuration test$
----------------------------------------------
命令说明:
sudo mkdir -p /Users/test/Documents/Development-Configuration/mykibanadata # 创建挂载配置目录
sudo chmod 777 /Users/test/Documents/Development-Configuration/mykibanadata # 赋值读写执行权限
sudo echo "server.host: 0.0.0.0" >> /Users/test/Documents/Development-Configuration/mykibanadata/config/kibana.yml # kibana.yml存在就向该文件写入内容,不存在就创建该文件并写入内容
sudo echo "elasticsearch.hosts: http://0.0.0.0:9200" >> /Users/test/Documents/Development-Configuration/mykibanadata/config/kibana.yml # 192.168.124.2 表示宿主机elasticsearch的容器ip跟端口
创建kibana容器并运行容器:
test-MBP:~ test$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
kibana 7.8.0 df0a0da46dd1 17 months ago 1.29GB
elasticsearch 7.8.0 121454ddad72 17 months ago 810MB
mobz/elasticsearch-head 5 b19a5c98e43b 4 years ago 824MB
test-MBP:~ test$ docker run --name kibana7.8.0 -v /Users/test/Documents/Development-Configuration/mykibanadata/config/kibana.yml:/usr/share/kibana/config/kibana.yml -p 5601:5601 -it kibana:7.8.0
log [15:28:58.856] [warning][plugins-discovery] Expect plugin "id" in camelCase, but found: apm_oss
log [15:28:58.889] [warning][plugins-discovery] Expect plugin "id" in camelCase, but found: triggers_actions_ui
log [15:29:37.879] [info][plugins-service] Plugin "visTypeXy" is disabled.
log [15:29:37.880] [info][plugins-service] Plugin "endpoint" is disabled.
log [15:29:37.881] [info][plugins-service] Plugin "ingestManager" is disabled.
log [15:29:37.882] [info][plugins-service] Plugin "lists" is disabled.
log [15:29:42.736] [info][plugins-system] Setting up [94] plugins: [taskManager,licensing,observability,eventLog,encryptedSavedObjects,code,usageCollection,ossTelemetry,telemetryCollectionManager,telemetry,telemetryCollectionXpack,kibanaLegacy,devTools,translations,uiActions,statusPage,share,newsfeed,mapsLegacy,mapsLegacyLicensing,kibanaUtils,kibanaReact,inspector,embeddable,advancedUiActions,embeddableEnhanced,drilldowns,indexPatternManagement,esUiShared,discover,charts,bfetch,expressions,data,home,cloud,console,consoleExtensions,apm_oss,searchprofiler,painlessLab,grokdebugger,management,upgradeAssistant,reporting,licenseManagement,indexManagement,remoteClusters,crossClusterReplication,indexLifecycleManagement,watcher,advancedSettings,telemetryManagementSection,fileUpload,dataEnhanced,visualizations,visTypeVislib,visTypeVega,visTypeTimeseries,rollup,visTypeTimelion,features,security,snapshotRestore,transform,ingestPipelines,canvas,visTypeTable,visTypeTagcloud,visTypeMetric,visTypeMarkdown,inputControlVis,savedObjects,navigation,lens,graph,maps,visualize,dashboard,dashboardEnhanced,savedObjectsManagement,spaces,actions,case,alerting,alertingBuiltins,triggers_actions_ui,infra,monitoring,logstash,uptime,ml,siem,apm]
log [15:29:42.752] [warning][config][encryptedSavedObjects][plugins] Generating a random key for xpack.encryptedSavedObjects.encryptionKey. To be able to decrypt encrypted saved objects attributes after restart, please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml
log [15:29:43.050] [warning][config][plugins][security] Generating a random key for xpack.security.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.security.encryptionKey in kibana.yml
log [15:29:43.051] [warning][config][plugins][security] Session cookies will be transmitted over insecure connections. This is not recommended.
log [15:29:43.139] [warning][actions][actions][plugins] APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.
log [15:29:43.162] [warning][alerting][alerting][plugins][plugins] APIs are disabled due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml.
log [15:29:43.221] [info][monitoring][monitoring][plugins] config sourced from: production cluster
log [15:29:43.223] [warning][monitoring][monitoring][plugins] X-Pack Monitoring Cluster Alerts will not be available: undefined
log [15:29:43.346] [info][crossClusterReplication][plugins] Your basic license does not support crossClusterReplication. Please upgrade your license.
log [15:29:43.348] [info][plugins][watcher] Your basic license does not support watcher. Please upgrade your license.
log [15:29:43.355] [info][kibana-monitoring][monitoring][monitoring][plugins] Starting monitoring stats collection
log [15:29:43.664] [info][savedobjects-service] Waiting until all Elasticsearch nodes are compatible with Kibana before starting saved objects migrations...
log [15:29:43.709] [info][savedobjects-service] Starting saved objects migrations
log [15:29:43.775] [info][savedobjects-service] Creating index .kibana_task_manager_1.
log [15:29:43.819] [info][savedobjects-service] Creating index .kibana_1.
log [15:29:46.131] [info][savedobjects-service] Pointing alias .kibana to .kibana_1.
log [15:29:46.384] [info][savedobjects-service] Pointing alias .kibana_task_manager to .kibana_task_manager_1.
log [15:29:46.720] [info][savedobjects-service] Finished in 2948ms.
log [15:29:46.837] [info][savedobjects-service] Finished in 3062ms.
log [15:29:46.842] [info][plugins-system] Starting [72] plugins: [taskManager,licensing,observability,eventLog,encryptedSavedObjects,code,usageCollection,ossTelemetry,telemetryCollectionManager,telemetry,telemetryCollectionXpack,kibanaLegacy,translations,share,discover,bfetch,expressions,data,home,cloud,console,consoleExtensions,apm_oss,searchprofiler,painlessLab,grokdebugger,management,upgradeAssistant,reporting,licenseManagement,indexManagement,remoteClusters,crossClusterReplication,indexLifecycleManagement,watcher,advancedSettings,fileUpload,dataEnhanced,visualizations,visTypeVislib,visTypeVega,visTypeTimeseries,rollup,visTypeTimelion,features,security,snapshotRestore,transform,ingestPipelines,canvas,visTypeTable,visTypeTagcloud,visTypeMetric,visTypeMarkdown,inputControlVis,lens,graph,visualize,dashboard,savedObjectsManagement,spaces,actions,case,alerting,alertingBuiltins,infra,monitoring,logstash,uptime,ml,siem,apm]
log [15:29:55.860] [info][status][plugin:kibana@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:55.873] [info][status][plugin:elasticsearch@7.8.0] Status changed from uninitialized to yellow - Waiting for Elasticsearch
log [15:29:55.875] [info][status][plugin:elasticsearch@7.8.0] Status changed from yellow to green - Ready
log [15:29:55.878] [info][status][plugin:xpack_main@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:55.892] [info][status][plugin:monitoring@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:55.896] [warning][plugins][reporting] Generating a random key for xpack.reporting.encryptionKey. To prevent sessions from being invalidated on restart, please set xpack.reporting.encryptionKey in kibana.yml
log [15:29:55.905] [warning][plugins][reporting] Chromium sandbox provides an additional layer of protection, but is not supported for Linux Centos 7.8.2003 OS. Automatically setting 'xpack.reporting.capture.browser.chromium.disableSandbox: true'.
log [15:29:55.910] [warning][reporting] Enabling the Chromium sandbox provides an additional layer of protection.
log [15:29:59.326] [info][status][plugin:reporting@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.443] [info][status][plugin:spaces@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.455] [info][status][plugin:security@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.468] [info][status][plugin:dashboard_mode@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.474] [info][status][plugin:beats_management@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.598] [info][status][plugin:maps@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.630] [info][plugins][taskManager][taskManager] TaskManager is identified by the Kibana UUID: 1baf3b6d-07d3-4e6b-b666-7826a09a398d
log [15:29:59.639] [info][status][plugin:task_manager@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.651] [info][status][plugin:encryptedSavedObjects@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.663] [info][status][plugin:console_legacy@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.679] [info][status][plugin:apm_oss@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.696] [info][status][plugin:region_map@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.700] [info][status][plugin:ui_metric@7.8.0] Status changed from uninitialized to green - Ready
log [15:29:59.752] [info][listening] Server running at http://0.0.0.0:5601
log [15:30:00.730] [info][server][Kibana][http] http server running at http://0.0.0.0:5601
进入kibana7.8.0容器进行配置kibana.yml文件:
test-MBP:~ test$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5e20e2c924bc elasticsearch:7.8.0 "/tini -- /usr/local…" 18 minutes ago Up 9 minutes 0.0.0.0:9200->9200/tcp, 0.0.0.0:9300->9300/tcp elasticsearch7.8.0
2169b5c22236 kibana:7.8.0 "/usr/local/bin/dumb…" 11 hours ago Up 5 minutes 0.0.0.0:5601->5601/tcp kibana7.8.0
test-MBP:~ test$ docker exec -it 2169b5c22236 /bin/bash
bash-4.2$ ls
LICENSE.txt bin data optimize src
NOTICE.txt built_assets node package.json webpackShims
README.txt config node_modules plugins x-pack
bash-4.2$ cd config
bash-4.2$ ls
kibana.yml
bash-4.2$ vi kibana.yml
bash-4.2$ exit
kibana.yml文件:
#####----------kibana服务相关----------#####
#提供服务的端口,监听端口
server.port: 5601
# 主机地址,可以是ip,主机名
server.host: 0.0.0.0
# 在代理后面运行,则可以指定安装Kibana的路径
# 使用server.rewriteBasePath设置告诉Kibana是否应删除basePath
# 接收到的请求,并在启动时防止过时警告
# 此设置不能以斜杠结尾
#server.basePath: ""
# 指定Kibana是否应重写以server.basePath为前缀的请求,或者要求它们由反向代理重写,默认false
#server.rewriteBasePath: false
# 传入服务器请求的最大有效负载大小,以字节为单位,默认1048576
#server.maxPayloadBytes: 1048576
# 该kibana服务的名称,默认your-hostname
#server.name: "your-hostname"
# 服务的pid文件路径,默认/var/run/kibana.pid
#pid.file: /var/run/kibana.pid
#####----------elasticsearch相关----------#####
# kibana访问es服务器的URL,就可以有多个,以逗号","隔开
elasticsearch.hosts: ["http://宿主机IP:9200"]
# 当此值为true时,Kibana使用server.host设定的主机名
# 当此值为false时,Kibana使用连接Kibana实例的主机的主机名
# 默认ture
#elasticsearch.preserveHost: true
# Kibana使用Elasticsearch中的索引来存储已保存的搜索,可视化和仪表板
# 如果索引尚不存在,Kibana会创建一个新索引
# 默认.kibana
#kibana.index: ".kibana"
# 加载的默认应用程序
# 默认home
#kibana.defaultAppId: "home"
# kibana访问Elasticsearch的账号与密码(如果ElasticSearch设置了的话)
#elasticsearch.username: "kibana_system"
#elasticsearch.password: "pass"
# 从Kibana服务器到浏览器的传出请求是否启用SSL
# 设置为true时,需要server.ssl.certificate和server.ssl.key
#server.ssl.enabled: true
#server.ssl.certificate: /path/to/your/server.crt
#server.ssl.key: /path/to/your/server.key
# 从Kibana到Elasticsearch启用SSL后,ssl.certificate和ssl.key的位置
#elasticsearch.ssl.certificate: /path/to/your/client.crt
#elasticsearch.ssl.key: /path/to/your/client.key
# PEM文件的路径列表
#elasticsearch.ssl.certificateAuthorities: [ "/path/to/your/CA.pem" ]
# 控制Elasticsearch提供的证书验证
# 有效值为none,certificate和full
#elasticsearch.ssl.verificationMode: full
# Elasticsearch服务器响应ping的时间,单位ms
#elasticsearch.pingTimeout: 1500
# Elasticsearch 的响应的时间,默认是30000,单位ms
elasticsearch.requestTimeout: 50000
# Kibana客户端发送到Elasticsearch的标头列表
# 如不发送客户端标头,请将此值设置为空
#elasticsearch.requestHeadersWhitelist: []
# Kibana客户端发往Elasticsearch的标题名称和值
#elasticsearch.customHeaders: {}
# Elasticsearch等待分片响应的时间
#elasticsearch.shardTimeout: 30000
# Kibana刚启动时等待Elasticsearch的时间,单位ms,然后重试
#elasticsearch.startupTimeout: 5000
# 记录发送到Elasticsearch的查询
#elasticsearch.logQueries: false
#####----------日志相关----------#####
# kibana日志文件存储路径,默认stdout
logging.dest: stdout
# 此值为true时,禁止所有日志记录输出
# 默认false
#logging.silent: false
# 此值为true时,禁止除错误消息之外的所有日志记录输出
# 默认false
#logging.quiet: false
# 此值为true时,记录所有事件,包括系统使用信息和所有请求
# 默认false
#logging.verbose: false
#####----------其他----------#####
# 系统和进程取样间隔,单位ms,最小值100ms
# 默认5000ms
#ops.interval: 5000
# kibana web语言
# 默认en
i18n.locale: "zh-CN"
重启容器,访问 http://localhost:5601
可见如下图就成功了:
Relational DB | Elasticsearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | 类型(types) |
行(rows) | 文档(documents) |
字段(columns) | 字段(fields) |
elasticsearch(集群)中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多 个文档(行),每个文档中又包含多个字段(列)。Elasticsearch 所有数据格式都是 json 格式。
物理设计:
elasticsearch 在后台把每个索引划分成多个分片
,每分分片可以在集群中的不同服务器间迁移。
逻辑设计:
一个索引类型中,包含多个文档,比如说文档1,文档2。当我们索引一篇文档时,可以通过这样的一各顺序找到它:索引 > 类型 > 文档ID ,通过这个组合我们就能索引到某个具体的文档。注意:ID不必是整数,实际上它是个字符串。
之前说elasticsearch是面向文档
的 ,那么就意味着索引和搜索数据的最小单位是文档,elasticsearch中,文档有几个重要属性:
尽管我们可以随意的新增或者忽略某个字段,但是,每个字段的类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整型。因为elasticsearch会保存字段和类型之间的映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在elasticsearch中,类型有时候也称为映射类型。
类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器。类型中对于字段的定义称为映射,比如name 映射为字符串类型。我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么elasticsearch是怎么做的呢?elasticsearch会自动的将新字段加入映射,但是这个字段的不确定它是什么类型,elasticsearch就开始猜,如果这个值是18,那么elasticsearch会认为它是整型。但是elasticsearch也可能猜不对,所以最安全的方式就是提前定义好所需要的映射,这点跟关系型数据库殊途同归了,先定义好字段,然后再使用。
索引是映射类型的容器,elasticsearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。然后它们被存储到了各个分片上了。
一个集群至少有一个节点,而一个节点就是一个elasricsearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有个5个默认分片(primary shard,又称主分片)构成的,每一个主分片会有一个副本(replica shard ,又称复制分片)
上图是有3个节点的一个集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉 了,数据也不至于丟失。实际上,一个分片是一个Lucene素引,一个包含倒排索引
的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键宇。
elasticsearch使用的是一种称为倒排索引的结构,采用Lucene倒排索作为底层。这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含它的文档列表。例如,现在有两个文档,每个文档包含如下内容:
文档1包含的内容:
Study every day, good good up to forever
文档2包含的内容:
To forever, study every day, good good up
为了创建倒排索引,我们首先要将每个文档拆分成独立的词(或称为词条或者tokens),然后创建一个包含所有不重复的词条的排序列表,然后列出每个词条出现在哪个文档:
term(词条) | doc_1(文档) | doc_2(文档) |
---|---|---|
Study | √ | × |
To | × | × |
every | √ | √ |
forever | √ | √ |
day | √ | √ |
study | × | √ |
good | √ | √ |
every | √ | √ |
to | √ | × |
up | √ | √ |
现在,试图搜索 to forever
,只需要查看包含每个词条文档:
term(词条) | doc_1(文档) | doc_2(文档) |
---|---|---|
to | √ | × |
forever | √ | √ |
total | 2 | 1 |
所以doc_1(文档)权重(score)比doc_2(文档)要高。
两个文档都匹配,但是第一个文档比第二个匹配程度更高。如果没有别的条件,现在,这两个包含关键字的文档都将返回。
再来看一个示例,比如我们通过博客标签来搜索博客文章。那么倒排索引列表就是这样的一个结构:
如果要搜索含有python 标签的文章,那相对于查找所有原始数据而言,查找倒排索引后的数据将会快的多。只需要查看标签这一栏,然后获取相关的文章1D即可。完全过滤掉无关的所有数据,提高效率!
elasticsearch的索引和Lucene的索引对比:
在elasticsearch中,索引(库)这个词被频繁使用,这就是术语的使用。 在elasticsearch中,索引被分为多个分片,每份分片是一个Lucene的索引。所以—个elasticsearch索引是由多个Lucene索引组成的
。别问为什么,谁让elasticsearch使用Lucene作为底层呢!如无特指,说起索引!都是指elasticsearch的索引。
简单总结:
分词:即把一段中文或者别的划分成一个个的关键字,在搜索时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词,比如“天天向上”会被分为"天",“天”,"向”,“上”,这显然是不符合要求的,所以我们需要安装中文分词器来解决这个问题。
此提供了两个分词算法:ik_smart
和 ik_max_word
,其中 ik_smart 为最少切分
,ik_max_ word为最细粒度划分
!
使用 kibana 测试 ik 分词器中 ik_smart 最少切分
算法:
使用 kibana 测试 ik 分词器中 ik_max_word 最细腻度切分
算法:
需求:不让鬼灭之刃
这个词进行拆分, 但是,请求发现竟然被拆分了
自定义iK分词器配置来解决上面的需求:
一种软件架构风格、设计风格,不是一种规范,也不是标准,它不具有强制性。只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
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/索引1名称/类型名称/_search | 查询所有数据 |
安全性 :不会改变资源状态,可以理解为只读的;
幂等性 :执行1次和执行N次,对资源状态改变的效果是等价的。
安全性 | 幂等性 | |
---|---|---|
GET | √ | √ |
POST | × | × |
PUT | × | √ |
DELETE | × | √ |
安全性和幂等性均不保证反复请求能拿到相同的response。以 DELETE为例,第一次DELETE返回200表示删除成功,第二次返回404提示资源不存在,这是允许的。
# 创建索引(库)/类型(表)/文档ID(行)
PUT /test1/type1/1
{
# 字段名称 : 字段值
"name":"张三",
"age":18
}
# 以后版本会舍弃type1类型(表),就是说不写默认就是“_doc”,可以声明也可以不声明。
在 elasticsearch-head
可以看到创建的 test1
索引及数据:
那么 name
这个字段用不用指定类型呢,当然需要指定类型的啦。
一级分类 | 二级分类 | 具体类型 |
---|---|---|
核心类型 | 字符串类型 | text,keyword |
整数类型 | integer,long,short,byte | |
浮点类型 | double,float,half_float,scaled_float | |
逻辑类型 | boolean | |
日期类型 | date | |
范围类型 | range | |
二进制类型 | binary | |
复合类型 | 数组类型 | array |
对象类型 | object | |
嵌套类型 | nested | |
地理类型 | 地理坐标类型 | geo_point |
地理地图 | geo_shape | |
特殊类型 | IP类型 | ip |
范围类型 | completion | |
令牌计数类型 | token_count | |
附件类型 | attachment | |
抽取类型 | percolator |
# 创建索引及字段类型并没有设置值
# 索引名称
PUT /test2
{
# 映射
"mappings": {
# 属性
"properties": {
# 字段名称
"name": {
# 字段类型
"type": "text"
},
"age": {
"type": "integer"
},
"birthday": {
"type": "date"
}
}
}
}
# 获取索引信息
GET test2
第一种方式:
# 第一方式:通过文档ID,修改字段值,如果字段没有传完整就会覆盖掉之前所有的字段
PUT /test3/_doc/1
{
"name":"李四",
"age":29
}
# 强烈建议第二种方式:通过文档ID,修改字段,用POST方式,请求后缀加上"_update"关键字即可
POST /test3/_doc/1/_update
{
# 具体要修改的文档下面的字段内容
"doc":{
"name":"法外狂徒张三",
"age":39
}
}
# 删除索引
DELETE /test2
先准备环境,创建文档
# 创建文档
PUT /king/user/1
{
"name":"张三",
"age":34,
"desc":"法外狂徒张三",
"tags":["法律学习爱好者","狂妄","知名度高"]
}
PUT /king/user/2
{
"name":"李四",
"age":33,
"desc":"法外狂徒张三的好基友",
"tags":["好立友代言人","低调","知名度高"]
}
PUT /king/user/3
{
"name":"王五",
"age":34,
"desc":"法外狂徒张三及李四的好基友",
"tags":["隔壁老王","高手","知名度高"]
}
PUT /king/user/4
{
"name":"张三三",
"age":38,
"desc":"法外狂徒张三三",
"tags":["法律学习爱好者","狂妄","知名度高"]
}
# 条件查询,/索引/类型/_search表示es搜索关键字?q表示query意思=字段:字段值
GET /king/user/_search?q=name:李四
# 构建精确条件查询
GET /king/user/_search
{
# es查询query关键字
"query":{
# es查询相同关键字
"match": {
# 字段:字段值
"name": "张三"
}
}
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
"match": {
"name": "张三"
}
},
# 查询指定字段
"_source":["name","desc"]
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
"match": {
"name": "张三"
}
},
# es排序关键字
"sort":[
{
# 要排序的字段名称
"age":{
# 排序方式:asc 升序;desc 倒序
"order":"desc"
}
}
]
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
"match": {
"name": "张三"
}
},
"sort":[
{
"age":{
"order":"desc"
}
}
],
# 当前页码
"from":0,
# 当页要显示数据的条数
"size":1
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
# es布尔查询关键字
"bool": {
# must (and),所有的条件都要符合,类比 where name = “张三” and age = 34
"must": [
{
# es查询相同关键字
"match": {
# 字段:字段值
"name": "张三"
}
},
{
"match": {
"age": "34"
}
}
]
}
}
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
"bool": {
# must_not (not),所有的条件都要符合,类比 where not age = 34
"must_not": [
{
# es查询相同关键字
"match": {
# 字段:字段值
"age": 34
}
}
]
}
}
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
# es布尔查询关键字
"bool": {
# should (or),所有的条件都要符合,类比 where name = “某某” or age = 34
"should": [
{
# es查询相同关键字
"match": {
# 字段:字段值
"name": "某某"
}
},
{
"match": {
"age": "38"
}
}
]
}
}
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
"bool": {
"must": [
{
"match": {
"name":"张三"
}
}
],
# es过滤查询关键字
"filter": {
# es范围查询关键字
"range": {
# 字段名称
"age": {
# 大于
"gt": 10,
# 小于
"lt": 53
}
}
}
}
}
}
# 构建精确条件查询
GET /king/user/_search
{
"query":{
"match": {
"tags": "高手 低"
}
}
}
term 查询是直接通过倒排索引指定的词条进行精确查找
的!
关于分词:
分词器对两种类型:text、keyword 处理
text:
# 字段类型是text时,请求数据会被分词器进行拆分
GET _analyze
{
# 分词模式 :默认标准
"analyzer": "standard",
"text": "法外狂徒张三及李四的好基友"
}
# 字段类型是keyword时,请求数据不会被分词进行拆分
GET _analyze
{
# 分词模式:keyword
"analyzer": "keyword",
"text": "法外狂徒张三及李四的好基友"
}
# 创建索引并设置映射关系
PUT /kingdb
{
# 映射关系
"mappings": {
# 属性
"properties": {
# 字段名
"name":{
# 类型 :text
"type": "text"
},
# 字段名
"desc":{
# 类型 :keyword
"type": "keyword"
}
}
}
}
# 创建文档并赋值
PUT /kingdb/_doc/1
{
"name":"张三",
"desc":"法外狂徒张三"
}
PUT /kingdb/_doc/2
{
"name":"张三2",
"desc":"法外狂徒张三2"
}
# 精确查询 name 字段,name 字段类型是text类型会被分词器进行分词
GET /kingdb/_search
{
"query": {
"term": {
"name": "三"
}
}
}
# 精确查询 desc 字段,desc 字段类型是keyword类型不会被分词器进行分词
GET /kingdb/_search
{
"query": {
"term": {
"desc": "法外狂徒张三"
}
}
}
# 高亮查询
GET /king/user/_searc
{
# es查询query关键字
"query": {
# es查询相同关键字
"match": {
# 字段:字段值
"name": "张三"
}
},
# es高亮关键字
"highlight":{
# 字段集
"fields": {
# 要设置的高亮字段名称
"name": {}
}
}
}
GET /king/user/_searc
{
# es查询query关键字
"query": {
# es查询相同关键字
"match": {
# 字段:字段值
"name": "张三"
}
},
# es高亮关键字
"highlight":{
# 标签前缀
"pre_tags": "" ,
# 标签后缀
"post_tags": "",
# 字段集
"fields": {
# 要设置的高亮字段名称
"name": {}
}
}
}
pom.xml
文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.1version>
<relativePath/>
parent>
<groupId>com.kinggroupId>
<artifactId>es-apiartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>es-apiname>
<description>es-apidescription>
<properties>
<java.version>1.8java.version>
<elasticsearch.version>7.8.0elasticsearch.version>
properties>
<dependencies>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
主启动类文件:
package com.king.esapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EsApiApplication {
public static void main(String[] args) {
SpringApplication.run(EsApiApplication.class, args);
}
}
elasticsearch
配置文件:
package com.king.esapi.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Elasticsearch配置文件
*/
@Configuration
public class ElasticsearchClientConfig {
/**
* 创建es官方提供的es客户端
* @return
*/
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
// 连接 elasticsearch 服务端
new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9201, "http")));
return client;
}
}
elasticsearch API
测试类文件:
package com.king.esapi;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
@SpringBootTest
class EsApiApplicationTests {
/**
* 注入es客户端对象
* @Qualifier:从多个相同类型并满足装配要求的 bean 中找到我们想要的,避免让Spring脑裂
*/
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
/**
* 创建索引
*/
@Test
void testCreateIndex() {
// 创建索引对象
CreateIndexRequest createIndexRequest = new CreateIndexRequest("king_index");
try {
// 客户端执行请求,第一个参数:索引对象,第二个参数:请求项对象,返回创建响应对象
CreateIndexResponse createIndexResponse = client.indices().create(createIndexRequest, RequestOptions.DEFAULT);
System.out.println("创建的索引名称:" + createIndexResponse.index());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断索引是否存在
*/
@Test
void testExistIndex() {
// 获取索引对象
GetIndexRequest getIndexRequest = new GetIndexRequest("king_index","king");
try {
// 判断索引是否存在
boolean exists = client.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
System.out.println(exists ? "存在" : "不存在");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除索引
*/
@Test
void testDeleteIndex() {
// 删除索引对象
DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest("test1","test3","king_index");
try {
// 客户端执行请求,第一个参数:索引对象,第二个参数:请求项对象,返回确认响应对象
AcknowledgedResponse delete = client.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged() ? "删除成功" : "删除失败");
} catch (IOException e) {
e.printStackTrace();
}
}
}
其他相关文件同上。
User 实体类文件:
package com.king.esapi.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User {
private String name;
private int age;
}
elasticsearch API
测试类文件:
package com.king.esapi;
import com.alibaba.fastjson.JSON;
import com.king.esapi.pojo.User;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.FetchSourceContext;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@SpringBootTest
class EsApiApplicationTests {
/**
* 注入es客户端对象
* @Qualifier:从多个相同类型并满足装配要求的 bean 中找到我们想要的,避免让Spring脑裂
*/
@Autowired
@Qualifier("restHighLevelClient")
private RestHighLevelClient client;
/**
* 添加文档
*/
@Test
void testAddDocument(){
// 创建一个用户对象
User user = new User("张三", 35);
// 要操作的索引对象
IndexRequest indexRequest = new IndexRequest("king_index");
// 文档ID
indexRequest.id("1");
// 请求的最大超时时间
indexRequest.timeout(TimeValue.timeValueSeconds(1));
// 将user对象转json格式添加文档中
indexRequest.source(JSON.toJSONString(user), XContentType.JSON);
try {
// 客户端执行请求,第一个参数:索引对象,第二个参数:请求项对象,返回索引响应对象
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println(indexResponse.toString()); // IndexResponse[index=king_index,type=_doc,id=1,version=3,result=created,seqNo=4,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]
System.out.println(indexResponse.status()); // CREATED
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 判断文档是否存在
*/
@Test
void testExistsDocument(){
// 获取文档,第一个参数:索引名称,第二个参数:文档ID
GetRequest getRequest = new GetRequest("king_index","1");
// 不获取返回的 _source 的上下文
getRequest.fetchSourceContext(new FetchSourceContext(false));
getRequest.storedFields("_none_");
try {
// 客户端执行请求,第一个参数:索引对象,第二个参数:请求项对象,返回布尔值
boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);
System.out.println(exists ? "该文档存在" : "该文档不存在");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 获取文档信息
*/
@Test
void testGetDocument(){
// 获取文档,第一个参数:索引名称,第二个参数:文档ID
GetRequest getRequest = new GetRequest("king_index","1");
try {
// 客户端执行获取请求,第一个参数:索引对象,第二个参数:请求项对象,返回获取响应对象
GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
System.out.println(getResponse.getSourceAsString()); // {"age":35,"name":"张三"}
System.out.println(getResponse); // {"_index":"king_index","_type":"_doc","_id":"1","_version":7,"_seq_no":8,"_primary_term":1,"found":true,"_source":{"age":35,"name":"张三"}}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 更新文档信息
*/
@Test
void testUpdateDocument(){
// 更新文档,第一个参数:索引名称,第二个参数:文档ID
UpdateRequest updateRequest = new UpdateRequest("king_index","1");
// 请求的最大超时时间
updateRequest.timeout("1s");
User user = new User("法外狂徒张三", 37);
// 设置文档内容及内容类型
updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
try {
// 客户端执行更新请求,第一个参数:索引对象,第二个参数:请求项对象,返回更新响应对象
UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
System.out.println(updateResponse.toString()); // UpdateResponse[index=king_index,type=_doc,id=1,version=8,seqNo=9,primaryTerm=1,result=updated,shards=ShardInfo{total=2, successful=1, failures=[]}]
System.out.println(updateResponse.status()); // OK
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 删除文档
*/
@Test
void testDeleteDocument(){
// 删除文档,第一个参数:索引名称,第二个参数:文档ID
DeleteRequest deleteRequest = new DeleteRequest("king_index","1");
// 请求的最大超时时间,超时将不执行
deleteRequest.timeout("1s");
try {
// 客户端执行删除请求,第一个参数:索引对象,第二个参数:请求项对象,返回删除响应对象
DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
System.out.println(deleteResponse.toString()); // DeleteResponse[index=king_index,type=_doc,id=1,version=10,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]
System.out.println(deleteResponse.status()); // OK
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 批量插入数据
*/
@Test
void testBulkRequest(){
// 创建一个批量请求对象
BulkRequest bulkRequest = new BulkRequest();
// 设置请求超时时间,超时将不执行
bulkRequest.timeout("10s");
List<User> userList = new ArrayList<>();
userList.add(new User("test1",35));
userList.add(new User("test2",36));
userList.add(new User("test3",37));
userList.add(new User("test4",38));
userList.add(new User("test5",39));
userList.add(new User("test6",40));
for (int i = 0; i < userList.size(); i++) {
// 批量添加
bulkRequest.add(
// 索引名称
new IndexRequest("king_index")
// 文档ID
.id(""+(i+1))
.source(JSON.toJSONString(userList.get(i)),XContentType.JSON));
}
try {
// 客户端执行批量请求,第一个参数:批量请求对象,第二个参数:请求项对象,返回批量响应对象
BulkResponse bulkItemResponses = client.bulk(bulkRequest,RequestOptions.DEFAULT);
System.out.println(bulkItemResponses.hasFailures() ? "操作失败" : "操作成功"); // 注意点:hasFailures()方法,true是失败,false是成功
System.out.println(bulkItemResponses.status());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 搜索
*/
@Test
void testSearch(){
// 搜索对象
SearchRequest searchRequest = new SearchRequest("king_index");
// 构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构建精确查询
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name","test3");
// 将精确查询添加到构建搜索对象中
searchSourceBuilder.query(termQueryBuilder);
// 构建相同值查询
// MatchQueryBuilder matchAllQueryBuilder = QueryBuilders.matchQuery("name","test3");
// 构建高亮字段
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 设置要高亮的字段名称
highlightBuilder.field("name");
// 如要多个字段高亮,此项要为false
highlightBuilder.requireFieldMatch(true);
// 设置前缀标签
highlightBuilder.preTags(""
);
// 设置后缀标签
highlightBuilder.postTags("");
// 最大高亮分片数
highlightBuilder.fragmentSize(800000);
// 从第一个分片获取高亮片段
highlightBuilder.numOfFragments(0);
// 将高亮查询添加到构建搜索对象中
searchSourceBuilder.highlighter(highlightBuilder);
// 设置分页
// 当前第几页
searchSourceBuilder.from(0);
// 当前页显示多少条数据
searchSourceBuilder.size(10);
// 设置请求超时时间60秒
searchSourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS));
// 搜索请求对象设置要构建搜索对象的值
searchRequest.source(searchSourceBuilder);
try {
// 客户端执行搜索请求,第一个参数:搜索对象,第二个参数:请求项对象,返回搜索响应对象
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(JSON.toJSONString(searchResponse.getHits())); // {"fragment":true,"hits":[{"fields":{},"fragment":false,"highlightFields":{},"id":"3","matchedQueries":[],"primaryTerm":0,"rawSortValues":[],"score":1.540445,"seqNo":-2,"sortValues":[],"sourceAsMap":{"name":"test3","age":37},"sourceAsString":"{\"age\":37,\"name\":\"test3\"}","sourceRef":{"fragment":true},"type":"_doc","version":-1}],"maxScore":1.540445,"totalHits":{"relation":"EQUAL_TO","value":1}}
List<String> highlightList=new ArrayList<>();
// 遍历搜索响应对象的 Hits 的值
for (SearchHit documentFields : searchResponse.getHits().getHits()) {
// 输出文档值
System.out.println(documentFields.getSourceAsMap()); // {name=test3, age=37}
// 获取高亮字段
Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
// System.out.println(highlightFields); // {name=[name], fragments[[test3
]]}
// System.out.println(highlightFields.get("name").getFragments()[0]); // test3
highlightList.add(highlightFields.get("name").getFragments()[0].toString());
}
// 遍历高亮字段
for (String s : highlightList) {
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
pom.xml
文件:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.6.1version>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>es-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>es-demoname>
<description>es-demodescription>
<properties>
<java.version>1.8java.version>
<elasticsearch.version>7.8.0elasticsearch.version>
properties>
<dependencies>
<dependency>
<groupId>org.jsoupgroupId>
<artifactId>jsoupartifactId>
<version>1.10.2version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-elasticsearchartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
exclude>
excludes>
configuration>
plugin>
plugins>
build>
project>
主启动类:
package com.example.esdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EsDemoApplication.class, args);
}
}
application.properties
文件:
# 端口
server.port=9999
# 关闭thymeleaf缓存
spring.thymeleaf.cache=false
配置类:
package com.example.esdemo.config;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Elasticsearch配置文件
*/
@Configuration
public class ElasticsearchClientConfig {
/**
* 创建es官方提供的es客户端
* @return
*/
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
// 连接 elasticsearch 服务端
new HttpHost("localhost", 9200, "http"),
new HttpHost("localhost", 9201, "http")));
return client;
}
}
工具类:
package com.example.esdemo.utils;
import com.example.esdemo.pojo.Content;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
/**
* 网页解析工具
*/
public class HtmlParseUtil {
public static void main(String[] args) throws IOException {
parseJD("java").forEach(System.out::println);
}
public static List<Content> parseJD(String keyword) throws IOException {
/// 使用前需要联网
// 请求url
String url = "http://search.jd.com/search?keyword=" + keyword;
// 1.解析网页(jsoup 解析返回的对象是浏览器Document对象)
Document document = Jsoup.parse(new URL(url), 30000);
// 使用document可以使用在js对document的所有操作
// 2.获取元素(通过id)
Element j_goodsList = document.getElementById("J_goodsList");
// 3.获取J_goodsList ul 每一个 li
Elements lis = j_goodsList.getElementsByTag("li");
// System.out.println(lis);
// 4.获取li下的 img、price、name
// list存储所有li下的内容
List<Content> contents = new ArrayList<Content>();
for (Element li : lis) {
// 由于网站图片使用懒加载,将src属性替换为data-lazy-img
String img = li.getElementsByTag("img").eq(0).attr("data-lazy-img");// 获取li下 第一张图片
String name = li.getElementsByClass("p-name").eq(0).text();
String price = li.getElementsByClass("p-price").eq(0).text();
// 封装为对象
Content content = new Content(name,img,price);
// 添加到list中
contents.add(content);
}
// System.out.println(contents);
// 5.返回 list
return contents;
}
}
pojo
层:
package com.example.esdemo.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content implements Serializable {
private String name;
private String img;
private String price;
}
Service
层:
package com.example.esdemo.service;
import com.alibaba.fastjson.JSON;
import com.example.esdemo.pojo.Content;
import com.example.esdemo.utils.HtmlParseUtil;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
public class ContentService {
@Autowired
private RestHighLevelClient restHighLevelClient;
/**
* 从页面解析到数据并插入到elasticsearch中
* @param keyword
* @return
*/
public Boolean parseContent(String keyword){
try {
// 解析页面获取到数据
List<Content> contentList = HtmlParseUtil.parseJD(keyword);
// 批量请求对象
BulkRequest bulkRequest = new BulkRequest();
// 设置请求超时时间,超级将不再请求
bulkRequest.timeout("2m");
// 将解析到的数据批量添加es中
for (int i = 0; i < contentList.size(); i++) {
bulkRequest.add(
// 添加到指定索引中,不指定文档ID,随机生成文档ID
new IndexRequest("jd_goods")
// 添加数据
.source(JSON.toJSONString(contentList.get(i)), XContentType.JSON));
}
// 客户端执行批量请求,第一个参数:批量请求对象,第二个参数:请求项对象,返回批量响应对象
BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
// 注意点:hasFailures()方法,true是失败,false是成功,这里就取非值了
return !bulkResponse.hasFailures();
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* 分页查询
* @param keyword 关键词
* @param pageNo 当前页码
* @param pageSize 当前页数据条数
* @return
*/
public List<Map<String,Object>> searchPages(String keyword,Integer pageNo,Integer pageSize){
if (pageNo <= 1){
pageNo = 1;
}
// 创建"jd_goods"索引搜索对象
SearchRequest searchRequest = new SearchRequest("jd_goods");
// 构建搜索条件对象
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 设置分页
searchSourceBuilder.from(pageNo);
searchSourceBuilder.size(pageSize);
// 精准查询,添加查询条件,第一个参数:es文档中的字段名称,要匹配字段名称的值
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name",keyword);
searchSourceBuilder.query(termQueryBuilder);
// 设置查询超时时间,超时将不再查询
searchSourceBuilder.timeout(new TimeValue(60,TimeUnit.SECONDS));
// 将构建查询条件对象添加到搜索对象中
searchRequest.source(searchSourceBuilder);
// 执行搜索
try {
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
List<Map<String,Object>> mapList = new ArrayList<>();
// 遍历搜索响应对象里的数据
for (SearchHit documentFields : searchResponse.getHits().getHits()) {
mapList.add(documentFields.getSourceAsMap());
}
return mapList;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* 高亮分页查询
* @param keyword 关键字
* @param pageIndex 当前页码
* @param pageSize 当前页数据条数
* @return
* @throws IOException
*/
public List<Map<String, Object>> highlightSearchPages(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
if (pageIndex <= 1){
pageIndex = 1;
}
SearchRequest searchRequest = new SearchRequest("jd_goods");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 精确查询,添加查询条件
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", keyword);
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
searchSourceBuilder.query(termQueryBuilder);
// 分页
searchSourceBuilder.from(pageIndex);
searchSourceBuilder.size(pageSize);
// 高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
// 设置高亮文档字段
highlightBuilder.field("name");
// 设置高亮前缀标签样式
highlightBuilder.preTags("");
// 设置高亮后缀标签痒死
highlightBuilder.postTags("");
// 关闭多个高亮显示
highlightBuilder.requireFieldMatch(false);
// 将高亮对象添加到构建搜索条件对象中
searchSourceBuilder.highlighter(highlightBuilder);
// 执行查询
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
// 解析结果
SearchHits hits = searchResponse.getHits();
List<Map<String, Object>> results = new ArrayList<>();
for (SearchHit documentFields : hits.getHits()) {
// 获取es数据中的高亮字段
Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
HighlightField name = highlightFields.get("name");
// 获取es源数据,使用新的字段值(高亮),覆盖旧的字段值,因为高亮字段在es数据的highlight结构下面,所以要将es的_source下面的name替换成highlight下面的name
Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
// 判断highlight结构下高亮字段不等于null时进行替换_source下的name字段变成高亮字段并返回出去
if (name != null){
// 拿到highlight结构下高亮字段片段
Text[] fragments = name.fragments();
StringBuilder new_name = new StringBuilder();
// 遍历highlight结构下高亮字段片段
for (Text text : fragments) {
// 添加到stringBuilder中
new_name.append(text);
}
// 执行替换
sourceAsMap.put("name",new_name.toString());
}
results.add(sourceAsMap);
}
return results;
}
}
Controller
层:
package com.example.esdemo.controller;
import com.example.esdemo.service.ContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
import java.util.Map;
@Controller
public class ContentController {
@Autowired
private ContentService contentService;
@RequestMapping("/parseContentAndInsertEs/{keyword}")
@ResponseBody
public String parseContentAndInsertEs(@PathVariable String keyword) throws Exception {
return contentService.parseContent(keyword) ? "插入成功" : "插入失败";
}
@RequestMapping("/searchPages/{keyword}/{pageNo}/{pageSize}")
@ResponseBody
public List<Map<String,Object>> searchPages(@PathVariable String keyword,@PathVariable Integer pageNo,@PathVariable Integer pageSize){
return contentService.searchPages(keyword,pageNo,pageSize);
}
@RequestMapping("/highlightSearchPages/{keyword}/{pageNo}/{pageSize}")
@ResponseBody
public List<Map<String, Object>> highlightSearchPages(@PathVariable String keyword,@PathVariable Integer pageNo,@PathVariable Integer pageSize) throws Exception{
return contentService.highlightSearchPages(keyword,pageNo,pageSize);
}
}
package com.example.esdemo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping({"/","index"})
public String index(){
return "index";
}
}
src/main/resources/static/css/style.css
文件:
body, button, fieldset, form, h1, input, legend, li, p, ul {
margin: 0;
padding: 0
}
body, button, input {
font: 12px/1.5 tahoma, arial, "\5b8b\4f53";
-ms-overflow-style: scrollbar
}
button, h1, input {
font-size: 100%
}
em {
font-style: normal
}
ul {
list-style: none
}
a {
text-decoration: none
}
a:hover {
text-decoration: underline
}
legend {
color: #000
}
fieldset, img {
border: 0
}
#content, #header {
margin-left: auto;
margin-right: auto
}
html {
zoom: expression(function(ele){ ele.style.zoom = "1"; document.execCommand("BackgroundImageCache", false, true); }(this))
}
@font-face {
font-family: mui-global-iconfont;
src: url(//at.alicdn.com/t/font_1401963178_8135476.eot);
src: url(//at.alicdn.com/t/font_1401963178_8135476.eot?#iefix) format('embedded-opentype'), url(//at.alicdn.com/t/font_1401963178_8135476.woff) format('woff'), url(//at.alicdn.com/t/font_1401963178_8135476.ttf) format('truetype'), url(//at.alicdn.com/t/font_1401963178_8135476.svg#iconfont) format('svg')
}
#mallPage {
width: auto;
min-width: 990px;
background-color: transparent
}
#content {
width: 990px;
margin: auto
}
#mallLogo {
float: left;
z-index: 9;
padding-top: 28px;
width: 280px;
height: 64px;
line-height: 64px;
position: relative
}
.page-not-market #mallLogo {
width: 400px
}
.clearfix:after, .clearfix:before, .headerCon:after, .headerCon:before {
display: table;
content: "";
overflow: hidden
}
#mallSearch legend {
display: none
}
.clearfix:after, .headerCon:after {
clear: both
}
.clearfix, .headerCon {
zoom: 1
}
#mallPage #header {
margin-top: -30px;
width: auto;
margin-bottom: 0;
min-width: 990px;
background: #fff
}
#header {
height: 122px;
margin-top: -26px !important;
background: #fff;
min-width: 990px;
width: auto !important;
position: relative;
z-index: 1000
}
#mallSearch #mq, #mallSearch fieldset, .mallSearch-input {
position: relative
}
.headerLayout {
width: 990px;
padding-top: 26px;
margin: 0 auto
}
.header-extra {
overflow: hidden
}
#mallSearch {
float: right;
padding-top: 25px;
width: 390px;
overflow: hidden
}
.mallSearch-form {
border: solid #FF0036;
border-width: 3px 0 3px 3px
}
.mallSearch-input {
background: #fff;
height: 30px
}
#mallSearch #mq {
color: #000;
margin: 0;
z-index: 2;
width: 289px;
height: 20px;
line-height: 20px;
padding: 5px 3px 5px 5px;
outline: 0;
border: none;
font-weight: 900;
background: url(data:image/gif;base64,R0lGODlhAQADAJEAAObm5t3d3ff39wAAACH5BAAAAAAALAAAAAABAAMAAAICDFQAOw==) repeat-x;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box
}
#mallSearch button {
position: absolute;
right: 0;
top: 0;
width: 90px;
border: 0;
font-size: 16px;
letter-spacing: 4px;
cursor: pointer;
color: #fff;
background-color: #FF0036;
height: 30px;
overflow: hidden;
font-family: '\5FAE\8F6F\96C5\9ED1', arial, "\5b8b\4f53"
}
#mallSearch .s-combobox {
height: 30px
}
#mallSearch .s-combobox .s-combobox-input:focus {
outline: 0
}
button::-moz-focus-inner {
border: 0;
padding: 0;
margin: 0
}
.page-not-market #mallSearch {
width: 540px !important
}
.page-not-market #mq {
width: 439px !important
}
/*** uncss> filename: http://localhost:9090/css/test.css ***/
#mallSearch {
float: none
}
.page-not-market #mallLogo {
width: 280px
}
.header-list-app #mallSearch {
width: 448px !important
}
.header-list-app #mq {
width: 347px !important
}
@media (min-width: 1210px) {
#header .headerCon, #header .headerLayout, .main {
width: 1190px !important
}
.header-list-app #mallSearch {
width: 597px !important
}
.header-list-app #mq {
width: 496px !important
}
}
@media (min-width: 600px) and (max-width: 800px) and (orientation: portrait) {
.pg .page {
min-width: inherit !important
}
.pg #mallPage, .pg #mallPage #header {
min-width: 740px !important
}
.pg #header .headerCon, .pg #header .headerLayout, .pg .main {
width: 740px !important
}
.pg #mallPage #mallLogo {
width: 260px
}
.pg #header {
min-width: inherit
}
.pg #mallSearch .mallSearch-input {
padding-right: 95px
}
.pg #mallSearch .s-combobox {
width: 100% !important
}
.pg #mallPage .header-list-app #mallSearch {
width: auto !important
}
.pg #mallPage .header-list-app #mallSearch #mq {
width: 100% !important;
padding: 5px 0 5px 5px
}
}
i {
font-style: normal
}
.main, .page {
position: relative
}
.page {
overflow: hidden
}
@font-face {
font-family: tm-list-font;
src: url(//at.alicdn.com/t/font_1442456441_338337.eot);
src: url(//at.alicdn.com/t/font_1442456441_338337.eot?#iefix) format('embedded-opentype'), url(//at.alicdn.com/t/font_1442456441_338337.woff) format('woff'), url(//at.alicdn.com/t/font_1442456441_338337.ttf) format('truetype'), url(//at.alicdn.com/t/font_1442456441_338337.svg#iconfont) format('svg')
}
::selection {
background: rgba(0, 0, 0, .1)
}
* {
-webkit-tap-highlight-color: rgba(0, 0, 0, .3)
}
b {
font-weight: 400
}
.page {
background: #fff;
min-width: 990px
}
#content {
margin: 0 !important;
width: 100% !important
}
.main {
margin: auto;
width: 990px
}
.main img {
-ms-interpolation-mode: bicubic
}
.fSort i {
background: url(//img.alicdn.com/tfs/TB1XClLeAY2gK0jSZFgXXc5OFXa-165-206.png) 9999px 9999px no-repeat
}
#mallSearch .s-combobox {
width: auto
}
::-ms-clear, ::-ms-reveal {
display: none
}
.attrKey {
white-space: nowrap;
text-overflow: ellipsis
}
.attrs {
border-top: 1px solid #E6E2E1
}
.attrs a {
outline: 0
}
.attr {
background-color: #F7F5F5;
border-color: #E6E2E1 #E6E2E1 #D1CCC7;
border-style: solid solid dotted;
border-width: 0 1px 1px
}
.attr ul:after, .attr:after {
display: block;
clear: both;
height: 0;
content: ' '
}
.attrKey {
float: left;
padding: 7px 0 0;
width: 10%;
color: #B0A59F;
text-indent: 13px
}
.attrKey {
display: block;
height: 16px;
line-height: 16px;
overflow: hidden
}
.attrValues {
position: relative;
float: left;
background-color: #FFF;
width: 90%;
padding: 4px 0 0;
overflow: hidden
}
.attrValues ul {
position: relative;
margin-right: 105px;
margin-left: 25px
}
.attrValues ul.av-collapse {
overflow: hidden
}
.attrValues li {
float: left;
height: 22px;
line-height: 22px
}
.attrValues li a {
position: relative;
color: #806F66;
display: inline-block;
padding: 1px 20px 1px 4px;
line-height: 20px;
height: 20px;
white-space: nowrap
}
.attrValues li a:hover {
color: #ff0036;
text-decoration: none
}
.brandAttr .attr {
border: 2px solid #D1CCC7;
margin-top: -1px
}
.brandAttr .attrKey {
padding-top: 9px
}
.brandAttr .attrValues {
padding-top: 6px
}
.brandAttr .av-collapse {
overflow: hidden;
max-height: 60px
}
.brandAttr li {
margin: 0 8px 8px 0
}
.brandAttr li a {
text-overflow: ellipsis;
overflow: hidden
}
.navAttrsForm {
position: relative
}
.relKeyTop {
padding: 4px 0 0;
margin-left: -13px;
height: 16px;
overflow: hidden;
width: 100%
}
.relKeyTop li {
display: inline-block;
border-left: 1px solid #ccc;
line-height: 1.1;
padding: 0 12px
}
.relKeyTop li a {
color: #999
}
.relKeyTop li a:hover {
color: #ff0036;
text-decoration: none
}
.filter i {
display: inline-block;
overflow: hidden
}
.filter {
margin: 10px 0;
padding: 5px;
position: relative;
z-index: 10;
background: #faf9f9;
color: #806f66
}
.filter i {
position: absolute
}
.filter a {
color: #806f66;
cursor: pointer
}
.filter a:hover {
color: #ff0036;
text-decoration: none
}
.fSort {
float: left;
height: 22px;
line-height: 20px;
line-height: 24px \9;
border: 1px solid #ccc;
background-color: #fff;
z-index: 10
}
.fSort {
position: relative
}
.fSort {
display: inline-block;
margin-left: -1px;
overflow: hidden;
padding: 0 15px 0 5px
}
.fSort:hover, a.fSort-cur {
color: #ff0036;
background: #F1EDEC
}
.fSort i {
top: 6px;
right: 5px;
width: 7px;
height: 10px;
line-height: 10px
}
.fSort .f-ico-arrow-d {
background-position: -22px -23px
}
.fSort-cur .f-ico-arrow-d, .fSort:hover .f-ico-arrow-d {
background-position: -30px -23px
}
i.f-ico-triangle-mb, i.f-ico-triangle-mt {
border: 4px solid transparent;
height: 0;
width: 0
}
i.f-ico-triangle-mt {
border-bottom: 4px solid #806F66;
top: 2px
}
i.f-ico-triangle-mb {
border-top: 4px solid #806F66;
border-width: 3px \9;
right: 6px \9;
top: 12px
}
:root i.f-ico-triangle-mb {
border-width: 4px \9;
right: 5px \9
}
i.f-ico-triangle-mb, i.f-ico-triangle-mt {
border: 4px solid transparent;
height: 0;
width: 0
}
i.f-ico-triangle-mt {
border-bottom: 4px solid #806F66;
top: 2px
}
i.f-ico-triangle-mb {
border-top: 4px solid #806F66;
border-width: 3px \9;
right: 6px \9;
top: 12px
}
:root i.f-ico-triangle-mb {
border-width: 4px \9;
right: 5px \9
}
.view:after {
clear: both;
content: ' '
}
.productImg, .productPrice em b {
vertical-align: middle
}
.product {
position: relative;
float: left;
padding: 0;
margin: 0 0 20px;
line-height: 1.5;
overflow: visible;
z-index: 1
}
.product:hover {
overflow: visible;
z-index: 3;
background: #fff
}
.product-iWrap {
position: absolute;
background-color: #fff;
margin: 0;
padding: 4px 4px 0;
font-size: 0;
border: 1px solid #f5f5f5;
border-radius: 3px
}
.product-iWrap * {
font-size: 12px
}
.product:hover .product-iWrap {
height: auto;
margin: -3px;
border: 4px solid #ff0036;
border-radius: 0;
-webkit-transition: border-color .2s ease-in;
-moz-transition: border-color .2s ease-in;
-ms-transition: border-color .2s ease-in;
-o-transition: border-color .2s ease-in;
transition: border-color .2s ease-in
}
.productPrice, .productShop, .productStatus, .productTitle {
display: block;
overflow: hidden;
margin-bottom: 3px
}
.view:after {
display: block
}
.view {
margin-top: 10px
}
.view:after {
height: 0
}
.productImg-wrap {
display: table;
table-layout: fixed;
height: 210px;
width: 100%;
padding: 0;
margin: 0 0 5px
}
.productImg-wrap a, .productImg-wrap img {
max-width: 100%;
max-height: 210px
}
.productImg {
display: table-cell;
width: 100%;
text-align: center
}
.productImg img {
display: block;
margin: 0 auto
}
.productPrice {
font-family: arial, verdana, sans-serif !important;
color: #ff0036;
font-size: 14px;
height: 30px;
line-height: 30px;
margin: 0 0 5px;
letter-spacing: normal;
overflow: inherit !important;
white-space: nowrap
}
.productPrice * {
height: 30px
}
.productPrice em {
float: left;
font-family: arial;
font-weight: 400;
font-size: 20px;
color: #ff0036
}
.productPrice em b {
margin-right: 2px;
font-weight: 700;
font-size: 14px
}
.productTitle {
display: block;
color: #666;
height: 14px;
line-height: 12px;
margin-bottom: 3px;
word-break: break-all;
font-size: 0;
position: relative
}
.productTitle * {
font-size: 12px;
font-family: \5FAE\8F6F\96C5\9ED1;
line-height: 14px
}
.productTitle a {
color: #333
}
.productTitle a:hover {
color: #ff0036 !important
}
.productTitle a:visited {
color: #551A8B !important
}
.product:hover .productTitle {
height: 14px
}
.productShop {
position: relative;
height: 22px;
line-height: 20px;
margin-bottom: 5px;
color: #999;
white-space: nowrap;
overflow: visible
}
.productStatus {
position: relative;
height: 32px;
border: none;
border-top: 1px solid #eee;
margin-bottom: 0;
color: #999
}
.productStatus span {
float: left;
display: inline-block;
border-right: 1px solid #eee;
width: 39%;
padding: 10px 1px;
margin-right: 6px;
line-height: 12px;
text-align: left;
white-space: nowrap
}
.productStatus a, .productStatus em {
margin-top: -8px;
font-family: arial;
font-size: 12px;
font-weight: 700
}
.productStatus em {
color: #b57c5b
}
.productStatus a {
color: #38b
}
.productImg-wrap {
position: relative
}
.product-iWrap {
min-height: 98%;
width: 210px
}
.view {
padding-left: 5px;
padding-right: 5px
}
.view {
width: 1023px
}
.view .product {
width: 220px;
margin-right: 33px
}
@media (min-width: 1210px) {
.view {
width: 1210px;
padding-left: 5px;
padding-right: 5px
}
.view .product {
width: 220px;
margin-right: 20px
}
}
@media (min-width: 600px) and (max-width: 800px) and (orientation: portrait) {
.view {
width: 775px;
padding-left: 5px;
padding-right: 5px
}
.view .product {
width: 220px;
margin-right: 35px
}
}
.product {
height: 372px
}
.grid-nosku .product {
height: 333px
}
src/main/resources/static/js/axios.min.js
文件:
省略。。。
src/main/resources/static/js/vue.min.js
文件:
省略。。。
src/main/resources/static/js/jquery.min.js
文件:
省略。。。
src/main/resources/templates/index.html
文件:
doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8"/>
<title>java-es 网页搜索实战title>
<link rel="stylesheet" href="../static/css/style.css" th:href="@{#request.getContextPath()/css/style.css}"/>
<script th:src="@{/js/jquery.min.js}">script>
head>
<body class="pg">
<div class="page">
<div id="app" class=" mallist tmall- page-not-market ">
<div id="header" class=" header-list-app">
<div class="headerlayout">
<div class="headercon ">
<h1 id="malllogo">
<img th:src="@{/images/logo.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" id="mq"
class="s-combobox-input" aria-haspopup="true">
div>
div>
<button type="submit" @click.prevent="searchkey" id="searchbtn">搜索button>
div>
fieldset>
form>
div>
div>
div>
div>
div>
<div id="content">
<div class="main">
<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 v-text="result.price">em>
p>
<p class="producttitle">
<a v-html="result.name">a>
p>
<div class="productshop">
<span>店铺: kingjava span>
div>
<p class="productstatus">
<span>月成交<em>999笔em>span>
<span>评价 <a>3a>span>
p>
div>
div>
div>
div>
div>
div>
div>
<script th:src="@{/js/vue.min.js}">script>
<script th:src="@{/js/axios.min.js}">script>
<script>
new Vue({
el: "#app", // 绑定ID,实现渲染
data: {
"keyword": '', // 搜索的关键字
"results": [] // 后端返回的结果
},
methods: {
// 自定义一个搜索方法
searchkey() {
var keyword = this.keyword;
console.log(keyword);
// 通过axios发送请求到后台
axios.get('highlightSearchPages/' + keyword + '/0/20').then(response => {
console.log(response.data);
this.results = response.data;
})
}
}
});
script>
body>
html>
请求 http://localhost:9999/parseContentAndInsertEs/java
把相关数据插入 es
中:
请求 http://localhost:9999/highlightSearchPages/java/0/20
在 es
搜索相关数据数据并高亮显现: