Spring Boot 整合分布式搜索引擎 Elastic Search 实现 自动补全功能

文章目录

⛄引言

本文参考黑马 分布式Elastic search

Elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要的内容

像京东这样的提示应该如何实现?

可通过ES实现该自动补全功能,搭载分词器配合使用!

本篇文章将讲解 Elastic Search 如何使用分词器实现自动补全功能,以及 在项目实战中如何通过完成自动补全的需求开发

一、分词器

为什么要使用分词器呢,因为我们要实现自动补全功能,要对输入的文字进行分词,从而更好的查询结果集

⛅拼音分词器

要实现根据字母做补全,就必须对文档按照拼音分词。在GitHub上恰好有elasticsearch的拼音分词插件。地址:https://github.com/medcl/elasticsearch-analysis-pinyin

下载zip,安装方式如下:

  • 解压,通过工具上传至 elasticsearch的plugin目录
  • 重启elasticsearch
  • 进行测试拼音分词器

重启命令: docker restart es

测试方法:

json 复制代码
POST /_analyze
{
  "text": "希尔顿酒店还不错",
  "analyzer": "pinyin"
}

⚡自定义分词器

默认的拼音分词器会将每个汉字单独分为拼音,而我们希望的是每个词条形成一组拼音,需要对拼音分词器做个性化定制,形成自定义分词器。

elasticsearch中分词器(analyzer)的组成包含三部分:

  • character filters:在tokenizer之前对文本进行处理。例如删除字符、替换字符
  • tokenizer:将文本按照一定的规则切割成词条(term)。例如keyword,就是不分词;还有ik_smart
  • tokenizer filter:将tokenizer输出的词条做进一步处理。例如大小写转换、同义词处理、拼音处理等

声明自定义分词器的语法如下:

json 复制代码
PUT /test
{
  "settings": {
    "analysis": {
      "analyzer": { // 自定义分词器
        "my_analyzer": {  // 分词器名称
          "tokenizer": "ik_max_word",
          "filter": "py"
        }
      },
      "filter": { // 自定义tokenizer filter
        "py": { // 过滤器名称
          "type": "pinyin", // 过滤器类型,这里是pinyin
		  "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "my_analyzer",
        "search_analyzer": "ik_smart"
      }
    }
  }
}

测试

总结

如何使用拼音分词器?

  • 下载pinyin分词器

  • 解压并放到elasticsearch的plugin目录

  • 重启即可

如何自定义分词器?

  • 创建索引库时,在settings中配置,可以包含三部分

  • character filter

  • tokenizer

  • filter

拼音分词器注意事项?

  • 为了避免搜索到同音字,搜索时不要使用拼音分词器

二、自动补全查询

elasticsearch提供了Completion Suggester查询来实现自动补全功能。这个查询会匹配以用户输入内容开头的词条并返回。为了提高补全查询的效率,对于文档中字段的类型有一些约束:

  • 参与补全查询的字段必须是completion类型。

    • 字段的内容一般是用来补全的多个词条形成的数组。

比如,一个这样的索引库:

json 复制代码
// 创建索引库
PUT test
{
  "mappings": {
    "properties": {
      "title":{
        "type": "completion"
      }
    }
  }
}

然后插入下面的数据:

json 复制代码
// 示例数据
POST test/_doc
{
  "title": ["Sony", "WH-1000XM3"]
}
POST test/_doc
{
  "title": ["SK-II", "PITERA"]
}
POST test/_doc
{
  "title": ["Nintendo", "switch"]
}

查询的DSL语句如下:

json 复制代码
// 自动补全查询
GET /test/_search
{
  "suggest": {
    "title_suggest": {
      "text": "sw" // 关键字
      "completion": {
        "field": "title", // 补全查询的字段
        "skip_duplicates": true, // 跳过重复的
        "size": 10 // 获取前10条结果
      }
    }
  }
}

测试出一条数据

三、自动补全

⌚业务需求

在页面实现 输入 文字或者拼音,自动提示匹配的列表数据

根据酒店数据和地址进行查询数据列表

⏰实现酒店搜索自动补全

现在,我们的hotel索引库还没有设置拼音分词器,需要修改索引库中的配置。但是我们知道索引库是无法修改的,只能删除然后重新创建。

另外,我们需要添加一个字段,用来做自动补全,将brand、suggestion、city等都放进去,作为自动补全的提示。

因此,总结一下,我们需要做的事情包括:

  1. 修改hotel索引库结构,设置自定义拼音分词器
  2. 修改索引库的name、all字段,使用自定义分词器
  3. 索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器
  4. 给HotelDoc类添加suggestion字段,内容包含brand、business
  5. 重新导入数据到hotel库
json 复制代码
// 酒店数据索引库
PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_anlyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion":{
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

修改HotelDoc实体

HotelDoc中要添加一个字段,用来做自动补全,内容为酒店品牌、城市、商圈等信息。按照自动补全字段的要求,最好是这些字段的数组。

因此我们在HotelDoc中添加一个suggestion字段,类型为List<String>,然后将brand、city、business等信息放到里面。

代码如下:

java 复制代码
@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;
    private List<String> suggestion;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
        //拼装数据,把数据一个个放到数组中
        if (this.business.contains("/") || this.business.contains("、") || this.business.contains(",")) {
            String[] arr = {};
            if (this.business.contains("/")) {
                arr = this.business.split("/");
            } else if (this.business.contains("、")) {
                arr = this.business.split("、");
            } else if (this.business.contains(",")) {
                arr = this.business.split(",");
            }
            this.suggestion = new ArrayList<>();
            this.suggestion.add(this.brand);
            //把数组中的元素一个个放进去
            Collections.addAll(this.suggestion, arr);
        } else {
            this.suggestion = Arrays.asList(this.brand, this.business);
        }
    }
}

