ElasticSearch 实战

1. 背景

Lucene 是一套信息检索工具包(jar包),不包含搜索引擎系统。包含:索引结构,排序,搜索规则…

ElasticSearch 基于 Lucene库 做了一些封装和增强,是一个分布式全文搜索引擎(全文搜索、结构化搜索、分析),可以通过简单的RESTful API来隐藏Lucene的复杂性,从而让全局搜索变得更简单。

2.ElasticSearch安装

  • JDK1.8,最低要求

ElasticSearch下载

ElasticSearch Head 插件下载

ElasticSearch IK分词器下载pei

bin 启动文件

config 配置文件

​ log4j2.properties 日志配置文件

​ jvm.options java 虚拟机相关配置

​ elasticsearch.yml elasticsearch 的配置文件!默认9200端口

  • ES默认端口9200,API调用默认9300

Logstash下载

  • Logstash默认端口5044

Kibana下载

  • Kibana默认端口5601

head当作数据展示的工具,所有的查询都在Kibana里做

ELK的关系

在这里插入图片描述

3. ES核心概念

  1. 索引
  2. 类型(mapping)
  3. 文档(documents)
Relational DBElasticSearch
数据库(database)索引(indices)
表(tables)类型(types)
行(rows)文档(documents)
字段(columns)fields
  • 物理设计:ES在后台把每个索引划分成多个分片,每个分片可以在集群中的不同服务器间迁移
  • 逻辑设计:索引一篇文档时,通过顺序找到它:索引 👉 类型 👉 文档ID

在这里插入图片描述

  1. 分片:倒排索引(inverted index)
  • 常规索引是文档到关键词的映射:文档 ——> 关键词
  • 倒排索引是关键词到文档的映射:关键词 ——> 文档

这样只要有关键词,立马就能找到她在那个文档里出现过,剩下的事就是把它揪出来了

4. ElasticSearch启动

应用打开方式
ElasticSearch打开elasticsearch.bat
head插件在E:/EssentialSoftware/Elasticsearch-Head下执行grunt server
Logstash在E:/EssentialSoftware/Logstash-7.7.0/bin下执行logstash -f logstash.conf
Kibana打开kibana.bat
  1. 开发工具使用Kibana,http://localhost:5601

在这里插入图片描述

5. ik分词器

ik分词器下载

5.1 ik_max_word 和 ik_smart

  • ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;
    在这里插入图片描述

  • ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase Query。
    在这里插入图片描述

5.2 查看ES下安装的插件

在这里插入图片描述

5.3 添加自己的字典

  1. 在 E:\EssentialSoftware\Elasticsearch-7.7.0\plugins\ik\config 路径下新建mydir.dic

  2. 在mydir.dic里写入自己的词语
    在这里插入图片描述

  3. 打开路径:E:\EssentialSoftware\Elasticsearch-7.7.0\plugins\ik\config\IKAnalyzer.cfg.xml ,在指定位置写入mydir.dic

在这里插入图片描述

  1. 重启ES

6. Rest风格

一种软件架构风格,不是硬性的标准。它主要用于客户端和服务器端交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

  • 基本的Rest命令
methodurldescription
PUTlocalhost:9200/索引名称/类型名称/文档id创建文档(指定文档id)
POSTlocalhost:9200/索引名称/类型名称创建文档(随机文档id)
POSTlocalhost:9200/索引名称/类型名称/文档id/_update修改文档
DELETElocalhost:9200/索引名称/类型名称/文档id删除文档
GETlocalhost:9200/索引名称/类型名称/文档id查询文档
POSTlocalhost:9200/索引名称/类型名称/文档id/_search查询文档中的所有数据

6.1 关于文档的操作

6.1 创建一个文档 PUT

PUT /test1/type1/doc1 
{
  "name": "大海的对面都是敌人",
  "age": 3
}

在这里插入图片描述
在这里插入图片描述

6.2 查询文档 GET

在这里插入图片描述
在这里插入图片描述

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

通过GET _cat/命令,可获得 ES 的很多信息

GET _cat/health
GET _cat/indices

在这里插入图片描述

6.3 修改文档 POST

