Elasticsearch---spring-boot-starter-data-elasticsearch整合攻略详解

Posted 普通网友

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Elasticsearch---spring-boot-starter-data-elasticsearch整合攻略详解相关的知识,希望对你有一定的参考价值。

前言

首先要确保自己引入的spring-boot-starter-data-elasticsearch版本与elasticsearch一直,通常情况下会遇到使用了如,spring-boot-dependencies,parent等版本依赖管理导致es版本不对应的情况。此时记得手动指定如下jar版本例如我使用的是7.12.0:

  <!-- 重写覆盖 spring-boot-dependencies 中的依赖版本  -->
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-elasticsearch</artifactId>
                <version>4.2.0</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-high-level-client</artifactId>
                <version>$elasticsearch.version</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch</groupId>
                <artifactId>elasticsearch</artifactId>
                <version>$elasticsearch.version</version>
            </dependency>
            <dependency>
                <groupId>org.elasticsearch.client</groupId>
                <artifactId>elasticsearch-rest-client</artifactId>
                <version>$elasticsearch.version</version>
            </dependency>

版本对应关系:https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#preface.versions

一、配置

这里简单使用配置文件方式演示

  elasticsearch:
    rest:
      username: elastic
      password: xxxxxx
      uris: http://xxxxxxxxxx:9200
      connection-timeout: 1000
      read-timeout: 1000

二、创建索引映射

这里我们尽量使用spring-data提供的自动创建方式,避免手动创建致使字段无法对应导致出错。尤其是date类型字段最容易产生问题,依照下方实例spring在启动时会自动判断是否已经创建如果没有会自动帮我们创建。同时使用@setting与@mapping注解通过导入json配置文件的方式丰富mapping创建。

@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Document 

    String indexName(); // 索引库的名称,个人建议以项目的名称命名

    @Deprecated
    String type() default ""; // 类型,7.x之后以废弃

    short shards() default 1;//默认分区数

    short replicas() default 1; // 每个分区默认的备份数

    String refreshInterval() default "1s"; // 刷新间隔

    String indexStoreType() default "fs"; // 索引文件存储类型

    boolean createIndex() default true;  // 是否创建索引

    VersionType versionType() default VersionType.EXTERNAL;  // 版本



