实现功能
• 搜索服务
- 将帖子保存至Elasticsearch服务器。
- 从Elasticsearch服务器删除帖子。
- 从Elasticsearch服务器搜索帖子。
• 发布事件
- 发布帖子时,将帖子异步的提交到Elasticsearch服务器。
- 增加评论时,将帖子异步的提交到Elasticsearch服务器。
- 在消费组件中增加一个方法,消费帖子发布事件。
• 显示结果
- 在控制器中处理搜索请求,在HTML上显示搜索结果
controller
@Controller
public class SearchController implements CommunityConstant {@Autowiredprivate ElasticsearchService elasticsearchService;@Autowiredprivate UserService userService;@Autowiredprivate LikeService likeService;@RequestMapping(value = "/search",method = RequestMethod.GET)public String search(String keyword, Page page, Model model){//搜索帖子org.springframework.data.domain.Page<DiscussPost> searchResult =elasticsearchService.searchDiscussPost(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<>();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";}
}
service
@Service
public class ElasticsearchService {@Autowiredprivate ElasticsearchTemplate elasticsearchTemplate;@Autowiredprivate DiscussPostRepository discussPostRepository;//当新发布(修改)一条帖子时,要将帖子数据传入到elasticsearch中public void saveDiscussPost(DiscussPost discussPost){discussPostRepository.save(discussPost);}//当删除一条帖子时,需要将elasticsearch中内容同步删除public void deleteDiscussPost(int id){discussPostRepository.deleteById(id);}//实现搜索业务public Page<DiscussPost> searchDiscussPost(String keyword,int current,int limit){SearchQuery searchQuery=new NativeSearchQueryBuilder() //构造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(PageRequest.of(current,limit)) //构造分页条件:PageRequest.of(0,10):当前页(从0开始 ,每页显示10条.withHighlightFields(//是否高亮显示:对搜索的关键词加标签new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")).build(); //返回SearchQuery接口实现类return elasticsearchTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() {@Overridepublic <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {//处理SearchHits hits = searchResponse.getHits(); //获取搜索的结果数据if(hits.totalHits<0){return null; //未获取到结果直接返回}//将获取到的结果装进list中List<DiscussPost> list = new ArrayList<>();//遍历命中数据for(SearchHit hit:hits){//将命中数据吧包装到实体类中DiscussPost post=new DiscussPost();//数据形式是以json形式存在的,并将json数据封装为了map格式//因此将数据以map形式取出--->转为string再存入DiscussPost对象中String id = hit.getSourceAsMap().get("id").toString();post.setId(Integer.valueOf(id));String userId = hit.getSourceAsMap().get("userId").toString();post.setUserId(Integer.valueOf(userId));String title = hit.getSourceAsMap().get("title").toString();//原始的titlepost.setTitle(title);String content = hit.getSourceAsMap().get("content").toString();post.setContent(content);String status = hit.getSourceAsMap().get("status").toString();post.setStatus(Integer.valueOf(status));String createTime = hit.getSourceAsMap().get("createTime").toString();post.setCreateTime(new Date(Long.valueOf(createTime)));String commentCount = hit.getSourceAsMap().get("commentCount").toString();post.setCommentCount(Integer.valueOf(commentCount));//处理显示高亮结果HighlightField titleField = hit.getHighlightFields().get("title");if(titleField!=null){//获取到高亮结果,将高亮结果对原内容进行替换post.setTitle(titleField.getFragments()[0].toString());}HighlightField contentField = hit.getHighlightFields().get("content");if(contentField!=null){//获取到高亮结果,将高亮结果对原内容进行替换post.setContent(contentField.getFragments()[0].toString());}list.add(post);}return new AggregatedPageImpl(list,pageable, hits.totalHits,searchResponse.getAggregations(),searchResponse.getScrollId(),hits.getMaxScore());}});}}
dao
@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost,Integer> {
}
producer
发送帖子后,需要将帖子同步到elasticsearch中
@RequestMapping(value = "/add",method = RequestMethod.POST)@ResponseBodypublic String addDiscussPost(String title,String content){//判断是否登录User user = hostHolder.getUser();if(user==null){//还未登陆无权限访问return CommunityUtil.getJsonString(403,"还未登陆!");}DiscussPost post=new DiscussPost();post.setUserId(user.getId());post.setTitle(title);post.setContent(content);post.setCreateTime(new Date());discussPostService.addDiscussPost(post);//发布帖子后,同步到elasticsearch中//利用事件进行发送Event event=new Event().setTopic(TOPIC_PUBLISH).setUserId(user.getId()).setEntityType(ENTITY_TYPE_POST).setEntityId(post.getId());eventProducer.fireEvent(event);return CommunityUtil.getJsonString(0,"发布成功!");}
添加评论后,需要更新elasticsearch中数据
@RequestMapping(value = "/add/{discussPostId}",method = RequestMethod.POST)public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment){comment.setStatus(0);comment.setCreateTime(new Date());comment.setUserId(hostHolder.getUser().getId());commentService.addComment(comment);//发送评论后发送通知通知对方Event event=new Event().setTopic(TOPIC_COMMENT).setUserId(hostHolder.getUser().getId()).setEntityType(comment.getEntityType()).setEntityId(comment.getEntityId()).setData("postId",discussPostId); //最终发送的消息还有点击查看,要链接到帖子详情页面,需要知道帖子id//帖子的作者:评论的是帖子帖子--》帖子表; 评论的是评论---》评论表if(comment.getEntityType()==ENTITY_TYPE_POST){DiscussPost target = discussPostService.findDiscussPostById(comment.getEntityId());event.setEntityUserId(target.getUserId());} else if (comment.getEntityType()==ENTITY_TYPE_COMMENT) {Comment target = commentService.findCommentById(comment.getEntityId());event.setEntityUserId(target.getUserId());}//生产者发送事件eventProducer.fireEvent(event);//触发发帖事件if(comment.getEntityType()==ENTITY_TYPE_POST){event=new Event().setTopic(TOPIC_PUBLISH).setUserId(comment.getUserId()).setEntityType(ENTITY_TYPE_POST).setEntityId(discussPostId);eventProducer.fireEvent(event);}return "redirect:/discuss/detail/" + discussPostId;}
consumer
//消费者消费发帖事件--->同步到elasticsearch中@KafkaListener(topics = TOPIC_PUBLISH)public void handleDiscussPost(ConsumerRecord record){//先进行判断record是否为空:未发事件或者发送的事件为空if(record==null|| record.value()==null){logger.error("发送的消息为空!");return;}//事件不为空:将事件转换为Event对象Event event= JSONObject.parseObject(record.value().toString(),Event.class);//判断对象是否为空if(event==null){logger.error("消息格式错误!");return;}//从事件中获取帖子idDiscussPost post = discussPostService.findDiscussPostById(event.getEntityId());//将查询到的帖子同步到elasticsearch中elasticsearchService.saveDiscussPost(post);}
application.properties
#配置ElasticsearchProperties spring.data.elasticsearch.cluster-name=(自己的cluster-name) spring.data.elasticsearch.cluster-nodes=自己的服务器ip:9300 spring.elasticsearch.rest.uris=自己的服务器ip:9200 spring.elasticsearch.rest.username=运行elasticsearch的用户 spring.elasticsearch.rest.password=用户密码 spring.data.elasticsearch.repositories.enabled=true #192.168.10.100:9200 (http端口 #192.168.10.100:9300 (tcp端口
html
<!-- 帖子列表 --><ul class="list-unstyled mt-4"><li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}"><img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;"><div class="media-body"><h6 class="mt-0 mb-3"><a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战<em>春招</em>,面试刷题跟他复习,一个月全搞定!</a></h6><div class="mb-3" th:utext="${map.post.content}">金三银四的金三已经到了,你还沉浸在过年的喜悦中吗? 如果是,那我要让你清醒一下了:目前大部分公司已经开启了内推,正式网申也将在3月份陆续开始,金三银四,<em>春招</em>的求职黄金时期已经来啦!!! 再不准备,作为19应届生的你可能就找不到工作了。。。作为20届实习生的你可能就找不到实习了。。。 现阶段时间紧,任务重,能做到短时间内快速提升的也就只有算法了, 那么算法要怎么复习?重点在哪里?常见笔试面试算法题型和解题思路以及最优代码是怎样的? 跟左程云老师学算法,不仅能解决以上所有问题,还能在短时间内得到最大程度的提升!!!</div><div class="text-muted font-size-12"><u class="mr-3" th:utext="${map.user.userName}">寒江雪</u>发布于 <b th:text="${#dates.format(map.post.createTime,'yyyy-MM-dd HH:mm:ss')}">2019-04-15 15:32:18</b><ul class="d-inline float-right"><li class="d-inline ml-2">赞 <i th:text="${map.likeCount}">11</i></li><li class="d-inline ml-2">|</li><li class="d-inline ml-2">回复 <i th:text="${map.post.commentCount}">7</i></li></ul></div></div></li></ul>