牛客社区项目(第六章)

本文涉及的产品
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: 牛客社区项目(第六章)
❤ 作者主页: 欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于 Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得 关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

第三章:Elasticsearch,分布式搜索引擎

一、Elasticsearch入门

  • Elasticsearch 简介

    • 一个分布式的、Restful风格的搜索引擎。
    • 支持对各种类型的数据的检索。
    • 搜索速度快,可以提供实时的搜索服务。
    • 便于水平扩展,每秒可以处理PB级海量数据。
  • Elasticsearch 术语

    • 索引、类型、文档、字段。
    • 集群、节点、分片、副本。

二、Spring整合Elasticsearch

  • 引入依赖

    • spring-boot-starter-data-elasticsearch
  • 配置 Elasticsearch

    • cluster-namecluster-nodes
  • Spring Data Elasticsearch

    • ElasticsearchTemplate
    • ElasticsearchRepository

1. 引入依赖

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

2. dao层

创建 elasticsearch 包,再此包下创建 PostRepository 类,代码如下:

@Repository
public interface DiscussPostRepository extends ElasticsearchRepository<DiscussPost,Integer> {

}

 

3. 实体类

DiscussPost 实体类中添加与 es 相关的注解,代码如下:

@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;

    @Field(type = FieldType.Integer)
    private int type; // 0:普通 1:置顶

    @Field(type = FieldType.Integer)
    private int status; //  0:正常 1:精华  2:拉黑

    @Field(type = FieldType.Date)
    private Date createTime;

    @Field(type = FieldType.Integer)
    private int commentCount;

    @Field(type = FieldType.Date)
    private double score;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public int getCommentCount() {
        return commentCount;
    }

    public void setCommentCount(int commentCount) {
        this.commentCount = commentCount;
    }

    public double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }

    @Override
    public String toString() {
        return "DiscussPost{" +
                "id=" + id +
                ", userId=" + userId +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", type=" + type +
                ", status=" + status +
                ", createTime=" + createTime +
                ", commentCount=" + commentCount +
                ", score=" + score +
                '}';
    }
}

 

此外,还需在 CommunityApplication 类中设置不进行netty检查:

@SpringBootApplication
public class CommunityApplication {

    @PostConstruct
    public void init() {
        // 解决netty启动冲突问题
        // see Netty4Utils.setAvailableProcessors()
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }

    public static void main(String[] args) {
        SpringApplication.run(CommunityApplication.class, args);
    }

}

 

4. 测试方法

在 测试包下添加 ElasticsearchTests 测试方法,代码如下:

@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class ElasticsearchTests {

    @Autowired
    private DiscussPostMapper discussMapper;

    @Autowired
    private DiscussPostRepository discussRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;


    @Autowired
    @Qualifier("client")
    private RestHighLevelClient restHighLevelClient;

    @Test
    public void testInsert(){
        discussRepository.save(discussMapper.selectDiscussPostById(241));
        discussRepository.save(discussMapper.selectDiscussPostById(242));
        discussRepository.save(discussMapper.selectDiscussPostById(243));
    }

    @Test
    public void testUpdate() {
        DiscussPost post = discussMapper.selectDiscussPostById(231);
        post.setContent("我是新人, 使劲灌水.");
        discussRepository.save(post);
    }

    @Test
    public void testDelete() {
        // discussRepository.deleteById(231);
        discussRepository.deleteAll();
    }

    //不带高亮的查询
    @Test
    public void noHighlightQuery() throws IOException {
        SearchRequest searchRequest = new SearchRequest("discusspost");

        //构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                //在discusspost索引的title和content字段中都查询“互联网寒冬”
                .query(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
                .sort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .sort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .sort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .from(0)
                .size(10);

        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        System.out.println(JSONObject.toJSON(searchResponse));

        List<DiscussPost> list = new LinkedList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            DiscussPost discussPost = JSONObject.parseObject(hit.getSourceAsString(), DiscussPost.class);
            System.out.println(discussPost);
            list.add(discussPost);
        }
    }


    //带高亮的查询
    @Test
    public void highlightQuery() throws Exception{
        SearchRequest searchRequest = new SearchRequest("discusspost");//discusspost是索引名,就是表名

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.field("content");
        highlightBuilder.requireFieldMatch(false);
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");

        //构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(QueryBuilders.multiMatchQuery("互联网寒冬", "title", "content"))
                .sort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .sort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .sort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .from(0)// 指定从哪条开始查询
                .size(10)// 需要查出的总记录条数
                .highlighter(highlightBuilder);//高亮

        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        List<DiscussPost> list = new LinkedList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            DiscussPost discussPost = JSONObject.parseObject(hit.getSourceAsString(), DiscussPost.class);

            // 处理高亮显示的结果
            HighlightField titleField = hit.getHighlightFields().get("title");
            if (titleField != null) {
                discussPost.setTitle(titleField.getFragments()[0].toString());
            }
            HighlightField contentField = hit.getHighlightFields().get("content");
            if (contentField != null) {
                discussPost.setContent(contentField.getFragments()[0].toString());
            }
            System.out.println(discussPost);
            list.add(discussPost);

        }
    }


}

 