/**
 * @author 池海
 * @version 1.0.0
 * @description: TODO
 * @date 2021/12/16
 *  * 注解说明
 *  * @Document:在类级别应用,以指示该类是映射到数据库的候选对象。最重要的属性是:
 *  *      indexName:用于存储此实体的索引的名称。它可以包含SpEL模板表达式,例如 "log-#T(java.time.LocalDate).now().toString()"
 *  *      type:映射类型。如果未设置,则使用小写的类的简单名称。(从版本4.0开始不推荐使用)
 *  *      shards:索引的分片数。
 *  *      replicas:索引的副本数。
 *  *      refreshIntervall:索引的刷新间隔。用于索引创建。默认值为“ 1s”。
 *  *      indexStoreType:索引的索引存储类型。用于索引创建。默认值为“ fs”。
 *  *      createIndex:标记是否在存储库引导中创建索引。默认值为true。请参见使用相应的映射自动创建索引
 *  *      versionType:版本管理的配置。默认值为EXTERNAL。
 *  * @Id:在字段级别应用,以标记用于标识目的的字段。
 *  * @Transient:默认情况下,存储或检索文档时,所有字段都映射到文档,此注释不包括该字段。
 *  * @PersistenceConstructor:标记从数据库实例化对象时要使用的给定构造函数,甚至是受保护的程序包。构造函数参数按名称映射到检索到的Document中的键值。
 *  * @Field:在字段级别应用并定义字段的属性,大多数属性映射到各自的Elasticsearch映射定义(以下列表不完整,请查看注释Javadoc以获得完整参考):
 *  *      name:字段名称,它将在Elasticsearch文档中表示,如果未设置,则使用Java字段名称。
 *  *      type:字段类型,可以是Text, Keyword, Long, Integer, Short, Byte, Double, Float, Half_Float,
 *               Scaled_Float, Date, Date_Nanos, Boolean, Binary, Integer_Range, Float_Range, Long_Range, Double_Range,
 *               Date_Range, Ip_Range, Object, Nested, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type。请参阅Elasticsearch映射类型
 *  *      format和日期类型的pattern定义。必须为日期类型定义format
 *  *      store:标记是否将原始字段值存储在Elasticsearch中,默认值为false。
 *  *      analyzer,searchAnalyzer,normalizer用于指定自定义分析和正规化。
 *  * @GeoPoint:将字段标记为geo_point数据类型。如果字段是GeoPoint类的实例,则可以省略。
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "longzhu")
@Builder
@Setting(settingPath = "/json/settings/settings.json")
public class Product 

    @Id
    long id;

    /** IP所属地域  如,日本、大陆、欧美 可组合 */
    @Field(type = FieldType.Keyword, copyTo = "all")
    private String territory;

    /** 所属ip 如,龙珠、海贼、火影 */
    @Field(type = FieldType.Keyword, copyTo = "all")
    private String ip;

    /** 分类 如,景品 GK  版权  手办 */
    @Field(type = FieldType.Keyword, copyTo = "all")
    private String type;

    /** 产品正式名称 */
    @CompletionField(analyzer = "completion_analyzer",searchAnalyzer = "ik_smart")
    private String name;

    /** 国语简称,即玩家起的简称 */
    @CompletionField(analyzer = "completion_analyzer",searchAnalyzer = "ik_smart")
    private String nickname;

    /** 简介 */
    @Field(type = FieldType.Text, analyzer = "text_analyzer")
    private String description;

    /** 生产商 */
    @Field(type = FieldType.Text,analyzer = "text_analyzer", copyTo = "all")
    private String production;

    /** 生产时间 */
    @Field(type = FieldType.Date,format = DateFormat.year_month_day)
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "uuuu-MM-dd", timezone = "GMT+8")
    private LocalDate productionTime;

    /** 出厂价格 默认单位人民币 -1.00 代表抽赏  -2.00 代表赠送 */
    @Field(type = FieldType.Double, copyTo = "all")
    private Double price;

    /** 发售形式 */
    @Field(type = FieldType.Text)
    private String releaseForm;

    /** 发售地区 */
    @Field(type = FieldType.Text)
    private String  salesArea;

    /** 发售数量 -1.00 代表未知 */
    @Field(type = FieldType.Text)
    private String  indultNumber;

    /** 最近价格  -1.00 代表未知   */
    @Field(type = FieldType.Double, index = false)
    private Double currentPrice;

    /** 商品比例 直接存储数字8代表1/8 6代表 1/6 */
    @Field(type = FieldType.Text, copyTo = "all")
    private String ratio;

    /** 商品高度 */
    @Field(type = FieldType.Double)
    private Double height;

    /** 商品材质 */
    @Field(type = FieldType.Text, copyTo = "all")
    private String material;

    /** 商品重量 */
    @Field(type = FieldType.Double)
    private Double weight;

    /** 总评分 */
    @Field(type = FieldType.Double)
    private Double grade;

    /** 档案创始人,即首个提交完善资料的用户 */
    @Field(type = FieldType.Long, index = false)
    private long originatorId;

    /** 元气贡献者, 即后续进行信息补充、纠错、数据更新的用户 */
    @Field(type = FieldType.Keyword)
    private long[] contributors ;


setting.json文件参考:


    "analysis": 
      "analyzer": 
        "text_analyzer":
          "tokenizer": "ik_max_word",
          "filter": "py"
        ,
        "completion_analyzer":
          "tokenizer": "keyword",
          "filter": "py"
        
      ,
      "filter": 
        "py":
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "lowercase": false,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        
      
    

启动后我们在kibana查询一下确保创建成功,GET /longzhu

三、使用spring-data为我们提供的便捷代理接口操作Es

基础操作使用很简单,我们只需要创建一个接口继承ElasticsearchRepository就可以帮我自动代理生成一些基础操作方法,同时也支持如spring-data-jpa一样通过方法名称自动帮我们生成对应查询操作,如我下方自定义了一个根据名称查询的接口。

@Repository
public interface ProductDao extends ElasticsearchRepository<Product, Long> 

    List<Product> findByName(String name);

详情可参考官方提问的文档https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#elasticsearch.repositories

四、@Query注解查询

这种方式用到的场景相对较少,我们简单看一下官方给出的实例

interface BookRepository extends ElasticsearchRepository<Book, String> 
    @Query(""match": "name": "query": "?0"")
    Page<Book> findByName(String name,Pageable pageable);

