欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

elasticsearch搜素关键字自动补全(suggest)

程序员文章站 2022-07-14 10:48:18
...

elasticsearch搜素关键字自动补全顾名思义 在搜索框搜索时能有提示列表可供选择。

最终效果如下:

elasticsearch搜素关键字自动补全(suggest)

该搜索优化功能是elasticsearch自带的即suggest,suggest即存储一个词库,每次搜索发送请求去词库中检索,匹配到即返回。

接下来我们一步一步实现上述功能。

1.建立索引

我这预先准备了一个房屋信息的索引house

{
  "settings": {
    "number_of_replicas": 0
  },
  "mappings": {
    "house": {
      "dynamic": false,
      "properties": {
        "houseId": {
          "type": "long"
        },
        "title": {
          "type": "text",
          "index": "analyzed",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "price": {
          "type": "integer"
        },
        "area": {
          "type": "integer"
        },
        "createTime": {
          "type": "date",
          "format": "strict_date_optional_time||epoch_millis"
        },
        "lastUpdateTime": {
          "type": "date",
          "format": "strict_date_optional_time||epoch_millis"
        },
        "cityEnName": {
          "type": "keyword"
        },
        "regionEnName": {
          "type": "keyword"
        },
        "direction": {
          "type": "integer"
        },
        "distanceToSubway": {
          "type": "integer"
        },
        "subwayLineName": {
          "type": "keyword"
        },
        "subwayStationName": {
          "type": "keyword"
        },
        "tags": {
          "type": "text"
        },
        "street": {
          "type": "keyword"
        },
        "district": {
          "type": "keyword"
        },
        "description": {
          "type": "text",
          "index": "analyzed",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "layoutDesc": {
          "type": "text",
          "index": "analyzed",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "traffic": {
          "type": "text",
          "index": "analyzed",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "roundService": {
          "type": "text",
          "index": "analyzed",
          "analyzer": "ik_smart",
          "search_analyzer": "ik_smart"
        },
        "rentWay": {
          "type": "integer"
        },
        "suggest": {
          "type": "completion"
        },
        "room": {
          "type": "integer"
        }
      }
    }
  }
}

注意关键字段suggest,type为completion

2.向建好的索引中添加数据

主要注意suggest字段中如何添加数据

 private boolean updateSuggest(HouseIndexTemplate indexTemplate) {
        //将分词字段加入AnalyzeRequestBuilder,通过ik_smart分词后会生成多个词组,然后将词组加入suggest字段
        AnalyzeRequestBuilder requestBuilder = new AnalyzeRequestBuilder(
                this.esClient, AnalyzeAction.INSTANCE, INDEX_NAME, indexTemplate.getTitle(),
                indexTemplate.getLayoutDesc(), indexTemplate.getRoundService(),
                indexTemplate.getDescription(), indexTemplate.getSubwayLineName(),
                indexTemplate.getSubwayStationName());
        //采用ik_smart分词
        requestBuilder.setAnalyzer("ik_smart");

        AnalyzeResponse response = requestBuilder.get();
        List<AnalyzeResponse.AnalyzeToken> tokens = response.getTokens();
        if (tokens == null) {
            logger.warn("Can not analyze token for house: " + indexTemplate.getHouseId());
            return false;
        }

        List<HouseSuggest> suggests = new ArrayList<>();
        for (AnalyzeResponse.AnalyzeToken token : tokens) {
            // 排序数字类型 & 小于2个字符的分词结果
            if ("<NUM>".equals(token.getType()) || token.getTerm().length() < 2) {
                continue;
            }

            HouseSuggest suggest = new HouseSuggest();
            suggest.setInput(token.getTerm());
            suggests.add(suggest);
        }

        // 定制化小区自动补全(不需要分词的字段手动额外加入)
        HouseSuggest suggest = new HouseSuggest();
        suggest.setInput(indexTemplate.getDistrict());
        suggests.add(suggest);

        indexTemplate.setSuggest(suggests);
        return true;
    }

如上代码表示:

首先将title、layoutDesc、roundService、description、subwayLineName、subwayStationName通过ik_smart分词后,将分词后的字词加入suggests 集合;

然后将不需要分词的字段district手动加入suggests集合;

其中HouseIndexTemplate 只是一个索引对象,里面封装了索引所需字段,注意其中suggest为 集合:

private List<HouseSuggest> suggest;

最后将HouseIndexTemplate对象写入elasticsearch中即可。

IndexResponse response = this.esClient.prepareIndex(INDEX_NAME, INDEX_TYPE)
                    .setSource(objectMapper.writeValueAsBytes(indexTemplate), XContentType.JSON).get();

写入后结构如下 :

elasticsearch搜素关键字自动补全(suggest)

3.开始做检索

前端js调用检索接口

 $('#keyword-box').autocomplete({
            minLength: 2, // 最小字符数,默认1
            delay: 300, // 延迟加载300ms
            source: function (request, response) { // 数据源
                $.ajax({
                    url: '/rent/house/autocomplete?prefix=' + request.term,
                    success: function (res) {
                        if (res.code === 200) {
                            response(res.data);
                        }
                    }
                });
            },
            select: function (event, ui) { // 选中事件
                $('#keyword-box').text(ui.item.value);
                window.location.href = locate_url(start, size);
            }
        });

后端接口和服务层:

controller:


    /**
     * 自动补全接口
     */
    @GetMapping("rent/house/autocomplete")
    @ResponseBody
    public ApiResponse autocomplete(@RequestParam(value = "prefix") String prefix) {

        if (prefix.isEmpty()) {
            return ApiResponse.ofStatus(ApiResponse.Status.BAD_REQUEST);
        }
        ServiceResult<List<String>> result = this.searchService.suggest(prefix);
        return ApiResponse.ofSuccess(result.getResult());
    }

service:

@Override
    public ServiceResult<List<String>> suggest(String prefix) {
        CompletionSuggestionBuilder suggestion = SuggestBuilders.completionSuggestion("suggest").prefix(prefix).size(5);

        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("autocomplete", suggestion);

        SearchRequestBuilder requestBuilder = this.esClient.prepareSearch(INDEX_NAME)
                .setTypes(INDEX_TYPE)
                .suggest(suggestBuilder);
        logger.debug(requestBuilder.toString());

        SearchResponse response = requestBuilder.get();
        Suggest suggest = response.getSuggest();
        if (suggest == null) {
            return ServiceResult.of(new ArrayList<>());
        }
        Suggest.Suggestion result = suggest.getSuggestion("autocomplete");

        int maxSuggest = 0;
        Set<String> suggestSet = new HashSet<>();

        for (Object term : result.getEntries()) {
            if (term instanceof CompletionSuggestion.Entry) {
                CompletionSuggestion.Entry item = (CompletionSuggestion.Entry) term;

                if (item.getOptions().isEmpty()) {
                    continue;
                }

                for (CompletionSuggestion.Entry.Option option : item.getOptions()) {
                    String tip = option.getText().string();
                    if (suggestSet.contains(tip)) {
                        continue;
                    }
                    suggestSet.add(tip);
                    maxSuggest++;
                }
            }

            if (maxSuggest > 5) {
                break;
            }
        }
        List<String> suggests = Lists.newArrayList(suggestSet.toArray(new String[]{}));
        return ServiceResult.of(suggests);
    }

elasticsearch搜素关键字自动补全(suggest)