经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Elasticsearch » 查看文章
ElasticSearch 实现分词全文检索 - SpringBoot 完整实现 Demo 附源码【完结篇】
来源:cnblogs  作者:VipSoft  时间:2023/3/24 9:10:15  对本文有异议

可以先看下列文章

目录

ElasticSearch 实现分词全文检索 - 概述
ElasticSearch 实现分词全文检索 - ES、Kibana、IK安装
ElasticSearch 实现分词全文检索 - Restful基本操作
ElasticSearch 实现分词全文检索 - Java SpringBoot ES 索引操作
ElasticSearch 实现分词全文检索 - Java SpringBoot ES 文档操作
ElasticSearch 实现分词全文检索 - 测试数据准备
ElasticSearch 实现分词全文检索 - term、terms查询
ElasticSearch 实现分词全文检索 - match、match_all、multimatch查询
ElasticSearch 实现分词全文检索 - id、ids、prefix、fuzzy、wildcard、range、regexp 查询
ElasticSearch 实现分词全文检索 - Scroll 深分页
ElasticSearch 实现分词全文检索 - delete-by-query
ElasticSearch 实现分词全文检索 - 复合查询
ElasticSearch 实现分词全文检索 - filter查询
ElasticSearch 实现分词全文检索 - 高亮查询
ElasticSearch 实现分词全文检索 - 聚合查询 cardinality
ElasticSearch 实现分词全文检索 - 经纬度查询
ElasticSearch 实现分词全文检索 - 搜素关键字自动补全(suggest)
ElasticSearch 实现分词全文检索 - SpringBoot 完整实现 Demo 附源码

需求

做一个类似百度的全文搜索功能

  • 搜素关键字自动补全(suggest)
    image
  • 分词全文搜索
    image

所用的技术如下:

  • ElasticSearch
  • Kibana 管理界面
  • IK Analysis 分词器
  • SpringBoot

实现流程

可以通过 Canal 对 MySQL binlog 进行数据同步,或者 flink 或者 SpringBoot 直接往ES里添加数据
当前以 SpringBoot 直接代码同步为例(小项目此方法简单)
image

全文步骤

image

  • 启动项目时,通过 Bean ,对 ES Client 对象实例化(ElasticSearchConfig.java) 点击查看:Java Bean 注册对象
  • 第一步:系统初始化,创建索引(EsIndexTest.createIndexTest)
  • 第二步:模拟后台管理员,在添加文章时,将要检查的字段内容,同步到ES中(EsIndexTest.addArticleTest)
  • 第三步:模拟用户搜索,在搜索框中查关键词“人工”(EsIndexTest.suggestTest) 点击查看:搜素关键字自动补全(suggest)
  • 第四步:返回自动补全关键词,选择“人工智能技术”进行查询(EsIndexTest.earchTest)

ES 搜索方法

  • term:完全匹配,搜索之前不会对搜索的关键字进行分词,直接拿关键字去文档分词库中的去匹配内容
  • terms:和term的查询机制是一样,区别是可以去多个Field字段中匹配
  • match:实际底层就是多个term查询,将多个term查询的结果给你封装到了一起
  • match_all:查出全部内容,不指定任何查询条件。
  • boolean match:基于一个Field匹配的内容,采用 and或 or 的方式 连接
  • multi_match:match 针对一个field做检索,multi_match 针对多个 field 进行检索。多个 field 对应一个text
  • id: 直接根据ID获取
  • ids: 根据多个ID查询,类似MySQL中的 where id in (1,2,3)
  • prefix:前缀查询,可以通过一个关键字去指定一个Field的前缀,从而查询到指定的文档
  • fuzzy: 模糊查询,我们输入字符的大概,ES就可以 (天天凯心)可以有错别字
  • wildcard:通配查询,和MySQL中的 like 差不多,可以在查询时,在字符串中指定通配符 * 和占位符?
  • range:范围查询,只针对数值类型,对某一个Field进行大于或小于的范围指定查询
  • regexp: 正则查询,通过你编写的正则表达式去匹配内容

PS:prefix,fuzzy,wildcard 和 regexp 查询效率相对比较低。要求效率比较高时,避免去使用]

分词器 analyzer 和 search_analyzer

  • 分词器 analyzer 的作用有二:
    一是 插入文档时,将 text 类型字段做分词,然后插入 倒排索引。
    二是 在查询时,先对 text 类型输入做分词, 再去倒排索引搜索。
  • 如果想要“索引”和“查询”, 使用不同的分词器,那么 只需要在字段上 使用 search_analyzer。这样,索引只看 analyzer,查询就看 search_analyzer。
    如果没有定义,就看有没有 analyzer,再没有就去使用 ES 预设。

