ElasticSearch7.6入门学习笔记

  • Post author:
  • Post category:其他


在学习ElasticSearch之前,先简单了解一下

Lucene


  • Doug Cutting开发



  • apache软件基金会

    4 jakarta项目组的一个子项目

  • 是一个

    开放源代码



    全文检索引擎工具包

  • 不是一个完整的全文检索引擎,而是一个全文检索引擎的架构

    ,提供了完整的查询引擎和索引引擎,部分

    文本分析

    引擎(英文与德文两种西方语言)
  • 当前以及最近几年最受欢迎的

    免费Java信息检索程序库


Lucene和ElasticSearch的关系:

  • ElasticSearch是基于Lucene 做了一下封装和增强

🔔一、ElasticSearch概述

官网:

Download Elasticsearch | Elastic


Elaticsearch

,简称为es,es是一个开源的

高扩展



分布式全文检索引擎

,它可以近乎

实时的存储



检索数据;

本身扩展性很好,可以扩展到上百台服务器,处理PB级别(大数据时代)的数据。es也使用java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的

目的

是通过简单的

RESTful API

来隐藏Lucene的复杂性,从而让全文搜索变得简单。

据国际权威的数据库产品评测机构DB Engines的统计,在2016年1月,ElasticSearch已超过Solr等,成为

排名第一的搜索引擎类应用


📢历史

多年前,一个叫做Shay Banon的刚结婚不久的失业开发者,由于妻子要去伦敦学习厨师,他便跟着也去了。在他找工作的过程中,为了给妻子构建一个食谱的搜索引擎,他开始构建一个早期版本的Lucene。

直接基于Lucene工作会比较困难,所以Shay开始抽象Lucene代码以便lava程序员可以在应用中添加搜索功能。他发布了他的第一个开源项目,叫做“Compass”。

后来Shay找到一份工作,这份工作处在高性能和内存数据网格的分布式环境中,因此高性能的、实时的、分布式的搜索引擎也是理所当然需要的。然后他决定重写Compass库使其成为一个独立的服务叫做Elasticsearch。

第一个公开版本出现在2010年2月,在那之后Elasticsearch已经成为Github上最受欢迎的项目之一,代码贡献者超过300人。一家主营Elasticsearch的公司就此成立,他们一边提供商业支持一边开发新功能,不过Elasticsearch将永远开源且对所有人可用。

Shay的妻子依旧等待着她的食谱搜索…..


👍谁在使用:

1、维基百科,类似百度百科,全文检索,高亮,搜索推荐/2

2、The Guardian (国外新闻网站) ,类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论) +社交网络数据(对某某新闻的相关看法) ,数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈(好,坏,热门,垃圾,鄙视,崇拜)

3、Stack Overflow (国外的程序异常讨论论坛) , IT问题,程序的报错,提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案

4、GitHub (开源代码管理),搜索 上千亿行代码

5、电商网站,检索商品

6、日志数据分析, logstash采集日志, ES进行复杂的数据分析,

ELK技术, elasticsearch+logstash+kibana


7、商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅牙膏的监控,如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买

8、BI系统,商业智能, Business Intelligence。比如说有个大型商场集团,BI ,分析一下某某区域最近3年的用户消费 金额的趋势以及用户群体的组成构成,产出相关的数张报表, **区,最近3年,每年消费金额呈现100%的增长,而且用户群体85%是高级白领,开-个新商场。ES执行数据分析和挖掘, Kibana进行数据可视化

9、国内:站内搜索(电商,招聘,门户,等等),IT系统搜索(OA,CRM,ERP,等等),数据分析(ES热门

的一一个使用场景)

🏆ES和Solr


🔈ElasticSearch简介

  • Elasticsearch是一个

    实时分布式搜索和分析引擎

    。 它让你以前所未有的速度处理大数据成为可能。
  • 它用于

    全文搜索、结构化搜索、分析

    以及将这三者混合使用:


  • 维基百科


    使用Elasticsearch提供

    全文搜索



    高亮关键字

    ,以及输入

    实时搜索

    (search-asyou-type)和

    搜索纠错

    (did-you-mean)等搜索建议功能。


  • 英国卫报


    使用Elasticsearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解公众对新发表的文章的回应。


  • StackOverflow


    结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。


  • Github


    使用Elasticsearch检索1300亿行的代码。
  • 但是Elasticsearch不仅用于大型企业,它还让像

    DataDog

    以及

    Klout

    这样的创业公司将最初的想法变成可扩展的解决方案。
  • Elasticsearch可以在你的笔记本上运行,也可以在数以百计的服务器上处理PB级别的数据。
  • Elasticsearch是一个基于Apache Lucene(TM)的开源搜索引擎。无论在开源还是专有领域, Lucene可被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。

    • 但是,

      Lucene只是一个库

      。 想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是, Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。
  • Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的

    目的

    是<mark>通过简单的

    RESTful API

    来隐藏Lucene的复杂性,从而让全文搜索变得简单。

