当用户在搜索框输入字符时,我们应该提示出与该字符有关的搜索项,如图:
image.png
这种根据用户输入的字母,提示完整词条的功能,就是自动补全了。
因为需要根据拼音字母来推断,因此要用到拼音分词功能。
安装Elasticsearch,Kibana,IK分词器,pinyin分词器

自定义分词器

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

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

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

文档分词时会依次由这三部分来处理文档:
image-20210723210427878.png

自定义分词器语法

关于py的配置,看官网文档
https://github.com/medcl/elasticsearch-analysis-pinyin

  1. PUT /test
  2. {
  3. "settings": {
  4. "analysis": {
  5. "analyzer": { #自定义分词器
  6. "my_analyzer": { #分词器名称
  7. "tokenizer": "ik_max_word",
  8. "filter": "py"
  9. }
  10. },
  11. "filter": { #自定义filter
  12. "py": { #过滤器名称
  13. "type": "pinyin", #过滤器类型,这里是pinyin分词器
  14. "keep_full_pinyin": false, #不要单字分词,词组分词
  15. "keep_joined_full_pinyin": true, #全拼
  16. "keep_original": true, #保留中文
  17. "limit_first_letter_length": 16,
  18. "remove_duplicated_term": true,
  19. "none_chinese_pinyin_tokenize": false
  20. }
  21. }
  22. }
  23. },
  24. "mappings": { #创建mapping映射
  25. "properties": {
  26. "name": {
  27. "type": "text",
  28. "analyzer": "my_analyzer", #创建倒排索引时用的分析器
  29. "search_analyzer": "ik_smart" #搜索时用的分析器
  30. }
  31. }
  32. }
  33. }

image.png

搜索时不能用拼音分词器的原因

image.png

自动补全查询

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

  • 参与补全查询的字段必须是completion类型。
  • 字段的内容一般是用来补全的多个词条形成的数组。

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

  1. #创建索引库
  2. PUT /test
  3. {
  4. "mappings": {
  5. "properties": {
  6. "title": {
  7. "type": "completion"
  8. }
  9. }
  10. }
  11. }
  1. #插入数据
  2. POST /test2/_doc
  3. {
  4. "title": ["Sony", "WH-1000XM3"]
  5. }
  6. POST /test2/_doc
  7. {
  8. "title": ["SK-II", "PITERA"]
  9. }
  10. POST /test2/_doc
  11. {
  12. "title": ["Nintendo", "switch"]
  13. }
  1. #自动补全查询
  2. GET /test2/_search
  3. {
  4. "suggest": {
  5. "title_suggest": { #自己取的名字
  6. "text": "s", #用户要自动补全的关键字
  7. "completion": {
  8. "field": "title", #补全查询的字段
  9. "skip_duplicates": true, #跳过重复的
  10. "size": 10 #获取前10条结果
  11. }
  12. }
  13. }
  14. }

image.png

实现酒店搜索框自动补全

