14、Elasticsearch开发搜索功能

该博客介绍了如何使用Elasticsearch管理帖子数据,包括保存、删除和搜索操作。实体类使用注解映射数据库字段,通过Repository接口进行数据交互。在业务层,实现了帖子的高亮分页搜索,并通过Kafka监听事件,异步更新Es中的帖子信息。在展示层,Controller处理搜索请求并返回搜索结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

搜索服务:

  • 将帖子保存在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中,所以不需要重新存放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值