The String that is set as the annotation argument must be a valid Elasticsearch JSON query. It will be sent to Easticsearch as value of the query element; if for example the function is called with the parameter John, it would produce the following query body:


  "query": 
    "match": 
      "name": 
        "query": "John"
      
    
  

五、restTemplate高级查询

1.常规

在进行restTemplate.search(Query var1, Class var2);调用时我们发现都需要传入一个query参数。从下图看到其下有三个实现类,我们最常用到的就是NatviveSearchQuery原生查询的方法。

下面是一个简单的分页+排序的案例:

 NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        NativeSearchQuery query = queryBuilder
                .withQuery(QueryBuilders.multiMatchQuery("景品","type"))
                // 分页
                .withPageable(PageRequest.of(0,10))
                // 根据查询结果按时间升序
                .withSort(SortBuilders.fieldSort("productionTime").order(SortOrder.ASC)).build();
        SearchHits<Product> searchHits = restTemplate.search(query,Product.class);
        searchHits.stream().iterator().forEachRemaining(System.out::println);


上面的写法也可以简写为:

withPageable(PageRequest.of(0,10, Sort.Direction.ASC, "productionTime"))


我们看到of()方法结尾是一个可变参数,所以允许我们传入多个排序字段。如果在日期相同的情况下我们想根据手办高度排序即只需要再追加一个高度字段

withPageable(PageRequest.of(0,10, Sort.Direction.ASC, "productionTime","height"))

更多有趣的API还需要我们自己去慢慢探索

2.高亮

// 默认样式
.withHighlightFields(new HighlightBuilder.Field("type"))

// 自定义
.withHighlightBuilder(new HighlightBuilder().field("type").preTags("<p style='color:yellow'> ").postTags("<p>"))

先自我介绍一下,小编13年上师交大毕业,曾经在小公司待过,去过华为OPPO等大厂,18年进入阿里,直到现在。深知大多数初中级java工程师,想要升技能,往往是需要自己摸索成长或是报班学习,但对于培训机构动则近万元的学费,着实压力不小。自己不成体系的自学效率很低又漫长,而且容易碰到天花板技术停止不前。因此我收集了一份《java开发全套学习资料》送给大家,初衷也很简单,就是希望帮助到想自学又不知道该从何学起的朋友,同时减轻大家的负担。添加下方名片,即可获取全套学习资料哦

13 . 外部中断实验

外部中断实验

介绍了STM32F10x 的中断,就来学习下外部中断。要实现的功能与按键实验一样,即通过按键控制LED,只不过这里采用外部中断方式进行控制。

1. 外部中断介绍

EXTI 简介
STM32F10x 外部中断/事件控制器(EXTI)包含多达20 个用于产生事件/中断请求的边沿检测器。EXTI 的每根输入线都可单独进行配置,以选择类型(中断或事件)和相应的触发事件(上升沿触发、下降沿触发或边沿触发),还可独立地被屏蔽。
EXTI 结构框图
在这里插入图片描述
EXTI 框图包含了EXTI 最核心内容,掌握了此框图,对EXTI 就有一个全局的把握,在编程的时候思路就非常清晰。从图中可以看到,总计有EXTI中断线20根,有很多信号线上都有标号9 样的“20”字样,这个表示在控制器内部类似的信号线路有20 个,这与STM32F10x 的EXTI 总共有20 个中断/事件线是吻合的。因此我们只需要理解其中一个的原理,其他的19个线路原理都是一样的。
EXTI 分为两大部分功能,一个产生中断,另一个产生事件,这两个功能从硬件上就有所差别,这个在框图中也有体现。从图中标号3 的位置处就分出了两条线路,一条是3-4-5 用于产生中断,另一条是3-6-7-8 用于产生事件。下面我们就来介绍下这两条线路:
(1)首先看下产生中断的这条线路(1-2-3-4-5)
1.标号1 为输入线,EXTI 控制器有20 个中断/事件输入线,这些输入线可以通过寄存器设置为任意一个GPIO,也可以是一些外设的事件,这部分内容我们会在后面专门讲解。输入线一般是存在电平变化的信号。
2.边沿检测电路,EXTI 可以对触发方式进行选择,通过上升沿触发选择寄存器和下降沿触发选择寄存器对应位的设置来控制信号触发。边沿检测电路以输入线作为信号输入端,如果检测到有边沿跳变就输出有效信号1 给红色框3 电路,否则输出无效信号0。而上升沿和下降沿触发选择这两个寄存器可以控制需要检测哪些类型的电平跳变过程,可以是只有上升沿触发、只有下降沿触发或者上升沿和下降沿都触发。
3.其实就是一个或门电路,一端输入信号线由标号2 提供,一端由软件中断事件寄存器提供,只要有一个为有效信号1,标号3 电路则输出有效信号1,否则为无效信号0。软件中断事件寄存器允许我们使用软件来启动中断/事件线,这个在某些地方非常有用。
4.其实就是一个与门电路,一端输入信号线由标号3 电路输出提供,一端由中断屏蔽寄存器提供,只有当两者都为有效信号1,标号4 电路才会输出有效信号1,否则输出无效。这样我们就可以简单的控制中断屏蔽寄存器来实现是否产生中断的目的。当我们把中断屏蔽寄存器设置为1 时,标号4 输出就取决于标号3 电路的输出。标号3 电路输出的信号会被保存到挂起寄存器内,如果确定标号3 电路输出为1 就会把挂起寄存器对应位置1。