POST /test1/type1/doc1/_update
{
  "doc": {
    "name": "但是我也要去"
  }
}

在这里插入图片描述

在这里插入图片描述

6.4 查询文档

GET test1/type1/_search?q=name:一舫

在这里插入图片描述

6.5 花式查询文档

6.5.1 查询
  • 查询文档中带“但是我”的字段
GET test1/type1/_search
{
  "query": {
    "match": {
      "name": "但是我"
    }
  }
}

在这里插入图片描述

6.5.2 过滤字段
  • 过滤掉"age"这个字段
GET test1/type1/_search
{
  "query": {
    "match": {
      "name": "舫"
    }
  }, 
  "_source": ["age"]
}

在这里插入图片描述

6.5.3 排序字段
  • 对"age"字段,用降序方式进行排序
GET test1/type1/_search
{
  "query": {
    "match": {
      "name": "舫"
    }
  }, 
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}

在这里插入图片描述

6.5.4 分页查询
  • "from"从第几个数据开始,"size"返回多少条数据(单页面的数据)
GET test1/type1/_search
{
  "query": {
    "match": {
      "name": "舫"
    }
  }, 
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2
}

在这里插入图片描述

6.5.5 布尔值查询
  • must(相当于and)
GET test1/type1/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "舫"
          }
        },
        {
          "match": {
            "age": 23
          }
        }
      ]
    }
  }, 
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2
}

在这里插入图片描述

  • should(相当于or)
GET test1/type1/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "name": "舫"
          }
        },
        {
          "match": {
            "age": 23
          }
        }
      ]
    }
  }, 
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2
}

在这里插入图片描述

  • must_not(相等于not)
GET test1/type1/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "name": "舫"
          }
        }
      ]
    }
  }, 
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2
}

在这里插入图片描述

6.5.6 高级过滤字段
GET test1/type1/_search
{
  "query": {
    "bool": {
      "must_not": [
        {
          "match": {
            "name": "舫"
          }
        }
      ], 
      "filter": {
        "range": {
          "age": {
            "gte": 10,
            "lte": 30
          }
        }
      }
    }
  }
}

在这里插入图片描述

6.5.7 短语查询
GET /test1/_search
{
  "query": {
    "match": {
      "hobby": "自由 跑步"
    }
  }
}

在这里插入图片描述

6.5.8 精确查询

6.2 常见的数据类型

在这里插入图片描述

6.3 关于索引的操作

6.3.1 创建一个索引

PUT /test2
{
  "mappings": {
    "properties": {
      "name": {
        "type": "text"
      },
      "age": {
        "type": "long"
      },
      "birthday": {
        "type": "date"
      }
    }
  }
}

在这里插入图片描述

在这里插入图片描述

7. ES 在 Spring Boot 中创建索引与文档

7.1 官方文档

ES官方文档

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

7.2 配置基本项目依赖

  1. 新建项目,选择导入的依赖

在这里插入图片描述

  1. 配置ES的依赖
<properties>
        <java.version>1.8</java.version>
        <!-- 这里SpringBoot默认配置的版本不匹配,我们需要自己配置版本!我这里使用7.7.0 -->
        <elasticsearch.version>7.7.0</elasticsearch.version>
</properties>

在这里插入图片描述

  1. 我们编写一个配置类,提供这个bean来进行操作
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;

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")));
        return client;
    }
}

7.3 常用方法

所需要的头文件和包

package com.example.esdemo;

import com.alibaba.fastjson.JSON;
import com.example.esdemo.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.concurrent.TimeUnit;

@SpringBootTest
class EsDemoApplicationTests {
    
    @Autowired
    @Qualifier("restHighLevelClient")
    RestHighLevelClient client;
}

7.3.1 创建索引

@Test
void testGreateIndex() throws IOException {
    // 1、创建索引请求
    CreateIndexRequest request = new CreateIndexRequest("yifang_index");
    // 2、客户端执行请求 IndicesClient,请求后获得响应
    CreateIndexResponse createIndexResponse =
        client.indices().create(request, RequestOptions.DEFAULT);
    System.out.println(createIndexResponse);
}

7.3.2 获取索引