ik analyzer

  • ik_max_word:会对文本做最细 力度的拆分
  • ik_smart:会对文本做最粗粒度的拆分
    两种 分词器的最佳实践: 索引时用 ik_max_word(面面俱到), 搜索时用 ik_smart(精准匹配)。
  1. "title":{
  2. "type":"text",
  3. "analyzer":"ik_max_word",
  4. "search_analyzer":"ik_smart"
  5. }

Field datatypes

  1. String:
  2. text: 一般用于全文检索。将当前的Field进行分词
  3. keyword: 当前 Field 不会被分词
  4. 数值类型:
  5. long integer short byte double float
  6. half_float:精度比float小一半
  7. scaled_float:根据一个longscaled来表达一个浮点型, long=345,scaled=100 => 3.45
  8. 时间类型:
  9. date:针对时间类型指定具体格式,ES 可以对日期格式,化为字符串存储,但是我们建议存储为毫秒值 long,节省空间
  10. 布尔类型:
  11. boolean:表达truefalse
  12. 二进制类型:
  13. binary:暂时支持Base64 encode string
  14. 范围类型(Range datatypes):
  15. long_range: 赋值时,无序指定具体的内容,只需要存储一个范围即可,指定gt,此,gtelte
  16. integer_range:同上
  17. double_range:同上
  18. float_range: 同上
  19. date_range:同上
  20. ip_range: 同上。
  21. 经纬度类型:
  22. geo_point: 用来存储经纬度,结合定位的经纬度,来计算出距离
  23. IP类型
  24. ip 可以存付IPV4IPV6
  25. Completion 类型(Completion datatype):
  26. completion 提供自动补全建议

详细代码如下:

POM.XML

elasticsearch-rest-client 一定要引用,否则报错

  1. [2023-03-10 10:57:41.793] [main] [WARN ] o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initEsClient' defined in class path resource [com/vipsoft/web/config/ElasticSearchConfig.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.elasticsearch.client.RestHighLevelClient] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
  2. 10:57:41,793 |-INFO in c.q.l.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy - Elapsed period: Tue Mar 07 14:07:19 CST 2023
  3. 10:57:41,793 |-INFO in c.q.l.co.rolling.helper.RenameUtil - Renaming file [.\logs\warn.log] to [.\logs\warn.log.2023-03-07]
  4. [2023-03-10 10:57:41.797] [main] [INFO ] org.apache.catalina.core.StandardService - Stopping service [Tomcat]
  5. [2023-03-10 10:57:41.812] [main] [INFO ] o.s.b.a.l.ConditionEvaluationReportLoggingListener -
  6. Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
  7. [2023-03-10 10:57:41.824] [main] [ERROR] org.springframework.boot.SpringApplication - Application run failed
  8. org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'initEsClient' defined in class path resource [com/vipsoft/web/config/ElasticSearchConfig.class]: Post-processing of merged bean definition failed; nested exception is java.lang.IllegalStateException: Failed to introspect Class [org.elasticsearch.client.RestHighLevelClient] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
  9. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:572)
  10. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517)
  11. at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323)
  12. at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:226)
  13. at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321)
  14. at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
  15. at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:893)
  16. at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:879)
  17. at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:551)
  18. at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
  19. at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747)
  20. at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
  21. at org.springframework.boot.SpringApplication.run(SpringApplication.java:315)
  22. at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226)
  23. at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
  24. at com.vipsoft.web.ESApplication.main(ESApplication.java:10)
  25. Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.elasticsearch.client.RestHighLevelClient] from ClassLoader [sun.misc.Launcher$AppClassLoader@18b4aac2]
  26. at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:481)
  27. at org.springframework.util.ReflectionUtils.doWithLocalMethods(ReflectionUtils.java:321)
  28. at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.buildLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:232)
  29. at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.findLifecycleMetadata(InitDestroyAnnotationBeanPostProcessor.java:210)
  30. at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(InitDestroyAnnotationBeanPostProcessor.java:149)
  31. at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.postProcessMergedBeanDefinition(CommonAnnotationBeanPostProcessor.java:294)
  32. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyMergedBeanDefinitionPostProcessors(AbstractAutowireCapableBeanFactory.java:1094)
  33. at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:569)
  34. ... 15 common frames omitted
  35. Caused by: java.lang.NoClassDefFoundError: org/elasticsearch/client/Cancellable
  36. at java.lang.Class.getDeclaredMethods0(Native Method)
  37. at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
  38. at java.lang.Class.getDeclaredMethods(Class.java:1975)
  39. at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:463)
  40. ... 22 common frames omitted
  41. Caused by: java.lang.ClassNotFoundException: org.elasticsearch.client.Cancellable
  42. at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
  43. at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
  44. at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
  45. at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
  46. ... 26 common frames omitted
  47. 10:57:41,825 |-INFO in c.q.l.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy - Elapsed period: Tue Mar 07 14:07:19 CST 2023
  48. 10:57:41,825 |-INFO in c.q.l.co.rolling.helper.RenameUtil - Renaming file [.\logs\error.log] to [.\logs\error.log.2023-03-07]
  49. Disconnected from the target VM, address: '127.0.0.1:1810', transport: 'socket'
  1. <dependency>
  2. <groupId>org.elasticsearch</groupId>
  3. <artifactId>elasticsearch</artifactId>
  4. <version>7.10.1</version>
  5. </dependency>
  6. <!--如果不引用,会报错-->
  7. <dependency>
  8. <groupId>org.elasticsearch.client</groupId>
  9. <artifactId>elasticsearch-rest-client</artifactId>
  10. <version>7.10.1</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>org.elasticsearch.client</groupId>
  14. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  15. <version>7.10.1</version>
  16. </dependency>
