1 Star 0 Fork 165

ElonChung / Java-Review

forked from flatfish / Java-Review 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
ElasticSerach详解.md 42.62 KB
一键复制 编辑 原始数据 按行查看 历史
icanci 提交于 2020-10-20 15:12 . fire 添加依赖

ElasticSerach 详解

ElasticSerach:搜索

  • 一个人
    • Doug Cutting
  • 货比三家
  • 安装
  • 生态圈
  • 分词器 ik
  • RestFul 操作ES
  • CRUD
  • SpringBoot集成ES
  • 实战 爬虫爬取
  • 模拟全文检索

以后需要使用搜索,使用ES

  • 大数据只有两个问题
    • 存储问题
    • 计算问题

搜索的三个产品

Lucene

是一套信息检索的包,jar包,不包含搜索引擎系统!

包含的:索引结构! 读写索引的工具,排序,搜索规则...工具类

Lucene和ElasticSearch的关系:

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

Solr
ElasticSearch

ElasticSearch概述

ElasticSearch,简称ES。是一个开源的高拓展的分布式全文检索的搜索引擎,它可以近乎实时的存储、检索数据,本身拓展性很好。可以拓展到上百台服务器。目的是通过简单的RestFul API 来隐藏Lucene作为核心。

谁在使用 三剑客:ELK - ElasticSearch+logstash+kibana

  • 维基百科
  • Github
  • 电商网站
  • 日志数据分析
  • Stack Overflow
  • 商品价格监控网站
  • BI系统,商业只能

ES和Solr的区别

ES

ES是一个实时分布式搜索引擎和分析引擎,它可以让以前所未有的速度处理大数据成功可能。

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

Solr

Solr 是Apache 下的一个顶级项目,采用Java开发,它是基于Lucene的全文搜索服务器

用POST的方法向Solr服务器发送一个面试Filed以及其内容的文档,Solr根据XML文档删除、增加、更新索引

向外提供类似于 WebService 的API接口

Lucene

Lucene是Apache基金会 4 Jakarta的一个子项目

ES和Solr 总结

  • ES基本上是开箱即用的,非常简单,Solr安装稍微复杂
  • Solr利用Zookeeper进行分布式管理,而ES自带分布式协调功能
  • Solr提供更多形式的数据,如JSON、XML、CSV,而ES只能处理JSON文件
  • Solr官方提供的功能更多,而ES本身更加注重核心功能,高级功能多有第三方从插件提供,例如图形化界面需要Kibana支持
  • Solr查询快,但是更新索引慢(即插入删除慢),用户电商等查询多的应用
    • ES建立索引快。如FaceBook搜索
    • Solr是传统搜索应用的解决方案,但是ES适用于新兴的实时搜索应用
  • Solr成熟比较,有更大的更成熟的用户、开发和贡献社区,而ES相对开发维护者较少,更新太快,学习使用成本高

ElasticSearch安装

声明:JDK1.8,最低要求,ES客户端,界面工具

Java开发,ES的版本和我们之后的对应的Java核心jar包版本需要对应

下载

官网:https://www.elastic.co/cn/downloads/elasticsearch

ElasticSearch: https://mirrors.huaweicloud.com/elasticsearch/?C=N&O=D

logstash: https://mirrors.huaweicloud.com/logstash/?C=N&O=D

kibana: https://mirrors.huaweicloud.com/kibana/?C=N&O=D

Linux和Windows都可以学习

ELK三剑客,解压即用

需要NodeJS和Python

Windows安装

  • 解压
  • 1595222021474
  • 熟悉目录
    • bin 启动文件
    • config 配置文件
      • log4j2.properties 日志配置文件
      • jvm.options Java虚拟机配置
      • elasticsearch.yml ES的配置文件 默认是9200 端口 跨域的问题
    • lib jar包,基于Lucene
    • modules 功能模块
    • logs 日志
    • plugins 插件ik
  • 启动
  • 1595222595737
  • 1595222615892
  • 启动之后访问 localhost:9200 如上图