🔈Solr简介

  • Solr是Apache下的一个顶级开源项目,采用Java开发,它是

    基于Lucene的全文搜索服务器

    。Solr提供了比Lucene更为

    丰富的查询语言

    ,同时实现了

    可配置



    可扩展

    ,并

    对索引、搜索性能进行了优化
  • Solr可以

    独立运行

    ,运行在letty. Tomcat等这些Selrvlet容器中 , Solr 索引的实现方法很简单,<mark>用POST方法向Solr服务器发送一个描述Field及其内容的XML文档, Solr根据xml文档

    添加、删除、更新

    索引</mark>。Solr 搜索只需要发送HTTP GET请求,然后对Solr返回xml、json等格式的查询结果进行解析,组织页面布局。
  • Solr不提供构建UI的功能,

    Solr提供了一个管理界面,通过管理界面可以查询Solr的配置和运行情况。
  • Solr是基于lucene开发企业级搜索服务器,实际上就是封装了lucene.
  • Solr是一个独立的企业级搜索应用服务器,它

    对外提供类似于Web-service的API接口

    。用户可以通过http请求,向搜索引擎服务器提交-定格式的文件,生成索引;也可以通过提出查找请求,并得到返回结果。

🔈ElasticSearch与Solr比较


当单纯的对已有数据进行搜索时,Solr更快


当实时建立索引时,Solr会产生io阻塞,查询性能较差,ElasticSearch具有明显的优势


转变我们的搜索基础设施后从Solr ElasticSearch,我们看见一个即时~ 50x提高搜索性能!

📕总结

1、

es

基本是

开箱即用

(解压就可以用!) ,非常简单。Solr安装略微复杂一丢丢!

2、

Solr 利用Zookeeper进行分布式管理

,而

Elasticsearch<mark>自身带有分布式协调管理功能。


3、Solr 支持更多格式的数据,比如JSON、XML、 CSV ,而

Elasticsearch仅支持json文件格式



4、Solr 官方提供的功能更多,而Elasticsearch本身更注重于核心功能,高级功能多有第三方插件提供,例如图形化界面需要kibana友好支撑

5、

Solr 查询快,但更新索引时慢(即插入删除慢)

,用于电商等查询多的应用;


  • ES建立索引快(即查询慢)

    ,即

    实时性查询快

    ,用于facebook新浪等搜索。
  • Solr是传统搜索应用的有力解决方案,但Elasticsearch更适用于新兴的实时搜索应用。

6、Solr比较成熟,有一个更大,更成熟的用户、开发和贡献者社区,而Elasticsearch相对开发维护者较少,更新太快,学习使用成本较高。

🔔二、ElasticSearch安装

JDK8,最低要求


使用Java开发,必须保证

ElasticSearch

的版本与Java的核心jar包版本对应!(Java环境保证没错)

🔈Windows下安装

📕1.下载

下载地址:

下载 Elastic 产品 | Elastic

历史版本下载:

Past Releases of Elastic Stack Software | Elastic

解压即可(尽量将ElasticSearch相关工具放在统一目录下)

📕2.熟悉目录

bin 启动文件目录
config 配置文件目录
    1og4j2 日志配置文件
    jvm.options java 虚拟机相关的配置(默认启动占1g内存,内容不够需要自己调整)
    elasticsearch.ym1 elasticsearch 的配置文件! 默认9200端口!跨域!
1ib 
    相关jar包
modules 功能模块目录
plugins 插件目录
    ik分词器

📕3.启动

一定要检查自己的java环境是否配置好


🔈安装可视化界面


elasticsearch-head


使用前提

:需要安装nodejs



📕1.下载地址


GitHub – mobz/elasticsearch-head: A web front end for an elastic search cluster



📕2.安装

解压即可(尽量将ElasticSearch相关工具放在统一目录下)



📕3.启动

cd elasticsearch-head
# 安装依赖
npm install
# 启动
npm run start
# 访问
http://localhost:9100/

📕4.访问

存在跨域问题


开启跨域 (在elasticsearch解压目录config下elasticsearch.yml中添加)

# 开启跨域
http.cors.enabled: true
# 所有人访问
http.cors.allow-origin: "*"


重启Elasticsearch

🔈理解

如果你是初学者

索引可以看作是 “数据库”

类型可以看作是 “表”

文档可以看作是 “库中的数据(表中的行)”

这个head,我们只是把他

当作可视化数据展示工具

,之后

所有的查询都在kibana中进行


🔈安装kibana

Kibana是一个针对ElasticSearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana ,可以通过各种图表进行高级数据分析及展示。Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板( dashboard )实时显示Elasticsearch查询动态。设置Kibana非常简单。无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。

🔖1.下载地址

下载的版本需要与ElasticSearch版本对应


下载 Elastic 产品 | Elastic

历史版本下载:

Past Releases of Elastic Stack Software | Elastic

🔖2.安装

解压即可(尽量将ElasticSearch相关工具放在统一目录下)

🔖3.启动



localhost:5601

🔖4.开发工具

(Postman、curl、head、谷歌浏览器插件)也可以进行测试,但是还是建议使用Kibana进行测试

🔖5.汉化Kibana

编辑器打开


kibana解压目录/config/kibana.yml


,添加


i18n.locale: "zh-CN"


重启kibana