点击查看全部POM代码
  1. <dependencies>
  2. <dependency>
  3. <groupId>org.elasticsearch</groupId>
  4. <artifactId>elasticsearch</artifactId>
  5. <version>7.10.1</version>
  6. </dependency>
  7. <!--如果不引用,会报错-->
  8. <dependency>
  9. <groupId>org.elasticsearch.client</groupId>
  10. <artifactId>elasticsearch-rest-client</artifactId>
  11. <version>7.10.1</version>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.elasticsearch.client</groupId>
  15. <artifactId>elasticsearch-rest-high-level-client</artifactId>
  16. <version>7.10.1</version>
  17. </dependency>
  18. <dependency>
  19. <groupId>cn.hutool</groupId>
  20. <artifactId>hutool-all</artifactId>
  21. <version>5.3.6</version>
  22. </dependency>
  23. <dependency>
  24. <groupId>com.alibaba</groupId>
  25. <artifactId>fastjson</artifactId>
  26. <version>1.2.73</version>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-actuator</artifactId>
  35. </dependency>
  36. <dependency>
  37. <groupId>org.springframework.boot</groupId>
  38. <artifactId>spring-boot-starter-test</artifactId>
  39. <scope>test</scope>
  40. <exclusions>
  41. <exclusion>
  42. <groupId>org.junit.vintage</groupId>
  43. <artifactId>junit-vintage-engine</artifactId>
  44. </exclusion>
  45. </exclusions>
  46. </dependency>
  47. </dependencies>
  48. <build>
  49. <plugins>
  50. <plugin>
  51. <groupId>org.springframework.boot</groupId>
  52. <artifactId>spring-boot-maven-plugin</artifactId>
  53. </plugin>
  54. </plugins>
  55. </build>

详细程序

application.yml

  1. server:
  2. port: 8088
  3. application:
  4. name: ElasticSearch Demo
  5. Spring:
  6. es:
  7. cluster-name: VipSoft
  8. replicas-num: 1
  9. nodes: 172.16.3.88:9200
  10. keep-alive: 300 # 保持client 每 300秒 = 5分钟 发送数据保持http存活

ElasticSearchConfig.java

  1. package com.vipsoft.web.config;
  2. import org.apache.http.HttpHost;
  3. import org.elasticsearch.client.RestClient;
  4. import org.elasticsearch.client.RestHighLevelClient;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.boot.context.properties.ConfigurationProperties;
  8. import org.springframework.context.annotation.Bean;
  9. import org.springframework.context.annotation.Configuration;
  10. import java.time.Duration;
  11. import java.util.Arrays;
  12. @Configuration
  13. public class ElasticSearchConfig {
  14. Logger logger = LoggerFactory.getLogger(this.getClass());
  15. /**
  16. * 将 application.yml 中的配置,映射到 EsProperties 中,
  17. * 并 执行 EsProperties.init() 方法
  18. * @return
  19. */
  20. @Bean(initMethod = "init")
  21. @ConfigurationProperties(prefix = "spring.es")
  22. public EsProperties esProperties() {
  23. return new EsProperties();
  24. }
  25. @Bean
  26. public RestHighLevelClient initEsClient(EsProperties esProperties) {
  27. String[] nodes = esProperties.getNodes().split(EsProperties.SPLIT_NODES);
  28. HttpHost[] httpHosts = Arrays.stream(nodes).map(HttpHost::create).toArray(HttpHost[]::new);
  29. RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(httpHosts).setHttpClientConfigCallback(
  30. requestConfig -> requestConfig.setKeepAliveStrategy((response, context) -> Duration.ofSeconds(esProperties.getKeepAlive()).toMillis())));
  31. logger.info("初始化 es client nodes: {}", Arrays.toString(httpHosts));
  32. return client;
  33. }
  34. }