5.将挂起寄存器内容输入到NVIC 内,从而实现系统中断事件的控制。
(2)最后我们再来看下产生事件这条线路(1-2-3-6-7-8),前面1-2-3都是一样的,只是在3 的输出后产生分歧。
6.其实就是一个与门电路,一端来至标号3 电路的输出信号,一端来至事件屏蔽寄存器,只有两者都为有效电平1,标号6 输出才有效。当事件屏蔽寄存器设置为0 时,不管标号3 电路输出为1 还是0,标号6 电路输出均为0。当事件屏蔽寄存器设置为1 时,标号6 电路输出取决于标号3 电路输出,这样就可以简单的控制事件屏蔽寄存器来实现是否产生事件的目的。
7.脉冲发生器电路,其输入端只与标号6 电路输出有关,标号6 输出有效,脉冲发生器才会输出一个脉冲信号。
8.脉冲信号,由标号7 脉冲发生器产生,是事件线路的终端,此脉冲信号可供其他外设电路使用,比如定时器、ADC 等。这样的脉冲信号通常用来触发定时器、ADC 等开始转换。

从上面EXTI 框图可以看出,中断线路最终会输入到NVIC 控制器中,从而会运行中断服务函数,实现中断内功能,这个是软件级的。而事件线路最后产生的脉冲信号会流向其他的外设电路,是硬件级的。在EXTI 框图最顶端可以看到,其外设接口时钟是由PCLK2,即APB2 提供,所以在后面使能EXTI 时钟的时候一定要注意。

外部中断/事件线映射
STM32F10x 的EXTI 具有20 个中断/事件线,对应连接的外设说明如下表所示:
在这里插入图片描述
从上表可知,STM32F10x 的EXTI 供外部IO 口使用的中断线有16 根,但是我们使用的STM32F103 芯片却远远不止16 个IO 口, 那么STM32F103 芯片怎么解决这个问题的呢?因为STM32F103 芯片每个GPIO 端口(GPIOx)均有16 个管脚,因此把每个端口的16 个IO 对应那16 根中断线EXTI0-EXTI15 。
比如:
GPIOx.0-GPIOx.15(x=A,B,C,D,E,F,G)分别对应中断线EXTI0-EXTI15,这样一来每个中断线就对应了最多7 个IO 口,比如:GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0。但是中断线每次只能连接一个在IO 口上,这样就需要通过AFIO 的外部中断配置寄存器1 的EXTIx[3:0]位来决定对应的中断线映射到哪个GPIO 端口上,对于中断线映射到GPIO 端口上的配置函数在stm32f10x_gpio.c 和stm32f10x_gpio.h 中,所以使用到外部中断时要把这个文件加入到工程中。EXTI 的GPIO 映射图如图所示:
在这里插入图片描述
EXTI 配置步骤
接下来我们介绍下如何使用库函数对外部中断进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(EXTI 相关库函数在stm32f10x_exti.c 和stm32f10x_exti.h 文件中)
(1)使能IO 口时钟,配置IO 口模式为输入
由于本章使用开发板上4 个按键IO 口作为外部中断输入线,因此需要使能对应的IO 口时钟及配置IO 口模式,在按键实验章节中,我们就介绍过要把对应IO 口设置为输入模式,这部分配置与按键实验一样。
(2)开启AFIO 时钟,设置IO 口与中断线的映射关系
接下来我们需要将GPIO 映射到对应的中断线上,只要使用到外部中断,就必须先使能AFIO 时钟,前面已经说了它是挂接在APB2 总线上的,所以使能AFIO时钟库函数为:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