安装可视化界面 es head 插件

  • 下载地址: https://github.com/mobz/elasticsearch-head

  • 启动

    • npm install
    • npm run start
  • 跨域访问问题

    • 修改配置文件

    • http.cors.enabled: true
      http.cors.allow-origin: "*"
  • 索引就当作是一个数据库

    • 可以建立索引、文档
  • 这个 head 就是当作数据展示工具 kibana 使用爽

  • 1595224495736

了解 ELK

ELK是ElasticSearch、Logstash、Kibana三大开源框架首字母的简称,市面上也称为 Elastic Stack。

  • ES是底层框架
  • Logstash 是ELK的中央数据流引擎,用于从不同目标(文件/数据存储/MQ)收集不同的格式,经过过滤之后输出到不同的地方(文件MQ/Redis/ElasticSearch/Kafka)
  • Kibana用来显示数据

收集清晰数据=>搜索、存储

安装Kibana

Kibana是一个针对ElasticSearch的开源分析和展示可视化平台,用来搜索、查看、交互存储在ElasticSearch索引的数据,使用Kibana可以通过各种图表进行高级数据分析以及展示。Kibana让海量的数据更容易理解。

它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示ElasticSearch查询状态,设置Libana十分简单,无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动ElasticSearch索引检测

官网: https://www.elastic.co/cn/kibana

Kibana版本要和ES版本对应

启动测试

1595226849873

进入bin目录下进程执行 kibana.bat

1595227050115

访问网址测试:http://localhost:5601

1595227135033

开发工具(Post、Curl、Head、谷歌浏览器插件测试)

1595227524403

以后所有的操作都在这里进行编写

汉化!修改配置文件,修改之后重启即可,据说Hadoop的环境很难搭建

i18n.locale: "zh-CN"

1595227899299

ES核心概念

概述

集群,节点,索引,类型,文档,分片,映射

ES是面向文档。关系型数据库和ES的对比 一切都是JSON

关系型数据库 ElasticSearch
数据库(Database) 索引(indices)
表(tables) types(慢慢被弃用)
行(rows) documents
字段(columns) fields

ES集群中可以包含多个索引(数据库),每个索引中可以包含多个类型(表),每个类型下又包含多个文档(行),每个文档中又包含多个字段(列)

物理设计

ES在后台把索引划分为多个分片,每个分片可以在集群中的不同服务之间迁移

默认单个机器就是一个集群

{
    "name": "DESKTOP-0QNFHPB",
    "cluster_name": "elasticsearch",
    "cluster_uuid": "WEED5DuuT1Oycb19zH_hrw",
    "version": {
        "number": "7.6.1",
        "build_flavor": "default",
        "build_type": "zip",
        "build_hash": "aa751e09be0a5072e8570670309b1f12348f023b",
        "build_date": "2020-02-29T00:15:25.529771Z",
        "build_snapshot": false,
        "lucene_version": "8.4.0",
        "minimum_wire_compatibility_version": "6.8.0",
        "minimum_index_compatibility_version": "6.0.0-beta1"
    },
    "tagline": "You Know, for Search"
}

逻辑设计

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

文档

就是我们的一条条数据

user
1 zhangsan 18
2 lisi 19

之前所说的ES是面向文档的,那么就意味着索引和搜索数据的最小单位是文档,ES中,文档有几个重要属性

  • 自我包含 一篇文档同时包含字段和对应的值,也就是同时包含 key:value
  • 可以是层次型的,一个文档中包含自文档,复杂的逻辑实体就是这样来的
  • 灵活的结构,文档不依赖预先定义的模式,我们知道关系型数据库中,要提前定义字段才能使用,在ES中,对于字段是非常灵活的,有时候,我们可以忽略该字段,或者动态添加一个新的字段

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

类型

类型是文档的逻辑容器,就像关系型数据库一样,表格是行的容器,类型对于字段称为映射,比如name映射围殴字符串类型。我们所文档是无模式的,它们不需要拥有映射中所定义的所有字段,比如新增一个字段,那么ES怎么做的呢?ES会自动的讲新字段加入映射,但是这个总段不确定它是什么类型,ES就开始猜,如果这个值是18,那么ES会认为他是整型。但是ES也有可能猜不对,素以最安全的方式就是提前定义好所需的映射,这点即和关系型数据库殊途同归了,先定义好字段,然后再使用。