执行方法重新导入酒店数据

java 复制代码
@Test
    void testBulkRequest() throws IOException {
        // 查询所有的酒店数据
        List<Hotel> list = hotelService.list();

        // 1.准备Request
        BulkRequest request = new BulkRequest();
        // 2.准备参数
        for (Hotel hotel : list) {
            // 2.1.转为HotelDoc
            HotelDoc hotelDoc = new HotelDoc(hotel);
            // 2.2.转json
            String json = JSON.toJSONString(hotelDoc);
            // 2.3.添加请求
            request.add(new IndexRequest("hotel").id(hotel.getId().toString()).source(json, XContentType.JSON));
        }

        // 3.发送请求
        client.bulk(request, RequestOptions.DEFAULT);
    }

自动补全查询API

自动补全查询DSL 对应代码

自动补全结果解析 对应代码DSL

核心源码

在Controller类新增接口

java 复制代码
@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {
    return hotelService.getSuggestions(prefix);
}

Service业务代码

java 复制代码
public List<String> getSuggestions(String prefix) {
        //1. 准备request
        SearchRequest request = new SearchRequest("hotel");
        //2. 准备DSL
        request.source().suggest(new SuggestBuilder().addSuggestion("suggestions",
                SuggestBuilders.completionSuggestion("suggestion")
                        .prefix(prefix)
                        .skipDuplicates(true)
                        .size(10)));
        try {
            //3. 发送请求
            SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            //4. 解析结果
            Suggest suggest = response.getSuggest();

            //根据补全查询名称获取补全结果
            CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
            //获取options
            List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
            //遍历
            List<String> result = new ArrayList<>(options.size());
            for (CompletionSuggestion.Entry.Option option : options) {
                result.add(option.getText().toString());
            }
            return result;
        } catch (Exception e) {	
            e.printStackTrace();
        }
        return null;
    }

四、效果图

⛵小结

以上就是【Bug 终结者 】对 Spring Boot 整合分布式搜索引擎 Elastic Search 实现 自动补全功能 的简单介绍,ES搜索引擎无疑是最优秀的分布式搜索引擎,使用它,可大大提高项目的灵活、高效性! 技术改变世界!!!

如果这篇【文章】有帮助到你,希望可以给【Bug 终结者 】点个赞👍,创作不易,如果有对【后端技术 】、【前端领域 】感兴趣的小可爱,也欢迎关注❤️❤️❤️ 【Bug 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!

相关推荐
雪碧聊技术23 分钟前
Spring Boot项目如何彻底禁用Nacos(服务发现+配置中心),本地启动不再依赖Nacos
spring boot·nacos·服务发现
深海鱼在掘金4 小时前
深入浅出RAG——第1章:认识RAG
人工智能·搜索引擎
长不胖的路人甲4 小时前
RabbitMQ 死信队列 DLQ
分布式·rabbitmq
可乐ea4 小时前
【Redis八股|第8篇】Redis 分布式锁原理与 Redisson 使用
数据库·redis·分布式·面试题·redis八股
LitchiCheng4 小时前
DGX Spark部署Isaac Sim(一键安装脚本)
大数据·elasticsearch·spark
Flittly4 小时前
【日常小问】Docker 部署 Nacos 2.4.3,Dubbo 3.3 连接 Config Service 失败排查
java·spring boot·dubbo
逸Y 仙X7 小时前
Spark部署
大数据·分布式·spark
Elasticsearch7 小时前
Semantic_text 字段默认嵌入模型变为 .jina-embeddings-v5-text-small
elasticsearch
BD_Marathon8 小时前
什么是Spark
大数据·分布式·spark
xian_wwq8 小时前
【学习笔记】分布式推理:TP / PP / EP / CP 并行策略详解(20/35)
笔记·分布式·学习·ai