EsProperties.java

  1. package com.vipsoft.web.config;
  2. import org.slf4j.Logger;
  3. import org.slf4j.LoggerFactory;
  4. public class EsProperties {
  5. Logger logger = LoggerFactory.getLogger(this.getClass());
  6. public static final String SPLIT_NODES = ";";
  7. /**
  8. * 集群名称
  9. */
  10. private String clusterName;
  11. /**
  12. * 集群副本数
  13. */
  14. private Integer replicasNum;
  15. /**
  16. * 链接时长
  17. */
  18. private Integer keepAlive;
  19. /**
  20. * 节点列表
  21. */
  22. private String nodes;
  23. public void init() {
  24. String[] nodes = this.getNodes().split(EsProperties.SPLIT_NODES);
  25. if (this.getReplicasNum() == null) {
  26. int replicasNum = nodes.length - 1;
  27. this.setReplicasNum(replicasNum);
  28. logger.info("初始化 EsProperties 未指定副本数,设置默认副本数(节点数-1) replicasNum: {}", replicasNum);
  29. } else {
  30. logger.info("初始化 EsProperties 设置副本数 replicasNum: {}", this.getReplicasNum());
  31. }
  32. }
  33. public String getClusterName() {
  34. return clusterName;
  35. }
  36. public void setClusterName(String clusterName) {
  37. this.clusterName = clusterName;
  38. }
  39. public Integer getReplicasNum() {
  40. return replicasNum;
  41. }
  42. public void setReplicasNum(Integer replicasNum) {
  43. this.replicasNum = replicasNum;
  44. }
  45. public String getNodes() {
  46. return nodes;
  47. }
  48. public void setNodes(String nodes) {
  49. this.nodes = nodes;
  50. }
  51. public Integer getKeepAlive() {
  52. return keepAlive;
  53. }
  54. public void setKeepAlive(Integer keepAlive) {
  55. this.keepAlive = keepAlive;
  56. }
  57. }

