牛客网项目——项目开发(八):开发社区搜索功能

在这里插入图片描述

1. ElasticsearchService

1.1 注入bean

@Autowired
private DiscussPostRepository discussRepository;

@Autowired
private ElasticsearchTemplate elasticTemplate;

1.2 保存(修改)和删除

public void saveDiscussPost(DiscussPost post) {
    discussRepository.save(post);
}

public void deleteDiscussPost(int id) {
    discussRepository.deleteById(id);
}

1.3 搜索 searchDiscussPost

  1. 返回参数:Page类型数据,封装多条数据
  2. 传入参数:关键字keyword,当前页数current,每页显示数据limit
  3. 搜索逻辑,参考前置技术(九)
    1. 查询关键字
    2. 排序
    3. 分页
    4. 高亮显示
public Page<DiscussPost> searchDiscussPost(String keyword, int current, int limit) {
    SearchQuery 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(PageRequest.of(current, limit))
            .withHighlightFields(
                    new HighlightBuilder.Field("title").preTags("<em>").postTags("</em>"),
                    new HighlightBuilder.Field("content").preTags("<em>").postTags("</em>")
            ).build();

    return elasticTemplate.queryForPage(searchQuery, DiscussPost.class, new SearchResultMapper() {
        @Override
        public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
            SearchHits hits = response.getHits();
            if (hits.getTotalHits() <= 0) {
                return null;
            }

            List<DiscussPost> list = new ArrayList<>();
            for (SearchHit hit : hits) {
                DiscussPost post = new 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();
                post.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.getTotalHits(), response.getAggregations(), response.getScrollId(), hits.getMaxScore());
        }
    });
}

2. DiscussPostController.addDiscussPost

发帖后把帖子存入es

@Autowired
private EventProducer eventProducer;

// 触发发帖事件
Event event = new Event()
        .setTopic(TOPIC_PUBLISH)
        .setUserId(user.getId())
        .setEntityType(ENTITY_TYPE_POST)
        .setEntityId(post.getId());
eventProducer.fireEvent(event);

CommunityConstant 中增加常量

/**
 * 主题: 发帖
 */
String TOPIC_PUBLISH = "publish";

3. CommentController.addComment

触发评论帖子事后后触发发帖事件

if (comment.getEntityType() == ENTITY_TYPE_POST) {
    // 触发发帖事件
    event = new Event()
            .setTopic(TOPIC_PUBLISH)
            .setUserId(comment.getUserId())
            .setEntityType(ENTITY_TYPE_POST)
            .setEntityId(discussPostId);
    eventProducer.fireEvent(event);
}

4. EventConsumer.handlePublishMessage

消费发帖事件

// 消费发帖事件
@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);
}

5. SearchController

5.1 注入属性

@Autowired
private ElasticsearchService elasticsearchService;

@Autowired
private UserService userService;

@Autowired
private LikeService likeService;

5.2 搜索帖子

  1. 调用service查询
  2. 把数据存入一个map
  3. 把数据传给模板
  4. 传递分页信息
// search?keyword=xxx
@RequestMapping(path = "/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";
}

6. index.html

<!-- 搜索 -->
<form class="form-inline my-2 my-lg-0" method="get" th:action="@{/search}">
	<input class="form-control mr-sm-2" type="search" aria-label="Search" name="keyword" th:value="${keyword}"/>
	<button class="btn btn-outline-light my-2 my-sm-0" type="submit">搜索</button>
</form>

7. search.html

  1. 模板
  2. 路径
  3. 头部复用
  4. js路径
  5. 遍历
<li class="media pb-3 pt-3 mb-3 border-bottom" th:each="map:${discussPosts}">
  1. 头像
<img th:src="${map.user.headerUrl}" class="mr-4 rounded-circle" alt="用户头像" style="width:50px;height:50px;">
  1. 标题
<h6 class="mt-0 mb-3">
	<a th:href="@{|/discuss/detail/${map.post.id}|}" th:utext="${map.post.title}">备战<em>春招</em>,面试刷题跟他复习,一个月全搞定!</a>
</h6>
  1. 帖子作者
<u class="mr-3" th:utext="${map.user.username}">寒江雪</u>
  1. 发布时间,赞,回复信息
发布于 <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>
  1. 分页复用
<!-- 分页 -->
<nav class="mt-5" th:replace="index::pagination">
	<ul class="pagination justify-content-center">
		<li class="page-item"><a class="page-link" href="#">首页</a></li>
		<li class="page-item disabled"><a class="page-link" href="#">上一页</a></li>
		<li class="page-item active"><a class="page-link" href="#">1</a></li>
		<li class="page-item"><a class="page-link" href="#">2</a></li>
		<li class="page-item"><a class="page-link" href="#">3</a></li>
		<li class="page-item"><a class="page-link" href="#">4</a></li>
		<li class="page-item"><a class="page-link" href="#">5</a></li>
		<li class="page-item"><a class="page-link" href="#">下一页</a></li>
		<li class="page-item"><a class="page-link" href="#">末页</a></li>
	</ul>
</nav>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平什么阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值