javascript
java 搜索引擎 关键词高亮_和我一起打造个简单搜索之SpringDataElasticSearch关键词高亮(示例代码)...
前面幾篇文章詳細講解了 ElasticSearch 的搭建以及使用 SpringDataElasticSearch 來完成搜索查詢,但是搜索一般都會有搜索關鍵字高亮的功能,今天我們把它給加上。
系列文章
環(huán)境依賴
本文以及后續(xù) es 系列文章都基于 5.5.3 這個版本的 elasticsearch ,這個版本比較穩(wěn)定,可以用于生產(chǎn)環(huán)境。
SpringDataElasticSearch 的基本使用可以看我的上一篇文章 和我一起打造個簡單搜索之SpringDataElasticSearch入門,本文就不再贅述。
高亮關鍵字實現(xiàn)
前文查詢是通過寫一個接口來繼承 ElasticsearchRepository 來實現(xiàn)的,但是如果要實現(xiàn)高亮,我們就不能這樣做了,我們需要使用到 ElasticsearchTemplate來完成。
查看這個類的源碼
public class ElasticsearchTemplate implements ElasticsearchOperations, ApplicationContextAware {
...
}
可以看到,ElasticsearchTemplate 實現(xiàn)了接口 ApplicationContextAware,所以這個類是被 Spring 管理的,可以在類里面直接注入使用。
代碼如下:
@Slf4j
@Component
public class HighlightBookRepositoryTest extends EsSearchApplicationTests {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@Resource
private ExtResultMapper extResultMapper;
@Test
public void testHighlightQuery() {
BookQuery query = new BookQuery();
query.setQueryString("穿越");
// 復合查詢
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 以下為查詢條件, 使用 must query 進行查詢組合
MultiMatchQueryBuilder matchQuery = QueryBuilders.multiMatchQuery(query.getQueryString(), "name", "intro", "author");
boolQuery.must(matchQuery);
PageRequest pageRequest = PageRequest.of(query.getPage() - 1, query.getSize());
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(boolQuery)
.withHighlightFields(
new HighlightBuilder.Field("name").preTags("").postTags(""),
new HighlightBuilder.Field("author").preTags("").postTags(""))
.withPageable(pageRequest)
.build();
Page books = elasticsearchTemplate.queryForPage(searchQuery, Book.class, extResultMapper);
books.forEach(e -> log.info("{}", e));
// 穿越小道人
}
}
注意這里 的
Page books = elasticsearchTemplate.queryForPage(searchQuery, Book.class, extResultMapper);
這里返回的是分頁對象。
查詢方式和上文的差不多,只不過是是 Repository 變成了 ElasticsearchTemplate,操作方式也大同小異。
這里用到了 ExtResultMapper,請接著看下文。
自定義ResultMapper
ResultMapper 是用于將 ES 文檔轉換成 Java 對象的映射類,因為 SpringDataElasticSearch 默認的的映射類 DefaultResultMapper 不支持高亮,因此,我們需要自己定義一個 ResultMapper。
復制 DefaultResultMapper 類,重命名為 ExtResultMapper,對構造方法名稱修改為正確的值。
新增一個方法,用于將高亮的內(nèi)容賦值給需要轉換的 Java 對象內(nèi)。
在 mapResults 方法內(nèi)調用這個方法。
注意:這個類可以直接拷貝到你的項目中直接使用!
我寫這么多,只是想說明為什么這個類是這樣的。
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import org.apache.commons.beanutils.PropertyUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetItemResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHitField;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.ElasticsearchException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.ScriptedField;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.DefaultEntityMapper;
import org.springframework.data.elasticsearch.core.EntityMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.*;
/**
* 類名稱:ExtResultMapper
* 類描述:自定義結果映射類
* 創(chuàng)建人:WeJan
* 創(chuàng)建時間:2018-09-13 20:47
*/
@Component
public class ExtResultMapper extends AbstractResultMapper {
private MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext;
public ExtResultMapper() {
super(new DefaultEntityMapper());
}
public ExtResultMapper(MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext) {
super(new DefaultEntityMapper());
this.mappingContext = mappingContext;
}
public ExtResultMapper(EntityMapper entityMapper) {
super(entityMapper);
}
public ExtResultMapper(
MappingContext extends ElasticsearchPersistentEntity>, ElasticsearchPersistentProperty> mappingContext,
EntityMapper entityMapper) {
super(entityMapper);
this.mappingContext = mappingContext;
}
@Override
public AggregatedPage mapResults(SearchResponse response, Class clazz, Pageable pageable) {
long totalHits = response.getHits().totalHits();
List results = new ArrayList<>();
for (SearchHit hit : response.getHits()) {
if (hit != null) {
T result = null;
if (StringUtils.hasText(hit.sourceAsString())) {
result = mapEntity(hit.sourceAsString(), clazz);
} else {
result = mapEntity(hit.getFields().values(), clazz);
}
setPersistentEntityId(result, hit.getId(), clazz);
setPersistentEntityVersion(result, hit.getVersion(), clazz);
populateScriptFields(result, hit);
// 高亮查詢
populateHighLightedFields(result, hit.getHighlightFields());
results.add(result);
}
}
return new AggregatedPageImpl(results, pageable, totalHits, response.getAggregations(), response.getScrollId());
}
private void populateHighLightedFields(T result, Map highlightFields) {
for (HighlightField field : highlightFields.values()) {
try {
PropertyUtils.setProperty(result, field.getName(), concat(field.fragments()));
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
throw new ElasticsearchException("failed to set highlighted value for field: " + field.getName()
+ " with value: " + Arrays.toString(field.getFragments()), e);
}
}
}
private String concat(Text[] texts) {
StringBuffer sb = new StringBuffer();
for (Text text : texts) {
sb.append(text.toString());
}
return sb.toString();
}
private void populateScriptFields(T result, SearchHit hit) {
if (hit.getFields() != null && !hit.getFields().isEmpty() && result != null) {
for (java.lang.reflect.Field field : result.getClass().getDeclaredFields()) {
ScriptedField scriptedField = field.getAnnotation(ScriptedField.class);
if (scriptedField != null) {
String name = scriptedField.name().isEmpty() ? field.getName() : scriptedField.name();
SearchHitField searchHitField = hit.getFields().get(name);
if (searchHitField != null) {
field.setAccessible(true);
try {
field.set(result, searchHitField.getValue());
} catch (IllegalArgumentException e) {
throw new ElasticsearchException("failed to set scripted field: " + name + " with value: "
+ searchHitField.getValue(), e);
} catch (IllegalAccessException e) {
throw new ElasticsearchException("failed to access scripted field: " + name, e);
}
}
}
}
}
}
private T mapEntity(Collection values, Class clazz) {
return mapEntity(buildJSONFromFields(values), clazz);
}
private String buildJSONFromFields(Collection values) {
JsonFactory nodeFactory = new JsonFactory();
try {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
JsonGenerator generator = nodeFactory.createGenerator(stream, JsonEncoding.UTF8);
generator.writeStartObject();
for (SearchHitField value : values) {
if (value.getValues().size() > 1) {
generator.writeArrayFieldStart(value.getName());
for (Object val : value.getValues()) {
generator.writeObject(val);
}
generator.writeEndArray();
} else {
generator.writeObjectField(value.getName(), value.getValue());
}
}
generator.writeEndObject();
generator.flush();
return new String(stream.toByteArray(), Charset.forName("UTF-8"));
} catch (IOException e) {
return null;
}
}
@Override
public T mapResult(GetResponse response, Class clazz) {
T result = mapEntity(response.getSourceAsString(), clazz);
if (result != null) {
setPersistentEntityId(result, response.getId(), clazz);
setPersistentEntityVersion(result, response.getVersion(), clazz);
}
return result;
}
@Override
public LinkedList mapResults(MultiGetResponse responses, Class clazz) {
LinkedList list = new LinkedList<>();
for (MultiGetItemResponse response : responses.getResponses()) {
if (!response.isFailed() && response.getResponse().isExists()) {
T result = mapEntity(response.getResponse().getSourceAsString(), clazz);
setPersistentEntityId(result, response.getResponse().getId(), clazz);
setPersistentEntityVersion(result, response.getResponse().getVersion(), clazz);
list.add(result);
}
}
return list;
}
private void setPersistentEntityId(T result, String id, Class clazz) {
if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity> persistentEntity = mappingContext.getRequiredPersistentEntity(clazz);
ElasticsearchPersistentProperty idProperty = persistentEntity.getIdProperty();
// Only deal with String because ES generated Ids are strings !
if (idProperty != null && idProperty.getType().isAssignableFrom(String.class)) {
persistentEntity.getPropertyAccessor(result).setProperty(idProperty, id);
}
}
}
private void setPersistentEntityVersion(T result, long version, Class clazz) {
if (mappingContext != null && clazz.isAnnotationPresent(Document.class)) {
ElasticsearchPersistentEntity> persistentEntity = mappingContext.getPersistentEntity(clazz);
ElasticsearchPersistentProperty versionProperty = persistentEntity.getVersionProperty();
// Only deal with Long because ES versions are longs !
if (versionProperty != null && versionProperty.getType().isAssignableFrom(Long.class)) {
// check that a version was actually returned in the response, -1 would indicate that
// a search didn‘t request the version ids in the response, which would be an issue
Assert.isTrue(version != -1, "Version in response is -1");
persistentEntity.getPropertyAccessor(result).setProperty(versionProperty, version);
}
}
}
}
注意這里使用到了 PropertyUtils ,需要引入一個 Apache 的依賴。
commons-beanutils
commons-beanutils
1.9.3
自定義 ResultMapper 寫好之后,添加 @Component 注解,表示為 Spring 的一個組件,在類中進行注入使用即可。
最后
本文示例項目地址:https://github.com/Mosiki/SpringDataElasticSearchQuickStartExample
有疑問?
歡迎來信,給我寫信
總結
以上是生活随笔為你收集整理的java 搜索引擎 关键词高亮_和我一起打造个简单搜索之SpringDataElasticSearch关键词高亮(示例代码)...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: excel怎么更改坐标轴刻度_如何用ex
- 下一篇: 学习opencv3_如何高效学习计算机视