@Test
void testExistIndex() throws IOException {
    GetIndexRequest request = new GetIndexRequest("yifang_index");
    boolean exists =
        client.indices().exists(request, RequestOptions.DEFAULT);
    System.out.println(exists);
}

7.3.3 删除索引

@Test
void testDeleteIndex() throws IOException {
    DeleteIndexRequest request = new DeleteIndexRequest("yifang_index");
    AcknowledgedResponse delete =
        client.indices().delete(request, RequestOptions.DEFAULT);
    System.out.println(delete.isAcknowledged());
}

在POJO下创建User类

package com.example.esdemo.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

@Data
@AllArgsConstructor
@Component
@NoArgsConstructor
public class User {
    private String name;
    private int age;
}

7.3.4 添加文档

@Test
void testAddDocment() throws IOException {
    // 创建对象
    User user = new User("xuyifang", 22);
    // 创建请求
    IndexRequest request = new IndexRequest("yifang_index");

    // 规则
    request.id("1");
    request.timeout("1s");

    // 将我们的数据放入请求   json
    request.source(JSON.toJSONString(user), XContentType.JSON);

    // 客户端发送请求
    IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
    System.out.println(indexResponse.toString());
    System.out.println(indexResponse.status());
}

7.3.5 获取文档信息,判断文档是否存在

@Test
void testIsExist() throws IOException {
    GetRequest getRequest = new GetRequest("yifang_index", "1");
    // 不获取返回的 _source 的上下文
    getRequest.fetchSourceContext(new FetchSourceContext(false));
    getRequest.storedFields("_none_");
    // 判断文档是否存在
    boolean exists = client.exists(getRequest, RequestOptions.DEFAULT);	
    System.out.println(exists);
}

7.3.6 更新文档信息

@Test
void testUpdateDocment() throws IOException {
    UpdateRequest updateRequest = new UpdateRequest("yifang_index", "1");
    updateRequest.timeout("1s");

    User user = new User("xuyifang", 22);
    updateRequest.doc(JSON.toJSONString(user), XContentType.JSON);

    UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
    System.out.println(updateResponse.status());
}

7.3.7 删除文档信息

@Test
void testDeleteDocment() throws IOException {
    DeleteRequest deleteRequest = new DeleteRequest("yifang_index", "1");	// index, id
    deleteRequest.timeout("1s");

    DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
    System.out.println(deleteResponse.status());
}

7.3.8 批量插入文档信息

@Test
void testBulkDocment() throws IOException {
    BulkRequest bulkRequest = new BulkRequest();
    bulkRequest.timeout("10s");

    ArrayList<User> userArrayList = new ArrayList<>();
    userArrayList.add(new User("Java 为什么搜不出来", 123));
    userArrayList.add(new User("Java 我真的搞不懂", 23));
    userArrayList.add(new User("Java 我真的搞不懂啊啊啊啊", 233));
    userArrayList.add(new User("Java 终于能搜出来了", 253));

   // 批处理请求,生成指定的id
    //        for (int i = 0; i < userArrayList.size(); i++) {
    //            bulkRequest.add(
    //                    new IndexRequest("yifang_index")
    //                    .id("" + (i+1))
    //                    .source(JSON.toJSONString(userArrayList.get(i)), XContentType.JSON)
    //            );
    //        }

    // 不指定id会生成随机的id
    for (int i = 0; i < userArrayList.size(); i++) {
        bulkRequest.add(
            new IndexRequest("yifang_index")
            .source(JSON.toJSONString(userArrayList.get(i)), XContentType.JSON)
        );
    }

    BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
    System.out.println(bulkResponse.hasFailures());     // 返回false,代表成功
}

7.3.9 查询文档

