【无标题】

可以先看下列文章

目录

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)
  • 分词全文搜索

所用的技术如下:

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

实现流程

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

全文步骤

  • 启动项目时,通过 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(精准匹配)。
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-json"><span style="color:#ff0000">"title"</span>:{
    <span style="color:#ff0000">"type"</span>:<span style="color:#a31515">"text"</span>,
    <span style="color:#ff0000">"analyzer"</span>:<span style="color:#a31515">"ik_max_word"</span>,
    <span style="color:#ff0000">"search_analyzer"</span>:<span style="color:#a31515">"ik_smart"</span>
}
</code></span></span>

Field datatypes

<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-vbnet"><span style="color:#00b0e8">String:</span>
 <span style="color:#0000ff">text</span>: 一般用于全文检索。将当前的Field进行分词
 keyword: 当前 Field 不会被分词 
数值类型:
 <span style="color:#a31515">long</span>、 <span style="color:#a31515">integer</span>、 <span style="color:#a31515">short</span>、 <span style="color:#a31515">byte</span>、 <span style="color:#a31515">double</span>、 float、
 half_float:精度比float小一半
 scaled_float:根据一个<span style="color:#a31515">long</span>和scaled来表达一个浮点型, <span style="color:#a31515">long</span>=<span style="color:#880000">345</span>,scaled=<span style="color:#880000">100</span> => <span style="color:#880000">3.45</span>
时间类型:
 <span style="color:#a31515">date</span>:针对时间类型指定具体格式,ES 可以对日期格式,化为字符串存储,但是我们建议存储为毫秒值 <span style="color:#a31515">long</span>,节省空间
布尔类型:
 <span style="color:#a31515">boolean</span>:表达<span style="color:#a31515">true</span>和<span style="color:#a31515">false</span>
二进制类型:
 <span style="color:#0000ff">binary</span>:暂时支持Base64 encode <span style="color:#a31515">string</span>
范围类型(Range datatypes):
 long_range: 赋值时,无序指定具体的内容,只需要存储一个范围即可,指定gt,此,gte,lte
 integer_range:同上
 double_range:同上
 float_range: 同上
 date_range:同上
 ip_range: 同上。
经纬度类型:
 geo_point: 用来存储经纬度,结合定位的经纬度,来计算出距离
IP类型
 ip: 可以存付IPV4、IPV6
Completion 类型(Completion datatype):
  completion 提供自动补全建议
</code></span></span>

详细代码如下:

POM.XML

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

<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-css"><span style="color:#2b91af">[2023-03-10 10:57:41.793]</span> <span style="color:#2b91af">[main]</span> <span style="color:#2b91af">[WARN ]</span> o<span style="color:#880000">.s</span><span style="color:#880000">.b</span><span style="color:#880000">.w</span><span style="color:#880000">.s</span><span style="color:#880000">.c</span><span style="color:#880000">.AnnotationConfigServletWebServerApplicationContext</span> - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name <span style="color:#a31515">'initEsClient'</span> defined in class path resource [com/vipsoft/web/config/ElasticSearchConfig.class]: Post-processing of merged bean definition failed; nested exception is java<span style="color:#880000">.lang</span><span style="color:#880000">.IllegalStateException</span>: Failed to introspect Class [org.elasticsearch.client.RestHighLevelClient] from ClassLoader [sun.misc.Launcher$AppClassLoader@<span style="color:#880000">18</span>b4aac2]
<span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41</span>,<span style="color:#880000">793</span> |-INFO in c.q.l.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy - Elapsed period: Tue Mar <span style="color:#880000">07</span> <span style="color:#880000">14</span>:<span style="color:#880000">07</span>:<span style="color:#880000">19</span> CST <span style="color:#880000">2023</span>
<span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41</span>,<span style="color:#880000">793</span> |-INFO in c.q.l.co.rolling.helper.RenameUtil - Renaming file [.\logs\warn.log] to [.\logs\warn.log.<span style="color:#880000">2023</span>-<span style="color:#880000">03</span>-<span style="color:#880000">07</span>]
[<span style="color:#880000">2023</span>-<span style="color:#880000">03</span>-<span style="color:#880000">10</span> <span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41.797</span>] [main] [INFO ] org.apache.catalina.core.StandardService - Stopping service [Tomcat]
[<span style="color:#880000">2023</span>-<span style="color:#880000">03</span>-<span style="color:#880000">10</span> <span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41.812</span>] [main] [INFO ] o.s.b.a.l.ConditionEvaluationReportLoggingListener - 

Error starting ApplicationContext. To display the conditions report re-run your application with <span style="color:#a31515">'debug'</span> enabled.
[<span style="color:#880000">2023</span>-<span style="color:#880000">03</span>-<span style="color:#880000">10</span> <span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41.824</span>] [main] [ERROR] org.springframework.boot.SpringApplication - Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name <span style="color:#a31515">'initEsClient'</span> defined in class path resource [com/vipsoft/web/config/ElasticSearchConfig.class]: Post-processing of merged bean definition failed; nested exception is java<span style="color:#880000">.lang</span><span style="color:#880000">.IllegalStateException</span>: Failed to introspect Class [org.elasticsearch.client.RestHighLevelClient] from ClassLoader [sun.misc.Launcher$AppClassLoader@<span style="color:#880000">18</span>b4aac2]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.<span style="color:#0000ff">doCreateBean</span>(AbstractAutowireCapableBeanFactory.java:<span style="color:#880000">572</span>)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.<span style="color:#0000ff">createBean</span>(AbstractAutowireCapableBeanFactory.java:<span style="color:#880000">517</span>)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$<span style="color:#880000">0</span>(AbstractBeanFactory.java:<span style="color:#880000">323</span>)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.<span style="color:#0000ff">getSingleton</span>(DefaultSingletonBeanRegistry.java:<span style="color:#880000">226</span>)
	at org.springframework.beans.factory.support.AbstractBeanFactory.<span style="color:#0000ff">doGetBean</span>(AbstractBeanFactory.java:<span style="color:#880000">321</span>)
	at org.springframework.beans.factory.support.AbstractBeanFactory.<span style="color:#0000ff">getBean</span>(AbstractBeanFactory.java:<span style="color:#880000">202</span>)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.<span style="color:#0000ff">preInstantiateSingletons</span>(DefaultListableBeanFactory.java:<span style="color:#880000">893</span>)
	at org.springframework.context.support.AbstractApplicationContext.<span style="color:#0000ff">finishBeanFactoryInitialization</span>(AbstractApplicationContext.java:<span style="color:#880000">879</span>)
	at org.springframework.context.support.AbstractApplicationContext.<span style="color:#0000ff">refresh</span>(AbstractApplicationContext.java:<span style="color:#880000">551</span>)
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.<span style="color:#0000ff">refresh</span>(ServletWebServerApplicationContext.java:<span style="color:#880000">141</span>)
	at org.springframework.boot.SpringApplication.<span style="color:#0000ff">refresh</span>(SpringApplication.java:<span style="color:#880000">747</span>)
	at org.springframework.boot.SpringApplication.<span style="color:#0000ff">refreshContext</span>(SpringApplication.java:<span style="color:#880000">397</span>)
	at org.springframework.boot.SpringApplication.<span style="color:#0000ff">run</span>(SpringApplication.java:<span style="color:#880000">315</span>)
	at org.springframework.boot.SpringApplication.<span style="color:#0000ff">run</span>(SpringApplication.java:<span style="color:#880000">1226</span>)
	at org.springframework.boot.SpringApplication.<span style="color:#0000ff">run</span>(SpringApplication.java:<span style="color:#880000">1215</span>)
	at com.vipsoft.web.ESApplication.<span style="color:#0000ff">main</span>(ESApplication.java:<span style="color:#880000">10</span>)