ElasticSearchUtil.java

  1. package com.vipsoft.web.utils;
  2. import org.elasticsearch.action.bulk.BulkRequest;
  3. import org.elasticsearch.action.bulk.BulkResponse;
  4. import org.elasticsearch.action.index.IndexRequest;
  5. import org.elasticsearch.action.index.IndexResponse;
  6. import org.elasticsearch.action.search.SearchRequest;
  7. import org.elasticsearch.action.search.SearchResponse;
  8. import org.elasticsearch.client.RequestOptions;
  9. import org.elasticsearch.client.RestHighLevelClient;
  10. import org.elasticsearch.client.indices.CreateIndexRequest;
  11. import org.elasticsearch.client.indices.CreateIndexResponse;
  12. import org.elasticsearch.client.indices.GetIndexRequest;
  13. import org.elasticsearch.common.settings.Settings;
  14. import org.elasticsearch.common.xcontent.XContentBuilder;
  15. import org.elasticsearch.common.xcontent.XContentType;
  16. import org.elasticsearch.index.query.QueryBuilders;
  17. import org.elasticsearch.search.builder.SearchSourceBuilder;
  18. import org.elasticsearch.search.sort.ScoreSortBuilder;
  19. import org.elasticsearch.search.sort.SortOrder;
  20. import org.elasticsearch.search.suggest.SuggestBuilder;
  21. import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
  22. import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
  23. import org.springframework.beans.factory.annotation.Autowired;
  24. import org.springframework.stereotype.Service;
  25. import org.springframework.util.CollectionUtils;
  26. import java.util.ArrayList;
  27. import java.util.List;
  28. import java.util.Map;
  29. @Service
  30. public class ElasticSearchUtil {
  31. /**
  32. * ES 服务注入
  33. */
  34. @Autowired
  35. private RestHighLevelClient esClient;
  36. /**
  37. * 判断索引是否存在
  38. */
  39. public Boolean indexExists(String indexName) throws Exception {
  40. GetIndexRequest request = new GetIndexRequest(indexName);
  41. return esClient.indices().exists(request, RequestOptions.DEFAULT);
  42. }
  43. /**
  44. * 创建 ES 索引
  45. */
  46. public CreateIndexResponse createIndex(String indexName, Settings.Builder settings, XContentBuilder mappings) throws Exception {
  47. //将 Settings 和 Mappings 封装到一个Request 对象中
  48. CreateIndexRequest request = new CreateIndexRequest(indexName)
  49. .settings(settings)
  50. .mapping(mappings);
  51. //通过 client 对象去连接ES并执行创建索引
  52. return esClient.indices().create(request, RequestOptions.DEFAULT);
  53. }
  54. /**
  55. * 批量创建 ES 文档
  56. */
  57. public IndexResponse createDoc(String indexName, String id, String json) throws Exception {
  58. //准备一个Request对象
  59. IndexRequest request = new IndexRequest(indexName);
  60. request.id(id); //手动指定ID
  61. request.source(json, XContentType.JSON);
  62. //request.opType(DocWriteRequest.OpType.INDEX); 默认使用 OpType.INDEX,如果 id 重复,会进行 覆盖更新, resp.getResult().toString() 返回 UPDATE
  63. //request.opType(DocWriteRequest.OpType.CREATE); 如果ID重复,会报异常 => document already exists
  64. //通过 Client 对象执行添加
  65. return esClient.index(request, RequestOptions.DEFAULT);
  66. }
  67. /**
  68. * 批量创建 ES 文档
  69. *
  70. * @param jsonMap Key = id,Value = json
  71. */
  72. public BulkResponse batchCreateDoc(String indexName, Map<String, String> jsonMap) throws Exception {
  73. //准备一个Request对象
  74. BulkRequest bulkRequest = new BulkRequest();
  75. for (String id : jsonMap.keySet()) {
  76. IndexRequest request = new IndexRequest(indexName)
  77. .id(id) //手动指定ID
  78. .source(jsonMap.get(id), XContentType.JSON);
  79. bulkRequest.add(request);
  80. }
  81. //通过 Client 对象执行添加
  82. return esClient.bulk(bulkRequest, RequestOptions.DEFAULT);
  83. }
  84. /**
  85. * 自动补全 根据用户的输入联想到可能的词或者短语
  86. *
  87. * @param indexName 索引名称
  88. * @param field 搜索条件字段
  89. * @param keywords 搜索关键字
  90. * @param size 匹配数量
  91. * @return
  92. * @throws Exception
  93. */
  94. public List<String> suggest(String indexName, String field, String keywords, int size) throws Exception {
  95. //定义返回
  96. List<String> suggestList = new ArrayList<>();
  97. //构建查询请求
  98. SearchRequest searchRequest = new SearchRequest(indexName);
  99. //通过查询构建器定义评分排序
  100. SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
  101. searchSourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
  102. //构造搜索建议语句,搜索条件字段
  103. CompletionSuggestionBuilder completionSuggestionBuilder = new CompletionSuggestionBuilder(field);
  104. //搜索关键字
  105. completionSuggestionBuilder.prefix(keywords);
  106. //去除重复
  107. completionSuggestionBuilder.skipDuplicates(true);
  108. //匹配数量
  109. completionSuggestionBuilder.size(size);
  110. searchSourceBuilder.suggest(new SuggestBuilder().addSuggestion("article-suggest", completionSuggestionBuilder));
  111. //article-suggest为返回的字段,所有返回将在article-suggest里面,可写死,sort按照评分排序
  112. searchRequest.source(searchSourceBuilder);
  113. //定义查找响应
  114. SearchResponse suggestResponse = esClient.search(searchRequest, RequestOptions.DEFAULT);
  115. //定义完成建议对象
  116. CompletionSuggestion completionSuggestion = suggestResponse.getSuggest().getSuggestion("article-suggest");
  117. List<CompletionSuggestion.Entry.Option> optionsList = completionSuggestion.getEntries().get(0).getOptions();
  118. //从optionsList取出结果
  119. if (!CollectionUtils.isEmpty(optionsList)) {
  120. optionsList.forEach(item -> suggestList.add(item.getText().toString()));
  121. }
  122. return suggestList;
  123. }
  124. /**
  125. * 前缀查询,可以通过一个关键字去指定一个Field的前缀,从而查询到指定的文档
  126. */
  127. public SearchResponse prefixQuery(String indexName, String searchField, String searchKeyword) throws Exception {
  128. //创建Request对象
  129. SearchRequest request = new SearchRequest(indexName);
  130. //XX开头的关键词查询
  131. SearchSourceBuilder builder = new SearchSourceBuilder();
  132. builder.query(QueryBuilders.prefixQuery(searchField, searchKeyword));
  133. request.source(builder);
  134. //执行查询
  135. return esClient.search(request, RequestOptions.DEFAULT);
  136. }
  137. /**
  138. * 通过 QueryBuilder 构建多字段匹配如:QueryBuilders.multiMatchQuery("人工智能","title","content")
  139. * multi_match => https://www.cnblogs.com/vipsoft/p/17164544.html
  140. */
  141. public SearchResponse search(String indexName, QueryBuilder query, int currPage, int pageSize) throws Exception {
  142. SearchRequest request = new SearchRequest(indexName);
  143. SearchSourceBuilder builder = new SearchSourceBuilder();
  144. int start = (currPage - 1) * pageSize;
  145. builder.from(start);
  146. builder.size(pageSize);
  147. builder.query(query);
  148. request.source(builder);
  149. return esClient.search(request, RequestOptions.DEFAULT);
  150. }
  151. //TODO 其它功能
  152. }