@Test
void testSearchDocment() throws IOException {
    SearchRequest searchRequest = new SearchRequest("yifang_index");
    // 构建搜索条件
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	
    // 精准匹配
    TermQueryBuilder queryBuilder = QueryBuilders.termQuery("name", "java");
    searchSourceBuilder.query(queryBuilder);
    searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
    // 多项精准匹配
    // TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("name", "java", "ava");
    // 匹配所有
	// MatchAllQueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
    // 匹配一部分
    // MatchQueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "java");
    // 模糊匹配
    // FuzzyQueryBuilder queryBuilder = QueryBuilders.fuzzyQuery("name", "java");
    // 模糊匹配(不知有何区别)
    // WildcardQueryBuilder queryBuilder = QueryBuilders.wildcardQuery("name", "*av*");

    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    System.out.println(JSON.toJSONString(searchResponse.getHits()));
    System.out.println("=======================================================");

    for (SearchHit documentFields: searchResponse.getHits().getHits()){
        System.out.println(JSON.toJSONString(documentFields.getSourceAsMap()));
    }
}

8. 项目实战

8.1 初始化项目

  1. 启动 ES 和 ES head 服务,见第4章
  2. 使用springboot快速构建服务,比之前多个Thymeleaf

在这里插入图片描述

  1. 修改依赖 pom.xml
<properties>
    <java.version>1.8</java.version>
    <!-- 修改elasticsearch的版本 -->
    <elasticsearch.version>7.7.0</elasticsearch.version>
</properties>

<dependencies>
    <!-- jSoup解析网页 -->
    <dependency>
        <groupId>org.jsoup</groupId>
        <artifactId>jsoup</artifactId>
        <version>1.10.2</version>
    </dependency>
	<!-- fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.38</version>
    </dependency>
<dependencies>
  1. 配置 application.properties
server.port=9090
# 关闭 thymeleaf 的缓存
spring.thymeleaf.cache=false
  1. 导入前端素材
<html xmlns:th="http://www.thymeleaf.org">
  1. 编写IndexController进行跳转测试
package com.example.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {
    // http://localhost:9090/ 与 http://localhost:9090/index 都能访问
    @GetMapping({"/", "/index"})
    public String index() {
        return "index";
    }
}

8.2 Jsoup

  1. pom.xml 中导入 Jsoup依赖
<!-- jSoup解析网页 -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.10.2</version>
</dependency>
  1. 编写一个工具类 HtmlParseUtil.java
package com.example.utils;

import com.example.pojo.Content;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;

@Component
public class HtmlParseUtil {

    public ArrayList<Content> parseJD(String keywords) throws IOException {
        String key = URLEncoder.encode(keywords, "utf-8");
        String url = "https://search.jd.com/Search?keyword=" + key;

        Document document = Jsoup.parse(new URL(url), 30000);
        Element element = document.getElementById("J_goodsList");
        Elements elements = element.getElementsByTag("li");

        ArrayList<Content> goodsList = new ArrayList<>();

        for (Element el: elements) {
            String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
            String price = el.getElementsByClass("p-price").eq(0).text();
            String title = el.getElementsByClass("p-name").eq(0).text();
            Content content = new Content();
            content.setImg(img);
            content.setPrice(price);
            content.setTitle(title);
            goodsList.add(content);
        }
        return goodsList;
    }
}
  1. 封装一个实体类 Content.java 保存爬取下来的数据
package com.example.pojo;

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

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Content {
    private String img;
    private String price;
    private String title;
    // 可以自己添加属性
}

8.3 业务编写

  1. 导入 ElasticsearchClientConfig.java 配置类
package com.example.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;

@Configuration
public class ElasticSearchClientConfig {

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("localhost", 9200, "http")));
        return client;
    }
}
  1. 编写业务 ContentService.java
package com.example.service;

import com.alibaba.fastjson.JSON;
import com.example.pojo.Content;
import com.example.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.*;
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.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;

    // 1、解析数据放入 es 索引中
    public Boolean parseContent(String keywords) throws IOException {
        List<Content> contents = new HtmlParseUtil().parseJD(keywords);
        // 把查询到的数据放入 es 中
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("2m");

        for (int i = 0; i < contents.size(); i++) {
            bulkRequest.add(
                    new IndexRequest("jd_goods")
                    .source(JSON.toJSONString(contents.get(i)), XContentType.JSON)
            );
        }

        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        return !bulk.hasFailures();
    }

    // 2、获取这些数据实现搜索功能
    public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) throws IOException {
        if (pageNo <= 1) {
            pageNo = 1;
        }

        // 条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 分页
        searchSourceBuilder.from(pageNo);
        searchSourceBuilder.size(pageSize);

        // 精准匹配
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
        searchSourceBuilder.query(termQueryBuilder);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));

        // 执行搜索
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        // 解析结果
        ArrayList<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields: searchResponse.getHits().getHits()) {
            list.add(documentFields.getSourceAsMap());
        }
        return list;
    }
}
  1. Controller.java