索引

就是数据库

索引是映射类型的容器,ES中的索引是一个非常强大的文档集合,索引存储了映射类型的字段和其他设置。然后他们被存储到了各个分片上

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

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

每一个底层的分片都是一个Lucene索引,一个包含倒排索引的文件目录,倒排索引的结构使得ES再不扫描全表的情况下,就能告诉你文档包含哪些特定的关键字,

倒排索引

ES使用的是一种倒排索引的结构,采用Lucene到排序最为底层,这种结构适用于快速的全文搜索,一个索引由文档中所有不重复的列表构成,对于每一个词,都有一个包含他的文件列表,

IK分词插件

什么是IK分词器?

分词:就是把一段中文或者别的划分成一个个的关键字,我们在搜索的时候会把我们的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词器是讲每个字看作一个词,比如:"你好,世界" => "你","好","世","界",",",这显然是不符合要求的,所以需要按章IK中文分词器来解决这个问题

IK提供了两个分词算法:ik_smart和ik_max_word,其中ik_smart为最少切分,ik_max_word为最细粒度划分!

安装

  • 官网: https://github.com/medcl/elasticsearch-analysis-ik

  • 下载完毕之后,放入到我们的ES插件即可

  • 重启即可,可以看到ik分词器已经被加载

  • 可以通过 elasticsearch-plugin list来查看安装的插件

  • 使用kibana操作

  • 查看不同的分词器效果

  • GET _analyze
    {
      "analyzer": "ik_smart",
      "text": "爱我中华,56个民族,中国共产党"
    }
    
    结果
    {
      "tokens" : [
        {
          "token" : "爱我",
          "start_offset" : 0,
          "end_offset" : 2,
          "type" : "CN_WORD",
          "position" : 0
        },
        {
          "token" : "中华",
          "start_offset" : 2,
          "end_offset" : 4,
          "type" : "CN_WORD",
          "position" : 1
        },
        {
          "token" : "56个",
          "start_offset" : 5,
          "end_offset" : 8,
          "type" : "TYPE_CQUAN",
          "position" : 2
        },
        {
          "token" : "民族",
          "start_offset" : 8,
          "end_offset" : 10,
          "type" : "CN_WORD",
          "position" : 3
        },
        {
          "token" : "中国共产党",
          "start_offset" : 11,
          "end_offset" : 16,
          "type" : "CN_WORD",
          "position" : 4
        }
      ]
    }
  • GET _analyze
    {
      "analyzer": "ik_max_word",
      "text": "爱我中华,56个民族,中国共产党"
    }
    
    结果
    
    {
      "tokens" : [
        {
          "token" : "爱我",
          "start_offset" : 0,
          "end_offset" : 2,
          "type" : "CN_WORD",
          "position" : 0
        },
        {
          "token" : "中华",
          "start_offset" : 2,
          "end_offset" : 4,
          "type" : "CN_WORD",
          "position" : 1
        },
        {
          "token" : "56",
          "start_offset" : 5,
          "end_offset" : 7,
          "type" : "ARABIC",
          "position" : 2
        },
        {
          "token" : "个",
          "start_offset" : 7,
          "end_offset" : 8,
          "type" : "COUNT",
          "position" : 3
        },
        {
          "token" : "民族",
          "start_offset" : 8,
          "end_offset" : 10,
          "type" : "CN_WORD",
          "position" : 4
        },
        {
          "token" : "中国共产党",
          "start_offset" : 11,
          "end_offset" : 16,
          "type" : "CN_WORD",
          "position" : 5
        },
        {
          "token" : "中国",
          "start_offset" : 11,
          "end_offset" : 13,
          "type" : "CN_WORD",
          "position" : 6
        },
        {
          "token" : "国共",
          "start_offset" : 12,
          "end_offset" : 14,
          "type" : "CN_WORD",
          "position" : 7
        },
        {
          "token" : "共产党",
          "start_offset" : 13,
          "end_offset" : 16,
          "type" : "CN_WORD",
          "position" : 8
        },
        {
          "token" : "共产",
          "start_offset" : 13,
          "end_offset" : 15,
          "type" : "CN_WORD",
          "position" : 9
        },
        {
          "token" : "党",
          "start_offset" : 15,
          "end_offset" : 16,
          "type" : "CN_CHAR",
          "position" : 10
        }
      ]
    }

    输出 超级喜欢狂神说Java

    发现问题:自己需要的词被拆开了

    这个时候就需要自己添加分词器

    进入目录:F:\EnvironmentHomes\elasticsearch-7.6.1\plugins\ik\config

    修改配置文件 IKAnalyzer.cfg.xml 指向自己的文件 my.dic

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
        <comment>IK Analyzer 扩展配置</comment>
        <!--用户可以在这里配置自己的扩展字典 -->
        <entry key="ext_dict">my.dic</entry>
        <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords"></entry>
        <!--用户可以在这里配置远程扩展字典 -->
        <!-- <entry key="remote_ext_dict">words_location</entry> -->
        <!--用户可以在这里配置远程扩展停止词字典-->
        <!-- <entry key="remote_ext_stopwords">words_location</entry> -->
    </properties>

    然后重启ES

    以后的话,我们需要自己配置 分词就在自己定义的dic文件执行配置即可