📢了解ELK

  • ELK是

    Elasticsearch、Logstash、 Kibana三大开源框架首字母大写简称

    。市面上也被成为Elastic Stack。

    • 其中Elasticsearch是一个基于Lucene、分布式、通过Restful方式进行交互的近实时搜索平台框架。

      • 像类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用Elasticsearch作为底层支持框架,可见Elasticsearch提供的搜索能力确实强大,市面上很多时候我们简称Elasticsearch为es。
    • Logstash是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ )收集的不同格式数据,经过过滤后支持输出到不同目的地(文件/MQ/redis/elasticsearch/kafka等)。
    • Kibana可以将elasticsearch的数据通过友好的页面展示出来 ,提供实时分析的功能。
  • 市面上很多开发只要提到ELK能够一致说出它是一个日志分析架构技术栈总称 ,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具有代表性。并非唯一性。

收集清洗数据(Logstash) ==> 搜索、存储(ElasticSearch) ==> 展示(Kibana)

🔔三、ElasticSearch核心概念

🏆概述

1、索引(ElasticSearch)

包含多个分片

2、字段类型(映射)

字段类型映射

3、文档

4、分片(Lucene索引,倒排索引)

ElasticSearch是面向文档,关系行数据库和ElasticSearch客观对比,一切是JSON

Relational DB ElasticSearch
数据库(database) 索引(indices)
表(tables) types <慢慢会被弃用!>
行(rows) documents
字段(columns)

fields

ElasticSearch(集群)中可以包含多个

索引

(数据库),每个索引中可以包含多个

类型

(表),每个类型下又包含多个

文档

(行),每个文档中又包含多个

字段

(列)。

🏆物理设计

ElasticSearch在后天把

每个索引划分成多个分片,每分分片可以在集群中的不同服务器间迁移。


一个人就是一个集群,即启动的ElasticSearch服务,默认就是一个集群,且默认集群名为ElasticSearch

🏆逻辑设计

一个索引类型中,包含多个文档,比如说文档1,文档2,当我们索引一篇文档时,可以通过这样的顺序找到它:索引=>类型=>文档ID,通过这个组合我们就能索引到某个具体的文档。注意ID不必时整数,实际上它是一个字符串。

📕文档(“行”)

就是一条条数据

之前说ElasticSearch时面向文档的,那么就意味着

索引和搜索数据的最小单位是文档,ElasticSearch中,文档有几个重要的属性:



1.自我包含,一篇文档同时包含字段和对应的值,也就是同时包含key:value

2.可以是层次型的,一个文档中包含子文档,复杂的逻辑实体就是这么来的!{就是一个json对象!FastJson进行自动转换}

3.灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在ElasticSearch中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态的添加一个新的字段。

尽管我们可以随意地新增或者忽略某个字段,但是每个字段地类型非常重要,比如一个年龄字段类型,可以是字符串也可以是整形,因为ElasticSearch会保存字段和类型之间地映射及其他的设置。这种映射具体到每个映射的每种类型,这也是为什么在ElasticSearch中,类型有时候也称为映射类型。

📕类型(“表”)

类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器,类型中对于字段的定义称为映射,比如name映射为字符串类型,我们说文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么ElasticSearch是怎么做的呢?

ElasticSearch会自动地将新字段加入映射,但是这个字段不确定他是什么类型,ElasticSearch就开始猜,如果这个值是18,那么ElasticSearch会认为它是整形,但是ElasticSearch也可能猜不对,所以

最安全地方式就是提前定义好所需要地映射

,这点跟关系型数据库殊途同归,先定义好字段,然后再使用,别整什么幺蛾子。

📕索引(“库”)

索引是映射类型的容器,ElasticSearch中的索引是一个非常大的文档集合。索引存储了映射类型的字段和其他设置。然后他们被存储到了各个分片上了,我们来研究下分片是如何工作的。

物理设计:节点和分片如何工作

一个集群至少有一个节点,而一个节点就是一个ElasticSearch进程,节点可以有多个索引默认的,如果你创建索引,那么索引将会有5个分片(primary shard,又称主分片)构成的,每一个主分片会有一个副本(replica shard,又称复制分片)

上图是一个有3个节点的集群,可以看到主分片和对应的复制分片都不会在同一个节点内,这样有利于某个节点挂掉了,数据也不至于失。实际上,

一个分片是一个Lucene索引(一个ElasticSearch索引包含多个Lucene索引)



一个包含倒排索引的文件目录,倒排索引的结构使得elasticsearch在不扫描全部文档的情况下,就能告诉你哪些文档包含特定的关键字

。不过,等等,倒排索引是什么鬼?

📕倒排索引(Lucene索引底层)

简单说就是按(文章关键字,对应的文档(0个或多个))形式建立索引,根据关键字就可以直接查询对应的文档(含关键字的),无需查询每一个文档。

🔔四、IK分词器(ElasticSearch插件)

IK分词器:中文分词器

分词:即把一段中文或者别的划分成一个个的关键字,我们在搜索的时候就会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后一一进行匹配操作,

默认的中文分词是将每个字看成一个词



不使用IK分词器的情况下

)。比如“小白程序员员”会被分成“小”,”白“,”程“,”序“,”员“,这显然是不符合要求的,所以我们需要安装中文分词器IK来解决这个问题。


