Java课程设计之——Elasticsearch篇

0、团队项目博客

1、主要使用的技术及开发工具

  • Elasticsearch 7.17.3
  • REST API
  • Elasticsearch java API Client 7.17.3
  • Kibana 7.17.3
  • Jackson 2.12.3

2、Elasticsearch简介

Elasticsearch 是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。可用于应用程序搜索,网站搜索等诸多使用场景。

使用ES,我们可以快速的开发一个自己的搜索引擎,ES会帮我们在庞大的数据中建索

3、mapping的设计思路

有四个数据要存进ES建索,分别是urltitletextdeclareTime,他们的数据类型分别是keyword、text、text、date;其中date的格式为xxxx-xx-xx 其中text类型均采用ik_max_word保证最大限度的分词,为了实现搜索提示,可以定义title的第二个type为completion,方便自动补全。综上,新建索引的操作如下

PUT /link-repo2
{
  "mappings": {
    "properties": {
      "url":{"type": "keyword"},
      "title":{
        "type": "text",
        "analyzer": "ik_max_word", 
        "fields": {
          "suggest": {
            "type": "completion",
            "analyzer": "ik_max_word"
          }
        }
      },
      "text":{
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "declareTime": {
        "type": "date"
      }
    }
  }
}

4、ES的搜索策略

4.1 Rest操作

在此课程设计中,我主要采用match分词搜索,分别在title和text中搜索,并附带相关的highlight和filler策略以作为高亮显示和按时间范围搜索的基础。

GET link-repo2/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "软银机器人杯”2019中国机器人技能大赛:我院学子获1项季军、1项二等奖",
            "fields": ["title", "text"],
            "analyzer": "ik_smart"
          }
        }
      ],
      "filter": [
        {
          "range": {
            "declareTime": {
              "gte": "2022-01-01"
            }
          }
        }
      ]
    }
  },
  "highlight": {
    "pre_tags": "<span class=\"hit-result\">",
    "post_tags": "</span>",
    "fields": {
      "title": {},
      "text": {}
    }
  },
  "from": 0,
  "size": 10
}

4.2 全文检索功能

multi_match表示多字段的分词搜索,query为搜索内容,fields为搜索字段,analyzer表示搜索文本的分词器。

4.3 按时间范围检索功能

在bool块里,通过must和filter的组合,must实现分词搜索,filter实现过滤时间;range块中的field为字段,gte表示大于等于。这样就可实现时间范围检索

4.4 高亮检索

highlight,搜索中关注title和text,当有匹配分词出现,就套上pre_tags和post_tags标签,“高亮”出来。这样就可以实现高亮查询的功能

4.5 分页搜索

from和size的组合,可以实现分页功能

5、使用Elasticsearch java API Client连接ES

团队项目的ES是有使用xpack插件保证安全性的,连接的时候要创建使用许可证才能成功连接

public static ElasticsearchClient getConnect() {
    // 创建许可证
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
    credentialsProvider.setCredentials(
        AuthScope.ANY, new UsernamePasswordCredentials(USERNAME, PASSWORD));
    // 导入许可证
    RestClientBuilder builder = RestClient.builder(new HttpHost(URL, PORT))
        .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder
                                    .setDefaultCredentialsProvider(credentialsProvider));
    // 建立连接
    restClient = builder.build();
    transport = new RestClientTransport(
        restClient, new JacksonJsonpMapper());
    return new ElasticsearchClient(transport);
}

6、代码实现

Search.java

package es;

import bean.ResultEntry;

import java.io.Reader;
import java.util.List;

/**
 * @author 开架大飞机
 * @description 搜索引擎功能接口
 * @date: 2022/12/17
 */
public interface Search {
    /**
     * 获取搜素结果数量
     * @return 搜索结果数量,类型为long
     */
    long getSearchCount();

    /**
     * 新建索引
     * @param reader 一个Reader类型的字符串,ResultFul风格的索引类型信息
     * @return 新建成功返回true,失败返回false
     */
    boolean newIndex(Reader reader);

    /**
     * 删除索引,目标为ESUtil的index
     * @return 删除成功返回true,失败返回false
     */
    boolean deleteIndex();

    /**
     * 传入一个条目,将条目插入到对应的Search中
     * @param entry 被插入的条目
     * @return 插入成功返回该条目,否则返回null
     */
    ResultEntry add(ResultEntry entry);

    /**
     * 全文检索,根据text查找, 待优化
     * @param searchText 待搜索文本
     * @return 搜索结果放在List集合中
     */
    List<ResultEntry> search(String searchText);

