SpringBoot 3.x集成ES 8.X 工具类整理

  近期用到了spring boot3.0,当然jdk也一起跟着升级到了17,后面有需求要用到ES,前后捣鼓了一整子发现,大部分的人都还在用7.X及以下的ES,所以找了很久的资料,主要是ES的语法有点不太好用,一大堆的lambda,索性就自己封装了一个工具类,语法类似于mybatis-plus,这样的语法我想大家应该都比较熟悉,话不多说了,直接上代码

项目结构

TestController
package com.xxx.xxx.es.test.controller;

import co.elastic.clients.elasticsearch._types.query_dsl.RegexpQuery;
import com.alibaba.fastjson2.JSONObject;
import com.xxx.xxx.es.test.entity.User;
import com.xxx.xxx.es.test.utils.ElasticsearchUtils;
import com.xxx.xxx.es.test.utils.ElasticsearchUtils.*;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;


@RestController
@RequestMapping("/es")
@RequiredArgsConstructor
public class TestController {

    private final ElasticsearchUtils elasticsearchUtils;

    @GetMapping("/test")
    public Object test() throws IOException {
        String index = "user-index";
        //删除索引测试
        deleteIndexTest(index);
        //创建索引测试
        createIndexTest(index);
        //新增数据测试
        addTest(index);
        //删除数据测试
        deleteTest(index);
        //修改数据测试
        updateTest(index);
        //条件查询测试
        List<?> listResult = getListTest(index);
        if (false){
            return getResultData(listResult);
        }
        //分页查询测试
        List<?> pageResult = getPageTest(index);
        if (false){
            return getResultData(pageResult);
        }
        //count
        long count = elasticsearchUtils.count(new EsSearchWrapper(index));
        //ES未封装方法
        List<?> esFunResult = getFunResultTest(index);
        if (true){
            return getResultData(esFunResult);
        }
        //最后我还做了分组的聚合,es虽然提供了分组的功能,但是没有帮我们把数据聚合,这里我按mysql的逻辑聚合了,用法也与之相似
        //分组查询测试
        List<Object> groupList = getGroupListTest(index);
        return getResultData(groupList);
    }

    private List<Object> getGroupListTest(String index) throws IOException {
        //加点数据
        List<User> addBatchList = new ArrayList<>();
        User user = new User();
        user.setId(9);
        user.setName("赵六小儿");
        user.setSex(1);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(10);
        user.setName("孙七小儿");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        elasticsearchUtils.addBatch(index, addBatchList);
        //方法1
//        EsSearchWrapper groupWrapper = new EsSearchWrapper<>(index);
//        groupWrapper.groupBy("sex", "name.keyword");
        //当然这里可以不select,我就没有让它报错(和mysql不同,mysql会报错), 但是如果只是要这两个字分组,就没必要查其他字段了
        //groupWrapper.select("sex","name");
        //lambda也支持,基本一直
        //EsSearchWrapper<User> groupWrapper = new EsSearchWrapper<>();
        //List<User> groupList = elasticsearchUtils.list(groupWrapper, User.class);
        //相当于 select sex,name from user group by sex,name
//        List<Object> groupList = elasticsearchUtils.list(groupWrapper);
        //方法2
        //分组如果要使用到函数的话 比如:max,min等 就要调用另一个重载方法
        EsSearchWrapper groupWrapper = new EsSearchWrapper<>(index);
        groupWrapper.groupBy(EsFunction.of("sex")
                , EsFunction.of("name.keyword")
                , EsFunction.of("id", EsFunctionEnum.SUM));
        //相当于 select sex,name,sum(id) from user group by sex,name
        List<Object> groupList = elasticsearchUtils.list(groupWrapper);
        return groupList;
    }

    private List<?> getFunResultTest(String index) throws IOException {
        //当然ES和mysql还是有点区别的,我所以保留了一些方法
        List<Object> funRestulList = elasticsearchUtils.list(new EsSearchWrapper<>(index)
                //分词,模糊查询 相当于 select * from user where (name like '%赵%' or name like '%小%')
                //当然ES 用的是倒排索引,效率会比mysql高  这里只是说查询出来的效果相似
//                .match(MatchQuery.of(f -> f.field("name").query("赵小")))
                //精确查询
//                .term(TermQuery.of(f -> f.field("name").value("赵六小儿")))
                //keyword text whidcard 等String类型 不支持
//                .range(RangeQuery.of(f -> f.field("id").gte(JsonData.of("7"))))
                //就是match的缩减版 通过slop 这个去控制范围
                //slop == 1 相当于 select * from user where name like '%赵_小%'
                //slop == 2 相当于 select * from user where (name like '%赵_小%' or name like '%赵__小%')
                //slop == 3 相当于 select * from user where (name like '%赵_小%' or name like '%赵__小%' or name like '%赵___小%')
                //以此类推
//                .matchPhrase(MatchPhraseQuery.of(f -> f.field("name").query("赵小").slop(1)))
                //类似 select * from user where name like '赵%'
//                .prefix(PrefixQuery.of(f -> f.field("name").value("赵")))
//                //*:通配符   ?:占位符   类似like      *相当于%  ?相当于_
//                .wildcard(WildcardQuery.of(f -> f.field("name.keyword").value("赵*??")))
                //就是允许你错别字,非特殊情况还是别用,非常消耗性能不说,还很容易和产品扯皮
//                .fuzzy(FuzzyQuery.of(f -> f.field("name.keyword").value("赵漏小儿")))
                //正则匹配 value 正则就是表达式
                .regexp(RegexpQuery.of(f -> f.field("name.keyword").value(".*")))
            );
        return funRestulList;
    }

    private List<?> getPageTest(String index) throws IOException {
        //分页查询
        EsPage<User> esPage = new EsPage<>(1, 10);
        EsSearchWrapper<User> pageWrapper = new EsSearchWrapper<>(index);
        pageWrapper.like(User::getName, "小儿");
        //注意:是and里需要拼接or的条件则必须先调用.or(),调用一次后,后面的拼接条件都是or
        pageWrapper.and(wrapper -> wrapper.or().eq(User::getId, "6").eq(User::getId, "7"));
        elasticsearchUtils.page(esPage, pageWrapper, User.class);
        return esPage.getRecords();
    }

    private List<?> getListTest(String index) throws IOException {
        //条件查询(全量滚动查询)
        EsSearchWrapper<User> listWrapper = new EsSearchWrapper<>(index);
        //如果AUTO或Keyword类型,会自动拼接.keyword 默认精确匹配
        listWrapper.like(User::getName, "小儿");
//        listWrapper.like("name.keyword", "小儿");
        List<User> userList = elasticsearchUtils.list(listWrapper, User.class);
        return userList;
    }

    private void updateTest(String index) throws IOException {
        //修改方法1
        User user = new User();
        user.setId(6);
        user.setName("赵六小儿");
        elasticsearchUtils.updateById(index, user);
        //修改方法2
//        EsUpdateWrapper<User> esUpdateWrapper = new EsUpdateWrapper<>(index);
//        esUpdateWrapper.doc(user);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esUpdateWrapper.refresh(true);
        List<User> updateBatchList = new ArrayList<>();
        user = new User();
        user.setId(7);
        user.setName("孙七小儿");
        updateBatchList.add(user);
        user = new User();
        user.setId(8);
        user.setName("周八小儿");
        updateBatchList.add(user);
        //批量修改1
        elasticsearchUtils.updateBatch(index, updateBatchList);
        //批量修改2
//        EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esBulkWrapper.refresh(true);
//        esBulkWrapper.update(updateBatchList);
//        elasticsearchUtils.exec(esBulkWrapper);
    }

    private void deleteTest(String index) throws IOException {
        //删除方法1
        elasticsearchUtils.deleteById(index, "3");
        //删除方法2
//        EsDeleteWrapper esDeleteWrapper = new EsDeleteWrapper();
//        esDeleteWrapper.id("1");
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esDeleteWrapper.refresh(true);
//        elasticsearchUtils.delete(esDeleteWrapper);
        //批量删除方法1
        elasticsearchUtils.deleteByIds(index, List.of("4", "5"));
        //批量删除方法2
//        EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esBulkWrapper.refresh(true);
//        esBulkWrapper.delete(List.of("2","3"));
//        elasticsearchUtils.exec(esBulkWrapper);
    }

    private void addTest(String index) throws IOException {
        User user = new User();
        user.setId(3);
        user.setName("张三");
        user.setSex(1);
        user.setCreateTime(new Date());
        //方法1
        elasticsearchUtils.add(index, user);
        //方法2
//        EsCreateWrapper<User> esCreateWrapper = new EsCreateWrapper<>(index);
//        esCreateWrapper.document(user);
//        elasticsearchUtils.add(esCreateWrapper);
        List<User> addBatchList = new ArrayList<>();
        user = new User();
        user.setId(4);
        user.setName("李四");
        user.setSex(1);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(5);
        user.setName("王五");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(6);
        user.setName("赵六");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(7);
        user.setName("孙七");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        user = new User();
        user.setId(8);
        user.setName("周八");
        user.setSex(2);
        user.setCreateTime(new Date());
        addBatchList.add(user);
        //批量新增方法1
        elasticsearchUtils.addBatch(index, addBatchList);
        //批量新增方法2
//        EsBulkWrapper<User> esBulkWrapper = new EsBulkWrapper<>(index);
//        //是否刷盘(true:更新成功后才返回,false:发送指令后直接返回成功,即异步更新)
//        esBulkWrapper.refresh(true);
//        esBulkWrapper.create(addBatchList);
//        elasticsearchUtils.exec(esBulkWrapper);
    }