Caused by: java.lang.IllegalStateException: Failed to introspect Class [org.elasticsearch.client.RestHighLevelClient] from ClassLoader [sun.misc.Launcher$AppClassLoader@<span style="color:#880000">18</span>b4aac2]
	at org.springframework.util.ReflectionUtils.<span style="color:#0000ff">getDeclaredMethods</span>(ReflectionUtils.java:<span style="color:#880000">481</span>)
	at org.springframework.util.ReflectionUtils.<span style="color:#0000ff">doWithLocalMethods</span>(ReflectionUtils.java:<span style="color:#880000">321</span>)
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.<span style="color:#0000ff">buildLifecycleMetadata</span>(InitDestroyAnnotationBeanPostProcessor.java:<span style="color:#880000">232</span>)
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.<span style="color:#0000ff">findLifecycleMetadata</span>(InitDestroyAnnotationBeanPostProcessor.java:<span style="color:#880000">210</span>)
	at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.<span style="color:#0000ff">postProcessMergedBeanDefinition</span>(InitDestroyAnnotationBeanPostProcessor.java:<span style="color:#880000">149</span>)
	at org.springframework.context.annotation.CommonAnnotationBeanPostProcessor.<span style="color:#0000ff">postProcessMergedBeanDefinition</span>(CommonAnnotationBeanPostProcessor.java:<span style="color:#880000">294</span>)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.<span style="color:#0000ff">applyMergedBeanDefinitionPostProcessors</span>(AbstractAutowireCapableBeanFactory.java:<span style="color:#880000">1094</span>)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.<span style="color:#0000ff">doCreateBean</span>(AbstractAutowireCapableBeanFactory.java:<span style="color:#880000">569</span>)
	... <span style="color:#880000">15</span> common frames omitted
Caused by: java.lang.NoClassDefFoundError: org/elasticsearch/client/Cancellable
	at java.lang.Class.<span style="color:#0000ff">getDeclaredMethods0</span>(Native Method)
	at java.lang.Class.<span style="color:#0000ff">privateGetDeclaredMethods</span>(Class.java:<span style="color:#880000">2701</span>)
	at java.lang.Class.<span style="color:#0000ff">getDeclaredMethods</span>(Class.java:<span style="color:#880000">1975</span>)
	at org.springframework.util.ReflectionUtils.<span style="color:#0000ff">getDeclaredMethods</span>(ReflectionUtils.java:<span style="color:#880000">463</span>)
	... <span style="color:#880000">22</span> common frames omitted
Caused by: java.lang.ClassNotFoundException: org.elasticsearch.client.Cancellable
	at java.net.URLClassLoader.<span style="color:#0000ff">findClass</span>(URLClassLoader.java:<span style="color:#880000">382</span>)
	at java.lang.ClassLoader.<span style="color:#0000ff">loadClass</span>(ClassLoader.java:<span style="color:#880000">418</span>)
	at sun.misc.Launcher$AppClassLoader.<span style="color:#0000ff">loadClass</span>(Launcher.java:<span style="color:#880000">355</span>)
	at java.lang.ClassLoader.<span style="color:#0000ff">loadClass</span>(ClassLoader.java:<span style="color:#880000">351</span>)
	... <span style="color:#880000">26</span> common frames omitted
<span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41</span>,<span style="color:#880000">825</span> |-INFO in c.q.l.core.rolling.DefaultTimeBasedFileNamingAndTriggeringPolicy - Elapsed period: Tue Mar <span style="color:#880000">07</span> <span style="color:#880000">14</span>:<span style="color:#880000">07</span>:<span style="color:#880000">19</span> CST <span style="color:#880000">2023</span>
<span style="color:#880000">10</span>:<span style="color:#880000">57</span>:<span style="color:#880000">41</span>,<span style="color:#880000">825</span> |-INFO in c.q.l.co.rolling.helper.RenameUtil - Renaming file [.\logs\error.log] to [.\logs\error.log.<span style="color:#880000">2023</span>-<span style="color:#880000">03</span>-<span style="color:#880000">07</span>]
Disconnected from the target VM, address: <span style="color:#a31515">'127.0.0.1:1810'</span>, transport: <span style="color:#a31515">'socket'</span>
</code></span></span>
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-xml"><span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.elasticsearch<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>elasticsearch<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>7.10.1<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>
<span style="color:#008000"><!--如果不引用,会报错--></span>
<span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.elasticsearch.client<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>elasticsearch-rest-client<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>7.10.1<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>
<span style="color:#0000ff"><<span style="color:#0000ff">dependency</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">groupId</span>></span>org.elasticsearch.client<span style="color:#0000ff"></<span style="color:#0000ff">groupId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">artifactId</span>></span>elasticsearch-rest-high-level-client<span style="color:#0000ff"></<span style="color:#0000ff">artifactId</span>></span>
    <span style="color:#0000ff"><<span style="color:#0000ff">version</span>></span>7.10.1<span style="color:#0000ff"></<span style="color:#0000ff">version</span>></span>
<span style="color:#0000ff"></<span style="color:#0000ff">dependency</span>></span>
</code></span></span>

点击查看全部POM代码

 

详细程序

application.yml
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-yaml language-yml"><span style="color:#ff0000">server:</span>
  <span style="color:#ff0000">port:</span> <span style="color:#880000">8088</span>
  <span style="color:#ff0000">application:</span>
    <span style="color:#ff0000">name:</span> <span style="color:#a31515">ElasticSearch</span> <span style="color:#a31515">Demo</span>
<span style="color:#ff0000">Spring:</span>
  <span style="color:#ff0000">es:</span>
    <span style="color:#ff0000">cluster-name:</span> <span style="color:#a31515">VipSoft</span>
    <span style="color:#ff0000">replicas-num:</span> <span style="color:#880000">1</span>
    <span style="color:#ff0000">nodes:</span> <span style="color:#880000">172.16</span><span style="color:#880000">.3</span><span style="color:#880000">.88</span><span style="color:#a31515">:9200</span>
    <span style="color:#ff0000">keep-alive:</span> <span style="color:#880000">300</span> <span style="color:#008000"># 保持client 每 300秒 = 5分钟 发送数据保持http存活</span>

