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 终结者】❤️❤️❤️,我将会给你带来巨大的【收获与惊喜】💝💝💝!

相关推荐
一线大码29 分钟前
SpringBoot 优雅实现接口的多实现类方式
java·spring boot·后端
Q_Q19632884751 小时前
python+uniapp基于微信小程序的助眠小程序
spring boot·python·小程序·django·flask·uni-app·node.js
摇滚侠1 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 属性优先级 行内写法 变量选择 笔记42
java·spring boot·笔记
摇滚侠1 小时前
Spring Boot 3零基础教程,WEB 开发 Thymeleaf 总结 热部署 常用配置 笔记44
java·spring boot·笔记
十年小站1 小时前
一、新建一个SpringBoot3项目
java·spring boot
最好束手就擒1 小时前
Elasticsearch批量写入50万数据
大数据·elasticsearch·jenkins
没有bug.的程序员1 小时前
金融支付分布式架构实战:从理论到生产级实现
java·分布式·微服务·金融·架构·分布式调度系统
在未来等你1 小时前
Elasticsearch面试精讲 Day 25:Elasticsearch SQL与数据分析
大数据·分布式·elasticsearch·搜索引擎·面试
程序员阿达2 小时前
开题报告之基于SpringBoot框架的路面故障信息上报系统设计与实现
java·spring boot·后端
哞哞不熬夜2 小时前
JavaEE--SpringIoC
java·开发语言·spring boot·spring·java-ee·maven