IK分词器提供了两个分词算法:ik_smart(最少切分)和ik_max_word(最细粒度划分)


🔈1.下载

版本要与ElasticSearch版本对应

下载地址:

Releases · medcl/elasticsearch-analysis-ik · GitHub

🔈2.安装

ik文件夹是自己创建的

解压即可(但是我们需要解压到这个ElasticSearch的plugins目录ik文件夹下)

🔈3.重启ElasticSearch

加载了IK分词器

🔈4.查看插件列表

🔈5.使用Kibana测试

ik_smart:最少切分

ik_max_word:最细粒度划分(穷尽词库的可能)

测试可以看出   这是把一句话中最有可能成为词的 所有划分

那么,我们需要手动将该词添加到分词器的词典中

elasticsearch目录/plugins/ik/config/IKAnalyzer.cfg.xml

打开

IKAnalyzer.cfg.xml

文件,扩展字典

重启ElasticSearch,测试

🔔五、Rest风格说明


一种软件架构风格

,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以

更简洁



更有层次



更易于实现缓存

等机制。

🔈基本Rest命令说明

method url地址 描述
PUT(创建,修改) localhost:9200/索引名称/类型名称/文档id 创建文档(指定文档id)
POST(创建) localhost:9200/索引名称/类型名称 创建文档(随机文档id)
POST(修改) localhost:9200/索引名称/类型名称/文档id/_update 修改文档
DELETE(删除) localhost:9200/索引名称/类型名称/文档id 删除文档
GET(查询) localhost:9200/索引名称/类型名称/文档id 查询文档通过文档ID
POST(查询) localhost:9200/索引名称/类型名称/文档id/_search 查询所有数据

🔈关于索引的基础操作

📕1.创建一个索引,添加

PUT /索引名/类型名/文档id

📕2.字段数据类型

  • 字符串类型

    • text、

      keyword

      • text:支持分词,全文检索,支持模糊、精确查询,不支持聚合,排序操作;text类型的最大支持的字符长度无限制,适合大字段存储;
      • keyword:不进行分词,直接索引、支持模糊、支持精确匹配,支持聚合、排序操作。keyword类型的最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定字符长度,超过给定长度后的数据将不被索引,无法通过term精确匹配检索返回结果。
  • 数值型

    • long、Integer、short、byte、double、float、

      half float



      scaled float
  • 日期类型

    • date
  • 布尔类型

    • boolean
  • 二进制类型

    • binary
  • 等等…

📕3.指定字段的类型(使用PUT)

类似于建库(建立索引和字段对应类型),也可看作规则的建立

📕4.获取3建立的规则

📕5.获取默认信息


_doc

默认类型(default type),type 在未来的版本中会逐渐弃用,因此产生一个默认类型进行代替

PUT /test3/_doc/1
{
  "name":"小白",
  "age":19,
  "birth":"2000-01-29"
}


GET test3

如果自己的文档字段没有被指定,那么ElasticSearch就会给我们默认配置字段类型

扩展:通过get_cat/可以获取ElasticSearch的当前的很多信息

📕6.修改

两种方案

①旧的(使用put覆盖原来的值)

  • 版本+1(_version)
  • 但是如果漏掉某个字段没有写,那么更新是没有写的字段,会消失
PUT /test3/_doc/1
{
  "name":"我是小白程序员",
  "age":22,
  "birth":"2000-01-29"
}

GET /test3/_doc/1


PUT /test3/_doc/1
{
  "name":"小白"
}


GET /test3/_doc/1

这里就可以看到version随着更新在递增,然后字段不全的情况下,是删除了没有填写的字段。

②新的(使用post的update)

  • 需要注意doc
  • 不会丢失字段
POST /test3/_doc/1/_update
{
  "doc":{
    "name":"post修改,version不加1",
    "age":23
  }
}


GET /test3/_doc/1

📕7.删除

GET /test1

DELETE /test1

🔈关于文档的基本操作

📕8.查询

查询匹配

  • match:匹配(会使用分词器解析(线分析文档,然后进行查询))
  • _source:过滤字段
  • sort:排序
  • form、size 分页
GET /test3/_doc/_search?q=name:post

GET jianyin/user/_search?q=name:小


//过滤掉无用属性
GET jianyin/user/_search
{
  "query": {
    "match": {
      "name": "小红"
    }
  },
"_source": ["name","desc"]
}


//排序
GET jianyin/user/_search
{
  "query": {
    "match": {
      "name": "小红"
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}


//分页
GET jianyin/user/_search
{
  "query": {
    "match": {
      "name": "小红"
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 1
}

//多条件
GET jianyin/user/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "小红"
          }
        },
        {
          "match": {
            "age": 23
          }
        }
      ]
    }
  }
}

//过滤器
GET jianyin/user/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "小"
          }
        }
      ],
      "filter": {
        "range": {
          "age": {
            "gt": 22
          }
        }
      }
    }
  }
}


//多条件查询
GET jianyin/user/_search
{
  "query": {
    "match": {
      "tags": "宅 暖"
    }
  }
}


我们之后使用Java操作es,所有的方法和对象就是key