</code></span></span>
ElasticSearchConfig.java
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.vipsoft.web.config;

<span style="color:#0000ff">import</span> org.apache.http.HttpHost;
<span style="color:#0000ff">import</span> org.elasticsearch.client.RestClient;
<span style="color:#0000ff">import</span> org.elasticsearch.client.RestHighLevelClient;
<span style="color:#0000ff">import</span> org.slf4j.Logger;
<span style="color:#0000ff">import</span> org.slf4j.LoggerFactory;
<span style="color:#0000ff">import</span> org.springframework.boot.context.properties.ConfigurationProperties;
<span style="color:#0000ff">import</span> org.springframework.context.annotation.Bean;
<span style="color:#0000ff">import</span> org.springframework.context.annotation.Configuration;

<span style="color:#0000ff">import</span> java.time.Duration;
<span style="color:#0000ff">import</span> java.util.Arrays;


<span style="color:#2b91af">@Configuration</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ElasticSearchConfig</span> {

    <span style="color:#a31515">Logger</span> <span style="color:#008000">logger</span> <span style="color:#ab5656">=</span> LoggerFactory.getLogger(<span style="color:#0000ff">this</span>.getClass());

    <span style="color:#008000">/**
     * 将 application.yml 中的配置,映射到 EsProperties 中,
     * 并 执行 EsProperties.init() 方法
     * <span style="color:#808080">@return</span>
     */</span>
    <span style="color:#2b91af">@Bean(initMethod = "init")</span>
    <span style="color:#2b91af">@ConfigurationProperties(prefix = "spring.es")</span>
    <span style="color:#0000ff">public</span> EsProperties <span style="color:#a31515">esProperties</span>() {
        <span style="color:#0000ff">return</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">EsProperties</span>();
    }

    <span style="color:#2b91af">@Bean</span>
    <span style="color:#0000ff">public</span> RestHighLevelClient <span style="color:#a31515">initEsClient</span>(EsProperties esProperties) {
        String[] nodes = esProperties.getNodes().split(EsProperties.SPLIT_NODES);
        HttpHost[] httpHosts = Arrays.stream(nodes).map(HttpHost::create).toArray(HttpHost[]::<span style="color:#0000ff">new</span>);
        <span style="color:#a31515">RestHighLevelClient</span> <span style="color:#008000">client</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">RestHighLevelClient</span>(RestClient.builder(httpHosts).setHttpClientConfigCallback(
                requestConfig -> requestConfig.setKeepAliveStrategy((response, context) -> Duration.ofSeconds(esProperties.getKeepAlive()).toMillis())));
        logger.info(<span style="color:#a31515">"初始化 es client nodes: {}"</span>, Arrays.toString(httpHosts));
        <span style="color:#0000ff">return</span> client;
    }
}

</code></span></span>
EsProperties.java
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.vipsoft.web.config;

<span style="color:#0000ff">import</span> org.slf4j.Logger;
<span style="color:#0000ff">import</span> org.slf4j.LoggerFactory;


