ElasticSearch实战之搜索项目,并高亮显示

一、前言

这几天学习了 Elasticsearch 的各种基本操作,为了加强对 Elasticsearch 的使用和理解,现在让我们通过一个搜索项目,来巩固知识吧。

让我们先来看一下项目的最终效果:

当我们在搜索框搜索 java 时,会显示 java 相关的书籍,并且搜索关键字 java 会显示红色高亮。
在这里插入图片描述
同理,当我们输入 vue 时,也是这样。

在这里插入图片描述

二、基础速过

下面都是一些基本的操作 API,有基础的可以直接略过。

1、索引操作

在此之前,我们需要在配置类中注入 elasticsearch 的客户端。

在这里插入图片描述
创建索引,需要引入客户端类。

    @Autowired
    @Qualifier("restHighLevelClient")
    private RestHighLevelClient client;
    
    @SneakyThrows
    void testCreateIndex() {
//        创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("user");
//        客户端执行请求,并接收响应
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(response);
    }

删除索引。

    @SneakyThrows
    void testDeleteIndex() {
//        删除索引请求
        DeleteIndexRequest request = new DeleteIndexRequest("user");
//        客户端执行请求,并接收响应
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
//        true则删除成功
        System.out.println(response.isAcknowledged());
    }

判断是否存在索引。

    @SneakyThrows
    void testExistIndex() {
//        获取索引请求
        GetIndexRequest request = new GetIndexRequest("user");
        boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

2、文档操作

创建文档,这里使用 IndexRequest。

    void testAddDocument() {
//        创建对象
        User user = new User("卷心菜", 20);
//        创建请求
        IndexRequest request = new IndexRequest("user");
//        put /user/_doc/1
        request.id("1");
//        将数据放入请求JSON
        request.source(JSON.toJSONString(user), XContentType.JSON);
//        客户端发送请求,获取响应结果
        IndexResponse indexResponse = client.index(request, RequestOptions.DEFAULT);
        System.out.println(indexResponse.toString());
//        CREATED,说明文档创建成功
        System.out.println(indexResponse.status());
    }

判断是否某个文档,这里使用 GetRequest。

    @SneakyThrows
    void testIfExists() {
        GetRequest request = new GetRequest("user", "1");
//        不获取返回的 _source 的上下文了,提高效率
//        request.fetchSourceContext(new FetchSourceContext(false));
//        request.storedFields("_none_");
        boolean exists = client.exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

根据文档id获取文档。

    @SneakyThrows
    void testGetDocument() {
        GetRequest request = new GetRequest("user", "1");
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
//        打印文档内容
        System.out.println(response.getSourceAsString());
    }

运行结果如下:
在这里插入图片描述更新文档,这里用到了 UpdateRequest。

    @SneakyThrows
    void testUpdateDocument() {
        UpdateRequest request = new UpdateRequest("user", "1");
        User user = new User("卷心菜", 21);
        request.doc(JSON.toJSONString(user), XContentType.JSON);
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
//        这里结果变成了2,说明文档被更新了
        System.out.println(response.getVersion());
    }

删除文档,这里用到了 DeleteRequest。

    @SneakyThrows
    void testDeleteRequest() {
        DeleteRequest request = new DeleteRequest("user", "1");
//        设置请求条件,超过1秒还没有删除成功,就取消请求
        request.timeout("1s");
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
//        OK,说明删除成功
        System.out.println(response.status());
    }

批量插入,这里用到了 BulkRequest。

    @SneakyThrows
    void testBulkRequest() {
        BulkRequest bulkRequest = new BulkRequest();
        List<User> list = new LinkedList<>();
        list.add(new User("Tom", 3));
        list.add(new User("Cat", 3));
        list.add(new User("Dog", 3));
        list.add(new User("Apple", 3));
        list.add(new User("Peak", 3));
        list.add(new User("Pen", 3));
//        批处理请求
        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(new IndexRequest("user")
                    .id("" + (i + 2))
                    .source(JSON.toJSONString(list.get(i)), XContentType.JSON));
        }
        BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
//        Bulk操作是否失败,返回false代表成功
        System.out.println(response.hasFailures());
    }

运行结果:
在这里插入图片描述

3、查找操作

先简单了解一下,这里用到了 SearchRequest、构造搜索条件的 SearchSourceBuilder 以及 QueryBuilders 工具类。

    @SneakyThrows
    void testSearch() {
        SearchRequest searchRequest = new SearchRequest("user");
//        构造搜索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//        查询条件,我们可以使用 QueryBuilders 工具来实现
//        termQuery 精确查找 matchAllQuery 匹配所有
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("age", 3);
        sourceBuilder.query(termQueryBuilder);
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//        查询结果以map形式返回
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            System.out.println(documentFields.getSourceAsMap());
        }
    }

三、获取数据

注意:我们对搜索的数据并不打算自己手动输入,而是直接获取其他网页的数据。

这里我使用的是解析网页的 jsoup,依赖版本如下:

        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.10.2</version>
        </dependency>

我们先去该页面打开其开发者模式,来查看其前端代码:

在这里插入图片描述

通过页面分析,我们可以得到以下信息:

J_goodsList:存放所有商品的容器
li 标签:存放每一个商品的小容器
p-img:存放商品图片 p-name:存放商品名称
p-price:存放商品价格

获取页面数据代码如下:

public class JsoupWithJavaFX extends Application {
    @Override
    public void start(Stage primaryStage) {
        WebView webView = new WebView();
        WebEngine webEngine = webView.getEngine();
        // 设置加载完成监听器
        webEngine.getLoadWorker().stateProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue == Worker.State.SUCCEEDED) {
                // 获取页面内容
                String html = (String) webEngine.executeScript("document.documentElement.outerHTML");
                Document document = Jsoup.parse(html);
                // 查找元素
                Element element = document.getElementById("J_goodsList");
                } else {
                    System.out.println("**** 未查询到数据 ***");
                }
            }
        });
        // 加载网页
        webEngine.load("https://search.jd.com/search?keyword=java&enc=utf-8");
        primaryStage.setScene(new Scene(webView, 800, 600));
        primaryStage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

四、编写实体类

我们需要从网页拿到图片地址,名称,价格这三个属性,我们将其包装为一个实体类,拿到数据后直接封装为对象即可。

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Content {
    private String img;
    private String title;
    private String price;
}

五、将数据存入ES

首先创建一个“jd_goods”索引,用来存储页面的数据。

为了方便,我们在获取页面数据时,就可以将数据写入ES,代码如下:
在这里插入图片描述
让我们打开ES,看看具体的数据信息。我们可以看到,ES中确实存储了图片、价格、名称信息:

在这里插入图片描述

六、编写service

在service里面,我们具体要完成的,一个是实现分页,一个是实现高亮显示

首先通过 new SearchRequest 构建搜索请求,接着 new SearchSourceBuilder 构建条件搜索构造器。通过 QueryBuilders 选择需要的查询模式。通过 new HighlightBuilder 可以构建高亮构造器,最后将查询到的结果进行一个解析。

具体代码如下:

@Service
public class EsServiceImpl implements EsService {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    @Override
    public List<Map<String, Object>> searchPage(String keyword, int pageNo, int pageSize) {
        if (pageNo <= 1) {
            pageNo = 1;
        }
        // 条件搜索
        SearchRequest searchRequest = new SearchRequest("jd_goods");
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

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

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

        // 执行搜索
        searchRequest.source(sourceBuilder);
        SearchResponse searchResponse = null;
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        // 解析结果
        List<Map<String, Object>> list = new ArrayList<>();
        for (SearchHit documentFields : searchResponse.getHits().getHits()) {
            Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
            HighlightField title = highlightFields.get("title");
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();// 最初的结构
            // 解析高亮的字段,将原来的字段换为我们高亮的字段即可!
            if (title != null) {
                String n_title = "";
                Text[] fragments = title.fragments();
                for (Text text : fragments) {
                    n_title += text;
                }
                sourceAsMap.put("title", n_title); // 高亮字段替换掉原来的内容即可!
            }
            list.add(sourceAsMap);
        }
        return list;
    }
}

七、编写controller层

这一层比较简单,没什么好说的。运行后,让我们看看客户端信息,可以看到,我们查询前5条数据,对搜索关键字 java 进行了一个 span 标签的修饰,后面用于前端给展示出来

在这里插入图片描述

代码如下:

@RestController
public class ContentController {
    @Resource
    private EsService esService;
    @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 esService.searchPage(keyword, pageNo, pageSize);
    }
}

八、导入前端运行

将写好的前端代码运行后,页面是这样的。

在这里插入图片描述
在搜索框中,我就可以实现搜索了!前端代码如下:

<!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}"/>
</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>
                                <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="#"> Java </a></li>
                                        <li><a href="#"> MySQL </a></li>
                                        <li><a href="#"> Redis </a></li>
                                        <li><a href="#"> Linux </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><b>¥</b>{{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 src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<script>
    new Vue({
        el: "#app",
        data: {
            keyword: '', //搜素的关键字
            results: []   //搜素的结果
        },
        methods: {
            searchKey() {
                let keyword = this.keyword;
                console.log(keyword);
                axios.get('search/' + keyword + '/0/5').then(response => {
                    console.log(response.data);
                    this.results = response.data; //绑定数据
                })
            }
        }
    });
</script>
</body>
</html>

相关推荐

  1. Vue 显示关键词附近内容显示关键词

    2024-04-01 13:16:04       25 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-01 13:16:04       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-01 13:16:04       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-01 13:16:04       82 阅读
  4. Python语言-面向对象

    2024-04-01 13:16:04       91 阅读

热门阅读

  1. 银联扫码接口开通流程及注意事项

    2024-04-01 13:16:04       46 阅读
  2. 【Spring】通过Spring收集自定义注解标识的方法

    2024-04-01 13:16:04       46 阅读
  3. 03-28 周四 Linux 并行工具使用xargs和parallel

    2024-04-01 13:16:04       41 阅读
  4. 装饰器模式:灵活增强功能的利器

    2024-04-01 13:16:04       40 阅读
  5. 手机投屏到电脑

    2024-04-01 13:16:04       37 阅读
  6. Leetcode 2810. 故障键盘

    2024-04-01 13:16:04       37 阅读
  7. Python PyQt5——QThread使用方法与代码实践

    2024-04-01 13:16:04       37 阅读
  8. Beginning of Device Change operation

    2024-04-01 13:16:04       34 阅读
  9. 安卓开发中的LiveData深度解析与实践

    2024-04-01 13:16:04       39 阅读