Rest风格说明

一种软件架构风格,而不是标准,知识提供了一组设计原则和约束条件。它主要用户客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

基本Rest命令说明

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

基于索引的基本操作

基础测试

  • 创建一个索引

  • # PUT /索引名/~类型名~/文档id
    # {请求体}
    PUT /test1/type1/1
    {
      "name":"ic学Java,狂神说",
      "age":3
    }
  • 完成了自动增加了索引 数据也成功的添加了,可以当作数据库学习

数据类型

  • 字符串类型
    • text
    • keyword
  • 数值类型
    • long
    • integer
    • short
    • byte
    • double
    • float
    • half float
    • scaled float
  • 日期类型
    • date
  • te布尔值类型
    • boolean
  • 二进制类型
    • binary
  • 等...

指定字段的类型

创建规则:

PUT /test2
{
    "mappings": {
        "properties": {
            "name":{
                "type": "text"
            },
            "age":{
                "type": "integer"
            },
            "birth":{
                "type": "date"
            }
        }
    }
}

获得规则:

GET test2

查看默认的信息

1595234554350

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

拓展:通过命令ElasticSearch索引情况,通过 get _cat 可以获得ES的很多信息

1595234754283

修改 提交使用PUT即可,然后覆盖,最新办法 曾经的办法

1595234872732

修改之后版本号会增加,然后状态时 update

现在的方法

1595235512031

删除

根据你的请求命令来判断是删除索引还是删除文档记录

使用Rest风格是ES推荐使用的

基于文档的基本操作(重点)

基本操作

添加数据

PUT /ic/user/1
{
  "name":"狂神说",
  "age":23,
  "desc":"你我山前没相见,山后别相逢",
  "tags":["宅男","许嵩","阳光色"]
}

PUT /ic/user/2
{
  "name":"璨詞",
  "age":23,
  "desc":"你我山前没相见,删后别相逢",
  "tags":["许嵩","阳光色"]
}

PUT /ic/user/3
{
  "name":"张三",
  "age":29,
  "desc":"座扣想张三",
  "tags":["渣男","阳光色"]
}

得到数据 使用GET

GET /ic/user/1 


GET ic/user/_search?q=name:张三

修改/更新数据 使用PUT

# 这样会导致数据丢失,不传值就是用  覆盖
PUT /ic/user/3
{
  "name":"张三2号",
  "age":29,
  "desc":"座扣想张三",
  "tags":["渣男","阳光色"]
}

更新 POST _update 推荐使用这种更新方式

# 这样也会导致数据丢失
POST /ic/user/3 
{
  "name":"张三3"
}

# 这里如果不加 _update 就会消除其他的数据
POST /ic/user/3 
{
  "doc":{
     "name":"张三4"
  }
}