然后,我们就可以把GPIO 映射到对应的中断线上,配置GPIO 与中断线映射的库函数如下:

void GPIO_EXTILineConfig(uint8_t GPIO_PortSource, uint8_tGPIO_PinSource);

比如我们将中断线0 映射到GPIOA 端口,那么就需要如下配置:

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

这时候GPIOA.0 管脚就与中断线0 连接起来,其他端口中断线的映射类似。
(3)配置中断分组(NVIC),使能中断我们知道EXTI 产生中断线路最终是流向NVIC 控制器的,由NVIC 调用中断服务函数,因此我们需要对NVIC 进行配置。NVIC 的配置在上一章介绍STM32 中断的时候已经讲过,这里就不重复,不清楚的朋友可以回过头看下。配置NVIC范例如下:

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//EXTI0 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; // 子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC 寄存器

(4)初始化EXTI,选择触发方式
配置好NVIC 后,我们还需要对中断线上的中断初始化,EXTI 初始化库函数,如下:

void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct);

函数形参是有一个结构体EXTI_InitTypeDef 类型的指针变量,EXTI_InitTypeDef 结构体成员变量如下:

typedef struct
{
uint32_t EXTI_Line; //中断/事件线
EXTIMode_TypeDef EXTI_Mode; //EXTI 模式
EXTITrigger_TypeDef EXTI_Trigger; //EXTI 触发方式
FunctionalState EXTI_LineCmd; //中断线使能或失能
}EXTI_InitTypeDef;

下面就来介绍下结构体内成员的意义:
EXTI_Line:EXTI 中断/事件线选择,可配置参数为EXTI0-EXTI20,可参考上表。
EXTI_Mode:EXTI 模式选择,可以配置为中断模式EXTI_Mode_Interrupt 和事件模式EXTI_Mode_Event。
EXTI_Trigger : 触发方式选择, 可以配置为上升沿触发EXTI_Trigger_Rising、下降沿触发EXTI_Trigger_Falling、上升沿和下降沿触发EXTI_Trigger_Rising_Falling。
EXTI_LineCmd:中断线使能或者失能,配置ENABLE 为使能,DISABLE 为失能,我们这里要使用外部中断,所以需使能。
(5)编写EXTI 中断服务函数
所有中断函数都在STM32F1 启动文件中,不知道中断函数名的可以打开启动文件查找。这里我们使用到的是外部中断,其函数名如下:
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
EXTI9_5_IRQHandler
EXTI15_10_IRQHandler
从函数名可以看到,前面0-4 个中断线都是独立的函数,中断线5-9 共用一个函数EXTI9_5_IRQHandler , 中断线10-15 也共用一个函数EXTI15_10_IRQHandler,所以要在编写对应中断服务函数时要注意。

硬件设计

硬件电路依然使用开发板上的4 个按键,其电路如图所示:
在这里插入图片描述
在这里插入图片描述
当按键按下时,对应的IO 口电平会发生变化,这时只要配置好对应端口的外部中断触发方式就可以触发中断。

软件设计

要实现外部中断方式控制LED,程序框架如下:
(1)初始化对应端口的EXTI
(2)编写EXTI 中断函数
(3)编写主函数
在前面介绍EXTI 配置步骤时,就已经讲解如何初始化EXTI。

EXTI 初始化函数
要使用外部中断,我们必须先对它进行配置。EXTI 初始化代码如下:

/****************************************************************
***************
* 函数名: My_EXTI_Init
* 函数功能: 外部中断初始化
* 输入: 无
* 输出: 无
*****************************************************************
**************/
void My_EXTI_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource2);//选择GPIO 管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource3);//选择GPIO 管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE, GPIO_PinSource4);//选择GPIO 管脚用作外部中断线路
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);//选择GPIO 管脚用作外部中断线路

//EXTI0 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;//EXTI0 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC 寄存器

//EXTI2 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;//EXTI2 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =2; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC 寄存器

//EXTI3 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;//EXTI3 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =1; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC 寄存器

//EXTI4 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;//EXTI4 中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;// 抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC 寄存器

EXTI_InitStructure.EXTI_Line=EXTI_Line0;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);

EXTI_InitStructure.EXTI_Line=EXTI_Line2|EXTI_Line3|EXTI_Line4;
EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;
EXTI_InitStructure.EXTI_LineCmd=ENABLE;
EXTI_Init(&EXTI_InitStructure);
}