<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">EsProperties</span> {

    <span style="color:#a31515">Logger</span> <span style="color:#008000">logger</span> <span style="color:#ab5656">=</span> LoggerFactory.getLogger(<span style="color:#0000ff">this</span>.getClass());

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">static</span> <span style="color:#0000ff">final</span> <span style="color:#a31515">String</span> <span style="color:#008000">SPLIT_NODES</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">";"</span>;

    <span style="color:#008000">/**
     * 集群名称
     */</span>
    <span style="color:#0000ff">private</span> String clusterName;

    <span style="color:#008000">/**
     * 集群副本数
     */</span>
    <span style="color:#0000ff">private</span> Integer replicasNum;

    <span style="color:#008000">/**
     * 链接时长
     */</span>
    <span style="color:#0000ff">private</span> Integer keepAlive;

    <span style="color:#008000">/**
     * 节点列表
     */</span>
    <span style="color:#0000ff">private</span> String nodes;

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">init</span>() {
        String[] nodes = <span style="color:#0000ff">this</span>.getNodes().split(EsProperties.SPLIT_NODES);
        <span style="color:#0000ff">if</span> (<span style="color:#0000ff">this</span>.getReplicasNum() == <span style="color:#a31515">null</span>) {
            <span style="color:#a31515">int</span> <span style="color:#008000">replicasNum</span> <span style="color:#ab5656">=</span> nodes.length - <span style="color:#880000">1</span>;
            <span style="color:#0000ff">this</span>.setReplicasNum(replicasNum);
            logger.info(<span style="color:#a31515">"初始化 EsProperties 未指定副本数,设置默认副本数(节点数-1) replicasNum: {}"</span>, replicasNum);
        } <span style="color:#0000ff">else</span> {
            logger.info(<span style="color:#a31515">"初始化 EsProperties 设置副本数 replicasNum: {}"</span>, <span style="color:#0000ff">this</span>.getReplicasNum());
        }
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getClusterName</span>() {
        <span style="color:#0000ff">return</span> clusterName;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setClusterName</span>(String clusterName) {
        <span style="color:#0000ff">this</span>.clusterName = clusterName;
    }

    <span style="color:#0000ff">public</span> Integer <span style="color:#a31515">getReplicasNum</span>() {
        <span style="color:#0000ff">return</span> replicasNum;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setReplicasNum</span>(Integer replicasNum) {
        <span style="color:#0000ff">this</span>.replicasNum = replicasNum;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getNodes</span>() {
        <span style="color:#0000ff">return</span> nodes;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setNodes</span>(String nodes) {
        <span style="color:#0000ff">this</span>.nodes = nodes;
    }

    <span style="color:#0000ff">public</span> Integer <span style="color:#a31515">getKeepAlive</span>() {
        <span style="color:#0000ff">return</span> keepAlive;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setKeepAlive</span>(Integer keepAlive) {
        <span style="color:#0000ff">this</span>.keepAlive = keepAlive;
    }
}

</code></span></span>
ElasticSearchUtil.java
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.vipsoft.web.utils;

<span style="color:#0000ff">import</span> org.elasticsearch.action.bulk.BulkRequest;
<span style="color:#0000ff">import</span> org.elasticsearch.action.bulk.BulkResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.action.index.IndexRequest;
<span style="color:#0000ff">import</span> org.elasticsearch.action.index.IndexResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.action.search.SearchRequest;
<span style="color:#0000ff">import</span> org.elasticsearch.action.search.SearchResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.client.RequestOptions;
<span style="color:#0000ff">import</span> org.elasticsearch.client.RestHighLevelClient;
<span style="color:#0000ff">import</span> org.elasticsearch.client.indices.CreateIndexRequest;
<span style="color:#0000ff">import</span> org.elasticsearch.client.indices.CreateIndexResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.client.indices.GetIndexRequest;
<span style="color:#0000ff">import</span> org.elasticsearch.common.settings.Settings;
<span style="color:#0000ff">import</span> org.elasticsearch.common.xcontent.XContentBuilder;
<span style="color:#0000ff">import</span> org.elasticsearch.common.xcontent.XContentType;
<span style="color:#0000ff">import</span> org.elasticsearch.index.query.QueryBuilders;
<span style="color:#0000ff">import</span> org.elasticsearch.search.builder.SearchSourceBuilder;
<span style="color:#0000ff">import</span> org.elasticsearch.search.sort.ScoreSortBuilder;
<span style="color:#0000ff">import</span> org.elasticsearch.search.sort.SortOrder;
<span style="color:#0000ff">import</span> org.elasticsearch.search.suggest.SuggestBuilder;
<span style="color:#0000ff">import</span> org.elasticsearch.search.suggest.completion.CompletionSuggestion;
<span style="color:#0000ff">import</span> org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
<span style="color:#0000ff">import</span> org.springframework.beans.factory.annotation.Autowired;
<span style="color:#0000ff">import</span> org.springframework.stereotype.Service;
<span style="color:#0000ff">import</span> org.springframework.util.CollectionUtils;

<span style="color:#0000ff">import</span> java.util.ArrayList;
<span style="color:#0000ff">import</span> java.util.List;
<span style="color:#0000ff">import</span> java.util.Map;

<span style="color:#2b91af">@Service</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ElasticSearchUtil</span> {

    <span style="color:#008000">/**
     * ES 服务注入
     */</span>
    <span style="color:#2b91af">@Autowired</span>
    <span style="color:#0000ff">private</span> RestHighLevelClient esClient;

    <span style="color:#008000">/**
     * 判断索引是否存在
     */</span>
    <span style="color:#0000ff">public</span> Boolean <span style="color:#a31515">indexExists</span>(String indexName) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#a31515">GetIndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">GetIndexRequest</span>(indexName);
        <span style="color:#0000ff">return</span> esClient.indices().exists(request, RequestOptions.DEFAULT);
    }

    <span style="color:#008000">/**
     * 创建 ES 索引
     */</span>
    <span style="color:#0000ff">public</span> CreateIndexResponse <span style="color:#a31515">createIndex</span>(String indexName, Settings.Builder settings, XContentBuilder mappings) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">//将 Settings 和 Mappings 封装到一个Request 对象中</span>
        <span style="color:#a31515">CreateIndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">CreateIndexRequest</span>(indexName)
                .settings(settings)
                .mapping(mappings);

        <span style="color:#008000">//通过 client 对象去连接ES并执行创建索引</span>
        <span style="color:#0000ff">return</span> esClient.indices().create(request, RequestOptions.DEFAULT);
    }

    <span style="color:#008000">/**
     * 批量创建 ES 文档
     */</span>
    <span style="color:#0000ff">public</span> IndexResponse <span style="color:#a31515">createDoc</span>(String indexName, String id, String json) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">//准备一个Request对象</span>
        <span style="color:#a31515">IndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">IndexRequest</span>(indexName);
        request.id(id); <span style="color:#008000">//手动指定ID</span>
        request.source(json, XContentType.JSON);
        <span style="color:#008000">//request.opType(DocWriteRequest.OpType.INDEX); 默认使用 OpType.INDEX,如果 id 重复,会进行  覆盖更新, resp.getResult().toString() 返回 UPDATE</span>
        <span style="color:#008000">//request.opType(DocWriteRequest.OpType.CREATE); 如果ID重复,会报异常 =>  document already exists</span>

        <span style="color:#008000">//通过 Client 对象执行添加</span>
        <span style="color:#0000ff">return</span> esClient.index(request, RequestOptions.DEFAULT);
    }

    <span style="color:#008000">/**
     * 批量创建 ES 文档
     *
     * <span style="color:#808080">@param</span> jsonMap Key = id,Value =  json
     */</span>
    <span style="color:#0000ff">public</span> BulkResponse <span style="color:#a31515">batchCreateDoc</span>(String indexName, Map<String, String> jsonMap) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">//准备一个Request对象</span>
        <span style="color:#a31515">BulkRequest</span> <span style="color:#008000">bulkRequest</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">BulkRequest</span>();
        <span style="color:#0000ff">for</span> (String id : jsonMap.keySet()) {
            <span style="color:#a31515">IndexRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">IndexRequest</span>(indexName)
                    .id(id) <span style="color:#008000">//手动指定ID</span>
                    .source(jsonMap.get(id), XContentType.JSON);
            bulkRequest.add(request);
        }

        <span style="color:#008000">//通过 Client 对象执行添加</span>
        <span style="color:#0000ff">return</span> esClient.bulk(bulkRequest, RequestOptions.DEFAULT);
    }

    <span style="color:#008000">/**
     * 自动补全 根据用户的输入联想到可能的词或者短语
     *
     * <span style="color:#808080">@param</span> indexName 索引名称
     * <span style="color:#808080">@param</span> field     搜索条件字段
     * <span style="color:#808080">@param</span> keywords  搜索关键字
     * <span style="color:#808080">@param</span> size      匹配数量
     * <span style="color:#808080">@return</span>
     * <span style="color:#808080">@throws</span> Exception
     */</span>
    <span style="color:#0000ff">public</span> List<String> <span style="color:#a31515">suggest</span>(String indexName, String field, String keywords, <span style="color:#a31515">int</span> size) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">//定义返回</span>
        List<String> suggestList = <span style="color:#0000ff">new</span> <span style="color:#a31515">ArrayList</span><>();
        <span style="color:#008000">//构建查询请求</span>
        <span style="color:#a31515">SearchRequest</span> <span style="color:#008000">searchRequest</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">SearchRequest</span>(indexName);
        <span style="color:#008000">//通过查询构建器定义评分排序</span>
        <span style="color:#a31515">SearchSourceBuilder</span> <span style="color:#008000">searchSourceBuilder</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">SearchSourceBuilder</span>();
        searchSourceBuilder.sort(<span style="color:#0000ff">new</span> <span style="color:#a31515">ScoreSortBuilder</span>().order(SortOrder.DESC));
        <span style="color:#008000">//构造搜索建议语句,搜索条件字段</span>
        <span style="color:#a31515">CompletionSuggestionBuilder</span> <span style="color:#008000">completionSuggestionBuilder</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">CompletionSuggestionBuilder</span>(field);
        <span style="color:#008000">//搜索关键字</span>
        completionSuggestionBuilder.prefix(keywords);
        <span style="color:#008000">//去除重复</span>
        completionSuggestionBuilder.skipDuplicates(<span style="color:#a31515">true</span>);
        <span style="color:#008000">//匹配数量</span>
        completionSuggestionBuilder.size(size);
        searchSourceBuilder.suggest(<span style="color:#0000ff">new</span> <span style="color:#a31515">SuggestBuilder</span>().addSuggestion(<span style="color:#a31515">"article-suggest"</span>, completionSuggestionBuilder));
        <span style="color:#008000">//article-suggest为返回的字段,所有返回将在article-suggest里面,可写死,sort按照评分排序</span>
        searchRequest.source(searchSourceBuilder);
        <span style="color:#008000">//定义查找响应</span>
        <span style="color:#a31515">SearchResponse</span> <span style="color:#008000">suggestResponse</span> <span style="color:#ab5656">=</span> esClient.search(searchRequest, RequestOptions.DEFAULT);
        <span style="color:#008000">//定义完成建议对象</span>
        <span style="color:#a31515">CompletionSuggestion</span> <span style="color:#008000">completionSuggestion</span> <span style="color:#ab5656">=</span> suggestResponse.getSuggest().getSuggestion(<span style="color:#a31515">"article-suggest"</span>);
        List<CompletionSuggestion.Entry.Option> optionsList = completionSuggestion.getEntries().get(<span style="color:#880000">0</span>).getOptions();
        <span style="color:#008000">//从optionsList取出结果</span>
        <span style="color:#0000ff">if</span> (!CollectionUtils.isEmpty(optionsList)) {
            optionsList.forEach(item -> suggestList.add(item.getText().toString()));
        }
        <span style="color:#0000ff">return</span> suggestList;
    }

    <span style="color:#008000">/**
     * 前缀查询,可以通过一个关键字去指定一个Field的前缀,从而查询到指定的文档
     */</span>
    <span style="color:#0000ff">public</span> SearchResponse <span style="color:#a31515">prefixQuery</span>(String indexName, String searchField, String searchKeyword) <span style="color:#0000ff">throws</span> Exception {

        <span style="color:#008000">//创建Request对象</span>
        <span style="color:#a31515">SearchRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">SearchRequest</span>(indexName);

        <span style="color:#008000">//XX开头的关键词查询</span>
        <span style="color:#a31515">SearchSourceBuilder</span> <span style="color:#008000">builder</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">SearchSourceBuilder</span>();
        builder.query(QueryBuilders.prefixQuery(searchField, searchKeyword));
        request.source(builder);

        <span style="color:#008000">//执行查询</span>
        <span style="color:#0000ff">return</span> esClient.search(request, RequestOptions.DEFAULT);
    }
 
    <span style="color:#008000">/**
    * 通过 QueryBuilder 构建多字段匹配如:QueryBuilders.multiMatchQuery("人工智能","title","content")
    * multi_match => https://www.cnblogs.com/vipsoft/p/17164544.html
    */</span>
    <span style="color:#0000ff">public</span> SearchResponse <span style="color:#a31515">search</span>(String indexName, QueryBuilder query, <span style="color:#a31515">int</span> currPage, <span style="color:#a31515">int</span> pageSize) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#a31515">SearchRequest</span> <span style="color:#008000">request</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">SearchRequest</span>(indexName);
        <span style="color:#a31515">SearchSourceBuilder</span> <span style="color:#008000">builder</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">SearchSourceBuilder</span>();
        <span style="color:#a31515">int</span> <span style="color:#008000">start</span> <span style="color:#ab5656">=</span> (currPage - <span style="color:#880000">1</span>) * pageSize;
        builder.from(start);
        builder.size(pageSize);
        builder.query(query);
        request.source(builder);
        <span style="color:#0000ff">return</span> esClient.search(request, RequestOptions.DEFAULT);
    }
    <span style="color:#008000">//TODO 其它功能</span>
}