# 这是推荐使用的方法
POST /ic/user/3/_update
{
  "doc":{
     "name":"张三4"
  }
}
复杂操作

1595240691987

1595240946678

输出结果,不想要那么多

1595241092562

我们之后使用Java操作ES的时候,所有的方法和对象就是这里的key

排序

1595242136666

分页查询

1595242253050

数据下标是从0开始的

/search/{current}/{pagesize}

布尔值查询

must 命令 所有的条件都要符合

should 符合一个就可以

1595243743939

1595243879035

1595243958003

过滤器

1595244063163

  • gt:大于
  • gte:大于等于
  • lt:小于
  • lte:小于等于
  • 可以使用单个条件和多个条件

匹配多个条件

1595244311029

精确查询

term 查询时直接通过倒排索引指定的词条进程精确查找的

关于分词,

  • 直接查询精确的值

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

两个类型 text会被分词器解析 但是 keyword不会被分词器解析

1595244772843

1595245177453

多个精确查询

高亮查询

1595245546777

1595245662890

  • 匹配
    • 按照条件匹配
    • 精确匹配
    • 区间范围匹配
    • 匹配字段过滤
  • 多条件查询
  • 高亮查询

集成SpringBoot

找文档

  • 找到原生的依赖

  • <!-- 加入ElasticSearch搜索引擎 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
  • 找到对象

  • 分析API的方法

  • 配置基本的项目

  • 问题:一定要保证导入的依赖和我们的ES版本是一致的

索引的操作

@SpringBootTest
class IcEsApiApplicationTests {

    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;

    @Test
    void createIndex() throws IOException {
        // 测试索引的创建
        // 创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("ic_index");
        // 执行创建请求
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response.index());
    }

    @Test
    public void getIndex() throws Exception{
        // 测试获取索引
        GetIndexRequest request = new GetIndexRequest("ic_index");
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    @Test
    public void deleteIndex() throws IOException{
        // 测试获取索引
        DeleteIndexRequest request = new DeleteIndexRequest("ic_index");
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }
}
package cn.icanci;

import cn.icanci.pojo.User;
import com.alibaba.fastjson.JSON;
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.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.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.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 org.elasticsearch.action.search.SearchRequest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;

@SpringBootTest
class IcEsApiApplicationTests {

    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;

