RsetClient操作Elasticsearch7.X

1、ElasticSearch7.X版本变动点

众所周知,ElasticSearch版本表更周期比较短,每次版本的迭代,都会出现一些功能上的大变动,项目中一旦使用了ES的版本,不敢轻易进行版本升级,因为变动点太多,很可能涉及到操作API的变更。下面来说下ES7.X上的变动点:

1.1 ElasticSearch7.X移除类型(type)
  • 从Elasticsearch的第一个发布版本以来,每一个文档都被存储在一个单独的索引里,并被赋予了一个type,一个映射类型代表着一个被索引的文档或实体的类型。

为什么移除类型: 起初,我们说"索引"和关系数据库的“库”是相似的,“类型”和“表”是对等的。
这是一个不正确的对比,导致了不正确的假设。在关系型数据库里,"表"是相互独立的,一个“表”里的列和另外一个“表”的同名列没有关系,互不影响。但在类型里字段不是这样的。
在一个Elasticsearch索引里,所有不同类型的同名字段内部使用的是同一个lucene字段存储。这可能导致一些问题,例如你希望同一个索引中"deleted"字段在一个类型里是存储日期值,在另外一个类型里存储布尔值。
最后,在同一个索引中,存储仅有小部分字段相同或者全部字段都不相同的文档,会导致数据稀疏,影响Lucene有效压缩数据的能力。
因为这些原因,我们决定从Elasticsearch中移除类型的概念。

1.2 集群连接变化:TransportClient被废弃
  • ES7的Java代码,只能使用restclient。然后,个人综合了一下,对于java编程,建议采用 High-level-rest-client 的方式操作ES集群。源码中已标明,如下图所示:
    在这里插入图片描述
1.3 High-level REST client 改变
  • 已删除接受Header参数的API方法;Cluster Health API默认为集群级别;
1.4 查询相关性速度优化:Weak-AND算法
  • 啥是weak-and算法?
    核心原理:取TOP N结果集,估算命中记录数。

简单来说,一般我们在计算文本相关性的时候,会通过倒排索引的方式进行查询,通过倒排索引已经要比全量遍历节约大量时间,但是有时候仍然很慢。
原因是很多时候我们其实只是想要top n个结果,一些结果明显较差的也进行了复杂的相关性计算,
而weak-and算法通过计算每个词的贡献上限来估计文档的相关性上限,从而建立一个阈值对倒排中的结果进行减枝,从而得到提速的效果。

1.5 间隔查询(Intervals queries):
  • 某些搜索用例(例如,法律和专利搜索)引入了查找单词或短语彼此相距一定距离的记录的需要。
    Elasticsearch 7.0中的间隔查询引入了一种构建此类查询的全新方式,与之前的方法(跨度查询span queries)相比,使用和定义更加简单。与跨度查询相比,间隔查询对边缘情况的适应性更强。
1.6 引入新的集群协调子系统
  • 移除 minimum_master_nodes 参数,让 Elasticsearch 自己选择可以形成仲裁的节点。典型的主节点选举现在只需要很短的时间就可以完成。
  • 集群的伸缩变得更安全、更容易,并且可能造成丢失数据的系统配置选项更少了。
  • 节点更清楚地记录它们的状态,有助于诊断为什么它们不能加入集群或为什么无法选举出主节点。
  • 时间戳纳秒级支持,提升数据精度
  • 新的 Circuit Breaker 在JVM 堆栈层面监测内存使用,Elasticsearch 比之前更加健壮。
  • 设置indices.breaker.fielddata.limit的默认值已从JVM堆大小的60%降低到40%。

2、项目集成

之前写过 TransportClient 操作ES的文章,里面也包括ES的安装教程,有兴趣的可以查看一下。
链接地址:https://blog.csdn.net/li521wang/article/details/83792552

这里主要讲一下SpringBoot项目集成RestHighLevelClient操作ES7.X。内容包含根据实体动态创建ES索引(包含ES基础类型与内嵌文档类型)、CRUD、同步批量操作、异步批量操作、删除索引、封装查询等常用方法的集成。适合做为项目中的工具类。

2.1 依赖增加

在项目的pom依赖中增加ElasticSearch7.4.0的依赖

        <!-- elasticsearch 7.4.0 -->
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.0</version>
        </dependency>
        
        <!-- commons工具类 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <!-- lombok工具 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
2.2 配置文件

在application.properties增加ElasticSearch配置文件,主要配置ES的集群名称、Host、用户名密码等信息
【注】 如果想增加ES的认证,需要安装x-pack插件,这里不再描述。

data.elasticsearch.cluster-name=my-application
data.elasticsearch.host=127.0.0.1:9200
data.elasticsearch.username=
data.elasticsearch.password=
data.elasticsearch.repositories.enable=true
data.elasticsearch.maxresout=1000000
2.3 ES配置类

创建ES的配置类ESConfig,获取application.properties中的配置信息创建自定义RestClientBuilder、RestHighLevelClient的Spring Bean对象

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Arrays;
import java.util.Objects;


/**
 * ES配置类 - RestClient
 *
 * @author lixiangx@leimingtech.com
 * @date 2019/11/16 19:27
 **/
@Slf4j
@Configuration
public class ESConfig {

    /**
     * 地址位数(ip:端口按:分割长度为2)
     */
    private static final int ADDRESS_LENGTH = 2;

    /**
     * 协议名称
     */
    private static final String HTTP_SCHEME = "http";

    /**
     * 权限验证
     */
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();

    /**
     * ES地址信息(ip:port)
     */
    @Value("${data.elasticsearch.host}")
    private String[] address;

    @Value("${data.elasticsearch.maxresout}")
    private String maxresout;

    /**
     * 用户名
     */
    @Value("${data.elasticsearch.username}")
    private String userName;

    /**
     * 密码
     */
    @Value("${data.elasticsearch.password}")
    private String password;