</code></span></span>
ArticleDTO.java
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.vipsoft.web.dto;

<span style="color:#008000">/**
 * 用于和 ES 交互的文章实体
 * 只需要匹配 标题、简介的文字,
 */</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ArticleDTO</span> {

    <span style="color:#008000">/**
     * ID 用于关联详细的数据库中的文章信息
     */</span>
    <span style="color:#0000ff">private</span> String id;

    <span style="color:#008000">/**
     * 标题 -- 用于查询
     */</span>
    <span style="color:#0000ff">private</span> String title;
    <span style="color:#008000">/**
     * 简介 -- 用于查询的结果,列表显示(不光显示标题,还要显示摘要)
     */</span>
    <span style="color:#0000ff">private</span> String summary;

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getId</span>() {
        <span style="color:#0000ff">return</span> id;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setId</span>(String id) {
        <span style="color:#0000ff">this</span>.id = id;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getTitle</span>() {
        <span style="color:#0000ff">return</span> title;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setTitle</span>(String title) {
        <span style="color:#0000ff">this</span>.title = title;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getSummary</span>() {
        <span style="color:#0000ff">return</span> summary;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setSummary</span>(String summary) {
        <span style="color:#0000ff">this</span>.summary = summary;
    }
}

</code></span></span>
ArticleInfo.java
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.vipsoft.web.entity;

<span style="color:#0000ff">import</span> java.util.Date;

<span style="color:#008000">/**
 * 用于和数据库交互的文章实体
 */</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">ArticleInfo</span> {

    <span style="color:#008000">/**
     * ID
     */</span>
    <span style="color:#0000ff">private</span> String id;

    <span style="color:#008000">/**
     * 标题
     */</span>
    <span style="color:#0000ff">private</span> String title;

    <span style="color:#008000">/**
     * 作者
     */</span>
    <span style="color:#0000ff">private</span> String author;

    <span style="color:#008000">/**
     * 简介
     */</span>
    <span style="color:#0000ff">private</span> String summary;

    <span style="color:#008000">/**
     * 内容
     */</span>
    <span style="color:#0000ff">private</span> String content;

    <span style="color:#008000">/**
     * 创建时间
     */</span>
    <span style="color:#0000ff">private</span> Date createTime;

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getId</span>() {
        <span style="color:#0000ff">return</span> id;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setId</span>(String id) {
        <span style="color:#0000ff">this</span>.id = id;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getTitle</span>() {
        <span style="color:#0000ff">return</span> title;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setTitle</span>(String title) {
        <span style="color:#0000ff">this</span>.title = title;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getAuthor</span>() {
        <span style="color:#0000ff">return</span> author;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setAuthor</span>(String author) {
        <span style="color:#0000ff">this</span>.author = author;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getSummary</span>() {
        <span style="color:#0000ff">return</span> summary;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setSummary</span>(String summary) {
        <span style="color:#0000ff">this</span>.summary = summary;
    }

    <span style="color:#0000ff">public</span> String <span style="color:#a31515">getContent</span>() {
        <span style="color:#0000ff">return</span> content;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setContent</span>(String content) {
        <span style="color:#0000ff">this</span>.content = content;
    }

    <span style="color:#0000ff">public</span> Date <span style="color:#a31515">getCreateTime</span>() {
        <span style="color:#0000ff">return</span> createTime;
    }

    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">setCreateTime</span>(Date createTime) {
        <span style="color:#0000ff">this</span>.createTime = createTime;
    }
}

</code></span></span>
EsIndexTest.java
<span style="color:#333333"><span style="background-color:#ffffff"><code class="language-java"><span style="color:#0000ff">package</span> com.vipsoft.web;


<span style="color:#0000ff">import</span> com.alibaba.fastjson.JSON;
<span style="color:#0000ff">import</span> com.vipsoft.web.config.EsProperties;
<span style="color:#0000ff">import</span> com.vipsoft.web.dto.ArticleDTO;
<span style="color:#0000ff">import</span> com.vipsoft.web.entity.ArticleInfo;
<span style="color:#0000ff">import</span> com.vipsoft.web.utils.ElasticSearchUtil;

<span style="color:#0000ff">import</span> org.elasticsearch.action.index.IndexResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.action.search.SearchResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.client.indices.CreateIndexResponse;
<span style="color:#0000ff">import</span> org.elasticsearch.common.settings.Settings;
<span style="color:#0000ff">import</span> org.elasticsearch.common.xcontent.XContentBuilder;
<span style="color:#0000ff">import</span> org.elasticsearch.common.xcontent.json.JsonXContent;
<span style="color:#0000ff">import</span> org.elasticsearch.search.SearchHit;
<span style="color:#0000ff">import</span> org.junit.jupiter.api.Test;
<span style="color:#0000ff">import</span> org.slf4j.Logger;
<span style="color:#0000ff">import</span> org.slf4j.LoggerFactory;
<span style="color:#0000ff">import</span> org.springframework.beans.BeanUtils;
<span style="color:#0000ff">import</span> org.springframework.beans.factory.annotation.Autowired;
<span style="color:#0000ff">import</span> org.springframework.boot.test.context.SpringBootTest;

<span style="color:#0000ff">import</span> java.util.*;

<span style="color:#008000">/**
 * 单元测试
 */</span>
<span style="color:#2b91af">@SpringBootTest</span>
<span style="color:#0000ff">public</span> <span style="color:#0000ff">class</span> <span style="color:#a31515">EsIndexTest</span> {
    <span style="color:#a31515">Logger</span> <span style="color:#008000">logger</span> <span style="color:#ab5656">=</span> LoggerFactory.getLogger(<span style="color:#0000ff">this</span>.getClass());

    <span style="color:#2b91af">@Autowired</span>
    <span style="color:#0000ff">private</span> ElasticSearchUtil elasticSearchUtil;
    <span style="color:#2b91af">@Autowired</span>
    <span style="color:#0000ff">private</span> EsProperties esProperties;

    <span style="color:#a31515">int</span> <span style="color:#008000">INDEX_NUMBER_OF_SHARDS</span> <span style="color:#ab5656">=</span> <span style="color:#880000">5</span>;

    <span style="color:#a31515">String</span> <span style="color:#008000">INDEX_NAME</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">"article-index"</span>;

    <span style="color:#008000">/**
     * 第一步:系统初始化,创建索引
     * 如果索引不存在,创建,输出
     */</span>
    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">createIndexTest</span>() <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#a31515">boolean</span> <span style="color:#008000">indexExists</span> <span style="color:#ab5656">=</span> elasticSearchUtil.indexExists(INDEX_NAME);
        <span style="color:#0000ff">if</span> (!indexExists) {
            <span style="color:#0000ff">try</span> {
                createIndex(INDEX_NAME);
                logger.info(<span style="color:#a31515">"索引【{}】,创建成功"</span>, INDEX_NAME);

                <span style="color:#008000">//测试效果 可再次查询验证。</span>
                indexExists = elasticSearchUtil.indexExists(INDEX_NAME);
                logger.info(<span style="color:#a31515">"索引【{}】, {}"</span>, INDEX_NAME, indexExists ? <span style="color:#a31515">"验证存在"</span> : <span style="color:#a31515">"验证不存在"</span>);
            } <span style="color:#0000ff">catch</span> (Exception e) {
                logger.error(e.getMessage(), e);
            }
        } <span style="color:#0000ff">else</span> {
            logger.info(<span style="color:#a31515">"索引【{}】已存在,无需创建"</span>, INDEX_NAME);
        }
    }

    <span style="color:#008000">/**
     * 第二步:模拟后台管理员,在添加文章时,将要检查的字段内容,同步到ES中
     */</span>
    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">addArticleTest</span>() <span style="color:#0000ff">throws</span> Exception {
        Map<Integer, String> titleMap = <span style="color:#0000ff">new</span> <span style="color:#a31515">HashMap</span><>();
        titleMap.put(<span style="color:#880000">1</span>, <span style="color:#a31515">"人工智能技术"</span>);
        titleMap.put(<span style="color:#880000">2</span>, <span style="color:#a31515">"人工智能软件 Chart GTP"</span>);
        titleMap.put(<span style="color:#880000">3</span>, <span style="color:#a31515">"Restful基本操作"</span>);
        titleMap.put(<span style="color:#880000">4</span>, <span style="color:#a31515">"Java SpringBoot ES 索引操作"</span>);
        titleMap.put(<span style="color:#880000">5</span>, <span style="color:#a31515">"Java SpringBoot ES 文档操作"</span>);
        titleMap.put(<span style="color:#880000">6</span>, <span style="color:#a31515">"人工呼吸"</span>);
        titleMap.put(<span style="color:#880000">7</span>, <span style="color:#a31515">"SpringBoot 全文检索实战"</span>);

        Map<Integer, String> introMap = <span style="color:#0000ff">new</span> <span style="color:#a31515">HashMap</span><>();
        introMap.put(<span style="color:#880000">1</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - 概述"</span>);
        introMap.put(<span style="color:#880000">2</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - ES、Kibana、IK安装"</span>);
        introMap.put(<span style="color:#880000">3</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - Restful基本操作"</span>);
        introMap.put(<span style="color:#880000">4</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - Java SpringBoot ES 索引操作"</span>);
        introMap.put(<span style="color:#880000">5</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - Java SpringBoot ES 文档操作"</span>);
        introMap.put(<span style="color:#880000">6</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - 经纬度查询"</span>);
        introMap.put(<span style="color:#880000">7</span>, <span style="color:#a31515">"ElasticSearch 实现分词全文检索 - SpringBoot 全文检索实战"</span>);

        <span style="color:#008000">//短信内容</span>
        Map<Integer, String> contentMap = <span style="color:#0000ff">new</span> <span style="color:#a31515">HashMap</span><>();
        contentMap.put(<span style="color:#880000">1</span>, <span style="color:#a31515">"【阿里云】尊敬的vipsoft:您有2台云服务器ECS配置升级成功。如有CPU、内存变更或0Mbps带宽升级,您需要在ECS控制台手动重启云服务器后才能生效。"</span>);
        contentMap.put(<span style="color:#880000">2</span>, <span style="color:#a31515">"为更好地为您提供服务,温馨提醒:您本月有1次抽奖机会,赢取大额通用流量,月月抽月月领,点击掌厅链接 原URL:http://wap.js.10086.cn/Mq 快来试试你的运气吧,如本月已参与请忽略【江苏移动心级服务,让爱连接】"</span>);
        contentMap.put(<span style="color:#880000">3</span>, <span style="color:#a31515">"国家反诈中心提醒:公检法机关会当面向涉案人员出示证件或法律文书,绝对不会通过网络给当事人发送通缉令、拘留证、逮捕证等法律文书,并要求转账汇款。\n"</span> +
                <span style="color:#a31515">"切记:公检法机关不存在所谓“安全账户”,更不会让你远程转账汇款!"</span>);
        contentMap.put(<span style="color:#880000">4</span>, <span style="color:#a31515">"【江苏省公安厅、江苏省通信管理局】温馨提示:近期利用苹果手机iMessage消息冒充熟人、冒充领导换号、添加新微信号等诈骗形式多发。如有收到类似短信,请您谨慎判断,苹果手机用户如无需要可关闭iMessage功能,以免上当受骗。"</span>);
        contentMap.put(<span style="color:#880000">5</span>, <span style="color:#a31515">"多一点快乐,少一点懊恼,不管钞票有多少,只有天天开心就好,累了就睡觉,生活的甜苦,自己来调味。收到信息就要开心的笑"</span>);
        contentMap.put(<span style="color:#880000">6</span>, <span style="color:#a31515">"黄金周好运每天交,我把祝福来送到:愿您生活步步高,彩票期期中,股票每天涨,生意年年旺,祝您新年新景象!"</span>);
        contentMap.put(<span style="color:#880000">7</span>, <span style="color:#a31515">"【阿里云】当你手机响,那是我的问候;当你收到短信,那有我的心声;当你翻阅短信,那有我的牵挂;当你筹备关机时,记得我今天说过周末快乐!"</span>);
        contentMap.put(<span style="color:#880000">8</span>, <span style="color:#a31515">"我刚去了一趟银行,取了无数的幸福黄金好运珠宝平安翡翠成功股票健康基金。嘘!别作声,统统的送给你,因为我想提“钱”祝你国庆节快乐!"</span>);
        contentMap.put(<span style="color:#880000">9</span>, <span style="color:#a31515">"一个人的精彩,一个人的打拼,一个人的承载,一个人的舞蹈。光棍节送你祝福,不因你是光棍,只因你生活色彩。祝你:快乐打拼,生活出彩!"</span>);
        contentMap.put(<span style="color:#880000">10</span>, <span style="color:#a31515">"爆竹响激情燃放,雪花舞祥风欢畅,烟火腾期待闪亮,感动涌心中激荡,心情美春节冲浪,愿景好心中珍藏,祝与福短信奉上:祝您身体健康,兔年吉祥!"</span>);

        <span style="color:#008000">//模似7次 添加文章</span>
        <span style="color:#0000ff">for</span> (<span style="color:#a31515">int</span> <span style="color:#008000">i</span> <span style="color:#ab5656">=</span> <span style="color:#880000">1</span>; i <= <span style="color:#880000">7</span>; i++) {
            <span style="color:#a31515">ArticleInfo</span> <span style="color:#008000">article</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">ArticleInfo</span>();
            article.setId(String.valueOf(i));
            article.setTitle(titleMap.get(i));
            article.setAuthor(<span style="color:#a31515">"VipSoft"</span>);
            article.setSummary(introMap.get(i));
            article.setContent(contentMap.get(i));
            article.setCreateTime(<span style="color:#0000ff">new</span> <span style="color:#a31515">Date</span>());
            <span style="color:#008000">//将article 保存到 MySQL --- 省略</span>
            <span style="color:#a31515">boolean</span> <span style="color:#008000">flag</span> <span style="color:#ab5656">=</span> <span style="color:#a31515">true</span>; <span style="color:#008000">//保存数据到 MySQL 数据库成功</span>
            <span style="color:#0000ff">if</span> (flag) {
                <span style="color:#008000">//将需要查询的数据,赋给DTO,更新到 ES中</span>
                <span style="color:#a31515">ArticleDTO</span> <span style="color:#008000">articleDTO</span> <span style="color:#ab5656">=</span> <span style="color:#0000ff">new</span> <span style="color:#a31515">ArticleDTO</span>();
                BeanUtils.copyProperties(article, articleDTO);
                <span style="color:#a31515">String</span> <span style="color:#008000">json</span> <span style="color:#ab5656">=</span> JSON.toJSONStringWithDateFormat(articleDTO, <span style="color:#a31515">"yyyyMMddHHmmss"</span>); <span style="color:#008000">//FastJson 将日期格式化 --这种格式方便做条件搜索</span>
                <span style="color:#a31515">IndexResponse</span> <span style="color:#008000">resp</span> <span style="color:#ab5656">=</span> elasticSearchUtil.createDoc(INDEX_NAME, articleDTO.getId(), json);
                logger.info(<span style="color:#a31515">" {}"</span>, resp.getResult().toString());
            }
        }
    }

   <span style="color:#008000">/**
     * 第三步:模拟用户搜索,输入关键词“人”,带出和人有关的关键词
     */</span>
    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">suggestTest</span>() <span style="color:#0000ff">throws</span> Exception {
        List<String>  resp = elasticSearchUtil.suggest(INDEX_NAME, <span style="color:#a31515">"title.suggest"</span>, <span style="color:#a31515">"人"</span>, <span style="color:#880000">2</span>);
        <span style="color:#008000">//4. 获取到 _source 中的数据,并展示</span>
        <span style="color:#0000ff">for</span> (String hit : resp) {
            System.out.println(hit);
        }
    }
    
    
    <span style="color:#008000">/**
     * 第四步:模拟用户搜索,在搜索框中选择提示搜索关键词
     */</span>
    <span style="color:#2b91af">@Test</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">earchTest</span>() <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">//Demo演示使用了 prefixQuery,实际应用时,会标题、摘要、内容,等多字段 Query组合查询</span>
        <span style="color:#a31515">SearchResponse</span> <span style="color:#008000">resp</span> <span style="color:#ab5656">=</span> elasticSearchUtil.prefixQuery(INDEX_NAME,<span style="color:#a31515">"title"</span>,<span style="color:#a31515">"人工智能"</span>);
        <span style="color:#008000">//4. 获取到 _source 中的数据,并展示</span>
        <span style="color:#0000ff">for</span> (SearchHit hit : resp.getHits().getHits()) {
            Map<String, Object> result = hit.getSourceAsMap();
            System.out.println(result);
        }
    }


    <span style="color:#008000">/**
    * 应用
    **/</span>
    <span style="color:#0000ff">public</span> <span style="color:#0000ff">void</span> <span style="color:#a31515">searchArticle</span>(String keywords, Integer articleType, <span style="color:#a31515">int</span> currPage, <span style="color:#a31515">int</span> pageSize) {
        <span style="color:#a31515">long</span> <span style="color:#008000">totalCount</span> <span style="color:#ab5656">=</span> <span style="color:#880000">0</span>;
        <span style="color:#a31515">long</span> <span style="color:#008000">totalPage</span> <span style="color:#ab5656">=</span> <span style="color:#880000">0</span>;
        <span style="color:#0000ff">try</span> {
            <span style="color:#a31515">BoolQueryBuilder</span> <span style="color:#008000">boolQueryBuilder</span> <span style="color:#ab5656">=</span> QueryBuilders.boolQuery();
            <span style="color:#008000">//多字段查询</span>
            <span style="color:#a31515">MultiMatchQueryBuilder</span> <span style="color:#008000">query</span> <span style="color:#ab5656">=</span> QueryBuilders.multiMatchQuery(keywords, <span style="color:#a31515">"title"</span>, <span style="color:#a31515">"content"</span>, <span style="color:#a31515">"author"</span>, <span style="color:#a31515">"summary"</span>);
            boolQueryBuilder.must(query);
            <span style="color:#0000ff">if</span> (articleType != <span style="color:#a31515">null</span>) {
                <span style="color:#008000">//指定类型下的搜索</span>
                <span style="color:#a31515">MatchPhraseQueryBuilder</span> <span style="color:#008000">matchPhraseQueryBuilder</span> <span style="color:#ab5656">=</span> QueryBuilders.matchPhraseQuery(<span style="color:#a31515">"articleType"</span>, articleType + <span style="color:#a31515">""</span>);
                boolQueryBuilder.must(matchPhraseQueryBuilder);
                <span style="color:#008000">//搜索小于今天的文章</span>
                <span style="color:#a31515">String</span> <span style="color:#008000">currentTime</span> <span style="color:#ab5656">=</span> DateUtil.format(<span style="color:#0000ff">new</span> <span style="color:#a31515">Date</span>(), <span style="color:#a31515">"yyyyMMdd235959"</span>);
                boolQueryBuilder.must(QueryBuilders.rangeQuery(<span style="color:#a31515">"createTime"</span>).lte(Long.valueOf(currentTime)));
            }
            <span style="color:#a31515">SearchResponse</span> <span style="color:#008000">resp</span> <span style="color:#ab5656">=</span> elasticSearchUtil.search(INDEX_NAME, boolQueryBuilder, currPage, pageSize);
            <span style="color:#a31515">SearchHits</span> <span style="color:#008000">hits</span> <span style="color:#ab5656">=</span> resp.getHits();
            totalCount = hits.getTotalHits().value;
            totalPage = (<span style="color:#a31515">long</span>) Math.ceil((<span style="color:#a31515">double</span>) totalCount / pageSize);
            <span style="color:#0000ff">for</span> (SearchHit hit : hits.getHits()) { 
                System.out.println(hit.getSourceAsString());
            }
        } <span style="color:#0000ff">catch</span> (Exception e) {
            e.printStackTrace();
            <span style="color:#0000ff">return</span> <span style="color:#a31515">null</span>;
        } 
    }


    <span style="color:#008000">/**
     * 创建索引
     *
     * <span style="color:#808080">@param</span> indexName
     * <span style="color:#808080">@throws</span> Exception
     */</span>
    <span style="color:#0000ff">void</span> <span style="color:#a31515">createIndex</span>(String indexName) <span style="color:#0000ff">throws</span> Exception {
        <span style="color:#008000">//准备索引的 settings</span>
        Settings.<span style="color:#a31515">Builder</span> <span style="color:#008000">settings</span> <span style="color:#ab5656">=</span> Settings.builder()
                .put(<span style="color:#a31515">"number_of_shards"</span>, INDEX_NUMBER_OF_SHARDS)   <span style="color:#008000">//分片数,可以使用常量</span>
                .put(<span style="color:#a31515">"number_of_replicas"</span>, esProperties.getReplicasNum()); <span style="color:#008000">//是否集群,需要多少副本,在配置文件中配置</span>

        <span style="color:#008000">//准备索引的结构 Mappings</span>
        <span style="color:#a31515">XContentBuilder</span> <span style="color:#008000">mappings</span> <span style="color:#ab5656">=</span> JsonXContent.contentBuilder()
                .startObject()
                .startObject(<span style="color:#a31515">"properties"</span>)
                .startObject(<span style="color:#a31515">"id"</span>).field(<span style="color:#a31515">"type"</span>, <span style="color:#a31515">"keyword"</span>).endObject()
                .startObject(<span style="color:#a31515">"title"</span>).field(<span style="color:#a31515">"type"</span>, <span style="color:#a31515">"text"</span>).field(<span style="color:#a31515">"analyzer"</span>, <span style="color:#a31515">"ik_max_word"</span>)  <span style="color:#008000">//对该字段进行分词</span>
                .startObject(<span style="color:#a31515">"fields"</span>).startObject(<span style="color:#a31515">"suggest"</span>).field(<span style="color:#a31515">"type"</span>, <span style="color:#a31515">"completion"</span>).field(<span style="color:#a31515">"analyzer"</span>, <span style="color:#a31515">"ik_max_word"</span>).endObject().endObject() <span style="color:#008000">//设置可以自动提示关键词</span>
                .endObject()
                .startObject(<span style="color:#a31515">"summary"</span>).field(<span style="color:#a31515">"type"</span>, <span style="color:#a31515">"text"</span>).field(<span style="color:#a31515">"analyzer"</span>, <span style="color:#a31515">"ik_max_word"</span>).endObject()  <span style="color:#008000">//对该字段进行分词</span>
                .startObject(<span style="color:#a31515">"createDate"</span>).field(<span style="color:#a31515">"type"</span>, <span style="color:#a31515">"date"</span>).field(<span style="color:#a31515">"format"</span>, <span style="color:#a31515">"yyyy-MM-dd HH:mm:ss"</span>).endObject()
                .endObject()
                .endObject();

        <span style="color:#a31515">CreateIndexResponse</span> <span style="color:#008000">resp</span> <span style="color:#ab5656">=</span> elasticSearchUtil.createIndex(indexName, settings, mappings);

        <span style="color:#008000">//输出</span>
        logger.info(<span style="color:#a31515">"CreateIndexResponse => {} "</span>, resp.toString());
    }
}

</code></span></span>

程序结构

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2301_76429513

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值