package com.example.controller;

import com.example.service.ContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@RestController
public class ContentController {

    @Autowired
    private ContentService contentService;
	
    // http://localhost:9090/parse/java
    @GetMapping("/parse/{keyword}")
    public boolean parse (@PathVariable("keyword") String keyword) throws IOException {
        return contentService.parseContent(keyword);
    }
	
    // http://localhost:9090/search/java/1/10
    @GetMapping("/search/{keyword}/{pageNo}/{pageSize}")
    public List<Map<String, Object>> search(@PathVariable("keyword") String keyword,
                                            @PathVariable("pageNo") int pageNo,
                                            @PathVariable("pageSize") int pageSize) throws IOException {

        return contentService.searchPage(keyword, pageNo, pageSize);
    }
}

8.4 前端逻辑

index.html

  1. 导入vue和axios的依赖,这里导入后无法识别,重启IDEA就好了
<script th:src="@{/js/axios.min.js}"></script>
<script th:src="@{/js/vue.min.js}"></script>
  1. 初始化Vue对象,给外层div绑定app对象!
<script>

    new Vue({
        el:'#app',
        data:{
            keyword: '',    // 搜索的关键字
            results: []     // 搜索的结果
        }
    })
    
</script>
  1. 绑定搜索框及相关事件

在这里插入图片描述

  1. 编写方法,获取后端传递的数据
<script>

    new Vue({
        el:'#app',
        data:{
            keyword: '',    // 搜索的关键字
            results: []     // 搜索的结果
        },
        methods:{
            searchKey() {
                var keyword = this.keyword;
                console.log(keyword);
                // 对接后端的接口
                axios.get('search/' + keyword + "/1/50").then(response=>{
                    console.log(response);
                    this.results = response.data;       // 绑定数据!
                })
            }
        }
    })

</script>
  1. 渲染解析回来的数据!

在这里插入图片描述

8.5 搜索高亮

  1. 编写业务类,处理高亮字段

ContentService.java

// 3、获取这些数据实现搜索高亮功能
public List<Map<String, Object>> searchHighlightPage(String keyword, int pageNo, int pageSize) throws IOException {
    if (pageNo <= 1) {
        pageNo = 1;
    }

    // 条件搜索
    SearchRequest searchRequest = new SearchRequest("jd_goods");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

    // 分页
    searchSourceBuilder.from(pageNo);
    searchSourceBuilder.size(pageSize);

    // 精准匹配
    TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", keyword);
    searchSourceBuilder.query(termQueryBuilder);
    searchSourceBuilder.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>");
    searchSourceBuilder.highlighter(highlightBuilder);

    // 执行搜索
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

    // 解析结果
    ArrayList<Map<String, Object>> list = new ArrayList<>();

    for (SearchHit hit: searchResponse.getHits().getHits()) {

        // 解析高亮的字段
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField title = highlightFields.get("title");
        Map<String, Object> sourceAsMap = hit.getSourceAsMap();     // 原来的结果

        // 将原来的字段替换成我们的高亮字段即可
        if (title != null) {
            Text[] fragments = title.fragments();
            String newTitle = "";
            for (Text text : fragments) {
                newTitle += text;
            }
            sourceAsMap.put("title", newTitle);     // 原来的字段替换成我们的高亮字段
        }
        list.add(sourceAsMap);
    }
    return list;
}
  1. controller

ContentController.java

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

    return contentService.searchHighlightPage(keyword, pageNo, pageSize);
}
  1. vue解析html

index.html

<!--标题-->
<p class="productTitle">
    <a v-html="result.title"></a>
</p>
  1. 最终效果

在这里插入图片描述

感谢狂神说ElasticSearch!!!
狂神说ElasticSearch

  • 4
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值