快速上手使用Elasticsearch实现高亮查询
1. 引入依赖
高版本Elasticsearch的json解析需2.0.1版本,需手动修改jakarta.json版本.
<!--es-->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.1.0</version>
<exclusions>
<exclusion>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
2.创建Elasticsearch客户端并加入到bean中
@Component
public class BeanUtils {
@Bean
public ElasticsearchClient client(){
RestClient restClient = RestClient.builder(new HttpHost(ES_HOST, 9200)).build();
// 使用Jackson映射器创建传输层
ElasticsearchTransport transport = new RestClientTransport(
restClient, new JacksonJsonpMapper()
);
return new ElasticsearchClient(transport);
}
}
3. 在Elasticsearch中创建索引
PUT /essay_show
{
"mappings": {
"properties": {
"all": {
"type": "text",
"analyzer": "ik_smart"
},
"createTime": {
"type": "keyword",
"copy_to": [
"all"
]
},
"description": {
"type": "text",
"analyzer": "ik_smart",
"copy_to": "all"
},
"id": {
"type": "keyword",
"copy_to": [
"all"
]
},
"image": {
"type": "keyword",
"index": false
},
"title": {
"type": "text",
"copy_to": [
"all"
],
"analyzer": "ik_smart"
},
"traffic": {
"type": "integer",
"copy_to": [
"all"
]
},
"userId": {
"type": "keyword",
"index": false
},
"nickName":{
"type": "keyword"
},
"avatar":{
"type": "keyword",
"index": false
},
"giveLike":{
"type": "integer",
"index": false
},
"favorites":{
"type": "integer",
"index": false
},
"comments":{
"type": "integer",
"index": false
},
"categories":{
"properties": {
"category":{
"properties":{
"id":{
"type":"keyword",
"index":false
},
"categoryName":{
"type":"keyword"
}
}
}
}
}
}
}
}
4.定时跟新索引库内容
注入客户端
@Resource
private ElasticsearchClient client;
//定时任务每两分钟更新一次
@Scheduled(cron = "0 */1 * * * ?")
public void updateESEssayByTitle() throws IOException {
List<Essay> subEssay = essayMapper.getShowEssay();
List<EssayShowDTO> essays = subEssay.stream().map((item)->{
EssayShowDTO essayShowDTO = new EssayShowDTO();
BeanUtil.copyProperties(item, essayShowDTO);
essayShowDTO.setImage(JSONUtil.toList(item.getImages(), String.class));
return essayShowDTO;
}).collect(Collectors.toList());
for (EssayShowDTO essay : essays) {
LambdaQueryWrapper<Member> memberWrapper = new LambdaQueryWrapper<>();
memberWrapper.select(Member::getAvatar, Member::getNickName).eq(Member::getId, essay.getUserId());
Member member = userService.getOne(memberWrapper);
essay.setNickName(member.getNickName());
essay.setAvatar(member.getAvatar());
//TODO 查询点赞量
LambdaQueryWrapper<GiveLike> giveLikeWrapper = new LambdaQueryWrapper<>();
giveLikeWrapper.eq(GiveLike::getEssayId, essay.getId());
int giveLike = giveLikeService.count(giveLikeWrapper);
essay.setGiveLike(giveLike);
//TODO 查询评论量
LambdaQueryWrapper<Comments> commentsWrapper = new LambdaQueryWrapper<>();
commentsWrapper.eq(Comments::getFlag, 0);
commentsWrapper.eq(Comments::getEssayId, essay.getId());
int comments = commentsService.count(commentsWrapper);
essay.setComments(comments);
//TODO 查询分类
List<Long> categoryIds = categoryRelationMapper.getCategoryId(essay.getId());
List<Category> categories = new ArrayList<>();
for (Long categoryId : categoryIds) {
Category category = categoryService.getById(categoryId);
categories.add(category);
}
essay.setCategories(categories);
}
//删除旧的
List<BulkOperation> oldList = essays.stream().map((item) -> {
return new BulkOperation.Builder().delete(
d -> d.index(ES_ESSAY_SHOW_INDEX).id(item.getId().toString())).build();
}).collect(Collectors.toList());
client.bulk(e -> e.index(ES_ESSAY_SHOW_INDEX).operations(oldList));
//加入新的
List<BulkOperation> list = essays.stream().map((item) -> {
return new BulkOperation.Builder().create(
d -> d.document(item).id(item.getId().toString()).index(ES_ESSAY_SHOW_INDEX)).build();
}).collect(Collectors.toList());
client.bulk(e -> e.index(ES_ESSAY_SHOW_INDEX).operations(list));
}
5.高亮搜索
@Override
@Transactional//事务控制,保证多表操作一致性
public R<List<EssayShowDTO>> findEssayByTitle(String title, int flag) throws IOException {
String sort = SORT_BY_TIME;
if (flag == 1) {
sort = SORT_BY_TRAFFIC;
}
//设置高亮条件
Map<String, HighlightField> highlightFieldMap = new HashMap<>();
highlightFieldMap.put("title", new HighlightField.Builder().requireFieldMatch(false).preTags("<h3 style='color:red;'>").postTags("</h3>").build());
highlightFieldMap.put("description", new HighlightField.Builder().requireFieldMatch(false).preTags("<h3 style='color:red;'>").postTags("</h3>").requireFieldMatch(false).build());
//TODO 高亮查询
String finalSort = sort;
SearchResponse<EssayShowDTO> searchResponse = client.search(s -> s.index(ES_ESSAY_SHOW_INDEX)
.query(q -> q.term(t -> t.field("title").value(title)))
.sort(o -> o.field(f -> f.field(finalSort).order(SortOrder.Desc)))
.highlight(h -> h.fields(highlightFieldMap))
, EssayShowDTO.class);
for (Hit<EssayShowDTO> hit : searchResponse.hits().hits()) {
//判断题目和描述是否存在高亮词
try {
hit.highlight().get("title").get(0);
} catch (Exception e) {
// e.printStackTrace();
//不存在,跳过本条记录
continue;
}
//TODO 将搜索的高亮标题设置到实体对象中
hit.source().setTitle(hit.highlight().get("title").get(0));
try {
hit.highlight().get("description").get(0);
} catch (Exception e) {
// e.printStackTrace();
continue;
}
//TODO 将搜索的高亮描述设置到实体对象中
hit.source().setDescription(hit.highlight().get("description").get(0));
}
List<EssayShowDTO> essayShowDTOS = searchResponse.hits().hits().stream().map((item) -> {
EssayShowDTO essayShowDTO = item.source();
return essayShowDTO;
}).collect(Collectors.toList());
return R.success(essayShowDTOS);
}