当前位置:   article > 正文

Springboot+Mybatis+Elasticsearch+RabbitMQ实现数据同步到ES及关键字搜索高亮展示_springboot elasticsearch类似mybatis查询

springboot elasticsearch类似mybatis查询

一、概述&介绍

**Elasticsearch: **

Elasticsearch 是基于Lucense 技术的搜索引擎(服务器),将数据进行缓存再进行查询。

​ 与数据库查询的比较:

​ (1)相当于sql查询的 like 模糊查询,但Elasticsearch支持分词模糊查询,比如字符串 “abcdef你 好abdcd” ,通过数据库查询 [select * from user where user_name like ‘%你 好%’; ]只能查询仅限于以“你 好”为整体得到相关的结果【abcdef你 好abdcd】或【abcdef你 好】或【你 好abdcd】等。而Elasticsearch搜索结果将“你 好”进行拆分查询,结果可以得到【abcdef你 好abdcd】【abcdef你】、【好abdcd】、【 好abd】,【ef你】等,可见查询效果更灵活范围更广。

 

RabbitMQ:

MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。

RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、 安全。AMQP协议更多用在企业系统内,对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。

二、使用场景:

Elasticsearch 使用场景:网站全局搜索、电商网站商品推荐、文章内容检索、文本分析等等。

RabbitMQ 使用场景:

  1. 解耦(为面向服务的架构(SOA)提供基本的最终一致性实现)
  2. 异步提升效率
  3. 流量削峰

官网:https://www.elastic.co/cn/

下载地址:https://www.elastic.co/cn/downloads/elasticsearch

三、环境描述:

技术架构:

后端:Springboot、Mybtis-Plus、Elasticsearch、RabbitMQ

前端:Freemark

四、环境搭建:

具体安装方式可以参考以下,本文不做过多讲解

Elasticsearch安装:

windows版本安装:https://blog.csdn.net/chen_2890/article/details/83757022

linux版本安装:https://blog.csdn.net/qq_32502511/article/details/86140486

启动系统变量限制问题参考https://www.cnblogs.com/zuikeol/p/10930685.html

RabbitMQ安装:

windows版本安装:https://blog.csdn.net/zhm3023/article/details/82217222

linux版本安装:https://www.cnblogs.com/rmxd/p/11583932.html

五、具体实现

本文实现为:

  1. 网站文章搜索,搜索内容根据标题、内容、文章描述进行搜索,实现分页搜索
  2. 发布文章数据异步同步到ES。

实现步骤描述:

  1. 与SpringBoot整合;
  • pom.xml导入maven依赖包
  1. <!-- springdata整合elasticsearch -->
  2. <dependency>
  3. <groupId>org.springframework.data</groupId>
  4. <artifactId>spring-data-elasticsearch</artifactId>
  5. </dependency>
  6. <!--整合rabbitmq-->
  7. <dependency>
  8. <groupId>org.springframework.boot</groupId>
  9. <artifactId>spring-boot-starter-amqp</artifactId>
  10. </dependency>
  • application.yml配置
  1. spring:
  2. #elasticsearch 配置
  3. data:
  4. elasticsearch:
  5. cluster-name: elasticsearch
  6. cluster-nodes: 127.0.0.1:9300
  7. repositories:
  8. enabled: true
  9. #rabbitmq 配置
  10. rabbitmq:
  11. username: mblog
  12. password: mblog
  13. host: 127.0.0.1
  14. port: 5672
  1. 新增文章时,同步数据到elasticsearch搜索引擎服务器中;