    /**
     * 配置RestClient构造器
     *
     * @date 2019/12/10 15:49
     * @author lixiangx@leimingtech.com
     **/
    @Bean
    public RestClientBuilder restClientBuilder() {
        HttpHost[] hosts = Arrays.stream(address)
                .map(this::makeHttpHost)
                .filter(Objects::nonNull)
                .toArray(HttpHost[]::new);
        log.info("hosts:{}", Arrays.toString(hosts));

        //配置权限验证
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
        return RestClient.builder(hosts).setHttpClientConfigCallback(httpClientBuilder -> httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider));
    }

    /**
     * 处理请求地址
     *
     * @param esAddress: ES地址信息
     * @date 2019/12/10 15:46
     * @author lixiangx@leimingtech.com
     **/
    private HttpHost makeHttpHost(String esAddress) {
        assert StringUtils.isNotEmpty(esAddress);
        String[] address = esAddress.split(":");
        if (address.length == ADDRESS_LENGTH) {
            String ip = address[0];
            int port = Integer.parseInt(address[1]);
            return new HttpHost(ip, port, HTTP_SCHEME);
        } else {
            return null;
        }
    }

    /**
     * 配置RestHighLevelClient Bean
     *
     * @date 2019/12/10 15:45
     * @author lixiangx@leimingtech.com
     **/
    @Bean(name = "highLevelClient")
    public RestHighLevelClient highLevelClient(@Autowired RestClientBuilder restClientBuilder) {
        return new RestHighLevelClient(restClientBuilder);
    }
}
2.4 创建常量类

创建常量类ElasticSearchConstant ,根据阿里巴巴开发手册中的规定,代码中不允许出现魔法值。

/**
 * ES常量类
 *
 * @author lixiangx@leimingtech.com
 * @version V1.0
 * @date 2019/12/11 10:39
 **/
public interface ElasticSearchConstant {

    /**
     * 查询多字段分割标识
     */
    String SPLIT_FLAG = ",";

    /**
     * 排序规则-升序
     */
    String SORT_ASC = "asc";

    /**
     * 排序规则-降序
     */
    String DESC_ASC = "desc";
}
2.5 自定义注解

创建自定义注解FieldInfo,配置实体类型、分词规则 。

import java.lang.annotation.*;

/**
 * 自定义ES注解,配置实体类型、分词规则
 *
 * @author lixiangx@leimingtech.com
 * @date 2019/12/10 17:07
 **/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FieldInfo {

    /**
     * @string text ,keyword
     * @Number long, integer, short, byte, double, float, half_float, scaled_float
     * @date date
     * @date_nanos date_nanos
     * @Range integer_range, float_range, long_range, double_range, date_range
     * @binary binary
     * @Nested nested
     */
    String type() default "string";

    /**
     * 分词器选择  0. not_analyzed   1. ik_smart 2. ik_max_word
     */
    int participle() default 0;
}

创建解析实体后存放实体实体类型、分词规则的对象 FieldMapping.java

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 解析实体后存放实体实体类型、分词规则的对象
 *
 * @author lixiangx@leimingtech.com
 * @date 2019/12/10 17:07
 **/
@Data
@NoArgsConstructor
public class FieldMapping {

    /**
     * 名称
     */
    private String field;

    /**
     * 类型
     */
    private String type;

    /**
     * 分词器选择  0. not_analyzed   1. ik_smart 2. ik_max_word
     */
    private int participle;

    /**
     * 嵌套集合
     */
    private List<FieldMapping> fieldMappingList;

    public FieldMapping(String field, String type, int participle) {
        this.field = field;
        this.type = type;
        this.participle = participle;
    }
}

解析实体上注解信息的工具类 FieldMappingUtils.java

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * Field工具类
 *
 * @author lixiang
 * @version V1.0
 * @date 2019/12/10 17:14
 **/
@Slf4j
public class FieldMappingUtils {

    /**
     * 获取实体注解上的FieldMapping
     *
     * @param clazz: 对象的class
     * @return 实体的FieldMapping集合
     * @date 2019/12/10 17:15
     * @author lixiangx@leimingtech.com
     **/
    public static List<FieldMapping> getFieldInfo(Class clazz) {
        return getFieldInfo(clazz, null);
    }


    /**
     * 获取实体注解上的FieldMapping
     *
     * @param clazz:     对象的class
     * @param fieldName: 类名称
     * @return 实体的FieldMapping集合
     * @date 2019/12/10 17:15
     * @author lixiangx@leimingtech.com
     **/
    public static List<FieldMapping> getFieldInfo(Class clazz, String fieldName) {

        // 返回Class中所有的字段(包括私有字段)
        Field[] fields = clazz.getDeclaredFields();

        // 创建FieldMapping集合
        List<FieldMapping> fieldMappingList = new ArrayList<>();

        for (Field field : fields) {

            // 获取字段上的FieldInfo对象
            FieldInfo fieldInfo = field.getAnnotation(FieldInfo.class);
            if (fieldInfo == null) {
                continue;
            }

            if ("object".equals(fieldInfo.type()) || "nested".equals(fieldInfo.type())) {
                // 字段FieldInfo注解的type是object
                // 获取字段的类型
                Class fc = field.getType();

                // 判断指定的Class对象是否为基本类型
                // (有九种预定义的Class对象代表的八个基本类型和void。这些都是由Java虚拟机创建的,并且具有相同的名称,
                // 它们代表即boolean, byte, char, short, int, long, float, 和double 等原始类型)。
                if (fc.isPrimitive()) {

                    // 获取Class的类名称(基本类型)
                    String name = field.getName();
                    if (StringUtils.isNotBlank(fieldName)) {
                        name = name + "." + fieldName;
                    }
                    fieldMappingList.add(new FieldMapping(name, fieldInfo.type(), fieldInfo.participle()));
                } else {
                    // 判断是否为List
                    if (fc.isAssignableFrom(List.class)) {
                        log.debug("List类型:{}", field.getName());
                        // 得到泛型类型
                        Type gt = field.getGenericType();
                        ParameterizedType pt = (ParameterizedType) gt;
                        Class listClass = (Class) pt.getActualTypeArguments()[0];

                        // 循环获取集合里面的实体FieldMapping
                        List<FieldMapping> fieldMappings = getFieldInfo(listClass, field.getName());
                        FieldMapping fieldMapping = new FieldMapping(field.getName(), fieldInfo.type(), fieldInfo.participle());
                        fieldMapping.setFieldMappingList(fieldMappings);
                        fieldMappingList.add(fieldMapping);
                    } else {
                        // 循环获取集合里面的实体FieldMapping
                        List<FieldMapping> fieldMappings = getFieldInfo(fc, field.getName());
                        FieldMapping fieldMapping = new FieldMapping(field.getName(), fieldInfo.type(), fieldInfo.participle());
                        fieldMapping.setFieldMappingList(fieldMappings);
                        fieldMappingList.add(fieldMapping);
                    }
                }

            } else {
                // 字段FieldInfo注解的type不是object
                String name = field.getName();
                fieldMappingList.add(new FieldMapping(name, fieldInfo.type(), fieldInfo.participle()));
            }

        }
        return fieldMappingList;

    }
}
2.6 创建封装查询条件的对象
  • PageModelDTO.java
