elasticsearch搜素关键字自动补全(suggest)
程序员文章站
2022-07-14 10:48:18
...
elasticsearch搜素关键字自动补全顾名思义 在搜索框搜索时能有提示列表可供选择。
最终效果如下:
该搜索优化功能是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();
写入后结构如下 :
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);
}
上一篇: 接口
下一篇: Android搜索关键字高亮显示