三、开发社区搜索功能

  • 搜索功能

    • 将帖子保存至 Elasticsearch 服务器。
    • Elasticsearch 服务器删除帖子。
    • Elasticsearch 服务器搜索帖子。
  • 发布事件

    • 发布帖子时,将帖子异步的提交到 Elasticsearch 服务器。
    • 增加评论时,将帖子异步的提交到 Elasticsearch 服务器。
    • 在消费组件增加一个方法,消费帖子发布事件。
  • 显示结果

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

1. dao层

discusspost-mapper.xml 中的 insertDiscussPost 中添加 KeyProperty 属性,插入的数据库生成的主键会自动放入到实体类中。

 <insert id="insertDiscussPost" parameterType="DiscussPost" keyProperty="id">
        insert into discuss_post(<include refid="insertFields"></include>)
        values(#{userId},#{title},#{content},#{type},#{status},#{createTime},#{commentCount},#{score})
 </insert>

 

2. service层

添加 ElasticsearchService 类, 代码如下:

@Service
public class ElasticsearchService {

    @Autowired
    private DiscussPostRepository discussPostRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Qualifier("client")
    @Autowired
    private RestHighLevelClient restHighLevelClient;

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

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

    public Map<String,Object> searchDiscussPost(String keyword, int current, int limit) throws Exception{
        Map<String, Object> result = new HashMap<>();

        SearchRequest searchRequest = new SearchRequest("discusspost");//discusspost是索引名,就是表名

        //高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.field("content");
        highlightBuilder.requireFieldMatch(false);
        highlightBuilder.preTags("<em>");
        highlightBuilder.postTags("</em>");


        //构建搜索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder()
                .query(QueryBuilders.multiMatchQuery(keyword, "title", "content"))
                .sort(SortBuilders.fieldSort("type").order(SortOrder.DESC))
                .sort(SortBuilders.fieldSort("score").order(SortOrder.DESC))
                .sort(SortBuilders.fieldSort("createTime").order(SortOrder.DESC))
                .from(current)// 指定从哪条开始查询
                .size(limit)// 需要查出的总记录条数
                .highlighter(highlightBuilder);

        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

        //获取查询到的总个数
        TotalHits totalHits=searchResponse.getHits().getTotalHits();
        int count=(int)totalHits.value;

        result.put("count",count);

        List<DiscussPost> list = new LinkedList<>();
        for (SearchHit hit : searchResponse.getHits().getHits()) {
            DiscussPost discussPost = JSONObject.parseObject(hit.getSourceAsString(), DiscussPost.class);

            // 处理高亮显示的结果
            HighlightField titleField = hit.getHighlightFields().get("title");
            if (titleField != null) {
                discussPost.setTitle(titleField.getFragments()[0].toString());
            }
            HighlightField contentField = hit.getHighlightFields().get("content");
            if (contentField != null) {
                discussPost.setContent(contentField.getFragments()[0].toString());
            }
            System.out.println(discussPost);
            list.add(discussPost);
        }

        result.put("data",list);
        //  return new AggregatedPageImpl()
        return result;
    }
}

 

3. controller层

添加 SearchController 类,代码如下:

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

 
DiscussPostController 类中对 addDiscussPost 方法进行重构,代码如下:

@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);

        // 报错的情况,会统一处理
        return CommunityUtil.getJSONString(0, "发布成功!");
    }

 