/**
 * 分页模型
 */
@Data
public class PageModelDTO implements Serializable {

    private static final long serialVersionUID = 192274358354304398L;
    /**
     * 数据总条数
     */
    private Long total;

    /**
     * 页码
     */
    private Integer pageNum = 1;

    /**
     * 条数
     */
    private Integer pageSize = 10;

    /**
     * 界定返回值类型 json;obj;
     */
    private String resultType = "string";

    /**
     * 是否分页
     */
    private Boolean isPage = true;

    /**
     * json格式返回值
     */
    private List<String> jsonRsList = new ArrayList<String>();

    /**
     * 直接从ES获取的结果
     */
    private SearchHit[] searchHits;

    /**
     * 区间查询参数
     */
    private Map<String, RangConditionDTO> rangConditionMap = new HashMap<>();

    /**
     * 时间区间查询参数
     */
    private Map<String, RangConditionsToTimeModelDTO> rangConditionsToTimeModelMap = new HashMap<>();

    /**
     * 模糊相等查询条件,多个查询条件","进行切割
     */
    private Map<String, Object> likeSearchCondition = new HashMap<>();

    /**
     * or条件查询
     */
    private Map<String, Object> orSearchCondition = new HashMap<>();

    /**
     * or条件查询
     */
    private Map<String, Object> orLikeSearchCondition = new HashMap<>();

    /**
     * or条件查询集合类操作
     */
    private List<Map<String, Object>> orSearchConditionList = new ArrayList<>();

    /**
     * 相等查询条件,多个查询条件","进行切割
     */
    private Map<String, Object> equalsSearchCondition = new HashMap<>();

    /**
     * in 查询
     */
    private Map<String, List> inSearchCondition = new HashMap<>();

    /**
     * 模糊不相等的条件,多个查询条件","进行切割
     */
    private Map<String, Object> noLikeSearchConditioin = new HashMap<>();

    /**
     * 不相等的条件,多个查询条件","进行切割
     */
    private Map<String, Object> noEqualsSearchConditioin = new HashMap<>();

    /**
     * 为空过滤
     */
    private List<String> isNullConditioin = new ArrayList<>();

    /**
     * 不为空过滤
     */
    private List<String> isNotNullConditioin = new ArrayList<>();

    /**
     * 排序字段,关键字asc,desc
     */
    private Map<String, String> sortFileds = new LinkedHashMap<>();

    /**
     * 排序字段集合,方便对排序顺序的控制 关键字asc,desc
     */
    private List<Map<String, String>> sortFiledsList = new ArrayList<>();

    /**
     * 高亮字段
     */
    private List<String> hightFieldList = new ArrayList<>();

    /**
     * 去重字段
     */
    private String collapseField;

    /**
     * 指定查询结果包含的字段
     */
    private String[] fetchSourceIncludes;

    /**
     * 指定查询结果不包含的字段
     */
    private String[] fetchSourceExcludes;

    /**
     * 分词字段
     */
    private Map<String, Object> analyzersField = new HashMap<>();

    public String getSortFileds(String key) {
        return sortFileds.get(key);
    }

    public RangConditionDTO getRangConditionMap(String key) {
        return rangConditionMap.get(key);
    }

    public RangConditionsToTimeModelDTO getRangConditionsToTimeModelMap(String key) {
        return rangConditionsToTimeModelMap.get(key);
    }

    public void setJsonRsList(String json) {
        this.jsonRsList.add(json);
    }
}
  • RangConditionDTO.java
/**
 * 区间查询
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class RangConditionDTO implements Serializable {

    private static final long serialVersionUID = 4872669865514539066L;
    /**
     * 开始区间
     */
    private String beginValue;

    /**
     * 结束区间
     */
    private String endValue;
}
  • RangConditionsToTimeModelDTO.java
/**
 * 时间区间查询
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class RangConditionsToTimeModelDTO implements Serializable {

    private static final long serialVersionUID = -1649669156877508723L;
    /**
     * 开始时间
     */
    private Timestamp beginTime;

    /**
     * 结束时间
     */
    private Timestamp endTime;
}
2.6 操作ES的工具类

内涵方法有单条保存数据、批量保存数据(同步)、批量保存数据(异步)、修改数据、批量修改数据(同步)、根据主键删除数据、批量删除ES索引、删除索引、根据主键查询索引名称、搜索 支持多种搜索方式(分页、区间、模糊、OR、IN、过滤)

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.leimingtech.modules.constant.ElasticSearchConstant;
import com.leimingtech.modules.dto.PageModelDTO;
import com.leimingtech.modules.dto.RangConditionDTO;
import com.leimingtech.modules.dto.RangConditionsToTimeModelDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.collapse.CollapseBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;
import java.util.Map;

/**
 * ES操作工具类(RestClient版)
 * 由于ES7.X移除了索引Type,此工具类只兼容ES7.X版本
 *
 * @author lixiangx@leimingtech.com
 * @date 2019/12/11 9:50
 **/
@Slf4j
@Component
public class EsDataUtils {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * ES 保存数据
     *
     * @param indexName: 索引名称
     * @param oid:       主键
     * @param paramJson: 参数Json
     * @param clazz:     保存实体的Class
     * @return 保存结果
     */
    public boolean saveData(String indexName, String oid, String paramJson, Class clazz) {

        // 判断索引是否存在
        boolean result = isIndexExists(indexName);

        if (!result) {
            boolean createResult = createIndexAndCreateMapping(indexName, FieldMappingUtils.getFieldInfo(clazz));
            if (!createResult) {
                log.info("索引[{}],主键[{}]创建失败", indexName, oid);
                return false;
            }
        }

        IndexRequest indexRequest = new IndexRequest(indexName);
        indexRequest.id(oid);
        indexRequest.source(paramJson, XContentType.JSON);

        IndexResponse response = null;
        try {
            response = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("索引[{}],主键[{}]保存异常:{}", indexName, oid, e);
            return false;
        }

        // 判断索引是新增还是修改 并对数量进行增加
        if (IndexResponse.Result.CREATED.equals(response.getResult())) {
            log.info("索引[{}],主键[{}]保存成功", indexName, oid);
            return true;
        } else if (IndexResponse.Result.UPDATED.equals(response.getResult())) {
            log.info("索引[{}],主键[{}]修改成功", indexName, oid);
            return true;
        }
        return false;
    }