ArticleDTO.java

  1. package com.vipsoft.web.dto;
  2. /**
  3. * 用于和 ES 交互的文章实体
  4. * 只需要匹配 标题、简介的文字,
  5. */
  6. public class ArticleDTO {
  7. /**
  8. * ID 用于关联详细的数据库中的文章信息
  9. */
  10. private String id;
  11. /**
  12. * 标题 -- 用于查询
  13. */
  14. private String title;
  15. /**
  16. * 简介 -- 用于查询的结果,列表显示(不光显示标题,还要显示摘要)
  17. */
  18. private String summary;
  19. public String getId() {
  20. return id;
  21. }
  22. public void setId(String id) {
  23. this.id = id;
  24. }
  25. public String getTitle() {
  26. return title;
  27. }
  28. public void setTitle(String title) {
  29. this.title = title;
  30. }
  31. public String getSummary() {
  32. return summary;
  33. }
  34. public void setSummary(String summary) {
  35. this.summary = summary;
  36. }
  37. }

ArticleInfo.java

  1. package com.vipsoft.web.entity;
  2. import java.util.Date;
  3. /**
  4. * 用于和数据库交互的文章实体
  5. */
  6. public class ArticleInfo {
  7. /**
  8. * ID
  9. */
  10. private String id;
  11. /**
  12. * 标题
  13. */
  14. private String title;
  15. /**
  16. * 作者
  17. */
  18. private String author;
  19. /**
  20. * 简介
  21. */
  22. private String summary;
  23. /**
  24. * 内容
  25. */
  26. private String content;
  27. /**
  28. * 创建时间
  29. */
  30. private Date createTime;
  31. public String getId() {
  32. return id;
  33. }
  34. public void setId(String id) {
  35. this.id = id;
  36. }
  37. public String getTitle() {
  38. return title;
  39. }
  40. public void setTitle(String title) {
  41. this.title = title;
  42. }
  43. public String getAuthor() {
  44. return author;
  45. }
  46. public void setAuthor(String author) {
  47. this.author = author;
  48. }
  49. public String getSummary() {
  50. return summary;
  51. }
  52. public void setSummary(String summary) {
  53. this.summary = summary;
  54. }
  55. public String getContent() {
  56. return content;
  57. }
  58. public void setContent(String content) {
  59. this.content = content;
  60. }
  61. public Date getCreateTime() {
  62. return createTime;
  63. }
  64. public void setCreateTime(Date createTime) {
  65. this.createTime = createTime;
  66. }
  67. }