CommentController 类中的 addComment 方法进行重构,代码如下:

 @RequestMapping(value = "/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);
       }

       return "redirect:/discuss/detail/" + discussPostId;
   }

 

4. view层

index.htmlsearch.html 页面进行修改。

5. 功能测试

在这里插入图片描述
在这里插入图片描述
 
 
创作不易,如果有帮助到你,请给题解==点个赞和收藏==,让更多的人看到!!!
==关注博主==不迷路,内容持续更新中。

相关实践学习
使用阿里云Elasticsearch体验信息检索加速
通过创建登录阿里云Elasticsearch集群,使用DataWorks将MySQL数据同步至Elasticsearch,体验多条件检索效果,简单展示数据同步和信息检索加速的过程和操作。
ElasticSearch 入门精讲
ElasticSearch是一个开源的、基于Lucene的、分布式、高扩展、高实时的搜索与数据分析引擎。根据DB-Engines的排名显示,Elasticsearch是最受欢迎的企业搜索引擎,其次是Apache Solr(也是基于Lucene)。 ElasticSearch的实现原理主要分为以下几个步骤: 用户将数据提交到Elastic Search 数据库中 通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据 当用户搜索数据时候,再根据权重将结果排名、打分 将返回结果呈现给用户 Elasticsearch可以用于搜索各种文档。它提供可扩展的搜索,具有接近实时的搜索,并支持多租户。
目录
相关文章
|
6月前
|
人工智能 自然语言处理 供应链
面向企业的 ChatGPT 究极手册:第一章到第二章
面向企业的 ChatGPT 究极手册:第一章到第二章
168 0
面向企业的 ChatGPT 究极手册:第一章到第二章
|
6月前
|
安全 架构师 Java
理论实战源码齐飞!架构师社区疯传的SpringSecurity进阶小册真香
安全管理是Java应用开发中无法避免的问题,随着Spring Boot和微服务的流行,Spring Security受到越来越多Java开发者的重视,究其原因,还是沾了微服务的光。作为Spring家族中的一员,其在和Spring家族中的其他产品如SpringBoot、Spring Cloud等进行整合时,是拥有众多同类型框架无可比拟的优势的。
87 0
|
5月前
|
SQL 存储 NoSQL
数据库原理速览:核心知识集萃与复习指南
数据库原理速览:核心知识集萃与复习指南
51 2
|
前端开发 Java 关系型数据库
仿牛客社区项目(第一章)
仿牛客社区项目(第一章)
350 0
仿牛客社区项目(第一章)
|
前端开发 安全 Java
仿牛客社区项目(第二章)
仿牛客社区项目(第二章)
265 0
仿牛客社区项目(第二章)
|
算法 容器
算法-蓝桥基础知识-持续更新中....
算法-蓝桥基础知识-持续更新中....
|
Java 索引
【排序引论】第一章 绪论
【排序引论】第一章 绪论
99 0
【排序引论】第一章 绪论
|
运维 搜索推荐 安全
第一章 学前必读
第一章 学前必读
|
JSON 小程序 JavaScript
全栈工程师之路-中级篇之小程序开发-第一章第三节阅读官方demo
全栈工程师之路-中级篇之小程序开发-第一章第三节阅读官方demo
179 0
全栈工程师之路-中级篇之小程序开发-第一章第三节阅读官方demo
下一篇
无影云桌面