    private void createIndexTest(String index) throws IOException {
        EsCreateIndexWrapper esCreateIndexWrapper = new EsCreateIndexWrapper(index);
        //如果不设置,每次查询最多返回10000条,当然这个要根据情况而定设置太大会影响性能
        esCreateIndexWrapper.setMaxResultWindow(100000);
        /**
         * 无论新增还是编辑,目前es是自动支持动态字段,所以加索引直接加实体字段即可,不需要主动去设置
         * 当然如果自定义的话可以调用下面setDocClass方法
         * 取@Field注解上的值  可以不设置,那么字段都是自动生成类型
         */
        esCreateIndexWrapper.setDocClass(User.class);
        elasticsearchUtils.createIndex(esCreateIndexWrapper);
    }

    private void deleteIndexTest(String index) throws IOException {
        //删除索引方法1
        elasticsearchUtils.deleteIndex(index);
        //删除索引方法2
//        EsDeleteIndexWrapper esDeleteIndexWrapper = new EsDeleteIndexWrapper(index);
//        elasticsearchUtils.deleteIndex(esDeleteIndexWrapper);
    }

    private String getResultData(List<?> result) {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>");
        html.append("<html>");
        html.append("<head>");
        html.append("<meta charset=\"utf-8\" />");
        html.append("<title></title>");
        html.append("</head>");
        html.append("<body>");
        html.append("<table border=\"1\" width=\"50%\" align=\"left\">");
        boolean title = true;
        for (Object p : result) {
            JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(p));
            if (title) {
                html.append("<tr>");
                for (String key : jsonObject.keySet()) {
                    html.append("<td>").append(key).append("</td>");
                }
                title = false;
            }
            html.append("</tr>");
            html.append("<tr>");
            for (String key : jsonObject.keySet()) {
                html.append("<td>").append(Objects.nonNull(jsonObject.get(key)) ? String.valueOf(jsonObject.get(key)) : "").append("</td>");
            }
            html.append("</tr>");
        }
        html.append("</table>");
        html.append("</body>");
        html.append("</html>");
        return html.toString();
    }


}
User
package com.xxx.xxx.es.test.entity;

import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.io.Serializable;
import java.util.Date;


@Data
public class User implements Serializable {
	//必须有一个id主键字段
	@Field(type= FieldType.Auto)
	private Integer id;
	//自动生成text+keyword,既支持分词又支持全匹配,当然如果数据量过大且需要模糊查询的话,会影响性能,可以考虑用Wildcard
	@Field(type= FieldType.Auto)
	private String name;
	/**
	 * 1:男 0:女
	 */
	@Field(type= FieldType.Integer)
	private Integer sex;
	@Field(type= FieldType.Date)
	private Date createTime;
}
ElasticsearchUtils
package com.xxx.xxx.es.test.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.Refresh;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.SourceFilter;
import co.elastic.clients.elasticsearch.indices.*;
import co.elastic.clients.json.JsonData;
import co.elastic.clients.util.ObjectBuilder;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;