EsIndexTest.java

  1. package com.vipsoft.web;
  2. import com.alibaba.fastjson.JSON;
  3. import com.vipsoft.web.config.EsProperties;
  4. import com.vipsoft.web.dto.ArticleDTO;
  5. import com.vipsoft.web.entity.ArticleInfo;
  6. import com.vipsoft.web.utils.ElasticSearchUtil;
  7. import org.elasticsearch.action.index.IndexResponse;
  8. import org.elasticsearch.action.search.SearchResponse;
  9. import org.elasticsearch.client.indices.CreateIndexResponse;
  10. import org.elasticsearch.common.settings.Settings;
  11. import org.elasticsearch.common.xcontent.XContentBuilder;
  12. import org.elasticsearch.common.xcontent.json.JsonXContent;
  13. import org.elasticsearch.search.SearchHit;
  14. import org.junit.jupiter.api.Test;
  15. import org.slf4j.Logger;
  16. import org.slf4j.LoggerFactory;
  17. import org.springframework.beans.BeanUtils;
  18. import org.springframework.beans.factory.annotation.Autowired;
  19. import org.springframework.boot.test.context.SpringBootTest;
  20. import java.util.*;
  21. /**
  22. * 单元测试
  23. */
  24. @SpringBootTest
  25. public class EsIndexTest {
  26. Logger logger = LoggerFactory.getLogger(this.getClass());
  27. @Autowired
  28. private ElasticSearchUtil elasticSearchUtil;
  29. @Autowired
  30. private EsProperties esProperties;
  31. int INDEX_NUMBER_OF_SHARDS = 5;
  32. String INDEX_NAME = "article-index";
  33. /**
  34. * 第一步:系统初始化,创建索引
  35. * 如果索引不存在,创建,输出
  36. */
  37. @Test
  38. void createIndexTest() throws Exception {
  39. boolean indexExists = elasticSearchUtil.indexExists(INDEX_NAME);
  40. if (!indexExists) {
  41. try {
  42. createIndex(INDEX_NAME);
  43. logger.info("索引【{}】,创建成功", INDEX_NAME);
  44. //测试效果 可再次查询验证。
  45. indexExists = elasticSearchUtil.indexExists(INDEX_NAME);
  46. logger.info("索引【{}】, {}", INDEX_NAME, indexExists ? "验证存在" : "验证不存在");
  47. } catch (Exception e) {
  48. logger.error(e.getMessage(), e);
  49. }
  50. } else {
  51. logger.info("索引【{}】已存在,无需创建", INDEX_NAME);
  52. }
  53. }
  54. /**
  55. * 第二步:模拟后台管理员,在添加文章时,将要检查的字段内容,同步到ES中
  56. */
  57. @Test
  58. void addArticleTest() throws Exception {
  59. Map<Integer, String> titleMap = new HashMap<>();
  60. titleMap.put(1, "人工智能技术");
  61. titleMap.put(2, "人工智能软件 Chart GTP");
  62. titleMap.put(3, "Restful基本操作");
  63. titleMap.put(4, "Java SpringBoot ES 索引操作");
  64. titleMap.put(5, "Java SpringBoot ES 文档操作");
  65. titleMap.put(6, "人工呼吸");
  66. titleMap.put(7, "SpringBoot 全文检索实战");
  67. Map<Integer, String> introMap = new HashMap<>();
  68. introMap.put(1, "ElasticSearch 实现分词全文检索 - 概述");
  69. introMap.put(2, "ElasticSearch 实现分词全文检索 - ES、Kibana、IK安装");
  70. introMap.put(3, "ElasticSearch 实现分词全文检索 - Restful基本操作");
  71. introMap.put(4, "ElasticSearch 实现分词全文检索 - Java SpringBoot ES 索引操作");
  72. introMap.put(5, "ElasticSearch 实现分词全文检索 - Java SpringBoot ES 文档操作");
  73. introMap.put(6, "ElasticSearch 实现分词全文检索 - 经纬度查询");
  74. introMap.put(7, "ElasticSearch 实现分词全文检索 - SpringBoot 全文检索实战");
  75. //短信内容
  76. Map<Integer, String> contentMap = new HashMap<>();
  77. contentMap.put(1, "【阿里云】尊敬的vipsoft:您有2台云服务器ECS配置升级成功。如有CPU、内存变更或0Mbps带宽升级,您需要在ECS控制台手动重启云服务器后才能生效。");
  78. contentMap.put(2, "为更好地为您提供服务,温馨提醒:您本月有1次抽奖机会,赢取大额通用流量,月月抽月月领,点击掌厅链接 原URL:http://wap.js.10086.cn/Mq 快来试试你的运气吧,如本月已参与请忽略【江苏移动心级服务,让爱连接】");
  79. contentMap.put(3, "国家反诈中心提醒:公检法机关会当面向涉案人员出示证件或法律文书,绝对不会通过网络给当事人发送通缉令、拘留证、逮捕证等法律文书,并要求转账汇款。\n" +
  80. "切记:公检法机关不存在所谓“安全账户”,更不会让你远程转账汇款!");
  81. contentMap.put(4, "【江苏省公安厅、江苏省通信管理局】温馨提示:近期利用苹果手机iMessage消息冒充熟人、冒充领导换号、添加新微信号等诈骗形式多发。如有收到类似短信,请您谨慎判断,苹果手机用户如无需要可关闭iMessage功能,以免上当受骗。");
  82. contentMap.put(5, "多一点快乐,少一点懊恼,不管钞票有多少,只有天天开心就好,累了就睡觉,生活的甜苦,自己来调味。收到信息就要开心的笑");
  83. contentMap.put(6, "黄金周好运每天交,我把祝福来送到:愿您生活步步高,彩票期期中,股票每天涨,生意年年旺,祝您新年新景象!");
  84. contentMap.put(7, "【阿里云】当你手机响,那是我的问候;当你收到短信,那有我的心声;当你翻阅短信,那有我的牵挂;当你筹备关机时,记得我今天说过周末快乐!");
  85. contentMap.put(8, "我刚去了一趟银行,取了无数的幸福黄金好运珠宝平安翡翠成功股票健康基金。嘘!别作声,统统的送给你,因为我想提“钱”祝你国庆节快乐!");
  86. contentMap.put(9, "一个人的精彩,一个人的打拼,一个人的承载,一个人的舞蹈。光棍节送你祝福,不因你是光棍,只因你生活色彩。祝你:快乐打拼,生活出彩!");
  87. contentMap.put(10, "爆竹响激情燃放,雪花舞祥风欢畅,烟火腾期待闪亮,感动涌心中激荡,心情美春节冲浪,愿景好心中珍藏,祝与福短信奉上:祝您身体健康,兔年吉祥!");
  88. //模似7次 添加文章
  89. for (int i = 1; i <= 7; i++) {
  90. ArticleInfo article = new ArticleInfo();
  91. article.setId(String.valueOf(i));
  92. article.setTitle(titleMap.get(i));
  93. article.setAuthor("VipSoft");
  94. article.setSummary(introMap.get(i));
  95. article.setContent(contentMap.get(i));
  96. article.setCreateTime(new Date());
  97. //将article 保存到 MySQL --- 省略
  98. boolean flag = true; //保存数据到 MySQL 数据库成功
  99. if (flag) {
  100. //将需要查询的数据,赋给DTO,更新到 ES中
  101. ArticleDTO articleDTO = new ArticleDTO();
  102. BeanUtils.copyProperties(article, articleDTO);
  103. String json = JSON.toJSONStringWithDateFormat(articleDTO, "yyyy-MM-dd HH:mm:ss"); //FastJson 将日期格式化
  104. IndexResponse resp = elasticSearchUtil.createDoc(INDEX_NAME, articleDTO.getId(), json);
  105. logger.info(" {}", resp.getResult().toString());
  106. }
  107. }
  108. }
  109. /**
  110. * 第三步:模拟用户搜索,输入关键词“人”,带出和人有关的关键词
  111. */
  112. @Test
  113. void suggestTest() throws Exception {
  114. List<String> resp = elasticSearchUtil.suggest(INDEX_NAME, "title.suggest", "人", 2);
  115. //4. 获取到 _source 中的数据,并展示
  116. for (String hit : resp) {
  117. System.out.println(hit);
  118. }
  119. }
  120. /**
  121. * 第四步:模拟用户搜索,在搜索框中选择提示搜索关键词
  122. */
  123. @Test
  124. void earchTest() throws Exception {
  125. //Demo演示使用了 prefixQuery,实际应用时,会标题、摘要、内容,等多字段 Query组合查询
  126. SearchResponse resp = elasticSearchUtil.prefixQuery(INDEX_NAME,"title","人工智能");
  127. //4. 获取到 _source 中的数据,并展示
  128. for (SearchHit hit : resp.getHits().getHits()) {
  129. Map<String, Object> result = hit.getSourceAsMap();
  130. System.out.println(result);
  131. }
  132. }
  133. /**
  134. * 创建索引
  135. *
  136. * @param indexName
  137. * @throws Exception
  138. */
  139. void createIndex(String indexName) throws Exception {
  140. //准备索引的 settings
  141. Settings.Builder settings = Settings.builder()
  142. .put("number_of_shards", INDEX_NUMBER_OF_SHARDS) //分片数,可以使用常量
  143. .put("number_of_replicas", esProperties.getReplicasNum()); //是否集群,需要多少副本,在配置文件中配置
  144. //准备索引的结构 Mappings
  145. XContentBuilder mappings = JsonXContent.contentBuilder()
  146. .startObject()
  147. .startObject("properties")
  148. .startObject("id").field("type", "keyword").endObject()
  149. .startObject("title").field("type", "text").field("analyzer", "ik_max_word") //对该字段进行分词
  150. .startObject("fields").startObject("suggest").field("type", "completion").field("analyzer", "ik_max_word").endObject().endObject() //设置可以自动提示关键词
  151. .endObject()
  152. .startObject("summary").field("type", "text").field("analyzer", "ik_max_word").endObject() //对该字段进行分词
  153. .startObject("createDate").field("type", "date").field("format", "yyyy-MM-dd HH:mm:ss").endObject()
  154. .endObject()
  155. .endObject();
  156. CreateIndexResponse resp = elasticSearchUtil.createIndex(indexName, settings, mappings);
  157. //输出
  158. logger.info("CreateIndexResponse => {} ", resp.toString());
  159. }
  160. }

程序结构

image

Gitee 源码地址: https://gitee.com/VipSoft/VipBoot/tree/develop/vipsoft-elasticsearch

原文链接:https://www.cnblogs.com/vipsoft/p/17202732.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号