仿牛客社区——开发社区搜索功能

news/2025/1/13 5:59:33/

实现功能

• 搜索服务

- 将帖子保存至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>

 


http://www.ppmy.cn/news/80484.html

相关文章

2023/5/24

四种权限修饰符 priavte(私有的)&#xff1a; default(缺省):它是针对本包访问而设计的&#xff0c;任何处于本包下的类、接口、异常等&#xff0c;都可以相互访问。 protected(受保护的):​ 当前类或子类可以访问&#xff0c;同时相同包内的其他类也可以访问protected成员&…

C++指针与引用

目录 一 指针 二 引用 一 指针 指针是一个变量,其值为另一个变量的内存位置的直接地址。在使用指针存储其他变量地址之前,需要指针进行行声明。 每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。…

任务列表todoList

union all与union的区别 InitBinder spring中是怎样加载servlet、filter的&#xff0c;进而探究springboot是怎样加载servlet的 熟悉oracle使用 excel操作总结 ThreadLocal整理的东西在哪里 Bean定义的bean&#xff0c;如果里面有使用Autowired&#xff0c;会注入么 &…

音视频源码调试前准备vs2019+qt5.15.2搭建可调试环境

安装vs2019qt,并且在windows环境上安装ffmpeg&#xff0c;尝试使用qtcdb进行调试&#xff0c;尝试使用vs2019加载qt的程序。 安装VS20195.12.2qt环境&#xff0c;并进行测试。 1&#xff1a;安装Visual Studio 2019, a.从官网下载&#xff0c;或者vs2019社区版本下载地址 ht…

【新星计划·2023】TCP协议与UDP协议讲解

前言 对于TCP协议与UDP协议&#xff0c;大家应该都有所耳闻。我们常用的网络通讯&#xff0c;比如浏览网页&#xff0c;软件聊天&#xff0c;都是通过这两种协议来进行数据传输的&#xff0c;下面我就来给大家讲解一下这两个协议。 一、什么是TCP、UDP TCP(Transmission Con…

Python篇——数据结构与算法(第三部分:归并排序;快速、归并、堆排序小结;深拷贝和浅拷贝区别)

1、归并排序——归并 假设现在的列表分为两段有序&#xff0c;如何将其合成为一个有序列表这种操作称为一次归并 归并过程描述&#xff1a;&#xff08;前提是两段列表分别有序&#xff09; 两段有序列表进行对比&#xff0c;1和2进行对比选出最小的数&#xff0c;1出列&#x…

《花雕学AI》34:用13种Prompt玩转AI聊天机器人—揭秘ChatGPT模型

引言&#xff1a; 聊天机器人是一种能够通过自然语言进行交流的智能系统&#xff0c;它可以模仿人类的对话方式&#xff0c;提供各种信息、服务或娱乐。随着人工智能技术的发展&#xff0c;聊天机器人的应用越来越广泛&#xff0c;从电商、教育、医疗、旅游等领域&#xff0c;到…

Mysql数据库分库分表

为什么使用分库分表&#xff1f; 传统的将数据集中存储至单一数据节点的解决方案&#xff0c;在性能、可用性和运维成本这三方面已经难于满足互联网的海量数据场景。 1&#xff09;性能 从性能方面来说&#xff0c;由于关系型数据库大多采用 B 树类型的索引&#xff0c;在数…