must(and),所有的条件都要符合   should(or),对应于mysql中的or

must_not 等价于过滤

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


🏆精确查询


term查询是直接通过倒排索引指定的词条进行精确查询的!

  • 适合查询 number、date、keyword ,不适合text


关于分词:


term,直接查询  精确的


match,会使用分词器解析(先分析分档,然后再通过分析的文档进行查询)


两个类型:text 能被分词器解析   keyword 不能被分词器解析

// 精确查询(必须全部都有,而且不可分,即按一个完整的词查询)
// term 直接通过 倒排索引 指定的词条 进行精确查找的
GET /blog/user/_search
{
  "query":{
    "term":{
      "desc":"年 "
    }
  }
}


text和keyword

  • text:


    • 支持分词



      全文检索

      、支持模糊、精确查询,不支持聚合,排序操作;
    • text类型的最大支持的字符长度无限制,适合大字段存储;
  • keyword:


    • 不进行分词



      直接索引

      、支持模糊、支持精确匹配,支持聚合、排序操作。
    • keyword类型的最大支持的长度为——32766个UTF-8类型的字符,可以通过设置ignore_above指定自持字符长度,超过给定长度后的数据将不被索引,

      无法通过term精确匹配检索返回结果

// 测试keyword和text是否支持分词
// 设置索引类型
PUT /test
{
  "mappings": {
    "properties": {
      "text":{
        "type":"text"
      },
      "keyword":{
        "type":"keyword"
      }
    }
  }
}
// 设置字段数据
PUT /test/_doc/1
{
  "text":"测试keyword和text是否支持分词",
  "keyword":"测试keyword和text是否支持分词"
}
// text 支持分词
// keyword 不支持分词
GET /test/_doc/_search
{
  "query":{
   "match":{
      "text":"测试"
   }
  }
}// 查的到
GET /test/_doc/_search
{
  "query":{
   "match":{
      "keyword":"测试"
   }
  }
}// 查不到,必须是 "测试keyword和text是否支持分词" 才能查到
GET _analyze
{
  "analyzer": "keyword",
  "text": ["测试liu"]
}// 不会分词,即 测试liu
GET _analyze
{
  "analyzer": "standard",
  "text": ["测试liu"]
}// 分为 测 试 liu
GET _analyze
{
  "analyzer":"ik_max_word",
  "text": ["测试liu"]
}// 分为 测试 liu


🏆高亮查询

/// 高亮查询
GET blog/user/_search
{
  "query": {
    "match": {
      "name":"流"
    }
  }
  ,
  "highlight": {
    "fields": {
      "name": {}
    }
  }
}
// 自定义前缀和后缀
GET blog/user/_search
{
  "query": {
    "match": {
      "name":"流"
    }
  }
  ,
  "highlight": {
    "pre_tags": "<p class='key' style='color:red'>",
    "post_tags": "</p>", 
    "fields": {
      "name": {}
    }
  }
}


📕9.插入一条数据

PUT /jianyin/user/1
{
  "name":"小白",
  "age":23,
  "desc":"一顿操作猛如虎",
  "tags":["技术宅","温暖"]
}

📕10.获取数据

GET jianyin/user/1

📕11.更新数据

①更新数据的第一种使用PUT

PUT /jianyin/user/3
{
  "name":"小白程序员",
  "age":22,
  "desc":["技术宅"],
  "tags":["靓女"]
}

②使用POST改动数据

(推荐使用)

POST /jianyin/user/3/_update
{
  "doc":{
    "name":"程序员",
    "age":22,
    "desc":"我是最暖的暖男"
  }
}

🔔六、集成SpringBoot、

🔈①创建项目

🔈②导入依赖

注意依赖版本和安装的版本一致

<properties>
    <java.version>1.8</java.version>
    <!-- 统一版本 -->
    <elasticsearch.version>7.6.1</elasticsearch.version>
</properties>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.70</version>
</dependency>
<!-- lombok需要安装插件 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

🔈③编写ElasticSearch配置文件并创建实体类

@Configuration
public class ElasticSearchConfig {
    // 注册 rest高级客户端 
    @Bean
    public RestHighLevelClient restHighLevelClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1",9200,"http")
                )
        );
        return client;
    }
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private static final long serialVersionUID = -3843548915035470817L;
    private String name;
    private Integer age;
}

🔈④测试

所有的测试都在测试文件中写

🕹️注入


RestHighLevelClient


@Resource
private RestHighLevelClient restHighLevelClient;

🕹️索引的操作

🔖创建索引

@Test
public void testCreateIndex() throws IOException{
     CreateIndexRequest request = new CreateIndexRequest("xiaobai_index");
     CreateIndexResponse response = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);

     System.out.println(response.isAcknowledged());//查看是否创建成功
     System.out.println(response);//查看返回对象
     restHighLevelClient.close();
    }

🔖索引获取,并判断是否存在

    //索引获取,并判断是否存在
    @Test
    public void testIndexIsExist() throws IOException {
        GetIndexRequest request = new GetIndexRequest("index");
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);//检查索引是否存在
        restHighLevelClient.close();
    }

