问答平台(6),社区搜索功能

搜索服务

1
2
3
- 将帖子保存至 Elasticsearch 服务器。
- 从 Elasticsearch 服务器删除帖子。
- 从 Elasticsearch 服务器搜索帖子。

发布事件

1
2
3
- 发布帖子时,将帖子异步地提交到 Elasticsearch 服务器。
- 增加评论时,将帖子异步地提交到 Elasticsearch 服务器。
- 在消费事件中增加一个方法,消费帖子发布事件。(发帖或更改事件存到 Kafka消息队列中,消费事件并将帖子存到 es服务器中)

显示结果

社区搜索功能-图示

  • 在控制器中处理搜索请求,在 HTML 上显示搜索结果。

业务层

  • ElasticsearchService: 新增
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    @Service
    public class ElasticsearchService {

    @Autowired
    private DiscussPostRepository discussepository;

    @Autowired
    private ElasticsearchTemplate elasticTemplate;

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

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

    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());
    }
    });
    }
    }

表现层

  • DiscussPostController: 修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    @RequestMapping(path = "/add", method = RequestMethod.POST)
    @ResponseBody
    public 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);

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

    // 计算帖子分数
    // String redisKey = RedisKeyUtil.getPostScoreKey();
    // redisTemplate.opsForSet().add(redisKey, post.getId());

    // 报错的情况,将来统一处理
    return CommunityUtil.getJSONString(0, "发布成功!");
    }
  • CommentController: 修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @RequestMapping(path = "/add/{discussPostId}", method = RequestMethod.POST)
    public String addComment(@PathVariable("discussPostId") int discussPostId, Comment comment) {
    comment.setUserId(hostHolder.getUser().getId());
    comment.setStatus(0);
    comment.setCreateTime(new Date());
    commentService.addComment(comment);

    // 触发评论事件
    Event event = new Event()
    .setTopic(TOPIC_COMMENT)
    .setUserId(hostHolder.getUser().getId())
    .setEntityType(comment.getEntityType())
    .setEntityId(comment.getEntityId())
    .setData("postId", discussPostId);
    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);

    // 计算帖子分数
    // String redisKey = RedisKeyUtil.getPostScoreKey();
    // redisTemplate.opsForSet().add(redisKey, discussPostId);
    }

    return "redirect:/discuss/detail/" + discussPostId;
    }
  • SearchController: 新增
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    @Controller
    public class SearchController implements CommunityConstant {

    @Autowired
    private ElasticsearchService elasticsearchService;

    @Autowired
    private UserService userService;

    @Autowired
    private LikeService likeService;

    // 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";
    }
    }

发布事件

  • EventConsumer: 修改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 消费发帖事件
    @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);
    }

页面

  • index.html: 修改
    1
    2
    <!-- 头部:复用 -->
    <!-- 搜索 -->
  • search.html: 修改
    1
    2
    3
    <!-- 内容 -->
    <!-- 帖子列表 -->
    <!-- 分页 -->

结果展示

搜索结果-图示


问答平台(6),社区搜索功能
https://lcf163.github.io/2020/06/08/问答平台(6),社区搜索功能/
作者
乘风的小站
发布于
2020年6月8日
许可协议