之前弄的htole酒店索引库,RestClient操作索引库
我们的hotel索引库还没有设置拼音分词器,需要修改索引库中的配置。但是我们知道索引库是无法修改的,只能删除然后重新创建。
另外,我们需要添加一个字段,用来做自动补全,将brand、suggestion、city等都放进去,作为自动补全的提示。
因此,总结一下,我们需要做的事情包括:

  1. 修改hotel索引库结构,设置自定义拼音分词器
  2. 修改索引库的name、all字段,使用自定义分词器
  3. 索引库添加一个新字段suggestion,类型为completion类型,使用自定义的分词器
  4. 给HotelDoc类添加suggestion字段,内容包含brand、business
  5. 重新导入数据到hotel库

    修改酒店映射结构

    如果突然这json的字段的含义,setting部分就在该文章,往上看看,mapping部分在索引库操作
    1. // 酒店数据索引库
    2. PUT /hotel
    3. {
    4. "settings": {
    5. "analysis": {
    6. "analyzer": {
    7. "text_anlyzer": {
    8. "tokenizer": "ik_max_word",
    9. "filter": "py"
    10. },
    11. "completion_analyzer": {
    12. "tokenizer": "keyword",
    13. "filter": "py"
    14. }
    15. },
    16. "filter": {
    17. "py": {
    18. "type": "pinyin",
    19. "keep_full_pinyin": false,
    20. "keep_joined_full_pinyin": true,
    21. "keep_original": true,
    22. "limit_first_letter_length": 16,
    23. "remove_duplicated_term": true,
    24. "none_chinese_pinyin_tokenize": false
    25. }
    26. }
    27. }
    28. },
    29. "mappings": {
    30. "properties": {
    31. "id":{
    32. "type": "keyword"
    33. },
    34. "name":{
    35. "type": "text",
    36. "analyzer": "text_anlyzer",
    37. "search_analyzer": "ik_smart",
    38. "copy_to": "all"
    39. },
    40. "address":{
    41. "type": "keyword",
    42. "index": false
    43. },
    44. "price":{
    45. "type": "integer"
    46. },
    47. "score":{
    48. "type": "integer"
    49. },
    50. "brand":{
    51. "type": "keyword",
    52. "copy_to": "all"
    53. },
    54. "city":{
    55. "type": "keyword"
    56. },
    57. "starName":{
    58. "type": "keyword"
    59. },
    60. "business":{
    61. "type": "keyword",
    62. "copy_to": "all"
    63. },
    64. "location":{
    65. "type": "geo_point"
    66. },
    67. "pic":{
    68. "type": "keyword",
    69. "index": false
    70. },
    71. "all":{
    72. "type": "text",
    73. "analyzer": "text_anlyzer",
    74. "search_analyzer": "ik_smart"
    75. },
    76. "suggestion":{
    77. "type": "completion",
    78. "analyzer": "completion_analyzer"
    79. }
    80. }
    81. }
    82. }

    修改HotelDoc实体

    HotelDoc中要添加一个字段,用来做自动补全,内容可以是酒店品牌、城市、商圈等信息。按照自动补全字段的要求,最好是这些字段的数组。
    因此我们在HotelDoc中添加一个suggestion字段,类型为List,然后将brand、city、business等信息放到里面。 ```java package cn.itcast.hotel.pojo;

import lombok.Data; import lombok.NoArgsConstructor;

import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List;

@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 suggestion;

  1. public HotelDoc(Hotel hotel) {
  2. this.id = hotel.getId();
  3. this.name = hotel.getName();
  4. this.address = hotel.getAddress();
  5. this.price = hotel.getPrice();
  6. this.score = hotel.getScore();
  7. this.brand = hotel.getBrand();
  8. this.city = hotel.getCity();
  9. this.starName = hotel.getStarName();
  10. this.business = hotel.getBusiness();
  11. this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
  12. this.pic = hotel.getPic();
  13. // 组装suggestion
  14. if(this.business.contains("/")){
  15. // business有多个值,需要切割
  16. String[] arr = this.business.split("/");
  17. // 添加元素
  18. this.suggestion = new ArrayList<>();
  19. this.suggestion.add(this.brand);
  20. Collections.addAll(this.suggestion, arr);
  21. }else {
  22. this.suggestion = Arrays.asList(this.brand, this.business);
  23. }
  24. }

}

  1. <a name="Uv62E"></a>
  2. ## 重新导入数据和测试
  3. 执行之前的批量导入数据方法,重新导入数据<br />[RestClient操作文档](https://www.yuque.com/shifeng-wl7di/bbpx3m/tggcbk?view=doc_embed&inner=wqQWQ)<br />导入完了数据查看数据<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21464164/1655288643299-6721b098-81a0-4d01-a784-4bec34eb7f2b.png#clientId=u5e70c3a6-d221-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=366&id=u0207478e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=769&originWidth=2329&originalType=binary&ratio=1&rotation=0&showTitle=false&size=131795&status=done&style=none&taskId=uad876f16-d18e-4874-80e7-c23fcd34e44&title=&width=1109.666748046875)<br />测试一下自动补全<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21464164/1655288822017-d8a4ac3d-86c6-4eb3-b648-0769f00566e7.png#clientId=u5e70c3a6-d221-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=439&id=ucb3506d4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=900&originWidth=2301&originalType=binary&ratio=1&rotation=0&showTitle=false&size=159026&status=done&style=none&taskId=u5be2dfc3-3aed-4362-8e49-ca7c0f818fb&title=&width=1123)
  4. <a name="dRluV"></a>
  5. # RestClient自动补全
  6. 请求部分<br />![image-20210723213759922.png](https://cdn.nlark.com/yuque/0/2022/png/21464164/1655289786780-02ed1dab-87ba-410d-aee3-1d9482b4e3be.png#clientId=u5e70c3a6-d221-4&crop=0&crop=0&crop=1&crop=1&from=drop&height=388&id=u24511b35&margin=%5Bobject%20Object%5D&name=image-20210723213759922.png&originHeight=620&originWidth=1524&originalType=binary&ratio=1&rotation=0&showTitle=false&size=147138&status=done&style=none&taskId=u02779b78-c617-4de0-af5d-d0b6232a5a3&title=&width=954)<br />响应解析部分<br />![image-20210723213917524.png](https://cdn.nlark.com/yuque/0/2022/png/21464164/1655289774499-046dd95b-615f-4165-a4fc-1446ce110b36.png#clientId=u5e70c3a6-d221-4&crop=0&crop=0&crop=1&crop=1&from=drop&height=464&id=u878d0d4f&margin=%5Bobject%20Object%5D&name=image-20210723213917524.png&originHeight=734&originWidth=1524&originalType=binary&ratio=1&rotation=0&showTitle=false&size=149279&status=done&style=none&taskId=ud645b71b-443f-4393-b032-2e8d68205b1&title=&width=964)
  7. ```java
  8. @Test
  9. void testSuggest() throws IOException {
  10. //1.准备请求
  11. SearchRequest request = new SearchRequest("hotel");
  12. //2.请求参数
  13. SuggestBuilder suggestBuilder =
  14. new SuggestBuilder()
  15. .addSuggestion("my_suggestion", SuggestBuilders
  16. .completionSuggestion("suggestion")
  17. .prefix("b")
  18. .skipDuplicates(true)
  19. .size(10)
  20. );
  21. request.source().suggest(suggestBuilder);
  22. //3.发送请求
  23. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  24. //4.处理结果
  25. Suggest suggest = response.getSuggest();
  26. //4.1.根据名称获取补全结果
  27. CompletionSuggestion mySuggestion = suggest.getSuggestion("my_suggestion");
  28. //4.2.获取options并解析
  29. for (CompletionSuggestion.Entry.Option option : mySuggestion.getOptions()) {
  30. //4.3.获取option中的text,也就是补全词条
  31. String text = option.getText().string();
  32. System.out.println(text);
  33. }
  34. }