🔖索引删除

    //索引删除
    @Test
    public void testDeleteIndex() throws IOException {
        DeleteIndexRequest request = new DeleteIndexRequest("xiaobai_index");
        AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete);
        restHighLevelClient.close();
    }

🕹️文档的操作

🔖文档的添加

 @Test
    public void testAddDocument() throws IOException {
        //创建一个对象
        User user = new User("xiaobai",23);
        //创建请求
        IndexRequest request = new IndexRequest("xiaobai_index");
        //制定规则PUT /xiaobai_index/_doc/1
        request.id("1");//设置文档id
        request.timeout(TimeValue.timeValueMillis(1000));
        //将我们的数据放到请求中
        request.source(JSON.toJSONString(user), XContentType.JSON);
        //客户端发送请求
        IndexResponse response = restHighLevelClient.index(request,RequestOptions.DEFAULT);
        System.out.println(response.status());//获取建立索引的状态信息 CREATED
        System.out.println(response);//查看返回内容
    }

🔖获取文档信息

    //文档信息的获取
    @Test
    public void testGetDocument() throws IOException {
        GetRequest request = new GetRequest("xiaobai_index","1");
        GetResponse response = restHighLevelClient.get(request,RequestOptions.DEFAULT);
        System.out.println(response.getSourceAsString());//打印文档内容
        System.out.println(request);
        restHighLevelClient.close();
    }

🔖文档的获取并判断是否存在

    //文档的获取,并判断是否存在
    @Test
    public void testDocumentIsExists() throws IOException {
        GetRequest request = new GetRequest("xiaobai_index","2");
        //不获取返回的,_source的上下文
        request.fetchSourceContext(new FetchSourceContext(false));
        request.storedFields("_none_");

        boolean exists = restHighLevelClient.exists(request,RequestOptions.DEFAULT);
        System.out.println(exists);
    }

🔖文档更新

//文档的更新
    @Test
    public void testUpdateDocument() throws IOException {
        UpdateRequest request = new UpdateRequest("xiaobai_index","1");
        User user = new User("zjy",23);
        request.doc(JSON.toJSONString(user),XContentType.JSON);
        UpdateResponse response = restHighLevelClient.update(request,RequestOptions.DEFAULT);
        System.out.println(response.status());
        restHighLevelClient.close();
    }

🔖文档的删除

    //文档的删除
    @Test
    public void testDeleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("xiaobai_index","1");
        request.timeout("1s");
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.println(response.status());
    }

🔖文档的查询

可以结合文档的批量插入来测试

    //文档的查询
/**
    * 使用QueryBuilder
    * termQuery("key", obj) 完全匹配
    * termsQuery("key", obj1, obj2..) 一次匹配多个值
    * matchQuery("key", Obj) 单个匹配, field不支持通配符, 前缀具高级特性
    * multiMatchQuery("text", "field1", "field2"..); 匹配多个字段, field有通
    配符才行
    * matchAllQuery(); 匹配所有文件
*/

    @Test
    public void testSearch() throws IOException {
        //1.创建查询请求对象
        SearchRequest searchRequest = new SearchRequest();
        //2.构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //(1)查询条件 使用QueryBuilders工具类创建
        //精确查询
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "xiaobai");
        //匹配查询
        //MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();


        //(2)其他《可有可无》:可以参考SearchSourceBuilder的字段部分
        //设置高亮
        searchSourceBuilder.highlighter(new HighlightBuilder());
        //分页
        //searchSourceBuilder.from(0);
        //searchSourceBuilder.size(2);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        //(3)条件投入
        searchSourceBuilder.query(termQueryBuilder);

        //3.添加条件到请求
        searchRequest.source(searchSourceBuilder);

        //4.客户端查询请求
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);

        //5.查询返回结果
        SearchHits hits = searchResponse.getHits();
        System.out.println(JSON.toJSONString(hits));
        System.out.println("===============================");
        for (SearchHit documentFields : hits.getHits()) {
            System.out.println(documentFields.getSourceAsString());
        }
    }

🔖批量添加数据

📃前面的操作都无法批量添加数据

    //这些api无法批量增加数据(只会保留最后一个source)
    @Test
    public void test() throws IOException {
        IndexRequest request = new IndexRequest("bulk");//没有id会自动生成一个随机id
        request.source(JSON.toJSONString(new User("yangyang",1)),XContentType.JSON);
        request.source(JSON.toJSONString(new User("xiaoyi",2)),XContentType.JSON);
        request.source(JSON.toJSONString(new User("xiaodeng",3)),XContentType.JSON);

        IndexResponse index = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        System.out.println(index.status());
    }

📃批量添加数据

    //批量添加数据
    //特殊的,项目中一般都会批量插入数据
    @Test
    public void testBulk() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");

        ArrayList<User> users = new ArrayList<>();
        users.add(new User("xiaobai-1",1));
        users.add(new User("xiaobai-2",2));
        users.add(new User("xiaobai-3",3));
        users.add(new User("xiaobai-4",4));
        users.add(new User("xiaobai-5",5));
        users.add(new User("xiaobai-6",6));

        //批量请求处理
        for (int i = 0; i < users.size(); i++) {
            bulkRequest.add(
                    //这里是数据信息
                    new IndexRequest("bulk")
                            .id(""+(i+1))//没有设置id  会自己生成一个随机id
                            .source(JSON.toJSONString(users.get(i)),XContentType.JSON)
            );
        }

        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest,RequestOptions.DEFAULT);
        System.out.println(bulkResponse.status());
    }