    @Test
    void createIndex() throws IOException {
        // 测试索引的创建
        // 创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("ic_index");
        // 执行创建请求
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response.index());
    }

    @Test
    public void getIndex() throws Exception {
        // 测试获取索引
        GetIndexRequest request = new GetIndexRequest("ic_index");
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    @Test
    public void deleteIndex() throws IOException {
        // 测试获取索引
        DeleteIndexRequest request = new DeleteIndexRequest("ic_index");
        AcknowledgedResponse delete = client.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }

    @Test
    public void testAddDocument() throws Exception {
        // 测试添加文档
        // 创建对象
        User user = new User("ic", 2);
        // 创建请求
        IndexRequest request = new IndexRequest("ic_index");
        // 设置规则 put /ic_index/_doc/1
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));
        // request.timeout("1s");
        // 将数据放入请求
        request.source(JSON.toJSONString(user), XContentType.JSON);
        // 客户端发送请求,获取相应的结果
        IndexResponse index = client.index(request, RequestOptions.DEFAULT);
        System.out.println(index.toString());
        System.out.println(index.status());
    }

    @Test
    public void testIsExists() throws Exception {
        // 判断文档是否存在  get /index/doc/1
        GetRequest ic_index = new GetRequest("ic_index", "1");
        // 不获取 返回的 _source 的上下文了
        ic_index.fetchSourceContext(new FetchSourceContext(false));
        ic_index.storedFields("_none_");
        boolean exists = client.exists(ic_index, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    @Test
    public void testGetInfo() throws Exception {
        // 判断文档是否存在  get /index/doc/1
        GetRequest ic_index = new GetRequest("ic_index", "1");
        GetResponse documentFields = client.get(ic_index, RequestOptions.DEFAULT);
        System.out.println(documentFields.getSourceAsString());
        System.out.println(documentFields);
    }

    @Test
    public void testUpdate() throws Exception {
        // 判断文档是否存在  get /index/doc/1
        UpdateRequest updateRequest = new UpdateRequest("ic_index", "1");
        updateRequest.timeout("1s");
        User user = new User("ic", 18);
        updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);
        UpdateResponse update = client.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println(update);
    }

    @Test
    public void testDelete() throws Exception {
        // 删除数据
        DeleteRequest deleteRequest = new DeleteRequest("ic_index", "1");
        deleteRequest.timeout("1s");
        DeleteResponse delete = client.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println(delete.status());
    }

    @Test
    public void testBulkRequest() throws Exception {
        // 批量增加数据
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");
        ArrayList<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            users.add(new User("ic" + i, i));
        }
        // 批处理请求
        for (int i = 0; i < users.size(); i++) {
            bulkRequest.add(new IndexRequest("ic_index")
                            .id("1" + i)
                            .source(JSON.toJSONString(users.get(i)), XContentType.JSON));
        }
        BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.hasFailures());
    }

    @Test
    public void testSearch() throws Exception {
        // 查询
        // 搜索请求,
        // 条件构造
        // termQueryBuilder
        // xxxQueryBuilder 类似于次
        SearchRequest searchRequest = new SearchRequest("ic_index");
        // 构建搜索的条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        // QueryBuilders.termQuery  精确匹配
        // QueryBuilders.matchAllQuery() 匹配所有
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "ic0");
        sourceBuilder.query(termQueryBuilder);
        sourceBuilder.from();
        sourceBuilder.size();
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(sourceBuilder);
        SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
        for (SearchHit searchHits : search.getHits()) {
            System.out.println(searchHits.getSourceAsMap());
        }
    }
}

实战

爬虫

爬取数据?数据库获取,消息队列中获取,都可以称为数据源

爬取数据:(获取请求返回的页面信息。筛选初我们自己想要的数据就可以了)