/**
 * @author: chr
 * @since: 1.0.0
 * @date: 2024/2/28 11:21
 * @des 基于8.7.1版本客户端代码封装
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class ElasticsearchUtils {

    private final ElasticsearchClient elasticsearchClient;

    private static final String ID_FIELD = "id";

    private static final String FLAG_KEYWORD = ".keyword";
    //这个建议放到缓存中,如redis等,这个定义了静态是不会被回收的,是一种内存泄漏,这里只是案例,当然如果你的系统小型,接口不多,内存也足够,影响忽略不计
    private static ConcurrentHashMap<Class<?>, String> lambdaCach = new ConcurrentHashMap<>(16);

    /**
     * 创建索引
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public boolean createIndex(EsCreateIndexWrapper wrapper) throws IOException {
        wrapper.initIndexSetting();
        log.info("索引:{} 创建-dsl json:{}", wrapper.getIndex(), wrapper.getCreateIndexRequestDsl().build().toString());
        CreateIndexResponse createIndexResponse = elasticsearchClient.indices().create(c -> wrapper.getCreateIndexRequest());
        log.info("索引:{} 创建成功{}", wrapper.getIndex(), createIndexResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 删除索引
     *
     * @param index 索引名称
     * @return
     * @throws IOException
     */
    public boolean deleteIndex(String index) throws IOException {
        return deleteIndex(new EsDeleteIndexWrapper(index));
    }

    /**
     * 删除索引
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public boolean deleteIndex(EsDeleteIndexWrapper wrapper) throws IOException {
        if (elasticsearchClient.indices().exists(request -> request.index(wrapper.getIndex())).value()) {
            log.info("索引:{} 创建-dsl json:{}", wrapper.getIndex(), wrapper.getDeleteIndexRequestDsl().build().toString());
            DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(s -> wrapper.getDeleteIndexRequest());
            log.info("索引:{} 删除成功{}", wrapper.getIndex(), deleteIndexResponse.toString());
            return Boolean.TRUE;
        }
        log.info("{}索引不存在", wrapper.getIndex());
        return Boolean.FALSE;
    }

    /**
     * 新增
     *
     * @param index 索引名称
     * @param t     doc
     * @return
     * @throws IOException
     */
    public <T> boolean add(String index, T t) throws IOException {
        return add(new EsCreateWrapper<T>(index).document(t).refresh(Boolean.TRUE));
    }

    /**
     * 新增
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public <T> boolean add(EsCreateWrapper<T> wrapper) throws IOException {
        log.info("索引:{} 新增-dsl json:{}", wrapper.getIndex(), wrapper.getCreateRequestDsl().build().toString());
        CreateResponse createResponse = elasticsearchClient.create(c -> (ObjectBuilder) wrapper.getCreateRequest());
        log.info("索引:{} 新增成功 {}", wrapper.getIndex(), createResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 删除
     *
     * @param index 索引名称
     * @param id    实体id
     * @return
     * @throws IOException
     */
    public boolean deleteById(String index, String id) throws IOException {
        return delete(new EsDeleteWrapper(index).id(id).refresh(Boolean.TRUE));
    }

    /**
     * 删除
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public boolean delete(EsDeleteWrapper wrapper) throws IOException {
        log.info("索引:{} 删除-dsl json:{}", wrapper.getIndex(), wrapper.getDeleteRequestDsl().build().toString());
        DeleteResponse deleteResponse = elasticsearchClient.delete(i -> wrapper.getDeleteRequest());
        log.info("索引:{} 删除成功:{}", wrapper.getIndex(), deleteResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 更新
     *
     * @param index 索引名称
     * @param t     doc实体
     * @return
     * @throws IOException
     */
    public <T> boolean updateById(String index, T t) throws IOException {
        return updateById(new EsUpdateWrapper<T>(index).doc(t).refresh(Boolean.TRUE), t.getClass());
    }

    /**
     * 更新
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public <T> boolean updateById(EsUpdateWrapper<T> wrapper, Class<?> tClass) throws IOException {
        log.info("索引{} 更新-dsl json:{}", wrapper.getIndex(), wrapper.getUpdateRequestDsl().build().toString());
        UpdateResponse<T> updateResponse = (UpdateResponse<T>) elasticsearchClient.update(u -> (ObjectBuilder) wrapper.getUpdateRequest(), tClass);
        log.info("索引{} 更新成功{}", wrapper.getIndex(), updateResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 根据Id查询
     *
     * @param index 索引名称
     * @param id
     * @return
     * @throws IOException
     */
    public Object getById(String index, String id) throws IOException {
        return getById(index, id, Object.class);
    }

    /**
     * 根据Id查询
     *
     * @param index  索引名称
     * @param id
     * @param tClass docClass
     * @return
     * @throws IOException
     */
    public <T> T getById(String index, String id, Class<T> tClass) throws IOException {
        List<T> list = list(new EsSearchWrapper<T>(index).eq(ID_FIELD, id), tClass);
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        return list.get(0);
    }

    /**
     * 批量新增
     *
     * @param index   索引名称
     * @param docList doc实体集合
     * @return
     * @throws IOException
     */
    public <T> boolean addBatch(String index, List<T> docList) throws IOException {
        return exec(new EsBulkWrapper<T>(index).create(docList).refresh(Boolean.TRUE));
    }

    /**
     * 批量更新
     *
     * @param index   索引名称
     * @param docList doc实体集合
     * @return
     * @throws IOException
     */
    public <T> boolean updateBatch(String index, List<T> docList) throws IOException {
        return exec(new EsBulkWrapper<T>(index).update(docList).refresh(Boolean.TRUE));
    }

    /**
     * 批量删除
     *
     * @param index 索引名称
     * @param ids   实体ids
     * @return
     * @throws IOException
     */
    public boolean deleteByIds(String index, List<String> ids) throws IOException {
        return exec(new EsBulkWrapper<>(index).delete(ids).refresh(Boolean.TRUE));
    }

    /**
     * 根据Id集合查询
     *
     * @param index 索引名称
     * @param ids
     * @return
     * @throws IOException
     */
    public List<Object> getByIds(String index, List<String> ids) throws IOException {
        return getByIds(index, ids, Object.class);
    }

    /**
     * 根据Id集合查询
     *
     * @param index  索引名称
     * @param ids
     * @param tClass docClass
     * @return
     * @throws IOException
     */
    public <T> List<T> getByIds(String index, List<String> ids, Class<T> tClass) throws IOException {
        return list(new EsSearchWrapper<T>(index).in(ID_FIELD, ids), tClass);
    }


    /**
     * 批量操作
     *
     * @param wrapper
     * @return
     * @throws IOException
     */
    public <T> boolean exec(EsBulkWrapper<T> wrapper) throws IOException {
        log.info("索引:{} 批量操作-dsl json:{}", wrapper.getIndex(), wrapper.getBulkRequestDsl().build().toString());
        BulkResponse bulkResponse = elasticsearchClient.bulk(s -> wrapper.getBulkRequest());
        log.info("索引:{} 批量操作成功:{}", wrapper.getIndex(), bulkResponse.toString());
        return Boolean.TRUE;
    }

    /**
     * 列表查询
     *
     * @param esSearchWrapper 搜索条件
     * @return
     * @throws IOException
     */
    public long count(EsSearchWrapper esSearchWrapper) throws IOException {
        log.info("索引{} 数量查询-dsl json:{}", esSearchWrapper.getIndex(), new CountRequest.Builder().index(esSearchWrapper.getIndex()).query(q -> q.bool(b -> esSearchWrapper.getBoolQueryCountDsl())).build().toString());
        CountResponse countResponse = elasticsearchClient.count(s -> new CountRequest.Builder().index(esSearchWrapper.getIndex()).query(q -> q.bool(b -> esSearchWrapper.getBoolQueryCount())));
        log.info("索引{} 数量查询结果 {}", esSearchWrapper.getIndex(), countResponse.toString());
        return countResponse.count();
    }

    /**
     * 列表查询
     *
     * @param esSearchWrapper 搜索条件
     * @return
     * @throws IOException
     */
    public List<Object> list(EsSearchWrapper<Object> esSearchWrapper) throws IOException {
        return list(esSearchWrapper, Object.class);
    }

    /**
     * 列表查询
     *
     * @param esSearchWrapper 搜索条件
     * @param tClass          doc类型
     * @return
     * @throws IOException
     */
    public <T> List<T> list(EsSearchWrapper<T> esSearchWrapper, Class<T> tClass) throws IOException {
        List<T> resultList = new ArrayList<>();
        long total = 0;
        //如果不设置返回窗口条数,es每次最多查询10000条
        esSearchWrapper.size(10000);
        if (Objects.nonNull(esSearchWrapper.getCustomSize())) {
            esSearchWrapper.size(esSearchWrapper.getCustomSize());
        }else {
            //虽然es查询可以设置参数返回总数,但这里先查询总数的原因是判断是否需要滚动查询,如果数据量不高的情况下无需滚动查询,避免造成游标不足的情况
            //当然这里性能最优的是将滚动查询与非滚动查询分开两个方法,这里的list查询是为了兼容两种情况,为解决数据量过大如果不采用滚动查询是返回不了所有数据的
            total = count(esSearchWrapper);
            if (total > 10000){
                //没有设置条数才使用滚动查询 滚动查询默认游标只有500个,并发高的情况尽量别使用滚动查询
                esSearchWrapper.scroll(2);
            }
        }
        esSearchWrapper.initBoolQuery();
        log.info("索引{} 列表查询-dsl json:{}", esSearchWrapper.getIndex(), esSearchWrapper.getSearchRequestDsl().build().toString());
        SearchResponse<Object> searchResponse = elasticsearchClient.search(s -> esSearchWrapper.getSearchRequest(), Object.class);
        log.info("索引{} 列表查询结果 {}", esSearchWrapper.getIndex(), searchResponse.toString());
        //如果设置了自定义size, 则无需滚动查询
        if (Objects.nonNull(esSearchWrapper.getCustomSize())) {
            searchResponse.hits().hits().forEach(p -> resultList.add(BeanUtil.copyProperties(p.source(), tClass)));
            return resultList;
        }
        //分组数据聚合处理
        putResultData(searchResponse.aggregations(), resultList, tClass, searchResponse.hits().hits());
        //超过1w,则采用游标查询
        String scrollId = searchResponse.scrollId();
        for (var i = 10000; i < total; i = i + 10000) {
            ScrollResponse<Object> scrollSearch = elasticsearchClient.scroll(s -> s.scrollId(scrollId).scroll(t -> t.time("2s")), Object.class);
            log.info("索引{} 滚动查询结果 {}", esSearchWrapper.getIndex(), scrollSearch.toString());
            putResultData(scrollSearch.aggregations(), resultList, tClass, scrollSearch.hits().hits());
        }
        //清除游标
        if (StringUtils.isNotBlank(scrollId)) {
            elasticsearchClient.clearScroll(c -> c.scrollId(scrollId));
        }
        return resultList;
    }

    private <T> void putResultData(Map<String, Aggregate> aggregateMap, List<T> resultList, Class<T> tClass, List<Hit<Object>> hits) {
        if (CollUtil.isNotEmpty(aggregateMap)) {
            //如果是分组则返回分组后的数据即可
            List<JSONObject> groupJsonList = new ArrayList<>();
            aggregateMap.forEach((k, v) -> putGroup(k, v, groupJsonList, null));
            resultList.addAll(BeanUtil.copyToList(groupJsonList, tClass));
        } else {
            hits.forEach(p -> resultList.add(BeanUtil.copyProperties(p.source(), tClass)));
        }
    }

    private void putGroup(String key, Aggregate aggregate, List<JSONObject> groupJsonList, JSONObject groupJson) {
        List<?> tBucketList = getTBucketList(aggregate);
        for (Object tBucket : tBucketList) {
            if (Objects.isNull(groupJson)) {
                groupJson = new JSONObject();
            } else {
                groupJson = BeanUtil.copyProperties(groupJson, JSONObject.class);
            }
            groupJson.put(key.replace(FLAG_KEYWORD, ""), getBucketValue(tBucket, aggregate));
            Map<String, Aggregate> aggregateMap = getTBucketAggregations(tBucket, aggregate);
            if (CollUtil.isNotEmpty(aggregateMap)) {
                JSONObject finalGroupJson = groupJson;
                aggregateMap.forEach((k, v) -> putGroup(k, v, groupJsonList, finalGroupJson));
            } else {
                groupJsonList.add(groupJson);
            }
        }
    }

    private Map<String, Aggregate> getTBucketAggregations(Object tBucket, Aggregate aggregate) {
        if (aggregate.isSterms()) {
            StringTermsBucket stringTermsBucket = (StringTermsBucket) tBucket;
            return stringTermsBucket.aggregations();
        } else if (aggregate.isLterms()) {
            LongTermsBucket longTermsBucket = (LongTermsBucket) tBucket;
            return longTermsBucket.aggregations();
        } else if (aggregate.isDterms()) {
            DoubleTermsBucket doubleTermsBucket = (DoubleTermsBucket) tBucket;
            return doubleTermsBucket.aggregations();
        }
        return new HashMap<>();
    }

    private Object getBucketValue(Object tBucket, Aggregate aggregate) {
        if (aggregate.isSterms()) {
            StringTermsBucket stringTermsBucket = (StringTermsBucket) tBucket;
            return stringTermsBucket.key().stringValue();
        } else if (aggregate.isLterms()) {
            LongTermsBucket longTermsBucket = (LongTermsBucket) tBucket;
            return longTermsBucket.key();
        } else if (aggregate.isDterms()) {
            DoubleTermsBucket doubleTermsBucket = (DoubleTermsBucket) tBucket;
            return doubleTermsBucket.key();
        } else if (aggregate.isSum()) {
            SumAggregate sumAggregate = (SumAggregate) tBucket;
            return sumAggregate.value();
        } else if (aggregate.isMax()) {
            MaxAggregate maxAggregate = (MaxAggregate) tBucket;
            return maxAggregate.value();
        } else if (aggregate.isAvg()) {
            AvgAggregate avgAggregate = (AvgAggregate) tBucket;
            return avgAggregate.value();
        } else if (aggregate.isMin()) {
            MinAggregate minAggregate = (MinAggregate) tBucket;
            return minAggregate.value();
        } else if (aggregate.isValueCount()) {
            ValueCountAggregate valueCountAggregate = (ValueCountAggregate) tBucket;
            return valueCountAggregate.value();
        }
        return null;
    }

    private List<?> getTBucketList(Aggregate aggregate) {
        //目前只定义了三种类型 string,long,double,有需要可自行扩展
        if (aggregate.isSterms()) {
            return aggregate.sterms().buckets().array();
        } else if (aggregate.isLterms()) {
            return aggregate.lterms().buckets().array();
        } else if (aggregate.isDterms()) {
            return aggregate.dterms().buckets().array();
        } else if (aggregate.isSum()) {
            return List.of(aggregate.sum());
        } else if (aggregate.isMax()) {
            return List.of(aggregate.max());
        } else if (aggregate.isAvg()) {
            return List.of(aggregate.avg());
        } else if (aggregate.isMin()) {
            return List.of(aggregate.min());
        } else if (aggregate.isValueCount()) {
            return List.of(aggregate.valueCount());
        }
        return new ArrayList<>();
    }

    /**
     * 分页查询
     *
     * @param esPage  分页参数
     * @param wrapper
     * @return
     * @throws IOException
     */
    public List<Object> page(EsPage<Object> esPage, EsSearchWrapper<Object> wrapper) throws IOException {
        return page(esPage, wrapper, Object.class);
    }

    /**
     * 分页查询
     *
     * @param esPage  分页参数
     * @param wrapper
     * @param tClass
     * @return
     * @throws IOException
     * @explain 用法与mybatisPlus语法相似
     * EsSearchWrapper<Org> wrapper = new EsSearchWrapper<>(index);
     * wrapper.eq("id",2);
     * //支持lambda
     * wrapper.eq(Org::getId,1);
     * //支持条件拼接 如果想用"或"条件只需要调用or()方法
     * warpper.and(w->w.or().eq(Org::getId,1).or().eq(Org::getId,2)))
     */
    public <T> List<T> page(EsPage<T> esPage, EsSearchWrapper<T> wrapper, Class<T> tClass) throws IOException {
        long from = (esPage.getCurrent() - 1L) * esPage.getSize();
        //设置参数
        wrapper.from((int) from).size((int) esPage.getSize()).trackTotalHits(Boolean.TRUE).initBoolQuery();
        log.info("索引{} 分页查询-dsl json:{}", wrapper.getIndex(), wrapper.getSearchRequestDsl().build().toString());
        SearchResponse<Object> searchResponse = elasticsearchClient.search(s -> wrapper.getSearchRequest(), Object.class);
        log.info("索引{} 分页查询结果:{}", wrapper.getIndex(), searchResponse.toString());
        esPage.setTotal(searchResponse.hits().total().value());
        esPage.setPages((esPage.getTotal() + esPage.getSize() - 1) / esPage.getSize());
        List<T> records = new ArrayList<>();
        searchResponse.hits().hits().forEach(p -> records.add(BeanUtil.copyProperties(p.source(), tClass)));
        esPage.setRecords(records);
        return records;
    }

    @Getter
    public static class EsCreateIndexWrapper {
        CreateIndexRequest.Builder createIndexRequest = new CreateIndexRequest.Builder();
        //用于打印dsl json
        CreateIndexRequest.Builder createIndexRequestDsl = new CreateIndexRequest.Builder();

        IndexSettings.Builder indexSetting = new IndexSettings.Builder();

        IndexSettings.Builder indexSettingDsl = new IndexSettings.Builder();

        private String index;

        public void setIndex(String index) {
            this.index = index;
            this.createIndexRequest.index(index);
            this.createIndexRequestDsl.index(index);
        }

        private void initIndexSetting() {
            this.createIndexRequest.settings(t -> indexSetting);
            this.createIndexRequestDsl.settings(t -> indexSettingDsl);
        }

        public EsCreateIndexWrapper() {
        }

        public EsCreateIndexWrapper(String index) {
            this.setIndex(index);
        }

        /**
         * 主要是分页查询的翻页是否超10000
         *
         * @param value
         * @return
         */
        public EsCreateIndexWrapper setMaxResultWindow(Integer value) {
            this.indexSetting.maxResultWindow(value);
            this.indexSettingDsl.maxResultWindow(value);
            return this;
        }

        public EsCreateIndexWrapper setDocClass(Class<?> tClass) {
            if (Objects.isNull(tClass)) {
                return this;
            }
            List<java.lang.reflect.Field> fieldList = new ArrayList<>();
            while (Objects.nonNull(tClass)) {
                fieldList.addAll(new ArrayList<>(Arrays.asList(tClass.getDeclaredFields())));
                tClass = tClass.getSuperclass();
            }
            Map<String, Property> map = new HashMap<>();
            for (java.lang.reflect.Field field : fieldList) {
                Field fieldAnnotation = field.getAnnotation(Field.class);
                if (Objects.nonNull(fieldAnnotation)) {
                    switch (fieldAnnotation.type()) {
                        case Text:
                            map.put(field.getName(), Property.of(f -> f.text(t -> t)));
                            break;
                        case Keyword:
                            map.put(field.getName(), Property.of(f -> f.keyword(k -> k)));
                            break;
                        case Wildcard:
                            map.put(field.getName(), Property.of(f -> f.wildcard(k -> k)));
                            break;
                        case Long:
                            map.put(field.getName(), Property.of(f -> f.long_(k -> k)));
                            break;
                        case Integer:
                            map.put(field.getName(), Property.of(f -> f.integer(k -> k)));
                            break;
                        case Double:
                            map.put(field.getName(), Property.of(f -> f.double_(k -> k)));
                            break;
                        case Date:
                            map.put(field.getName(), Property.of(f -> f.date(k -> k)));
                            break;
                        //其他的自行扩展
                        default:
                            break;
                    }
                }
            }
            if (CollUtil.isNotEmpty(map)) {
                createIndexRequest.mappings(m -> m.properties(map));
            }
            return this;
        }

        public EsCreateIndexWrapper setMaxOpenScrollContext(Integer value) {
            if (Objects.nonNull(value)) {
                indexSetting.maxSlicesPerScroll(value);
                indexSettingDsl.maxSlicesPerScroll(value);
            }
            return this;
        }

        //......(后续其他属性自行添加)
    }

    @Getter
    public static class EsDeleteIndexWrapper {
        DeleteIndexRequest.Builder deleteIndexRequest = new DeleteIndexRequest.Builder();

        DeleteIndexRequest.Builder deleteIndexRequestDsl = new DeleteIndexRequest.Builder();

        private String index;

        public void setIndex(String index) {
            this.index = index;
            this.deleteIndexRequest.index(index);
            this.deleteIndexRequestDsl.index(index);
        }

        public EsDeleteIndexWrapper() {
        }

        public EsDeleteIndexWrapper(String index) {
            this.setIndex(index);
        }
    }

    @Getter
    public static class EsCreateWrapper<T> {
        private CreateRequest.Builder<T> createRequest = new CreateRequest.Builder<>();
        private CreateRequest.Builder<T> createRequestDsl = new CreateRequest.Builder<>();
        private String index;

        public void setIndex(String index) {
            this.index = index;
            this.createRequest.index(index);
            this.createRequestDsl.index(index);
        }

        public EsCreateWrapper() {
        }

        public EsCreateWrapper(String index) {
            this.setIndex(index);
        }

        public EsCreateWrapper<T> document(T t) {
            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(t));
            //如果有主键id,则设置id主键
            if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
                createRequest.id(String.valueOf(jsonObject.get(ID_FIELD)));
                createRequestDsl.id(String.valueOf(jsonObject.get(ID_FIELD)));
            }
            createRequest.document(t);
            createRequestDsl.document(t);
            return this;
        }

        public EsCreateWrapper<T> refresh(boolean refresh) {
            createRequest.refresh(refresh ? Refresh.True : Refresh.False);
            createRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
            return this;
        }

    }

    @Getter
    public static class EsDeleteWrapper {
        private DeleteRequest.Builder deleteRequest = new DeleteRequest.Builder();
        private DeleteRequest.Builder deleteRequestDsl = new DeleteRequest.Builder();
        private String index;

        public void setIndex(String index) {
            this.index = index;
            this.deleteRequest.index(index);
            this.deleteRequestDsl.index(index);
        }

        public EsDeleteWrapper() {
        }

        public EsDeleteWrapper(String index) {
            this.setIndex(index);
        }

        public EsDeleteWrapper id(String id) {
            deleteRequest.id(id);
            deleteRequestDsl.id(id);
            return this;
        }

        public EsDeleteWrapper refresh(boolean refresh) {
            deleteRequest.refresh(refresh ? Refresh.True : Refresh.False);
            deleteRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
            return this;
        }

    }

    @Getter
    public static class EsUpdateWrapper<T> {
        UpdateRequest.Builder<Object, T> updateRequest = new UpdateRequest.Builder<>();
        //用于打印dsl json
        UpdateRequest.Builder<Object, T> updateRequestDsl = new UpdateRequest.Builder<>();

        private String index;

        public void setIndex(String index) {
            this.index = index;
            this.updateRequest.index(index);
            this.updateRequestDsl.index(index);
        }

        public EsUpdateWrapper() {
        }

        public EsUpdateWrapper(String index) {
            this.setIndex(index);
        }

        public EsUpdateWrapper<T> doc(T t) {
            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(t));
            //如果有主键id,则设置id主键
            if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
                updateRequest.id(String.valueOf(jsonObject.get(ID_FIELD)));
                updateRequestDsl.id(String.valueOf(jsonObject.get(ID_FIELD)));
            }
            updateRequest.doc(t);
            updateRequestDsl.doc(t);
            return this;
        }

        public EsUpdateWrapper<T> refresh(boolean refresh) {
            updateRequest.refresh(refresh ? Refresh.True : Refresh.False);
            updateRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
            return this;
        }

    }

    @Getter
    public static class EsSearchWrapper<T> {
        private SearchRequest.Builder searchRequest = new SearchRequest.Builder();

        private SearchRequest.Builder searchRequestDsl = new SearchRequest.Builder();

        private BoolQuery.Builder boolQuery = new BoolQuery.Builder();

        private BoolQuery.Builder boolQueryDsl = new BoolQuery.Builder();

        private BoolQuery.Builder boolQueryCount = new BoolQuery.Builder();

        private BoolQuery.Builder boolQueryCountDsl = new BoolQuery.Builder();

        private String index;

        private Integer customSize;

        public void setIndex(String index) {
            this.index = index;
            this.searchRequest.index(index);
            this.searchRequestDsl.index(index);
            //默认设置返回总条数
            this.searchRequest.trackTotalHits(track -> track.enabled(Boolean.TRUE));
            this.searchRequestDsl.trackTotalHits(track -> track.enabled(Boolean.TRUE));
            this.orCondition = false;
        }

        public EsSearchWrapper<T> initBoolQuery() {
            this.searchRequest.query(q -> q.bool(b -> this.boolQuery));
            this.searchRequestDsl.query(q -> q.bool(b -> this.boolQueryDsl));
            return this;
        }

        private boolean orCondition;

        public EsSearchWrapper() {
        }

        public EsSearchWrapper(String index) {
            this.setIndex(index);
        }

        public EsSearchWrapper<T> eq(boolean condition, SFunction<T, ?> column, Object val) {
            if (condition) {
                return eq(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> eq(SFunction<T, ?> column, Object val) {
            String filedName = getfiledNameWithCach(column);
            return eq(filedName, val);
        }

        public EsSearchWrapper<T> eq(boolean condition, String column, Object val) {
            if (condition) {
                return eq(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> eq(String column, Object val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                this.boolQuery.must(m -> m.term(t -> setTermQuery(column, t, val)));
                this.boolQueryDsl.must(m -> m.term(t -> setTermQuery(column, t, val)));
                this.boolQueryCount.must(m -> m.term(t -> setTermQuery(column, t, val)));
                this.boolQueryCountDsl.must(m -> m.term(t -> setTermQuery(column, t, val)));
            } else {
                this.boolQuery.should(m -> m.term(t -> setTermQuery(column, t, val)));
                this.boolQueryDsl.should(m -> m.term(t -> setTermQuery(column, t, val)));
                this.boolQueryCount.should(m -> m.term(t -> setTermQuery(column, t, val)));
                this.boolQueryCountDsl.should(m -> m.term(t -> setTermQuery(column, t, val)));
            }
            return this;
        }

        public EsSearchWrapper<T> in(boolean condition, SFunction<T, ?> column, Collection<?> vals) {
            if (condition) {
                return in(column, vals);
            }
            return this;
        }

        public EsSearchWrapper<T> in(SFunction<T, ?> column, Collection<?> vals) {
            String filedName = getfiledNameWithCach(column);
            return in(filedName, vals);
        }

        public EsSearchWrapper<T> in(boolean condition, String column, Collection<?> vals) {
            if (condition) {
                return in(column, vals);
            }
            return this;
        }

        public EsSearchWrapper<T> in(String column, Collection<?> vals) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            List<FieldValue> vList = new ArrayList<>();
            vals.forEach(p -> vList.add(FieldValue.of(String.valueOf(p))));
            if (!orCondition) {
                boolQuery.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
                boolQueryDsl.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
                boolQueryCount.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
                boolQueryCountDsl.must(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
            } else {
                boolQuery.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
                boolQueryDsl.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
                boolQueryCount.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
                boolQueryCountDsl.should(m -> m.terms(t -> t.field(column).terms(TermsQueryField.of(f -> f.value(vList)))));
            }
            return this;
        }

        public EsSearchWrapper<T> ne(boolean condition, SFunction<T, ?> column, Object val) {
            if (condition) {
                return ne(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> ne(SFunction<T, ?> column, Object val) {
            String filedName = getfiledNameWithCach(column);
            return ne(filedName, val);
        }

        public EsSearchWrapper<T> ne(boolean condition, String column, Object val) {
            if (condition) {
                return ne(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> ne(String column, Object val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
                boolQueryDsl.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
                boolQueryCount.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
                boolQueryCountDsl.mustNot(m -> m.term(t -> setTermQuery(column, t, val)));
            } else {
                boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
                boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
                boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
                boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.term(t -> setTermQuery(column, t, val)))));
            }
            return this;
        }

        public EsSearchWrapper<T> gt(boolean condition, SFunction<T, ?> column, Object val) {
            if (condition) {
                return gt(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> gt(SFunction<T, ?> column, Object val) {
            String filedName = getfiledNameWithCach(column);
            return gt(filedName, val);
        }

        public EsSearchWrapper<T> gt(boolean condition, String column, Object val) {
            if (condition) {
                return gt(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> gt(String column, Object val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
                boolQueryDsl.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
                boolQueryCount.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
                boolQueryCountDsl.must(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
            } else {
                boolQuery.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
                boolQueryDsl.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
                boolQueryCount.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
                boolQueryCountDsl.should(m -> m.range(t -> t.field(column).gt(JsonData.of(val))));
            }
            return this;
        }

        public EsSearchWrapper<T> ge(boolean condition, SFunction<T, ?> column, Object val) {
            if (condition) {
                return ge(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> ge(SFunction<T, ?> column, Object val) {
            String filedName = getfiledNameWithCach(column);
            return ge(filedName, val);
        }

        public EsSearchWrapper<T> ge(boolean condition, String column, Object val) {
            if (condition) {
                return ge(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> ge(String column, Object val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
                boolQueryDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
                boolQueryCount.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
                boolQueryCountDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
            } else {
                boolQuery.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
                boolQueryDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
                boolQueryCount.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
                boolQueryCountDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val))));
            }
            return this;
        }

        public EsSearchWrapper<T> lt(boolean condition, SFunction<T, ?> column, Object val) {
            if (condition) {
                return lt(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> lt(SFunction<T, ?> column, Object val) {
            String filedName = getfiledNameWithCach(column);
            return lt(filedName, val);
        }

        public EsSearchWrapper<T> lt(boolean condition, String column, Object val) {
            if (condition) {
                return lt(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> lt(String column, Object val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
                boolQueryDsl.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
                boolQueryCount.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
                boolQueryCountDsl.must(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
            } else {
                boolQuery.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
                boolQueryDsl.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
                boolQueryCount.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
                boolQueryCountDsl.should(m -> m.range(t -> t.field(column).lt(JsonData.of(val))));
            }
            return this;
        }

        public EsSearchWrapper<T> le(boolean condition, SFunction<T, ?> column, Object val) {
            if (condition) {
                return le(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> le(SFunction<T, ?> column, Object val) {
            String filedName = getfiledNameWithCach(column);
            return le(filedName, val);
        }

        public EsSearchWrapper<T> le(boolean condition, String column, Object val) {
            if (condition) {
                return le(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> le(String column, Object val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
                boolQueryDsl.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
                boolQueryCount.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
                boolQueryCountDsl.must(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
            } else {
                boolQuery.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
                boolQueryDsl.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
                boolQueryCount.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
                boolQueryCountDsl.should(m -> m.range(t -> t.field(column).lte(JsonData.of(val))));
            }
            return this;
        }

        public EsSearchWrapper<T> between(boolean condition, SFunction<T, ?> column, Object val1, Object val2) {
            if (condition) {
                return between(column, val1, val2);
            }
            return this;
        }

        public EsSearchWrapper<T> between(SFunction<T, ?> column, Object val1, Object val2) {
            String filedName = getfiledNameWithCach(column);
            return between(filedName, val1, val2);
        }

        public EsSearchWrapper<T> between(boolean condition, String column, Object val1, Object val2) {
            if (condition) {
                return between(column, val1, val2);
            }
            return this;
        }

        public EsSearchWrapper<T> between(String column, Object val1, Object val2) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryCount.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryCountDsl.must(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
            } else {
                boolQuery.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryCount.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryCountDsl.should(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
            }
            return this;
        }

        public EsSearchWrapper<T> notBetween(boolean condition, SFunction<T, ?> column, Object val1, Object val2) {
            if (condition) {
                return notBetween(column, val1, val2);
            }
            return this;
        }

        public EsSearchWrapper<T> notBetween(SFunction<T, ?> column, Object val1, Object val2) {
            String filedName = getfiledNameWithCach(column);
            return notBetween(filedName, val1, val2);
        }

        public EsSearchWrapper<T> notBetween(boolean condition, String column, Object val1, Object val2) {
            if (condition) {
                return notBetween(column, val1, val2);
            }
            return this;
        }

        public EsSearchWrapper<T> notBetween(String column, Object val1, Object val2) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryDsl.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryCount.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
                boolQueryCountDsl.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))));
            } else {
                boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
                boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
                boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
                boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.range(t -> t.field(column).gte(JsonData.of(val1)).lte(JsonData.of(val2))))));
            }
            return this;
        }

        public EsSearchWrapper<T> like(boolean condition, SFunction<T, ?> column, String val) {
            if (condition) {
                return like(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> like(SFunction<T, ?> column, String val) {
            String filedName = getfiledNameWithCach(column);
            return like(filedName, val);
        }

        public EsSearchWrapper<T> like(boolean condition, String column, String val) {
            if (condition) {
                return like(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> like(String column, String val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryCount.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryCountDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
            } else {
                boolQuery.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryCount.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryCountDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
            }
            return this;
        }

        public EsSearchWrapper<T> notLike(boolean condition, SFunction<T, ?> column, String val) {
            if (condition) {
                return notLike(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> notLike(SFunction<T, ?> column, String val) {
            String filedName = getfiledNameWithCach(column);
            return notLike(filedName, val);
        }

        public EsSearchWrapper<T> notLike(boolean condition, String column, String val) {
            if (condition) {
                return notLike(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> notLike(String column, String val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryCount.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
                boolQueryCountDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")));
            } else {
                boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
                boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
                boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
                boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val + "*")))));
            }
            return this;
        }

        public EsSearchWrapper<T> likeLeft(boolean condition, SFunction<T, ?> column, String val) {
            if (condition) {
                return likeLeft(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> likeLeft(SFunction<T, ?> column, String val) {
            String filedName = getfiledNameWithCach(column);
            return likeLeft(filedName, val);
        }

        public EsSearchWrapper<T> likeLeft(boolean condition, String column, String val) {
            if (condition) {
                return likeLeft(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> likeLeft(String column, String val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryCount.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryCountDsl.must(m -> m.wildcard(t -> t.field(column).value("*" + val)));
            } else {
                boolQuery.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryCount.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryCountDsl.should(m -> m.wildcard(t -> t.field(column).value("*" + val)));
            }
            return this;
        }

        public EsSearchWrapper<T> notLikeLeft(boolean condition, SFunction<T, ?> column, String val) {
            if (condition) {
                return notLikeLeft(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> notLikeLeft(SFunction<T, ?> column, String val) {
            String filedName = getfiledNameWithCach(column);
            return notLikeLeft(filedName, val);
        }

        public EsSearchWrapper<T> notLikeLeft(boolean condition, String column, String val) {
            if (condition) {
                return notLikeLeft(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> notLikeLeft(String column, String val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryCount.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
                boolQueryCountDsl.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)));
            } else {
                boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
                boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
                boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
                boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value("*" + val)))));
            }
            return this;
        }

        public EsSearchWrapper<T> likeRight(boolean condition, SFunction<T, ?> column, String val) {
            if (condition) {
                return likeRight(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> likeRight(SFunction<T, ?> column, String val) {
            String filedName = getfiledNameWithCach(column);
            return likeRight(filedName, val);
        }

        public EsSearchWrapper<T> likeRight(boolean condition, String column, String val) {
            if (condition) {
                return likeRight(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> likeRight(String column, String val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryDsl.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryCount.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryCountDsl.must(m -> m.wildcard(t -> t.field(column).value(val + "*")));
            } else {
                boolQuery.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryDsl.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryCount.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryCountDsl.should(m -> m.wildcard(t -> t.field(column).value(val + "*")));
            }
            return this;
        }

        public EsSearchWrapper<T> notLikeRight(boolean condition, SFunction<T, ?> column, String val) {
            if (condition) {
                return notLikeRight(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> notLikeRight(SFunction<T, ?> column, String val) {
            String filedName = getfiledNameWithCach(column);
            return notLikeRight(filedName, val);
        }

        public EsSearchWrapper<T> notLikeRight(boolean condition, String column, String val) {
            if (condition) {
                return notLikeRight(column, val);
            }
            return this;
        }

        public EsSearchWrapper<T> notLikeRight(String column, String val) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            if (!orCondition) {
                boolQuery.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryDsl.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryCount.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
                boolQueryCountDsl.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")));
            } else {
                boolQuery.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
                boolQueryDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
                boolQueryCount.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
                boolQueryCountDsl.should(s -> s.bool(b -> b.mustNot(m -> m.wildcard(t -> t.field(column).value(val + "*")))));
            }
            return this;
        }

        public EsSearchWrapper<T> orderBy(SFunction<T, ?> column, SortOrder sortOrder) {
            String filedName = getfiledNameWithCach(column);
            return orderBy(filedName, sortOrder);
        }

        public EsSearchWrapper<T> orderBy(String column, SortOrder sortOrder) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            searchRequest.sort(sort -> sort.field(f -> f.field(column).order(sortOrder)));
            searchRequestDsl.sort(sort -> sort.field(f -> f.field(column).order(sortOrder)));
            return this;
        }

        public EsSearchWrapper<T> orderByAsc(SFunction<T, ?> column) {
            String filedName = getfiledNameWithCach(column);
            return orderByAsc(filedName);
        }

        public EsSearchWrapper<T> orderByAsc(String column) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            searchRequest.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Asc)));
            searchRequestDsl.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Asc)));
            return this;
        }

        public EsSearchWrapper<T> orderByDesc(SFunction<T, ?> column) {
            String filedName = getfiledNameWithCach(column);
            return orderByDesc(filedName);
        }

        public EsSearchWrapper<T> orderByDesc(String column) {
            if (StringUtils.isBlank(column)) {
                return this;
            }
            searchRequest.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Desc)));
            searchRequestDsl.sort(sort -> sort.field(f -> f.field(column).order(SortOrder.Desc)));
            return this;
        }

        public EsSearchWrapper<T> and(boolean condition, SFunction<EsSearchWrapper<T>, EsSearchWrapper<T>> fn) {
            if (condition) {
                return and(fn);
            }
            return this;
        }

        public EsSearchWrapper<T> and(SFunction<EsSearchWrapper<T>, EsSearchWrapper<T>> fn) {
            boolQuery.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
            boolQueryDsl.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
            boolQueryCount.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
            boolQueryCountDsl.must(m -> m.bool(b -> fn.apply(new EsSearchWrapper<T>()).getBoolQuery()));
            return this;
        }

        public EsSearchWrapper<T> or() {
            orCondition = true;
            return this;
        }

        public EsSearchWrapper<T> from(Integer value) {
            searchRequest.from(value);
            searchRequestDsl.from(value);
            return this;
        }

        public EsSearchWrapper<T> size(Integer value) {
            searchRequest.size(value);
            searchRequestDsl.size(value);
            return this;
        }

        public EsSearchWrapper<T> trackTotalHits(Boolean value) {
            searchRequest.trackTotalHits(track -> track.enabled(value));
            searchRequestDsl.trackTotalHits(track -> track.enabled(value));
            return this;
        }

        /**
         * 秒
         *
         * @param value
         * @return
         */
        public EsSearchWrapper<T> scroll(Integer value) {
            searchRequest.scroll(t -> t.time(value + "s"));
            searchRequestDsl.scroll(t -> t.time(value + "s"));
            return this;
        }

        public EsSearchWrapper<T> select(SFunction<T, ?>... columns) {
            String[] values = new String[columns.length];
            for (int i = 0; i < columns.length; i++) {
                String filedName = getfiledNameWithCach(columns[i]);
                if (filedName.contains(FLAG_KEYWORD)) {
                    filedName = filedName.replace(FLAG_KEYWORD, "");
                }
                values[i] = filedName;
            }
            return select(values);
        }

        public EsSearchWrapper<T> select(String... values) {
            if (ArrayUtils.isEmpty(values)) {
                return this;
            }
            List<String> fields = new ArrayList<>();
            for (String value : values) {
                if (StringUtils.isNotBlank(value)) {
                    if (value.contains(",")) {
                        fields.addAll(Arrays.stream(value.split(",")).distinct().toList());
                    } else {
                        fields.add(value);
                    }
                }
            }
            if (CollUtil.isEmpty(fields)) {
                return this;
            }
            searchRequest.source(s -> s.filter(SourceFilter.of(f -> f.includes(fields))));
            searchRequestDsl.source(s -> s.filter(SourceFilter.of(f -> f.includes(fields))));
            return this;
        }

        public EsSearchWrapper<T> groupBy(SFunction<T, ?>... columns) {
            String[] values = new String[columns.length];
            for (int i = 0; i < columns.length; i++) {
                String filedName = getfiledNameWithCach(columns[i]);
                values[i] = filedName;
            }
            return groupBy(values);
        }

        public EsSearchWrapper<T> groupBy(String... values) {
            EsFunction<T>[] esFunction = new EsFunction[values.length];
            for (int i = 0; i < values.length; i++) {
                esFunction[i] = new EsFunction<T>();
                esFunction[i].setColumn(values[i]);
            }
            return groupBy(esFunction);
        }

        public EsSearchWrapper<T> groupBy(EsFunction<T>... esFunctions) {
            if (ArrayUtils.isEmpty(esFunctions)) {
                return this;
            }
            //保证函数放到最后(否则es会报错)
            esFunctions = esFunctionsSort(esFunctions);
            Map<String, Aggregation.Builder.ContainerBuilder> map = new HashMap<>();
            Map<String, Aggregation.Builder.ContainerBuilder> mapDsl = new HashMap<>();
            for (EsFunction<T> esFunction : esFunctions) {
                Aggregation.Builder aggregationBuilder = new Aggregation.Builder();
                Aggregation.Builder.ContainerBuilder containerBuilder = getContainerBuilder(aggregationBuilder, esFunction);
                map.put(esFunction.getColumn(), containerBuilder);
                Aggregation.Builder aggregationBuilderDsl = new Aggregation.Builder();
                Aggregation.Builder.ContainerBuilder containerBuilderDsl = aggregationBuilderDsl.terms(t -> t.field(esFunction.getColumn()));
                mapDsl.put(esFunction.getColumn(), containerBuilderDsl);
            }
            for (int i = esFunctions.length - 1; i >= 0; i--) {
                if (i == 0) {
                    Aggregation.Builder.ContainerBuilder containerBuilderFirst = map.get(esFunctions[i].getColumn());
                    Aggregation.Builder.ContainerBuilder containerBuilderFirstDsl = mapDsl.get(esFunctions[i].getColumn());
                    searchRequest.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilderFirst));
                    searchRequestDsl.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilderFirstDsl));
                    continue;
                }
                Aggregation.Builder.ContainerBuilder containerBuilder = map.get(esFunctions[i].getColumn());
                Aggregation.Builder.ContainerBuilder containerBuilderDsl = mapDsl.get(esFunctions[i].getColumn());
                Aggregation.Builder.ContainerBuilder containerBuilderPrev = map.get(esFunctions[i - 1].getColumn());
                Aggregation.Builder.ContainerBuilder containerBuilderPrevDsl = mapDsl.get(esFunctions[i - 1].getColumn());
                containerBuilderPrev.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilder));
                containerBuilderPrevDsl.aggregations(esFunctions[i].getColumn(), Aggregation.of(f -> containerBuilderDsl));
            }
            return this;
        }

        public EsSearchWrapper<T> last(boolean condition, LimitSize limitSize) {
            if (condition) {
                return last(limitSize);
            }
            return this;
        }

        public EsSearchWrapper<T> last(LimitSize limitSize) {
            this.customSize = limitSize.getLimitSize();
            return this;
        }

        public EsSearchWrapper<T> match(MatchQuery matchQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.match(matchQuery));
                boolQueryDsl.must(m -> m.match(matchQuery));
                boolQueryCount.must(m -> m.match(matchQuery));
                boolQueryCountDsl.must(m -> m.match(matchQuery));
            } else {
                boolQuery.should(m -> m.match(matchQuery));
                boolQueryDsl.should(m -> m.match(matchQuery));
                boolQueryCount.should(m -> m.match(matchQuery));
                boolQueryCountDsl.should(m -> m.match(matchQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> term(TermQuery termQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.term(termQuery));
                boolQueryDsl.must(m -> m.term(termQuery));
                boolQueryCount.must(m -> m.term(termQuery));
                boolQueryCountDsl.must(m -> m.term(termQuery));
            } else {
                boolQuery.should(m -> m.term(termQuery));
                boolQueryDsl.should(m -> m.term(termQuery));
                boolQueryCount.should(m -> m.term(termQuery));
                boolQueryCountDsl.should(m -> m.term(termQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> range(RangeQuery rangeQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.range(rangeQuery));
                boolQueryDsl.must(m -> m.range(rangeQuery));
                boolQueryCount.must(m -> m.range(rangeQuery));
                boolQueryCountDsl.must(m -> m.range(rangeQuery));
            } else {
                boolQuery.should(m -> m.range(rangeQuery));
                boolQueryDsl.should(m -> m.range(rangeQuery));
                boolQueryCount.should(m -> m.range(rangeQuery));
                boolQueryCountDsl.should(m -> m.range(rangeQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> matchPhrase(MatchPhraseQuery matchPhraseQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.matchPhrase(matchPhraseQuery));
                boolQueryDsl.must(m -> m.matchPhrase(matchPhraseQuery));
                boolQueryCount.must(m -> m.matchPhrase(matchPhraseQuery));
                boolQueryCountDsl.must(m -> m.matchPhrase(matchPhraseQuery));
            } else {
                boolQuery.should(m -> m.matchPhrase(matchPhraseQuery));
                boolQueryDsl.should(m -> m.matchPhrase(matchPhraseQuery));
                boolQueryCount.should(m -> m.matchPhrase(matchPhraseQuery));
                boolQueryCountDsl.should(m -> m.matchPhrase(matchPhraseQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> prefix(PrefixQuery prefixQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.prefix(prefixQuery));
                boolQueryDsl.must(m -> m.prefix(prefixQuery));
                boolQueryCount.must(m -> m.prefix(prefixQuery));
                boolQueryCountDsl.must(m -> m.prefix(prefixQuery));
            } else {
                boolQuery.should(m -> m.prefix(prefixQuery));
                boolQueryDsl.should(m -> m.prefix(prefixQuery));
                boolQueryCount.should(m -> m.prefix(prefixQuery));
                boolQueryCountDsl.should(m -> m.prefix(prefixQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> wildcard(WildcardQuery wildcardQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.wildcard(wildcardQuery));
                boolQueryDsl.must(m -> m.wildcard(wildcardQuery));
                boolQueryCount.must(m -> m.wildcard(wildcardQuery));
                boolQueryCountDsl.must(m -> m.wildcard(wildcardQuery));
            } else {
                boolQuery.should(m -> m.wildcard(wildcardQuery));
                boolQueryDsl.should(m -> m.wildcard(wildcardQuery));
                boolQueryCount.should(m -> m.wildcard(wildcardQuery));
                boolQueryCountDsl.should(m -> m.wildcard(wildcardQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> fuzzy(FuzzyQuery fuzzyQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.fuzzy(fuzzyQuery));
                boolQueryDsl.must(m -> m.fuzzy(fuzzyQuery));
                boolQueryCount.must(m -> m.fuzzy(fuzzyQuery));
                boolQueryCountDsl.must(m -> m.fuzzy(fuzzyQuery));
            } else {
                boolQuery.should(m -> m.fuzzy(fuzzyQuery));
                boolQueryDsl.should(m -> m.fuzzy(fuzzyQuery));
                boolQueryCount.should(m -> m.fuzzy(fuzzyQuery));
                boolQueryCountDsl.should(m -> m.fuzzy(fuzzyQuery));
            }
            return this;
        }

        public EsSearchWrapper<T> regexp(RegexpQuery regexpQuery) {
            if (!orCondition) {
                boolQuery.must(m -> m.regexp(regexpQuery));
                boolQueryDsl.must(m -> m.regexp(regexpQuery));
                boolQueryCount.must(m -> m.regexp(regexpQuery));
                boolQueryCountDsl.must(m -> m.regexp(regexpQuery));
            } else {
                boolQuery.should(m -> m.regexp(regexpQuery));
                boolQueryDsl.should(m -> m.regexp(regexpQuery));
                boolQueryCount.should(m -> m.regexp(regexpQuery));
                boolQueryCountDsl.should(m -> m.regexp(regexpQuery));
            }
            return this;
        }

        private EsFunction<T>[] esFunctionsSort(EsFunction<T>[] esFunctions) {
            List<EsFunction> functionList = new ArrayList<>();
            for (EsFunction<T> esFunction : esFunctions) {
                if (Objects.isNull(esFunction.getEsFunctionEnum())) {
                    //如果为null,则放到首位
                    functionList.add(0, esFunction);
                    continue;
                }
                functionList.add(esFunction);
            }
            return functionList.toArray(new EsFunction[functionList.size()]);
        }

        private Aggregation.Builder.ContainerBuilder getContainerBuilder(Aggregation.Builder aggregationBuilder, EsFunction<T> esFunction) {
            if (Objects.isNull(esFunction.getEsFunctionEnum())) {
                return aggregationBuilder.terms(t -> t.field(esFunction.getColumn()));
            }
            return switch (esFunction.getEsFunctionEnum()) {
                case COUNT -> aggregationBuilder.valueCount(count -> count.field(esFunction.getColumn()));
                case MAX -> aggregationBuilder.max(max -> max.field(esFunction.getColumn()));
                case MIN -> aggregationBuilder.min(min -> min.field(esFunction.getColumn()));
                case AVG -> aggregationBuilder.avg(avg -> avg.field(esFunction.getColumn()));
                case SUM -> aggregationBuilder.sum(sum -> sum.field(esFunction.getColumn()));
            };
        }

        private String getfiledNameWithCach(SFunction<T, ?> column) {
            String filedName = lambdaCach.get(column.getClass());
            if (StringUtils.isNotBlank(filedName)) {
                return filedName;
            }
            try {
                Method method = column.getClass().getDeclaredMethod("writeReplace");
                //不检测方法是否为public或者private  大大提高了性能(近10倍)
                ReflectionUtils.makeAccessible(method);
                SerializedLambda lambda = (SerializedLambda) method.invoke(column);
                filedName = methodToProperty(lambda.getImplMethodName());
                FieldType fieldType = getEsFieldType(lambda, filedName);
                if (Objects.nonNull(fieldType) && (fieldType.equals(FieldType.Auto) || fieldType.equals(FieldType.Keyword))) {
                    //Auto类型默认不分词
                    filedName = filedName + FLAG_KEYWORD;
                } else if (Objects.isNull(fieldType) && "()Ljava/lang/String;".equals(lambda.getImplMethodSignature())) {
                    //如果String类型且没有自定义ES类型,那么默认是text+keyword,则自动拼接.keyword(使用lambda默认不分词)
                    filedName = filedName + FLAG_KEYWORD;
                }
                if (StringUtils.isNotBlank(filedName)) {
                    lambdaCach.put(column.getClass(), filedName);
                }
            } catch (Exception e) {
                log.error("getfiledNameWithCach-参数转换异常", e);
            }
            return filedName;
        }

        private FieldType getEsFieldType(SerializedLambda lambda, String filedName) {
            FieldType fieldType = null;
            try {
                String methodTypeStr = lambda.getInstantiatedMethodType();
                String entityPath = methodTypeStr.substring(methodTypeStr.indexOf("(L") + "(L".length(), methodTypeStr.indexOf(";)Ljava/lang/Object;"));
                Class<?> clazz = Class.forName(entityPath.replace("/", "."));
                java.lang.reflect.Field field = clazz.getDeclaredField(filedName);
                Field fieldAnnotation = field.getAnnotation(Field.class);
                if (Objects.nonNull(fieldAnnotation)) {
                    fieldType = fieldAnnotation.type();
                }
            } catch (Exception e) {
                log.error("加载实体es-FieldType异常", e);
            }
            return fieldType;
        }

        private String methodToProperty(String name) {
            if (name.startsWith("is")) {
                name = name.substring(2);
            } else {
                if (!name.startsWith("get") && !name.startsWith("set")) {
                    return "";
                }
                name = name.substring(3);
            }
            if (name.length() == 1 || name.length() > 1 && !Character.isUpperCase(name.charAt(1))) {
                name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
            }
            return name;
        }

        private TermQuery.Builder setTermQuery(String column, TermQuery.Builder t, Object val) {
            t.field(column);
            if (val instanceof Long) {
                t.value(objectToLong(val));
            } else if (val instanceof Integer) {
                t.value(objectToInteger(val).longValue());
            } else if (val instanceof Double) {
                t.value(objectToDouble(val));
            } else if (val instanceof Boolean) {
                t.value(objectToBoolean(val));
            } else {
                t.value(String.valueOf(val));
            }
            return t;
        }

        private Integer objectToInteger(Object val) {
            return (Integer) val;
        }


        private Boolean objectToBoolean(Object val) {
            return (Boolean) val;
        }

        private Double objectToDouble(Object val) {
            return (Double) val;
        }

        private Long objectToLong(Object val) {
            return (Long) val;
        }
    }

    @Getter
    public static class EsBulkWrapper<T> {
        BulkRequest.Builder bulkRequest = new BulkRequest.Builder();
        List<BulkOperation> bulkOperations = new ArrayList<>();
        //用于打印dsl json
        BulkRequest.Builder bulkRequestDsl = new BulkRequest.Builder();
        List<BulkOperation> bulkOperationsDsl = new ArrayList<>();

        private String index;

        public void setIndex(String index) {
            this.index = index;
            this.bulkRequest.index(index);
            this.bulkRequestDsl.index(index);
        }

        public EsBulkWrapper() {
            this.bulkRequest.operations(bulkOperations);
            this.bulkRequestDsl.operations(bulkOperationsDsl);
        }

        public EsBulkWrapper(String index) {
            this.bulkRequest.operations(bulkOperations);
            this.bulkRequestDsl.operations(bulkOperationsDsl);
            this.setIndex(index);
        }

        public EsBulkWrapper<T> delete(List<String> ids) {
            ids.forEach(id -> {
                bulkOperations.add(BulkOperation.of(b -> b.delete(d -> d.id(id))));
                bulkOperationsDsl.add(BulkOperation.of(b -> b.delete(d -> d.id(id))));
            });
            return this;
        }

        public EsBulkWrapper<T> create(List<T> docList) {
            docList.forEach(doc -> {
                        bulkOperations.add(BulkOperation.of(b -> b.index(i -> {
                            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
                            //如果有主键id,则设置id主键
                            if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
                                i.id(String.valueOf(jsonObject.get(ID_FIELD)));
                            }
                            return i.document(doc);
                        })));
                        bulkOperationsDsl.add(BulkOperation.of(b -> b.index(i -> {
                            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
                            //如果有主键id,则设置id主键
                            if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
                                i.id(String.valueOf(jsonObject.get(ID_FIELD)));
                            }
                            return i.document(doc);
                        })));
                    }
            );
            return this;
        }

        public EsBulkWrapper<T> update(List<T> docList) {
            docList.forEach(doc -> {
                        bulkOperations.add(BulkOperation.of(b -> b.update(i -> {
                            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
                            //如果有主键id,则设置id主键
                            if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
                                i.id(String.valueOf(jsonObject.get(ID_FIELD)));
                            }
                            return i.action(a -> a.doc(doc));
                        })));
                        bulkOperationsDsl.add(BulkOperation.of(b -> b.update(i -> {
                            JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(doc));
                            //如果有主键id,则设置id主键
                            if (Objects.nonNull(jsonObject.get(ID_FIELD))) {
                                i.id(String.valueOf(jsonObject.get(ID_FIELD)));
                            }
                            return i.action(a -> a.doc(doc));
                        })));
                    }
            );
            return this;
        }


        public EsBulkWrapper<T> refresh(boolean refresh) {
            bulkRequest.refresh(refresh ? Refresh.True : Refresh.False);
            bulkRequestDsl.refresh(refresh ? Refresh.True : Refresh.False);
            return this;
        }

    }

    @Data
    public static class EsPage<T> {
        private long current = 1L;
        private long size = 10L;
        private long total = 0L;
        private long pages = 0L;
        private List<T> records = Collections.emptyList();

        public EsPage() {
        }

        public EsPage(long current, long size) {
            this.current = current;
            this.size = size;
        }

    }

    public interface SFunction<T, R> extends Function<T, R>, Serializable {
    }

    @Data
    public static class EsFunction<T> {
        private String column;
        private EsFunctionEnum esFunctionEnum;

        public static <T> EsFunction of(SFunction<T, ?> sfColumn, EsFunctionEnum esFunctionEnum) {
            String column = new EsSearchWrapper<T>().getfiledNameWithCach(sfColumn);
            return of(column, esFunctionEnum);
        }

        public static EsFunction of(String column, EsFunctionEnum esFunctionEnum) {
            EsFunction function = new EsFunction<>();
            function.setColumn(column);
            function.setEsFunctionEnum(esFunctionEnum);
            return function;
        }

        public static <T> EsFunction of(SFunction<T, ?> sfColumn) {
            String column = new EsSearchWrapper<T>().getfiledNameWithCach(sfColumn);
            return of(column);
        }

        public static EsFunction of(String column) {
            EsFunction function = new EsFunction<>();
            function.setColumn(column);
            return function;
        }
    }

    @Getter
    public static class LimitSize {
        private Integer limitSize;

        private LimitSize(Integer limitSize) {
            this.limitSize = limitSize;
        }

        public static LimitSize of(Integer limitSize) {
            return new LimitSize(limitSize);
        }
    }


    @Getter
    public enum EsFunctionEnum {
        COUNT("count", "去重总数"),
        MAX("max", "去重最大值"),
        MIN("min", "去重最小值"),
        AVG("avg", "去重平均值"),
        SUM("sum", "去重总和"),
        ;

        private final String code;
        private final String desc;

        EsFunctionEnum(String code, String desc) {
            this.code = code;
            this.desc = desc;
        }
    }
}
EsTestApplication
package com.xxx.xxx.es.test;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class EsTestApplication {

    public static void main(String[] args) {
        SpringApplication.run(EsTestApplication.class, args);
    }

}

application.yml

spring:
  profiles:
    active: dev
  elasticsearch:
    uris: localhost:9200
    username: elastic
    password: xxx
server:
  port: 8080

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>xxx-es-test</artifactId>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath></relativePath>
    </parent>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>3.2.3</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.12</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <version>3.2.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.21</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.28</version>
            <scope>provided</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
        <dependency>
            <groupId>com.alibaba.fastjson2</groupId>
            <artifactId>fastjson2</artifactId>
            <version>2.0.31</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>

    </dependencies>

</project>

这是一个自建测试项目。

最后在总结两个问题吧

1.分页查询无法查询后1w条的数据

我想用过es的分页的都会发现这个问题,es默认最多只能返回符合条件的前1w条数据,如果想看最后几页的数据,超过了1w条就会报错,如果对性能要求没那么高的话,可以修改这个默认值,这里我在工具类里提供了一个方法可以在创建索引的时候去设置

2.打印dsl json串

当访问es出现异常的时候,我们需要排查问题,需要看日志,这时就需要看你发送的json是什么来分析你的条件是否正确,在我自测的时候发现,这个客户端的代码重写了一个toString()方法,可以打印出你发送的json串,如下图

但是客户端的对象大部分都是只能build一次,比如

如果重复调用会抛出"Object builders can only be used once",他只允许你创建一次。

所以我在工具类里会创建两个对象,一个用于执行,一个用于打印dsl-json

目前使用的话大概就是遇到这两个问题吧,希望能帮到大家0.0

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 1.x集成ElasticsearchES)可以通过使用Spring Data Elasticsearch来实现。Spring Data ElasticsearchSpring Data项目的一部分,它提供了与Elasticsearch集成,简化了与ES的交互。 以下是Spring Boot 1.x集成ES的步骤: 1. 添加依赖:在`pom.xml`文件中添加Spring Data Elasticsearch的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> ``` 2. 配置连接信息:在`application.properties`或`application.yml`文件中配置ES的连接信息,包括主机、端口等: ```properties spring.data.elasticsearch.cluster-nodes=localhost:9200 ``` 3. 创建实体类:创建与ES索引对应的实体类,并使用注解标记字段与索引的映射关系。例如: ```java @Document(indexName = "my_index", type = "my_type") public class MyEntity { @Id private String id; @Field(type = FieldType.Text) private String name; // 其他字段... // getter和setter方法... } ``` 4. 创建Repository接口:创建继承自`ElasticsearchRepository`的接口,用于对ES进行CRUD操作。例如: ```java public interface MyEntityRepository extends ElasticsearchRepository<MyEntity, String> { // 自定义查询方法... } ``` 5. 使用Repository进行操作:在需要使用ES的地方注入`MyEntityRepository`,即可使用其提供的方法进行数据操作。例如: ```java @Autowired private MyEntityRepository myEntityRepository; public void saveEntity(MyEntity entity) { myEntityRepository.save(entity); } public MyEntity findById(String id) { return myEntityRepository.findById(id).orElse(null); } // 其他操作方法... ``` 以上是Spring Boot 1.x集成ES的基本步骤,你可以根据实际需求进行进一步的操作和配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值