👑实战

🔖创建SpringBoot项目


🔖修改application.properties文件

👑后端代码

🔖编写ElasticSearchClientConfig配置文件

package com.xiaobai.esdemo2.config;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Configuration;

/**
 * @author 小白程序员
 * @date 2023/8/6 17:18
 */
@Configuration
public class ElasticSearchClientConfig {

    public RestHighLevelClient restHighLevelClient(){
        return new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("127.0.0.1",9200,"http")
                )
        );
    }
}

🔖编写实体类

package com.xiaobai.esdemo2.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Content {
private String title; // 商品名称
private String price; // 商品价格
private String img; // 商品封面
// 大家可以自行扩展使用
}

🔖编写爬虫

package com.xiaobai.esdemo2.utils;

import com.xiaobai.esdemo2.domain.Content;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 小白程序员
 * @date 2023/8/6 16:49
 */
public class HtmlParseUtil {

    public List<Content> parseJD(String keywords) throws Exception {
        //jsoup不能抓取ajax的请求,除非自己模拟浏览器进行请求
        //1.https://search.jd.com/Search?keyword=java
        String url = "https://search.jd.com/Search?keyword="+keywords;
        //2.解析网页(需要联网)
        Document document = Jsoup.parse(new URL(url), 30000);

        //3.抓取搜索到的数据
        //Document就是我们js的Document对象,你可以看多很多js语法
        Element element = document.getElementById("J_goodsList");

        //4.找到所有的li元素
        Elements elements = element.getElementsByTag("li");

        ArrayList<Content> goodsList = new ArrayList<>();
        System.out.println(elements.get(1));
        //获取京东的商品信息
        for (Element el : elements) {
            //这种网站,一般为了保证效率,一般会延时加载图片
            String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String title = el.getElementsByClass("p-name").eq(0).text();
            //封装数据
            Content content = new Content();
            content.setTitle(title);
            content.setPrice(price);
            content.setImg(img);
            goodsList.add(content);

            System.out.println(img);
            System.out.println(price);
            System.out.println(title);
            System.out.println("================================");
        }
        return goodsList;
    }


    //测试
    public static void main(String[] args) throws Exception {
        new HtmlParseUtil().parseJD("vue").forEach(System.out::println);
    }
}

🔖编写业务层存入es和搜索业务

package com.xiaobai.esdemo2.service;

import com.alibaba.fastjson.JSON;
import com.xiaobai.esdemo2.domain.Content;
import com.xiaobai.esdemo2.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.xcontent.XContentType;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;

import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author 小白程序员
 * @date 2023/8/6 17:21
 */
@Service
public class ContentService {

    @Resource
    private RestHighLevelClient restHighLevelClient;

    //1.解析数据存入es
    public Boolean parseContent(String keywords) throws Exception {
        //解析查询出来的数据
        List<Content> contents = new HtmlParseUtil().parseJD(keywords);
        //封装数据到索引库
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout(TimeValue.timeValueMinutes(2));
        bulkRequest.timeout("2m");
        for (int i = 0; i < contents.size(); i++) {
            bulkRequest.add(new IndexRequest("jd_goods")
                    .source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
        }
        BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return !bulkResponse.hasFailures();
    }

    //2.实现搜索功能,带分页处理
    public List<Map<String, Object>> searchContentPage(String keyword,int pageNo,int pageSize) throws IOException {
        if(pageNo<=1){
            pageNo = 1;
        }
        //基本的条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
        //精准匹配 queryBuilders根据自己要求配置查询条件即可
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
        sourceBuilder.query(termQueryBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        //搜索
        searchRequest.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //解析结果
        List<Map<String,Object>> list = new ArrayList<>();
        for (SearchHit documentFields : response.getHits().getHits()) {
            list.add(documentFields.getSourceAsMap());
        }
        return list;
    }


    // 3、实现搜索功能,带高亮
    public List<Map<String, Object>> searchContentHighlighter(String keyword,
                                                              int pageNo, int pageSize) throws IOException {
    // 基本的参数判断!
        if(pageNo <= 1){
            pageNo = 1;
        }
    // 基本的条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    // 分页
        sourceBuilder.from(pageNo);
        sourceBuilder.size(pageSize);
    // 精准匹配 QueryBuilders 根据自己要求配置查询条件即可!
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title",
                keyword);
        sourceBuilder.query(termQueryBuilder);
    // 高亮构建!
        HighlightBuilder highlightBuilder = new HighlightBuilder(); //生成高亮查询器
        highlightBuilder.field("title"); //高亮查询字段
        highlightBuilder.requireFieldMatch(false); //如果要多个字段高亮,这项要为false
        highlightBuilder.preTags("<span style=\"color:red\">"); //高亮设置
        highlightBuilder.postTags("</span>");
        sourceBuilder.highlighter(highlightBuilder);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    // 搜索
        searchRequest.source(sourceBuilder);
        SearchResponse response = restHighLevelClient.search(searchRequest,
                RequestOptions.DEFAULT);
        // 解析结果!
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit hit : response.getHits()) {
        //获取高亮字段
            Map<String, HighlightField> highlightFields =
                    hit.getHighlightFields();
            HighlightField titleField = highlightFields.get("title");
            Map<String, Object> source = hit.getSourceAsMap();
        //千万记得要记得判断是不是为空,不然你匹配的第一个结果没有高亮内容,那么就会报空指针异常,这个错误一开始真的搞了很久
            if(titleField!=null){
                Text[] fragments = titleField.fragments();
                String name = "";
                for (Text text : fragments) {
                    name += text;
                }
                source.put("title", name); //高亮字段替换掉原本的内容
            }
            list.add(source);
        }
        return list;
    }

}