在My_EXTI_Init()函数中,首先使能AFIO 时钟,并将4 个按键端口映射到对应中断线上,4 个按键连接端口是PA0、PE2、PE3、PE4。然后配置相应的NVIC并使能对应中断通道,由于4 个按键的IO 口占用了4 个中断线,属于不同的中断通道,需要分别对其配置,从NVIC 配置代码中可以看到4 个中断通道的响应优先级为0-3,所以NVIC 被分成了2 组,分组代码放在了主函数,只有一条语句:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组分2 组。

最后就是对EXTI 初始化,通过配置EXTI_InitStructure 结构体成员值实现EXTI 的配置,从代码中可以看到,中断线0(EXTI_Line0)被配置为上升沿触发,中断线2-4 被配置为下降沿触发,这是因为我们的按键K_UP 是高电平有效,而其他3 个按键是低电平有效,这个在按键实验中已经介绍过。其实如果你会配置一个中断线的EXTI,那么其他中断线都是类似的。

EXTI 中断函数

初始化EXTI 后,中断就已经开启了,当任意按键按下后会触发一次中断,这时程序就会进入中断服务函数执行,所以我们还需要编写对应的EXTI 中断函数,这里我们以PA0 管脚的K_UP 按键进行讲解,其他的按键的中断函数类似,具体代码如下:

/****************************************************************
***************
* 函数名: EXTI0_IRQHandler
* 函数功能: 外部中断0 函数
* 输入: 无
* 输出: 无
*****************************************************************
**************/
void EXTI0_IRQHandler(void)
{
if(EXTI_GetITStatus(EXTI_Line0)==1)
{
delay_ms(10);
if(K_UP==1)
{
led2=0;
}
}
EXTI_ClearITPendingBit(EXTI_Line0);
}

因为PA0 管脚对应的中断线是EXTI0,所以其中断函数为EXTI0_IRQHandler,这个从名字来看就很好理解。进入中断函数后,为了确保中断是否真的发生,我们还会对其中断标志位状态进行判断,获取EXTI 中断标志位状态函数如下:

EXTI_GetITStatus(EXTI_Line0);

函数参数EXTI_Line0 是所要判断的中断线, 可以为EXTI_Line0-EXTI_Line20。如果EXTI 中断线有中断发生,函数返回SET,否则返回RESET。SET 也可以用1 表示,RESET 可以用0 表示。在结束中断服务函数前,我们还会清除中断标志位,函数如下:

EXTI_ClearITPendingBit(EXTI_Line0);

在库函数内,还提供了两个函数用来判断外部中断状态以及清除外部状态标志位的函数EXTI_GetFlagStatus 和EXTI_ClearFlag,他们的作用和前面两个函数的作用类似。只是在EXTI_GetITStatus 函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而EXTI_GetFlagStatus 直接用来判断状态标志位。在中断函数内,还调用了delay_ms 函数,用于软件消抖,如果K_UP 确实按下了,就点亮D2 指示灯。

主函数
编写好EXTI 初始化和中断服务函数后,接下来就可以编写主函数了,代码如下:

/****************************************************************
***************
* 函数名: main
* 函数功能: 主函数
* 输入: 无
* 输出: 无
*****************************************************************
**************/
int main()
{
u8 i;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组分2 组
LED_Init();
KEY_Init();
My_EXTI_Init(); //外部中断初始化
while(1)
{
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}

主函数实现的功能很简单, 首先对NVIC 进行分组, 这里我们调用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2)函数,将NVIC 分为2 组即抢占优先级和响应优先级都占2 位,本套教程所有中断分组都采用这种设置,后面就不做重复。再对使用到的硬件端口时钟和IO 口初始化,然后调用我们前面编写的EXTI 的初始化函数,最后进入while 循环语句,不断让D1 指示间隔200ms
闪烁。有的朋友就会问,在主函数中怎么没有看到按键对LED 的控制呢?因为我们在My_EXTI_Init()函数内就已经把按键管脚映射到中断线上,并配置了相应的触发方式,当有按键按下,即会进入对应中断服务函数执行相应的功能程序,LED的控制就在中断函数内完成的。

以上是关于Elasticsearch---spring-boot-starter-data-elasticsearch整合攻略详解的主要内容,如果未能解决你的问题,请参考以下文章