文章数据表结构:

  1. DROP TABLE IF EXISTS `mto_post`;
  2. CREATE TABLE `mto_post` (
  3. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  4. `author_id` bigint(20) DEFAULT NULL,
  5. `channel_id` int(11) DEFAULT NULL,
  6. `comments` int(11) NOT NULL,
  7. `created` datetime DEFAULT NULL,
  8. `favors` int(11) NOT NULL,
  9. `featured` int(11) NOT NULL,
  10. `status` int(11) NOT NULL,
  11. `summary` varchar(140) DEFAULT NULL,
  12. `tags` varchar(64) DEFAULT NULL,
  13. `thumbnail` varchar(128) DEFAULT NULL,
  14. `title` varchar(64) DEFAULT NULL,
  15. `views` int(11) NOT NULL,
  16. `weight` int(11) NOT NULL,
  17. PRIMARY KEY (`id`),
  18. KEY `IK_CHANNEL_ID` (`channel_id`)
  19. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

数据同步到Elasticsearch搜索引擎服务器:

  1. @Service
  2. public class PostServiceImpl implements PostService {
  3. @Autowired
  4. private PostMapper postMapper;
  5. @Autowired
  6. private PostAttributeMapper postAttributeMapper;
  7. @Autowired
  8. private TagService tagService;
  9. @Autowired
  10. private RabbitTemplate rabbitTemplate;
  11. @Override
  12. @Transactional
  13. public long post(PostVO post) {
  14. Post po = new Post();
  15. BeanUtils.copyProperties(post, po);
  16. po.setStatus(post.getStatus());
  17. // 处理摘要
  18. if (StringUtils.isBlank(post.getSummary())) {
  19. po.setSummary(trimSummary(post.getEditor(), post.getContent()));
  20. } else {
  21. po.setSummary(post.getSummary());
  22. }
  23. postMapper.insert(po);
  24. tagService.batchUpdate(po.getTags(), po.getId());
  25. String key = ResourceLock.getPostKey(po.getId());
  26. AtomicInteger lock = ResourceLock.getAtomicInteger(key);
  27. try {
  28. synchronized (lock){
  29. PostAttribute attr = new PostAttribute();
  30. attr.setContent(post.getContent());
  31. attr.setEditor(post.getEditor());
  32. attr.setPostId(po.getId());
  33. postAttributeMapper.insert(attr);
  34. countResource(po.getId(), null, attr.getContent());
  35. onPushEvent(po, PostUpdateEvent.ACTION_PUBLISH);
  36. //使用rabbitmq同步到elasticsearch搜索引擎服务器
  37. rabbitmqSend(po, ESMqMessage.CREATE_OR_UPDATE);
  38. return po.getId();
  39. }
  40. }finally {
  41. ResourceLock.giveUpAtomicInteger(key);
  42. }
  43. }
  44. /**
  45. * rabbitmq发送
  46. *
  47. * @param po 文章实体对象
  48. * @param type 类型:CREATE_OR_UPDATE 创建or更新索引;REMOVE 删除索引
  49. */
  50. private void rabbitmqSend(Post po, String type) {
  51. rabbitTemplate.convertAndSend(RabbitConstant.ES_EXCHAGE, RabbitConstant.ES_ROUTING_KEY,
  52. new ESMqMessage(po.getId(), type));
  53. }
  54. }
  1. /**
  2. * @ClassName: RabbitConstant
  3. * @Auther: Jerry
  4. * @Date: 2020/5/15 9:23
  5. * @Desctiption: rabbit常量
  6. * @Version: 1.0
  7. */
  8. public class RabbitConstant {
  9. /**es同步队列*/
  10. public final static String ES_QUEUE = "es_queue";
  11. public final static String ES_EXCHAGE = "es_exchage";
  12. public final static String ES_ROUTING_KEY = "es_routing_key";
  13. }
  1. /**
  2. * @ClassName: ESMqMessage
  3. * @Auther: Jerry
  4. * @Date: 2020/5/14 16:58
  5. * @Desctiption: 文章相关消息队列
  6. * @Version: 1.0
  7. */
  8. @Data
  9. @AllArgsConstructor
  10. public class ESMqMessage implements Serializable {
  11. private static final long serialVersionUID = 3572599349158869479L;
  12. /**
  13. * 新增或修改
  14. */
  15. public final static String CREATE_OR_UPDATE = "create_or_update";
  16. /**
  17. * 删除
  18. */
  19. public final static String REMOVE = "remove";
  20. /**
  21. * 文章id
  22. */
  23. private long postId;
  24. /**
  25. * 文章操作类型
  26. */
  27. private String action;
  28. }
  1. @Slf4j
  2. @Component
  3. @RabbitListener(queues = RabbitConstant.ES_QUEUE)
  4. public class ESMqHandler {
  5. @Autowired
  6. private PostSearchService postSearchService;
  7. @RabbitHandler
  8. public void handler(ESMqMessage message) {
  9. log.info("PostMqHandler -------> mq 收到一条消息: {}", message.toString());
  10. switch (message.getAction()) {
  11. case ESMqMessage.CREATE_OR_UPDATE:
  12. postSearchService.createOrUpdateIndex(message);
  13. break;
  14. case ESMqMessage.REMOVE:
  15. postSearchService.removeIndex(message);
  16. break;
  17. default:
  18. log.error("没找到对应的消息类型,请注意!! --》 {}", message.toString());
  19. break;
  20. }
  21. }
  22. }

实体类:

  1. @Data
  2. @AllArgsConstructor
  3. @NoArgsConstructor
  4. @ToString
  5. @Document(indexName = "es_article_index", type = "doc",
  6. useServerConfiguration = true, createIndex = false)
  7. public class Articles implements Serializable {
  8. private static final long serialVersionUID = -728655685413761417L;
  9. /**
  10. * ID
  11. */
  12. @Id
  13. private Long id;
  14. /**
  15. * 状态
  16. */
  17. private int status;
  18. /**
  19. * 标题
  20. */
  21. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  22. private String title;
  23. /**
  24. * 内容
  25. */
  26. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  27. private String summary;
  28. /**
  29. * 标签
  30. */
  31. @Field(type = FieldType.Text, analyzer = "ik_max_word")
  32. private String tags;
  33. /**
  34. * 创建时间
  35. */
  36. private Date created;
  37. /**
  38. * 更新时间
  39. */
  40. private Date updated;
  41. /**
  42. * 作者id
  43. */
  44. private Long authorId;
  45. /**
  46. * 作者
  47. */
  48. private Object author;
  49. /**
  50. * 分组/模块
  51. */
  52. private int channelId;
  53. /**
  54. * 分组/模块
  55. */
  56. private Object channel;
  57. /**
  58. * 收藏数
  59. */
  60. private int favors;
  61. /**
  62. * 评论数
  63. */
  64. private int comments;
  65. /**
  66. * 阅读数
  67. */
  68. private int views;
  69. /**
  70. * 推荐状态
  71. */
  72. private int featured;
  73. /**
  74. * 预览图
  75. */
  76. private String thumbnail;
  77. }

搜索接口:

  1. /**
  2. * @ClassName: ArticlesRepository
  3. * @Auther: Jerry
  4. * @Date: 2020/4/20 11:32
  5. * @Desctiption: 文章搜索
  6. * @Version: 1.0
  7. */
  8. public interface ArticlesRepository extends ElasticsearchRepository<Articles, Long> {
  9. }
  1. 分页关键词搜索高亮展示具体实现;

    (1)controller实现:

  1. /**
  2. * 文章搜索
  3. * @author langhsu
  4. *
  5. */
  6. @Controller
  7. public class SearchController extends BaseController {
  8. @Autowired
  9. private PostSearchService postSearchService;
  10. @RequestMapping("/search")
  11. public String search(HttpServletRequest request, String kw, ModelMap model) {
  12. try {
  13. if (StringUtils.isNotEmpty(kw)) {
  14. int pageNo = ServletRequestUtils.getIntParameter(request, "pageNo", 1);
  15. int pageSize = ServletRequestUtils.getIntParameter(request, "pageSize", 10);
  16. IPage<Articles> page = postSearchService.search(pageNo, pageSize,kw);
  17. model.put("results", page);
  18. }
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. model.put("kw", kw);
  23. return view(Views.SEARCH);
  24. }
  25. }

​ (2)service实现:

  1. @Slf4j
  2. @Service
  3. @Transactional(readOnly = true)
  4. public class PostSearchServiceImpl implements PostSearchService {
  5. @Autowired
  6. private ElasticsearchTemplate elasticsearchTemplate;
  7. @Autowired
  8. private PostService postService;
  9. @Autowired
  10. private ChannelService channelService;
  11. @Autowired
  12. private ArticlesRepository articlesRepository;
  13. @Override
  14. public IPage<Articles> search(int page, int size, String term) throws Exception {
  15. BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery()
  16. .should(QueryBuilders.matchQuery("title", term))
  17. .should(QueryBuilders.matchQuery("summary", term))
  18. .should(QueryBuilders.matchQuery("tags", term));
  19. // 创建高亮查询
  20. NativeSearchQueryBuilder nativeSearchQuery = new NativeSearchQueryBuilder();
  21. nativeSearchQuery.withQuery(boolQueryBuilder);
  22. nativeSearchQuery.withHighlightFields(new HighlightBuilder.Field("title"),
  23. new HighlightBuilder.Field("summary"),
  24. new HighlightBuilder.Field("tags"));
  25. nativeSearchQuery.withHighlightBuilder(new HighlightBuilder().preTags("<span style='color:red'>").postTags("</span>"));
  26. // 设置分页,页码要减1
  27. nativeSearchQuery.withPageable(PageRequest.of(page - 1, size));
  28. // 分页对象
  29. AggregatedPage<Articles> eSearchPage = elasticsearchTemplate.queryForPage(nativeSearchQuery.build(), Articles.class,
  30. new SearchResultMapper() {
  31. @Override
  32. public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
  33. ArrayList<Articles> list = new ArrayList<Articles>();
  34. SearchHits hits = response.getHits();
  35. for (SearchHit searchHit : hits) {
  36. if (hits.getHits().length <= 0) {
  37. return null;
  38. }
  39. Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
  40. Integer id = (Integer) sourceAsMap.get("id");
  41. String title = (String) sourceAsMap.get("title");
  42. Object author = sourceAsMap.get("author");
  43. String summary = (String) sourceAsMap.get("summary");
  44. String tags = (String) sourceAsMap.get("tags");
  45. Object channel = sourceAsMap.get("channel");
  46. String thumbnail = (String) sourceAsMap.get("thumbnail");
  47. Integer favors = (Integer) sourceAsMap.get("favors");
  48. Integer comments = (Integer) sourceAsMap.get("comments");
  49. Integer views = (Integer) sourceAsMap.get("views");
  50. Integer featured = (Integer) sourceAsMap.get("featured");
  51. Date created = new Date((Long) sourceAsMap.get("created"));
  52. Articles seArticleVo = new Articles();
  53. HighlightField highLightField = searchHit.getHighlightFields().get("title");
  54. if (highLightField == null) {
  55. seArticleVo.setTitle(title);
  56. } else {
  57. seArticleVo.setTitle(highLightField.fragments()[0].toString());
  58. }
  59. highLightField = searchHit.getHighlightFields().get("summary");
  60. if (highLightField == null) {
  61. seArticleVo.setSummary(summary);
  62. } else {
  63. seArticleVo.setSummary(highLightField.fragments()[0].toString());
  64. }
  65. highLightField = searchHit.getHighlightFields().get("tags");
  66. if (highLightField == null) {
  67. seArticleVo.setTags(tags);
  68. } else {
  69. seArticleVo.setTags(highLightField.fragments()[0].toString());
  70. }
  71. highLightField = searchHit.getHighlightFields().get("id");
  72. if (highLightField == null) {
  73. seArticleVo.setId(id.longValue());
  74. } else {
  75. seArticleVo.setId(Long.parseLong(highLightField.fragments()[0].toString()));
  76. }
  77. seArticleVo.setAuthor(author);
  78. seArticleVo.setChannel(channel);
  79. seArticleVo.setCreated(created);
  80. seArticleVo.setThumbnail(thumbnail);
  81. seArticleVo.setFavors(favors);
  82. seArticleVo.setComments(comments);
  83. seArticleVo.setViews(views);
  84. seArticleVo.setFeatured(featured == null ? 0 : featured);
  85. list.add(seArticleVo);
  86. }
  87. AggregatedPage<T> pageResult = new AggregatedPageImpl<T>((List<T>) list, pageable, hits.getTotalHits());
  88. return pageResult;
  89. }
  90. });
  91. long pageNum = Long.valueOf(eSearchPage.getNumber());
  92. long pageSize = Long.valueOf(eSearchPage.getPageable().getPageSize());
  93. Page page1 = new Page(pageNum, pageSize);
  94. page1.setRecords(eSearchPage.getContent());
  95. page1.setTotal(Long.valueOf(eSearchPage.getTotalElements()));
  96. return page1;
  97. }
  98. @Override
  99. public void createOrUpdateIndex(ESMqMessage message) {
  100. long postId = message.getPostId();
  101. Post post = postService.getPostById(postId);
  102. Articles articles = BeanMapUtil.post2Articles(post);
  103. UserVO author = userService.get(post.getAuthorId());
  104. Channel channel = channelService.getById(post.getChannelId());
  105. articles.setAuthor(author);
  106. articles.setChannel(channel);
  107. articlesRepository.save(articles);
  108. log.info("es 索引更新成功! ---> {}", articles.toString());
  109. }
  110. @Override
  111. public void removeIndex(ESMqMessage message) {
  112. long postId = message.getPostId();
  113. articlesRepository.deleteById(postId);
  114. log.info("es 索引删除成功! ---> {}", message.toString());
  115. }
  116. }

六、总结

使用Elasiticsearch 时需要注意的几个问题:

(1)分页需要重新计算页码,执行查询时需要设置nativeSearchQuery.withPageable(new PageRequest(request.getPageNum() - 1, request.getPageSize())); 查询到结果后需要计算页码;

(2)ES查询结果后,单独处理关键字,命中关键字部分通过withHighlightBuilder().preTags方法设置命中文本标记。

​ nativeSearchQuery.withHighlightBuilder(new HighlightBuilder().preTags("<span style=‘color:red’>").postTags(""));

finally,大功告成!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/172839
推荐阅读
相关标签
  

闽ICP备14008679号