这个地方一定要看清楚导入的包,否则会报错的。

🔖编写控制层

package com.xiaobai.esdemo2.controller;

import com.xiaobai.esdemo2.service.ContentService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * @author 小白程序员
 * @date 2023/8/6 17:40
 */
@RestController
public class ContentController {

    @Resource
    private ContentService contentService;

    @GetMapping("/parse/{keyword}")
    public Boolean parse(@PathVariable("keyword") String keyword) throws Exception {
        return contentService.parseContent(keyword);
    }


    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
                                            @PathVariable("pageNo") int pageNo,
                                            @PathVariable("pageSize") int pageSize) throws IOException {
        return contentService.searchContentHighlighter(keyword,pageNo,pageSize);
    }
}


以上后端的代码就写完了!

👑前端代码

目录结构


static/css/style.css

/*** uncss> filename: http://localhost:9090/css/global.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
}


static/images/jdlogo.png


static/js


这里需要下载axios.min.js    jquery.min.js   vue.min.js


templates/

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <title>小白Java-ES仿京东实战</title>
    <link rel="stylesheet" th:href="@{/css/style.css}"/>
    <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 ">
                    <!-- Logo-->
                    <h1 id="mallLogo">
                        <img th:src="@{/images/jdlogo.png}" alt="">
                    </h1>
                    <div class="header-extra">
                        <!--搜索-->
                        <div id="mallSearch" class="mall-search">
                            <form name="searchTop" class="mallSearch-form clearfix">
                                <fieldset>
                                    <legend>天猫搜索</legend>
                                    <div class="mallSearch-input clearfix">
                                        <div class="s-combobox" id="s-combobox-685">
                                            <div class="s-combobox-input-wrap">
                                                <input v-model="keyword"  type="text" autocomplete="off" id="mq"
                                                       class="s-combobox-input"  aria-haspopup="true">
                                            </div>
                                        </div>
                                        <button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
                                    </div>
                                </fieldset>
                            </form>
                            <ul class="relKeyTop">
                                <li><a>小白Java</a></li>
                                <li><a>小白前端</a></li>
                                <li><a>小白Linux</a></li>
                                <li><a>小白大数据</a></li>
                                <li><a>小白聊理财</a></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 商品详情页面 -->
        <div id="content">
            <div class="main">
                <!-- 品牌分类 -->
                <form class="navAttrsForm">
                    <div class="attrs j_NavAttrs" style="display:block">
                        <div class="brandAttr j_nav_brand">
                            <div class="j_Brand attr">
                                <div class="attrKey">
                                    品牌
                                </div>
                                <div class="attrValues">
                                    <ul class="av-collapse row-2">
                                        <li><a href="#"> 狂神说 </a></li>
                                        <li><a href="#"> Java </a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </form>
                <!-- 排序规则 -->
                <div class="filter clearfix">
                    <a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
                </div>
                <!-- 商品详情 -->
                <div class="view grid-nosku" >
                    <div class="product" v-for="result in results">
                        <div class="product-iWrap">
                            <!--商品封面-->
                            <div class="productImg-wrap">
                                <a class="productImg">
                                    <img :src="result.img">
                                </a>
                            </div>
                            <!--价格-->
                            <p class="productPrice">
                                <em v-text="result.price"></em>
                            </p>
                            <!--标题-->
                            <p class="productTitle">
                                <a v-html="result.title"></a>
                            </p>
                            <!-- 店铺名 -->
                            <div class="productShop">
                                <span>店铺: 狂神说Java </span>
                            </div>
                            <!-- 成交信息 -->
                            <p class="productStatus">
                                <span>月成交<em>999笔</em></span>
                                <span>评价 <a>3</a></span>
                            </p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
<script th:src="@{/js/vue.min.js}"></script>
<script th:src="@{/js/axios.min.js}"></script>
<script>
    new Vue({
        el:"#app",
        data:{
            "keyword": '', // 搜索的关键字
            "results":[] // 后端返回的结果
        },
        methods:{
            searchKey(){
                var keyword = this.keyword;
                console.log(keyword);
                axios.get('search/'+keyword+'/0/20').then(response=>{
                    console.log(response.data);
                    this.results=response.data;
                })
            }
        }
    });
</script>
</body>
</html>

*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。完结!



版权声明:本文为zoroly原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。