    /**
     * 全文检索,先对searchText进行分词,结果按匹配度评分降序返回对应的页数内容
     * @param searchText 待搜索文本
     * @param page 页数
     * @return page页的searchText的搜索结果
     */
    List<ResultEntry> search(String searchText, int page);

    /**
     * 全文检索,对searchText分词,返回在最早发布日期后、对应的页数的结果
     * @param searchText 待搜索文本
     * @param page 页数
     * @param beginDate 最早发布日期
     * @return 对应的搜索结果
     */
    List<ResultEntry> search(String searchText, int page, String beginDate);

    /**
     * 全文检索,对searchText进行分词,返回在最早发布日期和最晚发布日期间对应页数的结果
     * @param searchText 待搜索文本
     * @param page 页数
     * @param beginDate 最早发布日期
     * @param endDate 最晚发布日期
     * @return 对应的搜索结果
     */
    List<ResultEntry> search(String searchText, int page, String beginDate, String endDate);

    /**
     * 获取搜索建议,传入前缀,返回匹配前缀的字符串集合
     * @param prefix 字符串前缀
     * @return 匹配的字符串集合
     */
    List<String> getSearchSuggest(String prefix);
    /**
     * 释放Search的资源
     */
    void close();   // 关闭资源
}

新建索引

public boolean newIndex(Reader reader) {
    CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder()
        .withJson(reader)
        .index(EsUtil.index)
        .build();

    CreateIndexResponse response = null;
    try {
        response = client.indices().create(createIndexRequest);
    } catch (Exception e) {
        // 创建索引引发的异常
    }
    if(response != null) {
        return Objects.requireNonNullElse(response.acknowledged(), false);
    } else {
        return false;
    }
}

删除索引

public boolean deleteIndex() {
    DeleteIndexResponse deleteIndexResponse = null;
    try {
        deleteIndexResponse = client.indices().delete(d -> d
                                                      .index(EsUtil.index));
    } catch (Exception e) {
        // 删除索引引发的所有异常
    }
    if(deleteIndexResponse == null) {
        return false;
    } else {
        return deleteIndexResponse.acknowledged();
    }
}

添加文档

public ResultEntry add(ResultEntry entry) {
        try {
            client.index(i -> i
                    .index(EsUtil.index).document(entry));
        } catch (IOException e) {
            return null;
        }
        return entry;
    }

全文检索

public List<ResultEntry> search(String searchText, int page) {
    // 页数从0开始编号
    int value = (page - 1) * 10;
    SearchResponse<ResultEntry> search = null;

    try {
        search = client.search(s -> s
                               .index(EsUtil.index)
                               .query(q -> q
                                      .multiMatch(m -> m
                                                  .query(searchText)
                                                  .fields("title", "text")
                                                  .analyzer("ik_smart")))
                               .highlight(h -> h
                                          .preTags("<span class=\"hit-result\">")
                                          .postTags("</span>")
                                          .fields("title", builder -> builder)
                                          .fields("text", builder -> builder))
                               .from(value)
                               .size(10)
                               , ResultEntry.class);
    } catch (IOException e) {
        e.printStackTrace();
    }

    return dealSearchResponse(search);
}

按时间范围全文检索

public List<ResultEntry> search(String searchText, int page, String beginDate) {
    // 页数从0开始编号
    int value = (page - 1) * 10;
    JsonData jsonBeginDate = JsonData.of(beginDate);
    SearchResponse<ResultEntry> search = null;
    try {
        search = client.search(s -> s
                               .index(EsUtil.index)
                               .query(q -> q
                                      .bool(b -> b
                                            .must(b1 -> b1
                                                  .multiMatch(b2 -> b2
                                                              .query(searchText)
                                                              .fields("title", "text")
                                                              .analyzer("ik_smart")))
                                            .filter(b3 -> b3
                                                    .range(b4 -> b4
                                                           .field("declareTime")
                                                           .gte(jsonBeginDate)))))
                               .highlight(h -> h
                                          .preTags("<span class=\"hit-result\">")
                                          .postTags("</span>")
                                          .fields("title", builder -> builder)
                                          .fields("text", builder -> builder))
                               .from(value)
                               .size(10)
                               , ResultEntry.class);
    } catch (IOException e) {
        e.printStackTrace();
    }

    return dealSearchResponse(search);
}

7、参考链接

官方新版java API文档

新版java api 操作示例

ES英文文档

ES中文文档

ES设置账号密码

Java连接ES有账号密码的情况

posted @ 2023-01-10 22:26  lrui1  阅读(62)  评论(0编辑  收藏  举报