jsoup包 tika 爬取电影和音乐

  • 导入依赖

  • <!-- 解析网页 -->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.10.2</version>
    </dependency>
  • @Component
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Content {
        private String title;
        private String img;
        private String price;
    }
  • @Configuration
    public class ElasticSearchConfig {
        @Bean
        public RestHighLevelClient restHighLevelClient() {
            RestHighLevelClient restHighLevelClient = new RestHighLevelClient(
                RestClient.builder(
                    new HttpHost("127.0.0.1", 9200, "http")
                )
            );
            return restHighLevelClient;
        }
    }
  • @Component
    public class HtmlParseUtil {
        public List<Content> parseJD(String keyword) throws IOException {
            // 获得请求 https://search.jd.com/Search?keyword=java
            // 前提,需要联网 ajax 不能获取
            String url = "https://search.jd.com/Search?keyword=" + keyword;
            // 解析网页  就是js页面对象
            Document parse = Jsoup.parse(new URL(url), 30000);
            // 所有在 JS 中能使用的方法都可以获取
            Element element = parse.getElementById("J_goodsList");
            // 获取所有的元素
            Elements li = element.getElementsByTag("li");
            // 获取元素的内容
            ArrayList<Content> goodLists = new ArrayList<Content>();
            for (Element ele : li) {
                String img = ele.getElementsByTag("img").eq(0).attr("src");
                String price = ele.getElementsByClass("p-price").eq(0).text();
                String title = ele.getElementsByClass("p-name").eq(0).text();
                goodLists.add(new Content(title, img, price));
            }
            return goodLists;
        }
    }
  • package cn.icanci.service;
    
    /**
     * @Author: icanci
     * @ProjectName: ic-es-jd
     * @PackageName: cn.icanci.service
     * @Date: Created in 2020/7/21 7:51
     * @ClassAction: 业务编写
     */
    @Service
    public class ContentService {
        @Autowired
        @Qualifier("restHighLevelClient")
        private RestHighLevelClient client;
        @Autowired
        private HtmlParseUtil htmlParseUtil;
    
        /**
         * 解析数据放到 ES 索引中
         *
         * @param keyword
         * @return
         * @throws Exception
         */
        public boolean parseContent(String keyword) throws Exception {
            List<Content> contents = htmlParseUtil.parseJD(keyword);
            // 把查询的数据放在 es 中
            BulkRequest bulkRequest = new BulkRequest();
            bulkRequest.timeout("2m");
            if (contents == null || contents.size() == 0) {
                return false;
            }
            for (int i = 0; i < contents.size(); i++) {
                bulkRequest.add(new IndexRequest("jd_goods")
                                .source(JSON
                                        .toJSONString(contents.get(i)), XContentType.JSON));
            }
            BulkResponse bulk = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            return !bulk.hasFailures();
        }
    
        /**
         * 获取这些数据实现搜索功能
         *
         * @param keyword
         * @param pageNo
         * @param pageSize
         * @return
         */
        public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws Exception {
            if (pageNo < 1) {
                pageNo = 1;
            }
            if (pageSize < 1) {
                pageSize = 20;
            }
            // 条件搜索
            SearchRequest searchRequest = new SearchRequest("jd_goods");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 分页实现
            sourceBuilder.from(pageNo);
            sourceBuilder.size(pageSize);
            // 精准匹配
            TermQueryBuilder title = QueryBuilders.termQuery("title", keyword);
            sourceBuilder.query(title);
            sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
            // 执行搜索
            searchRequest.source(sourceBuilder);
            SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
            // 解析结果
            ArrayList<Map<String, Object>> maps = new ArrayList<>();
            for (SearchHit hit : search.getHits().getHits()) {
                maps.add(hit.getSourceAsMap());
            }
            return maps;
        }
    
    
        /**
         * 获取这些数据实现搜索功能
         *
         * @param keyword
         * @param pageNo
         * @param pageSize
         * @return
         */
        public List<Map<String, Object>> searchPageHigh(String keyword, int pageNo, int pageSize) throws Exception {
            if (pageNo < 1) {
                pageNo = 1;
            }
            if (pageSize < 1) {
                pageSize = 20;
            }
            // 条件搜索
            SearchRequest searchRequest = new SearchRequest("jd_goods");
            SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
            // 分页实现
            sourceBuilder.from(pageNo);
            sourceBuilder.size(pageSize);
            // 精准匹配
            TermQueryBuilder title = QueryBuilders.termQuery("title", keyword);
            sourceBuilder.query(title);
            sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
            // 高亮
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("title");
            // 多个高亮一个
            highlightBuilder.requireFieldMatch(false);
            highlightBuilder.preTags("<span style='color:red'>");
            highlightBuilder.postTags("</span>");
            sourceBuilder.highlighter(highlightBuilder);
            // 执行搜索
            searchRequest.source(sourceBuilder);
            SearchResponse search = client.search(searchRequest, RequestOptions.DEFAULT);
            // 解析结果
            ArrayList<Map<String, Object>> maps = new ArrayList<>();
            for (SearchHit hit : search.getHits().getHits()) {
                // 解析高亮的字段
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                HighlightField title1 = highlightFields.get("title");
                // 原来的结果
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                //
                if (title1 != null) {
                    Text[] fragments = title1.fragments();
                    String new_title = "";
                    for (Text fragment : fragments) {
                        new_title += fragment;
                    }
                    // 替换原来的内容
                    sourceAsMap.put("title", new_title);
                }
                maps.add(sourceAsMap);
            }
            return maps;
        }
    }
  • package cn.icanci.controller;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * @Author: icanci
     * @ProjectName: ic-es-jd
     * @PackageName: cn.icanci.controller
     * @Date: Created in 2020/7/21 7:51
     * @ClassAction: 请求编写
     */
    @RestController
    public class ContentController {
        @Autowired
        private ContentService contentService;
    
        @GetMapping("/parse/{keyword}")
        public boolean parse(@PathVariable("keyword") String keyword) throws Exception {
            System.out.println("keyword=>" + keyword);
            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 Exception {
            // 获取数据实现搜索功能
            return contentService.searchPage(keyword, pageNo, pageSize);
        }
    
    
        @GetMapping("/searchHighLight/{keyword}/{pageNo}/{pageSize}")
        public List<Map<String, Object>> searchHighLight(
            @PathVariable("keyword") String keyword,
            @PathVariable("pageNo") int pageNo,
            @PathVariable("pageSize") int pageSize) throws Exception {
            // 获取数据实现搜索功能 并高亮
            return contentService.searchPageHigh(keyword, pageNo, pageSize);
        }
    }
前后端分离
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8"/>
    <title>IC-ES仿京东实战</title>
    <link rel="stylesheet" th:href="@{/css/style.css}"/>
</head>

<body class="pg">
<div class="page" id="app">
    <div id="mallPage" class=" mallist tmall- page-not-market ">
        <!-- 头部搜索 -->
        <div id="header" class=" header-list-app">
            <div class="headerLayout">
                <div class="headerCon ">
                    <!-- Logo-->
                    <h1 id="mallLogo">
                        <img th:src="@{/images/jdlogo.png}" alt="">
                    </h1>

                    <div class="header-extra">

                        <!--搜索-->
                        <div id="mallSearch" class="mall-search">
                            <form name="searchTop" class="mallSearch-form clearfix">
                                <fieldset>
                                    <legend>天猫搜索</legend>
                                    <div class="mallSearch-input clearfix">
                                        <div class="s-combobox" id="s-combobox-685">
                                            <div class="s-combobox-input-wrap">
                                                <input v-model="keyword" type="text" autocomplete="off" value="dd" id="mq"
                                                       class="s-combobox-input" aria-haspopup="true">
                                            </div>
                                        </div>
                                        <button type="submit" @click.prevent="searchKey" id="searchbtn">搜索</button>
                                    </div>
                                </fieldset>
                            </form>
                            <ul class="relKeyTop">
                                <li><a>Java</a></li>
                                <li><a>前端</a></li>
                                <li><a>Linux</a></li>
                                <li><a>大数据</a></li>
                                <li><a>理财</a></li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 商品详情页面 -->
        <div id="content">
            <div class="main">
                <!-- 品牌分类 -->
                <form class="navAttrsForm">
                    <div class="attrs j_NavAttrs" style="display:block">
                        <div class="brandAttr j_nav_brand">
                            <div class="j_Brand attr">
                                <div class="attrKey">
                                    品牌
                                </div>
                                <div class="attrValues">
                                    <ul class="av-collapse row-2">
                                        <li><a href="#"> 狂神说 </a></li>
                                        <li><a href="#"> Java </a></li>
                                    </ul>
                                </div>
                            </div>
                        </div>
                    </div>
                </form>

                <!-- 排序规则 -->
                <div class="filter clearfix">
                    <a class="fSort fSort-cur">综合<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">人气<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">新品<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">销量<i class="f-ico-arrow-d"></i></a>
                    <a class="fSort">价格<i class="f-ico-triangle-mt"></i><i class="f-ico-triangle-mb"></i></a>
                </div>

                <!-- 商品详情 -->
                <div class="view grid-nosku">

                    <div class="product" v-for="result in results">
                        <div class="product-iWrap">
                            <!--商品封面-->
                            <div class="productImg-wrap">
                                <a class="productImg">
                                    <img :src="result.img">
                                </a>
                            </div>
                            <!--价格-->
                            <p class="productPrice">
                                <em>{{result.price}}</em>
                            </p>
                            <!--标题-->
                            <p class="productTitle">
                                <a 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>

<!-- 前端使用Vue 实现前后端分离 -->
<script th:src="@{/js/jquery.min.js}"></script>
<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;
                // 对接后端的接口
                axios.get('searchHighLight/'+keyword+"/1/30").then(response=>{
                    console.log(response)
                    this.results = response.data; // 绑定数据
                })
            }
        }
    })
</script>

</body>
</html>
搜索高亮

已经包含在上述内容

1
https://gitee.com/elonchung/Java-Review.git
git@gitee.com:elonchung/Java-Review.git
elonchung
Java-Review
Java-Review
master

搜索帮助