搜索服务:
- 将帖子保存在Elasticsearch服务器中。
- 从Elasticsearch服务器删除帖子。
- 从Elasticsearch服务器搜索帖子。
发布事件: - 发布帖子时,将帖子异步的提交到Elasticsearch服务器。
- 增加评论时,将帖子异步的提交到Elasticsearch服务器。
- 在消费组件中增加一个方法,消费帖子发布事件。
显示结果: - 在控制器中处理搜索请求,在Html上显示搜索结果。
1、在实体类中添加注解
使用Elasticsearch搜索的实体(帖子),必须要将数据放到Es服务器中才能搜索。因此要配置数据库中的表、字段与Es中的索引、类型等的对应关系。
@id表示文档的id,文档类似数据库表中的行。
@Field(type = FieldType.Text, analyzer = “ik_max_word”, searchAnalyzer = “ik_smart”),type参数是文档中字段的类型,FieldType.Text会进行分词并建立索引的字符类型;analyzer = “ik_max_word” 指定存储时的解析器,尽可能多的进行分词;searchAnalyzer = “ik_smart” 搜索时使用的解析器。
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "discusspost", shards = 6, replicas = 3)
public class DiscussPost {
@Id
private int id;
@Field(type = FieldType.Integer)
private int userId;
// 帖子的标题
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String title;
// 帖子内容
@Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_smart")
private String content;
// 帖子类型 0-普通帖子 1-置顶帖子 2-精华帖子
@Field(type = FieldType.Integer)
private int type;
// 帖子状态 0-正常 1-精华 2-拉黑
@Field(type = FieldType.Integer)
private int status;
@Field(type = FieldType.Date)
private Date createTime;
// 帖子评论数量
@Field(type = FieldType.Integer)
private int commentCount;
//记录帖子的分数
@Field(type = FieldType.Double)
private double score;
}
dao
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost, Integer> {
}
业务层Service
获取命中的数据
将命中的数据进行处理,包装成Discusspost放入list内
高亮显示结果
@Service
public class ElasticsearchService {
@Autowired
private DiscussPostRepository discussPostRepository;
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 插入或修改帖子,保存到Es服务器
*
* @param post
*/
public void saveDiscussPost(DiscussPost post) {
discussPostRepository.save(post);
}
/**
* 删除帖子
*
* @param id
*/
public void deleteDiscussPost(int id) {
discussPostRepository.deleteById(id);
}
/**
* 搜索并高分分页显示
*
* @param keyword
* @param current
* @param limit
* @return
*/
public Page<DiscussPost> searchDiscussionPost(String keyword, int current, int limit) {
Pageable pageable = PageRequest.of(current, limit);
NativeSearchQuery searchQuery =
new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
.withSort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
.withSort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
.withPageable(pageable)
.withHighlightFields(
new HighlightBuilder.Field("title")
.preTags("<em>")
.postTags("</em>"),
new HighlightBuilder.Field("content")
.preTags("<em>")
.postTags("</em>"))
.build();
SearchHits<DiscussPost> searchHits =
elasticsearchRestTemplate.search(searchQuery, DiscussPost.class);
if (searchHits.getTotalHits() <= 0) {
return null;
}
List<DiscussPost> list = new ArrayList<>();
for (SearchHit<DiscussPost> hit : searchHits) {
DiscussPost content = hit.getContent();
DiscussPost post = new DiscussPost();
BeanUtils.copyProperties(content, post);
List<String> list1 = hit.getHighlightFields().get("title");
if (list1 != null) {
post.setTitle(list1.get(0));
}
List<String> list2 = hit.getHighlightFields().get("content");
if (list2 != null) {
post.setContent(list2.get(0));
}
list.add(post);
}
return new PageImpl<>(list, pageable, searchHits.getTotalHits());
}
}
表现层
在发布帖子和增加评论时,由生产者提交给消息队列,再由消费者异步处理,将帖子信息保存在搜索引擎Es服务器中。
/**
* 消费发帖事件
*
* @param record
*/
@KafkaListener(topics = {TOPIC_PUBLISH})
public void handlePublishMessage(ConsumerRecord record) {
if (record == null || record.value() == null) {
logger.error("消息内容为空!");
return;
}
Event event = JSONObject.parseObject(record.value().toString(), Event.class);
if (event == null) {
logger.error("消息格式错误!");
return;
}
// 搜索引擎保存帖子信息
DiscussPost post = discussPostService.findDiscussPostById(event.getEntityId());
elasticsearchService.saveDiscussPost(post);
}
显示显示帖子Controller
@GetMapping("/search")
public String search(String keyword, Page page, Model model){
// 搜索帖子
org.springframework.data.domain.Page<DiscussPost> searchResult =
elasticsearchService.searchDiscussionPost(keyword, page.getCurrent()-1, page.getLimit());
// 聚合数据
List<Map<String, Object>> discussPosts = new ArrayList<>();
if(searchResult != null){
for (DiscussPost post : searchResult) {
Map<String, Object> map = new HashMap<>(16);
// 帖子
map.put("post",post);
// 作者
map.put("user",userService.findUserById(post.getUserId()));
// 点赞数量
map.put("likeCount",likeService.findEntityLikeCount(ENTITY_TYPE_POST,post.getId()));
discussPosts.add(map);
}
}
model.addAttribute("discussPosts",discussPosts);
model.addAttribute("keyword",keyword);
page.setPath("/search?keyword=" + keyword);
page.setRows(searchResult==null ? 0 : (int) searchResult.getTotalElements());
return "site/search";
}
注意
在评论某条帖子后,由于Discusspost实体中评论数改变了,所以要重新放在Es服务器里面。
而点赞数量存储在Redis中,所以不需要重新存放。