    /**
     * ES 批量保存数据(同步)
     *
     * @param indexName:      索引名称
     * @param primaryKeyName: 主键名称
     * @param paramListJson:  数据集合JSON
     * @return 批量保存结果
     * @date 2019/12/10 18:34
     * @author lixiangx@leimingtech.com
     **/
    public boolean saveDataBatch(String indexName, String primaryKeyName, String paramListJson, Class clazz) {

        // 判断索引是否存在
        boolean result = isIndexExists(indexName);

        if (!result) {
            boolean createResult = createIndexAndCreateMapping(indexName, FieldMappingUtils.getFieldInfo(clazz));
            if (!createResult) {
                log.info("索引[{}]创建失败", indexName);
                return false;
            }
        }

        BulkRequest bulkRequest = packBulkIndexRequest(indexName, primaryKeyName, paramListJson);
        try {
            // 同步执行
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulk.hasFailures()) {
                for (BulkItemResponse item : bulk.getItems()) {
                    log.error("索引[{}],主键[{}]更新操作失败,状态为:[{}],错误信息:{}", indexName, item.getId(),
                            item.status(), item.getFailureMessage());
                }
                return false;
            }

            // 记录索引新增与修改数量
            Integer createdCount = 0;
            Integer updatedCount = 0;
            for (BulkItemResponse item : bulk.getItems()) {
                if (IndexResponse.Result.CREATED.equals(item.getResponse().getResult())) {
                    createdCount++;
                } else if (IndexResponse.Result.UPDATED.equals(item.getResponse().getResult())) {
                    updatedCount++;
                }
            }
            log.info("索引[{}]批量同步更新成功,共新增[{}]个,修改[{}]个", indexName, createdCount, updatedCount);
        } catch (IOException e) {
            log.error("索引[{}]批量同步更新出现异常", indexName);
            return false;
        }
        return true;
    }

    /**
     * ES 批量保存数据(异步)
     *
     * @param indexName:      索引名称
     * @param primaryKeyName: 主键名称
     * @param paramListJson:  数据集合JSON
     * @return 批量保存结果
     * @date 2019/12/10 18:34
     * @author lixiangx@leimingtech.com
     **/
    public boolean saveDataBatchAsync(String indexName, String primaryKeyName, String paramListJson) {

        BulkRequest bulkRequest = packBulkIndexRequest(indexName, primaryKeyName, paramListJson);
        try {
            //异步执行
            ActionListener<BulkResponse> listener = new ActionListener<BulkResponse>() {
                @Override
                public void onResponse(BulkResponse bulkResponse) {
                    if (bulkResponse.hasFailures()) {
                        for (BulkItemResponse item : bulkResponse.getItems()) {
                            log.error("索引[{}],主键[{}]更新操作失败,状态为:[{}],错误信息:{}", indexName, item.getId(),
                                    item.status(), item.getFailureMessage());
                        }
                    }
                }

                // 失败操作
                @Override
                public void onFailure(Exception e) {
                    log.error("索引[{}]批量异步更新出现异常:{}", indexName, e);
                }
            };
            restHighLevelClient.bulkAsync(bulkRequest, RequestOptions.DEFAULT, listener);
            log.info("异步批量更新索引[{}]中", indexName);
        } catch (Exception e) {
            log.info("异步批量更新索引[{}]出现异常:{}", indexName, e);
            return false;
        }
        return true;
    }

    /**
     * ES 修改数据
     *
     * @param indexName: 索引名称
     * @param oid:       主键
     * @param paramJson: 参数JSON
     * @return 修改结果
     * @date 2019/12/10 19:10
     * @author lixiangx@leimingtech.com
     **/
    public boolean updateData(String indexName, String oid, String paramJson) {

        UpdateRequest updateRequest = new UpdateRequest(indexName, oid);
        // 如果修改索引中不存在则进行新增
        updateRequest.docAsUpsert(true);
        updateRequest.doc(paramJson, XContentType.JSON);

        try {
            UpdateResponse updateResponse = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
            log.info("索引[{}],主键:[{}]操作结果:[{}]", indexName, oid, updateResponse.getResult());

            if (UpdateResponse.Result.CREATED.equals(updateResponse.getResult())) {
                // 新增
                log.info("索引:[{}],主键:[{}]新增成功", indexName, oid);
                return true;
            } else if (UpdateResponse.Result.UPDATED.equals(updateResponse.getResult())) {
                // 修改
                log.info("索引:[{}],主键:[{}]修改成功", indexName, oid);
                return true;
            } else if (UpdateResponse.Result.NOOP.equals(updateResponse.getResult())) {
                // 无变化
                log.info("索引:[{}] 主键:[{}] 无变化", indexName, oid);
                return true;
            }
        } catch (IOException e) {
            log.info("索引:[{}],主键:[{}] 更新异常:{}", indexName, oid, e);
            return false;
        }
        return false;
    }

    /**
     * ES 批量修改数据(同步)
     *
     * @param indexName:      索引名称
     * @param primaryKeyName: 主键名称
     * @param paramListJson:  数据集合JSON
     * @return 批量修改结果
     * @date 2019/12/10 18:34
     * @author lixiangx@leimingtech.com
     **/
    public boolean updateDataBatch(String indexName, String primaryKeyName, String paramListJson) {

        BulkRequest bulkRequest = packBulkUpdateRequest(indexName, primaryKeyName, paramListJson);
        try {
            // 同步执行
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulk.hasFailures()) {
                for (BulkItemResponse item : bulk.getItems()) {
                    log.error("索引[{}],主键[{}]修改操作失败,状态为:[{}],错误信息:{}", indexName, item.getId(),
                            item.status(), item.getFailureMessage());
                }
                return false;
            }

            // 记录索引新增与修改数量
            Integer createdCount = 0;
            Integer updatedCount = 0;
            for (BulkItemResponse item : bulk.getItems()) {
                if (IndexResponse.Result.CREATED.equals(item.getResponse().getResult())) {
                    createdCount++;
                } else if (IndexResponse.Result.UPDATED.equals(item.getResponse().getResult())) {
                    updatedCount++;
                }
            }
            log.info("索引[{}]批量修改更新成功,共新增[{}]个,修改[{}]个", indexName, createdCount, updatedCount);
        } catch (IOException e) {
            log.error("索引[{}]批量修改更新出现异常", indexName);
            return false;
        }
        return true;
    }

    /**
     * ES 根据主键删除数据
     *
     * @param indexName: 索引名称
     * @param oid:       主键ID
     * @return 操作结果
     * @date 2019/12/11 9:53
     * @author lixiangx@leimingtech.com
     **/
    public boolean deleteDate(String indexName, String oid) {

        DeleteRequest deleteRequest = new DeleteRequest(indexName);
        deleteRequest.id(oid);

        try {
            DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            if (deleteResponse.getResult() == DocWriteResponse.Result.NOT_FOUND) {
                log.error("索引[{}]主键[{}]删除失败", indexName, oid);
                return false;
            } else {
                log.info("索引[{}]主键[{}]删除成功", indexName, oid);
                return true;
            }
        } catch (IOException e) {
            log.error("删除索引[{}]出现异常[{}]", indexName, e);
            return false;
        }
    }

    /**
     * ES 批量删除ES索引
     *
     * @param indexName: 索引名称
     * @param ids:       主键集合
     * @return 操作结果
     * @date 2019/12/10 20:46
     * @author lixiangx@leimingtech.com
     **/
    public boolean bulkDelete(String indexName, List<Long> ids) {

        BulkRequest bulkRequest = packBulkDeleteRequest(indexName, ids);

        try {
            // 同步执行
            BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            if (bulk.hasFailures()) {
                for (BulkItemResponse item : bulk.getItems()) {
                    log.error("更新索引:{},主键:{}失败。信息为:{}", indexName, item.getId(), item.getFailureMessage());
                }
                return false;
            }

            // 记录索引新增与修改数量
            Integer deleteCount = 0;
            for (BulkItemResponse item : bulk.getItems()) {
                if (DeleteResponse.Result.DELETED.equals(item.getResponse().getResult())) {
                    deleteCount++;
                }
            }
            log.info("批量删除索引[{}]成功,共删除[{}]个", indexName, deleteCount);
        } catch (IOException e) {
            log.error("删除索引:{}批量保存数据出现异常:{}", indexName, e);
            return false;
        }
        return true;
    }

  
    /**
     * ES 删除索引
     *
     * @param indexName: 索引名称
     * @return 操作结果
     * @date 2019/12/11 16:59
     * @author lixiangx@leimingtech.com
     **/
    public boolean deleteIndex(String indexName) {
        // 判断索引是否存在
        boolean result = isIndexExists(indexName);
        if (!result) {
            log.error("索引[{}]不存在删除索引失败", indexName);
            return false;
        }
        try {
            DeleteIndexRequest deleteIndexRequest = new DeleteIndexRequest(indexName);
            deleteIndexRequest.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN);
            AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(deleteIndexRequest, RequestOptions.DEFAULT);
            if (!acknowledgedResponse.isAcknowledged()) {
                log.error("索引[{}]删除失败", indexName);
            }
            log.info("索引[{}]删除成功", indexName);
            return true;
        } catch (IOException e) {
            log.error("索引[{}]删除异常:{}", indexName, e);
        }
        return false;
    }

    /**
     * ES 根据主键查询索引名称
     *
     * @param indexName: 索引名称
     * @param oid:       主键
     * @return 返回数据JSON
     * @date 2019/12/11 9:48
     * @author lixiangx@leimingtech.com
     **/
    public String getDateById(String indexName, String oid) {

        // 判断索引是否存在
        if (!isIndexExists(indexName)) {
            return "";
        }

        GetRequest getRequest = new GetRequest(indexName, oid);
        try {
            GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
            String resultJson = getResponse.getSourceAsString();
            log.debug("索引[{}]主键[{}]查询结果[{}]", indexName, oid, resultJson);
            return resultJson;
        } catch (IOException e) {
            log.debug("索引[{}]主键[{}]查询异常:{}", indexName, oid, e);
            return "";
        }
    }

    /**
     * ES 搜索 支持多种搜索方式(分页、区间、模糊、OR、IN、过滤)
     *
     * @param pageModel: 搜索对象封装的实体
     * @param indexName: 索引名称
     * @return 查询返回的实体
     * @date 2019/12/11 10:00
     * @author lixiangx@leimingtech.com
     **/
    public PageModelDTO queryData(PageModelDTO pageModel, String indexName) {

        // 判断索引是否存在
        if (!isIndexExists(indexName)) {
            return pageModel;
        }
        // 获取页码、页面大小
        int pageStart = 0;
        int pageSize = 10000;
        if (pageModel.getIsPage()) {
            pageSize = pageModel.getPageSize();
            pageStart = (pageModel.getPageNum() - 1) * pageSize;
        }

        // 封装boolBuilder
        QueryBuilder boolBuilder = queryBuilder(pageModel);

        // 封装SearchSourceBuilder
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        searchSourceBuilder
                // 设置查询关键词
                .query(boolBuilder)
                // 设置查询数据的位置,分页用
                .from(pageStart)
                // 设置查询结果集的最大条数
                .size(pageSize)
                // 不展示分析逻辑
                .explain(false)
                // 配置高亮
                .highlighter(this.getHighlightBuilder(pageModel));

        //指定查询包含的字段,指定查询不包含的字段
        if (pageModel.getFetchSourceIncludes() != null || pageModel.getFetchSourceExcludes() != null) {
            searchSourceBuilder.fetchSource(pageModel.getFetchSourceIncludes(), pageModel.getFetchSourceExcludes());
        }

        //排序list,以安排排序的优先级顺序
        for (Map<String, String> map : pageModel.getSortFiledsList()) {
            for (String sortKey : map.keySet()) {
                if (ElasticSearchConstant.SORT_ASC.equalsIgnoreCase(pageModel.getSortFileds(sortKey))) {
                    searchSourceBuilder.sort(SortBuilders.fieldSort(sortKey).order(SortOrder.ASC));
                } else if (ElasticSearchConstant.DESC_ASC.equalsIgnoreCase(pageModel.getSortFileds(sortKey))) {
                    searchSourceBuilder.sort(SortBuilders.fieldSort(sortKey).order(SortOrder.DESC));
                }
            }
        }

        // 设置排序字段
        for (String sortKey : pageModel.getSortFileds().keySet()) {
            if (ElasticSearchConstant.SORT_ASC.equalsIgnoreCase(pageModel.getSortFileds(sortKey))) {
                searchSourceBuilder.sort(SortBuilders.fieldSort(sortKey).order(SortOrder.ASC));
            } else if (ElasticSearchConstant.DESC_ASC.equalsIgnoreCase(pageModel.getSortFileds(sortKey))) {
                searchSourceBuilder.sort(SortBuilders.fieldSort(sortKey).order(SortOrder.DESC));
            }
        }

        if (StringUtils.isNotBlank(pageModel.getCollapseField())) {
            searchSourceBuilder.collapse(new CollapseBuilder(pageModel.getCollapseField()));
        }

        SearchRequest searchRequest = new SearchRequest(indexName);
        searchRequest.source(searchSourceBuilder);

        // 设置查询类型
        // 1.SearchType.DFS_QUERY_THEN_FETCH = 精确查询
        // 2.SearchType.SCAN = 扫描查询,无序
        // 3.SearchType.COUNT = 不设置的话,这个为默认值,
        searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH);
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits searchHits = response.getHits();
            pageModel.setTotal(searchHits.getTotalHits().value);
            log.debug("共匹配到:" + searchHits.getTotalHits().value + "条记录!");

            SearchHit[] hits = searchHits.getHits();
            pageModel.setSearchHits(hits);
            for (SearchHit searchHit : hits) {
                Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                String json = JSON.toJSONString(sourceAsMap);
                pageModel.setJsonRsList(json);
            }
        } catch (IOException e) {
            log.error("查询索引[{}]数据出现异常{}", indexName, e);
            return pageModel;
        }

        return pageModel;
    }

    /**
     * 拼接高亮字段
     *
     * @param pageModel
     * @return
     */
    private HighlightBuilder getHighlightBuilder(PageModelDTO pageModel) {
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<span style='color:red;'>");
        highlightBuilder.postTags("</span>");

        List<String> fields = pageModel.getHightFieldList();

        for (String field : fields) {
            highlightBuilder.field(field);
        }
        return highlightBuilder;
    }

    /**
     * 拼接查询条件
     *
     * @param pageModel
     * @return
     */
    private BoolQueryBuilder queryBuilder(PageModelDTO pageModel) {
        BoolQueryBuilder boolBuilder = QueryBuilders.boolQuery();
        // 区间查询
        sectionSearch(pageModel, boolBuilder);
        // 模糊查询
        likeSearch(pageModel, boolBuilder);

        // or拼接查询
        BoolQueryBuilder orBoolQueryBuilder = QueryBuilders.boolQuery();
        Map<String, Object> orSearchCondition = pageModel.getOrSearchCondition();
        filterOrQuery(orSearchCondition, orBoolQueryBuilder);
        boolBuilder.must(orBoolQueryBuilder);

        // and (or)拼接查询
        for (Map<String, Object> searchCondition : pageModel.getOrSearchConditionList()) {
            BoolQueryBuilder termBoolQueryBuilder = QueryBuilders.boolQuery();
            filterOrQuery(searchCondition, termBoolQueryBuilder);
            boolBuilder.must(termBoolQueryBuilder);
        }

        // in查询
        for (String key : pageModel.getInSearchCondition().keySet()) {
            BoolQueryBuilder ptermsBoolQueryBuilder = QueryBuilders.boolQuery();
            TermsQueryBuilder termQueryBuilder = QueryBuilders.termsQuery(key, pageModel.getInSearchCondition().get(key));
            ptermsBoolQueryBuilder.should(termQueryBuilder);
            TermsQueryBuilder termQueryBuilder2 = QueryBuilders.termsQuery(key + ".keyword", pageModel.getInSearchCondition().get(key));
            ptermsBoolQueryBuilder.should(termQueryBuilder2);
            boolBuilder.must(ptermsBoolQueryBuilder);
        }

        // 为空匹配
        for (String key : pageModel.getIsNullConditioin()) {
            ExistsQueryBuilder existsQueryBuilder = QueryBuilders.existsQuery(key);
            boolBuilder.mustNot(existsQueryBuilder);
        }
        // 非空匹配
        for (String key : pageModel.getIsNullConditioin()) {
            ExistsQueryBuilder existsQueryBuilder = QueryBuilders.existsQuery(key);
            boolBuilder.must(existsQueryBuilder);
        }

        return boolBuilder;
    }


    /**
     * 区间查询
     *
     * @param pageModel
     * @param boolBuilder
     * @return
     */
    private BoolQueryBuilder sectionSearch(PageModelDTO pageModel, BoolQueryBuilder boolBuilder) {
        // 时间区间查询
        for (String key : pageModel.getRangConditionsToTimeModelMap().keySet()) {
            RangConditionsToTimeModelDTO rangConditionsToTimeModelMap = pageModel.getRangConditionsToTimeModelMap(key);
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(key);
            if (null != rangConditionsToTimeModelMap.getBeginTime()) {
                rangeQueryBuilder.gte(rangConditionsToTimeModelMap.getBeginTime().getTime());
            }
            if (null != rangConditionsToTimeModelMap.getEndTime()) {
                rangeQueryBuilder.lte(rangConditionsToTimeModelMap.getEndTime().getTime());
            }

            // 包括下界
            rangeQueryBuilder.includeLower(true);
            // 包括上界
            rangeQueryBuilder.includeUpper(false);
            boolBuilder.must(rangeQueryBuilder);
            log.debug("rangeQueryBuilder:" + rangeQueryBuilder);

        }

        // 其他数据区间查询
        for (String key : pageModel.getRangConditionMap().keySet()) {
            RangConditionDTO rangConditionMap = pageModel.getRangConditionMap(key);
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery(key);
            if (StringUtils.isNotBlank(rangConditionMap.getBeginValue())) {
                rangeQueryBuilder.gte(rangConditionMap.getBeginValue());
            }
            if (StringUtils.isNotBlank(rangConditionMap.getEndValue())) {
                rangeQueryBuilder.lte(rangConditionMap.getEndValue());

            }
            // 包括下界
            rangeQueryBuilder.includeLower(true);
            // 包括上界
            rangeQueryBuilder.includeUpper(false);
            boolBuilder.must(rangeQueryBuilder);
            log.debug("rangeQueryBuilder:" + rangeQueryBuilder);
        }
        return boolBuilder;
    }

    /**
     * 模糊匹配
     *
     * @param pageModel
     * @param boolBuilder
     * @return
     */
    private BoolQueryBuilder likeSearch(PageModelDTO pageModel, BoolQueryBuilder boolBuilder) {
        // 模糊匹配查询
        for (String key : pageModel.getLikeSearchCondition().keySet()) {
            Object machValue = pageModel.getLikeSearchCondition().get(key);
            if (null == machValue) {
                continue;
            }
            BoolQueryBuilder wildcardBoolQueryBuilder = QueryBuilders.boolQuery();
            for (String value : machValue.toString().split(ElasticSearchConstant.SPLIT_FLAG)) {

                WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery(key, "*" + value + "*");
                wildcardBoolQueryBuilder.should(wildcardQueryBuilder);
            }
            boolBuilder.must(wildcardBoolQueryBuilder);
        }
        // 精确匹配查询
        for (String key : pageModel.getEqualsSearchCondition().keySet()) {
            Object machValue = pageModel.getEqualsSearchCondition().get(key);
            if (null == machValue) {
                continue;
            }
            BoolQueryBuilder ptermBoolQueryBuilder = QueryBuilders.boolQuery();
            filterQuery(key, machValue, ptermBoolQueryBuilder);
            boolBuilder.must(ptermBoolQueryBuilder);

        }
        // 精确过滤查询
        for (String key : pageModel.getNoEqualsSearchConditioin().keySet()) {
            Object noMachValue = pageModel.getNoEqualsSearchConditioin().get(key);
            if (null == noMachValue) {
                continue;
            }
            BoolQueryBuilder noBoolQueryBuilder = QueryBuilders.boolQuery();

            filterQuery(key, noMachValue, noBoolQueryBuilder);

            boolBuilder.mustNot(noBoolQueryBuilder);
        }
        //模糊过滤查询
        for (String key : pageModel.getNoLikeSearchConditioin().keySet()) {
            Object noMachValue = pageModel.getNoLikeSearchConditioin().get(key);
            BoolQueryBuilder noBoolQueryBuilder = QueryBuilders.boolQuery();
            if (null == noMachValue) {
                continue;
            }
            for (String value : noMachValue.toString().split(ElasticSearchConstant.SPLIT_FLAG)) {
                WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery(key, value);
                noBoolQueryBuilder.should(wildcardQueryBuilder);
            }
            boolBuilder.mustNot(noBoolQueryBuilder);

        }
        return boolBuilder;
    }


    /**
     * 或条件拼接
     *
     * @param orSearchCondition
     * @param termBoolQueryBuilder
     */
    private void filterOrQuery(Map<String, Object> orSearchCondition, BoolQueryBuilder termBoolQueryBuilder) {
        for (String key : orSearchCondition.keySet()) {
            Object value = orSearchCondition.get(key);
            if (null == value) {
                continue;
            }
            BoolQueryBuilder sueryBuilder = QueryBuilders.boolQuery();
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(key + ".keyword", value);
            sueryBuilder.should(termQueryBuilder);
            TermQueryBuilder longtermQueryBuilder = QueryBuilders.termQuery(key, value);
            sueryBuilder.should(longtermQueryBuilder);
            termBoolQueryBuilder.should(sueryBuilder);
        }
    }

    /**
     * 非条件拼接
     *
     * @param key
     * @param noMachValue
     * @param noBoolQueryBuilder
     */
    private void filterQuery(String key, Object noMachValue, BoolQueryBuilder noBoolQueryBuilder) {
        for (String cv : noMachValue.toString().split(ElasticSearchConstant.SPLIT_FLAG)) {
            BoolQueryBuilder termBoolQueryBuilder = QueryBuilders.boolQuery();
            TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery(key + ".keyword", cv);
            termBoolQueryBuilder.should(termQueryBuilder);
            TermQueryBuilder longtermQueryBuilder = QueryBuilders.termQuery(key, cv);
            termBoolQueryBuilder.should(longtermQueryBuilder);
            noBoolQueryBuilder.should(termBoolQueryBuilder);
        }
    }

    /**
     * 判断索引是否存在(ES7.X)
     *
     * @param indexName: 索引名称
     * @return 是否存在结果
     * @date 2019/12/10 17:01
     * @author lixiangx@leimingtech.com
     **/
    public boolean isIndexExists(String indexName) {
        boolean exists = false;
        try {
            GetIndexRequest getIndexRequest = new GetIndexRequest(indexName);
            getIndexRequest.humanReadable(true);
            exists = restHighLevelClient.indices().exists(getIndexRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("判断索引:{} 是否存在异常,异常内容:{}", indexName, e);
        }
        return exists;
    }

    /**
     * 根据信息自动创建索引与mapping
     * 构建mapping描述
     *
     * @param indexName        索引名称
     * @param fieldMappingList 字段信息
     * @return 创建结果
     */
    private boolean createIndexAndCreateMapping(String indexName, List<FieldMapping> fieldMappingList) {
        try {
            // 开始封装ES索引的Mapping
            XContentBuilder mapping = packESMapping(fieldMappingList, null);
            mapping.endObject().endObject();

            // 进行索引的创建
            CreateIndexRequest createIndexRequest = new CreateIndexRequest(indexName);
            createIndexRequest.mapping(mapping);
            CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
            boolean acknowledged = createIndexResponse.isAcknowledged();
            if (acknowledged) {
                log.info("索引:{}创建成功", indexName);
                return true;
            } else {
                log.error("索引:{}创建失败", indexName);
                return false;
            }
        } catch (IOException e) {
            log.error("创建索引:{},出现异常:{}", indexName, e);
            return false;
        }
    }

    /**
     * 获取XContentBuilder实体创建ES的Mapping
     *
     * @param fieldMappingList: 实体对象的类型集合
     * @param mapping:          XContentBuilder实体
     * @return XContentBuilder实体
     * @date 2019/12/12 16:50
     * @author lixiangx@leimingtech.com
     **/
    private XContentBuilder packESMapping(List<FieldMapping> fieldMappingList, XContentBuilder mapping) throws IOException {

        if (mapping == null) {
            // 如果对象是空,首次进入,设置开始节点
            mapping = XContentFactory.jsonBuilder()
                    .startObject()
                    .startObject("properties");
        }

        // 循环实体对象的类型集合封装ES的Mapping
        for (FieldMapping info : fieldMappingList) {
            String field = info.getField();
            String dateType = info.getType();

            // 类型为空默认设置为string
            if (StringUtils.isBlank(dateType)) {
                dateType = "string";
            }
            dateType = dateType.toLowerCase();
            int participle = info.getParticiple();
            if ("string".equals(dateType)) {
                // 设置分词规则
                if (participle == 0) {
                    mapping.startObject(field)
                            .field("type", "keyword")
                            .endObject();
                } else if (participle == 1) {
                    mapping.startObject(field)
                            .field("type", "text")
                            .field("analyzer", "ik_smart")
                            .endObject();
                } else if (participle == 2) {
                    mapping.startObject(field)
                            .field("type", "text")
                            .field("analyzer", "ik_max_word")
                            .endObject();
                }
            } else if ("text".equals(dateType)) {
                // TODO lixiang 待优化
                mapping.startObject(field).field("fielddata", true)
                        .field("type", dateType).startObject("fields").startObject("keyword")
                        .field("ignore_above", 256).field("type", "keyword")
                        .endObject().endObject().endObject();
            } else if ("date".equals(dateType)) {
                // 设置时间格式
                mapping.startObject(field)
                        .field("type", dateType)
                        .field("format", "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis")
                        .endObject();
            } else if ("float".equals(dateType) || "double".equals(dateType)) {
                // 设置小数类型
                mapping.startObject(field)
                        .field("type", "scaled_float")
                        .field("scaling_factor", 100)
                        .endObject();
            } else if ("nested".equals(dateType)) {
                // 设置聚合类型
                mapping.startObject(field)
                        .field("type", dateType)
                        .startObject("properties");
                // 由于nested类型内嵌文档,因此需要封装内嵌文档的Mapping
                mapping = packESMapping(info.getFieldMappingList(), mapping);
                // 设置尾部节点
                mapping.endObject().endObject();
            } else {
                // object类型
                if (info.getFieldMappingList() != null) {
                    // 内嵌文档循环封装(Java类对象中嵌套对象)
                    mapping.startObject(field).startObject("properties");
                    mapping = packESMapping(info.getFieldMappingList(), mapping);
                    mapping.endObject().endObject();
                } else {
                    // 常量类型配置
                    mapping.startObject(field)
                            .field("type", dateType)
                            .field("index", true)
                            .endObject();
                }
            }
        }

        return mapping;
    }


    /**
     * 创建mapping
     *
     * @param indexName 索引
     * @param clazz     索引类型
     */
    public boolean createIndexAndCreateMapping(String indexName, Class clazz, boolean dropOldIndex) {

        if (this.isIndexExists(indexName)) {
            if (dropOldIndex) {
                if (deleteIndex(indexName)) {
                    return createIndexAndCreateMapping(indexName, FieldMappingUtils.getFieldInfo(clazz));
                } else {
                    log.error("索引[{}]删除失败", indexName);
                    return false;
                }
            }
            log.info("索引[{}]已经存在,忽略创建索引", indexName);
            return true;
        } else {
            return createIndexAndCreateMapping(indexName, FieldMappingUtils.getFieldInfo(clazz));
        }
    }

    /**
     * 获取批量操作的Request
     *
     * @param indexName:      索引名称
     * @param primaryKeyName: 主键名称
     * @param paramListJson:  数据集合JSON
     * @return BulkRequest对象
     * @date 2019/12/10 18:53
     * @author lixiangx@leimingtech.com
     **/
    private BulkRequest packBulkIndexRequest(String indexName, String primaryKeyName, String paramListJson) {
        BulkRequest bulkRequest = new BulkRequest();
        JSONArray jsonArray = JSONArray.parseArray(paramListJson);

        if (jsonArray == null) {
            return bulkRequest;
        }

        // 循环数据封装bulkRequest
        jsonArray.forEach(obj -> {
            Map<String, Object> map = (Map<String, Object>) obj;
            IndexRequest indexRequest = new IndexRequest(indexName);
            indexRequest.id(String.valueOf(map.get(primaryKeyName)));
            indexRequest.source(JSON.toJSONString(obj), XContentType.JSON);
            bulkRequest.add(indexRequest);
        });
        return bulkRequest;
    }


    /**
     * 获取批量操作的Request
     *
     * @param indexName:      索引名称
     * @param primaryKeyName: 主键名称
     * @param paramListJson:  数据集合JSON
     * @return BulkRequest对象
     * @date 2019/12/10 18:53
     * @author lixiangx@leimingtech.com
     **/
    private BulkRequest packBulkUpdateRequest(String indexName, String primaryKeyName, String paramListJson) {
        BulkRequest bulkRequest = new BulkRequest();
        JSONArray jsonArray = JSONArray.parseArray(paramListJson);
        if (jsonArray == null) {
            return bulkRequest;
        }

        jsonArray.forEach(obj -> {
            Map<String, Object> map = (Map<String, Object>) obj;
            UpdateRequest updateRequest = new UpdateRequest(indexName, String.valueOf(map.get(primaryKeyName)));
            // 如果修改索引中不存在则进行新增
            updateRequest.docAsUpsert(true);
            updateRequest.doc(JSON.toJSONString(obj), XContentType.JSON);
            bulkRequest.add(updateRequest);
        });
        return bulkRequest;
    }

    /**
     * 获取批量操作的实体
     *
     * @param indexName: 索引名称
     * @param ids:       主键ID集合
     * @return BulkRequest批量操作对象
     * @date 2019/12/10 20:53
     * @author lixiangx@leimingtech.com
     **/
    private BulkRequest packBulkDeleteRequest(String indexName, List<Long> ids) {
        BulkRequest bulkRequest = new BulkRequest();

        ids.forEach(id -> {
            DeleteRequest deleteRequest = new DeleteRequest(indexName);
            deleteRequest.id(String.valueOf(id));
            bulkRequest.add(deleteRequest);
        });
        return bulkRequest;
    }

}

到此RestHighLevelClient操作Elasticsearch7.X的工具类已经封装完毕,项目中使用时可以将ES工具类单独抽取成一个独立的模块,需要使用的模块增加ES工具类模块的依赖集合。

感谢以下文章

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值