用JAVA代码实现ES7搜索功能 elasticsearch数据

2021-03-20  本文已影响0人  一个忙来无聊的人

网上找了很多方案,大多数都是实现去重数量查询,没有实现总数据去重查询功能,最后找到一篇文章是命令行去重相关的功能 参考文章
另外,找了很久搜索建议实现,全是使用前缀进行搜索,这里使用另外一种取巧方式进行实现,并且和数据近乎实时

温馨提示:本文主要做三件事

一 、基础配置信息

 <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <elasticsearch.version>7.7.0</elasticsearch.version>
        <!--<spring.data.elasticsearch.version>3.1.0.RELEASE</spring.data.elasticsearch.version>-->
        <fastjson.version>1.2.70</fastjson.version>
        <project.build.targetJdk>1.8</project.build.targetJdk>
    </properties>

  <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <!--ES 引入pom文件信息 start -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>${elasticsearch.version}</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>${elasticsearch.version}</version>
        </dependency>

        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>${elasticsearch.version}</version>
        </dependency>
spring.elasticsearch.rest.uris=http://127.0.0.1:9200
spring.elasticsearch.rest.username=es
spring.elasticsearch.rest.password=es

二、保存数据(同步数据到搜索引擎),字段用ik分词

@Data
public class EsBatchAddReq<T> {
    /**
     * 系统编码
     */
    @NotBlank(message = "系统编码不能为空")
    private String systemCode;

    /**
     * 业务编码,类似表名
     */
    @NotBlank(message = "业务编码不能为空")
    private String businessCode;
  

/**
* 保存的搜索数据列表信息,也就是从业务端同步过来数据信息
* 
*/
    @NotEmpty(message = "搜索数据列表不能为空")
    private List<T> beanList;

 /**
     * 搜索建议的数据字段信息, 必须为上面的 (T)某一个字段
     *
     */
    private List<String> suggestEntities;

 /**
     * 此处生成的是ES的索引名称,系统编码和业务编码都必须是小写,有业务端自行定义
     *
     * @return
     */
 public String getEsCode() {
        return new StringBuilder(systemCode).append("_").append(businessCode).toString();
    }
    /**
     * 此处生成的是业务端自定义搜索建议的索引名称,前缀以suggest_开头
     *
     * @return
     */
    public String getSuggestCode() {
        return new StringBuilder("suggest_").append(systemCode).append("_").append(businessCode).toString();
    }

 public EsResponse upsetIndex(EsBatchAddReq requestParam) throws IOException {
        // 判断开启数字嗅探  并且判断是否需要创建自定义mapping,并且创建索引
        openNumber(requestParam);
        BulkRequest request = new BulkRequest();
        // 设置索引名称,索引名称由系统编码和业务编码组成。若业务比较简单,也可以仅设置为一个字段。此处是为了方便多系统多业务使用
        request.routing(requestParam.getEsCode());
        for (Object o : requestParam.getBeanList()) {
            JSONObject jsonObject = (JSONObject) JSON.toJSON(o);
            String id = jsonObject.getString(EsConstant.ES_ID);
            Assert.notNull(id, "实体类必须包含id");
            String data = JSONObject.toJSONString(o);
            // 实体类必须包含ES自定义主键,搜索引擎ES将根据自定义ID进行重复新验证。
            // 如果ID重复则修改数据,若ID不重复则新增数据
            request.add(new IndexRequest(requestParam.getEsCode()).id(id).source(data, XContentType.JSON));
        }
        BulkResponse bulk = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
        RestStatus status = bulk.status();
        return EsResponse.successRes(status.toString());
    }


    /**
     * 判断索引是否存在
     *  新增判断搜索建议索引是否存在
     * @param param
     */
// 此处命名太丑了 ,,,凑合用吧。。emmm
    private void openNumber(EsBatchAddReq param) throws IOException {
        // 创建数据存储索引
        GetIndexRequest indexRequest = new GetIndexRequest(param.getEsCode());
        boolean exists = restHighLevelClient.indices().exists(indexRequest, RequestOptions.DEFAULT);
        if (!exists) {
            createMapping(param.getEsCode(), param.getIkParticiple());
        }
        // 如果搜索建议字段为空 不创建搜索建议
        if (CollectionUtils.isEmpty(param.getSuggestEntities())){
            return;
        }

        // 创建搜索建议索引
        GetIndexRequest suggestIndex = new GetIndexRequest(param.getSuggestCode());
        boolean suggestExit = restHighLevelClient.indices().exists(suggestIndex, RequestOptions.DEFAULT);
        if (!suggestExit) {
           // 搜索建议为了便于搜索提示,不使用IK分词,直接使用默认分词
            createMapping(param.getSuggestCode(), Boolean.FALSE);
        }
    }

    /**
     * 创建索引
     * @param esCode
     * @param ikParticiple
     * @throws IOException
     */
    private void createMapping(String esCode, Boolean ikParticiple) throws IOException {
        JSONObject jsonObject = new JSONObject();
        Map<String, Object> mappingMap = new HashMap<>();
        // 开启数字嗅探
        mappingMap.put("numeric_detection", Boolean.TRUE);
        // 设置动态时间映射
        List<String> dateList = new ArrayList<>();
        dateList.add("MM/dd/yyyy");
        dateList.add("yyyy/MM/dd HH:mm:ss");
        dateList.add("yyyy-MM-dd");
        dateList.add("yyyy-MM-dd HH:mm:ss");
        mappingMap.put("dynamic_date_formats", dateList);

        //  判断是否创建映射,如果没有则创建映射.同时必须要使用IK分词才会手动创建映射
        if (ikParticiple) {
            Map<String, String> childMap = new HashMap<>();
            childMap.put("analysis.analyzer.default.type", "ik_max_word");
            Map<String, Object> map = new HashMap<>();
            map.put("index", childMap);
            // 设置分词器为IK 分词
            jsonObject.put("settings", map);
        }
        jsonObject.put("mapping", mappingMap);
        CreateIndexRequest createIndexRequest = new CreateIndexRequest(esCode).source(jsonObject.toJSONString(), XContentType.JSON);
        restHighLevelClient.indices().create(createIndexRequest, RequestOptions.DEFAULT);
    }

 @Autowired
    private EsLogic esLogic;
    @Autowired
    private SuggestLogic suggestLogic;
    /**
     * 批量新增或者修改的功能
     *
     * @param requestParam
     * @return
     * @throws IOException
     */
    @RequestMapping("/esSearch/upset")
    public EsResponse upsetIndex(@RequestBody @Valid EsBatchAddReq requestParam) throws IOException {
        // 1、保存搜索数据并且根据参数判断是否创建搜索建议接口
        EsResponse esResponse = esLogic.upsetIndex(requestParam);
        // 2、保存搜索建议数据信息
        suggestLogic.upsetSuggest(requestParam);
        return esResponse;
    }

搜索建议保存核心代码

@Component
@Slf4j
public class SuggestLogic {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    /**
     * 异步新增搜索建议
     *
     * @param requestParam
     */
    @Async
    public void upsetSuggest(EsBatchAddReq requestParam) throws IOException {
        if (CollectionUtils.isEmpty(requestParam.getSuggestEntities())) {
            return;
        }
        log.info("开始异步执行搜索建议处理:{}, {}", requestParam.getSuggestCode(), JSONObject.toJSONString(requestParam.getSuggestEntities()));

        List<EsSuggestEntity> esSuggestEntities = new ArrayList<>(1024);
        List<String> fieldNames = requestParam.getSuggestEntities();
        // 避免在循环中重复计算大小
        int size = fieldNames.size();
        List beanList = requestParam.getBeanList();
      // 外层循环遍历实体类
        for (Object o : beanList) {
            JSONObject jsonObject = (JSONObject) JSON.toJSON(o);
            // 实体类唯一主键
            String id = jsonObject.getString(EsConstant.ES_ID);
            // 搜索建议定义了一个删除标识字段,为了便于搜索时过滤掉已删除数据,如果不需要可以忽略
            String deleteFlag = jsonObject.getString(“deleteFlag”);
          // 取出每一个搜索建议字段的字段名称和字段值,
            for (int i = 0; i < size; i++) {
                EsSuggestEntity esSuggestEntity = new EsSuggestEntity();
                String fieldName = fieldNames.get(i);
              // 搜索建议的唯一主键信息用实体类id加上字段信息表示,此时也能保证唯一性
                String newId = new StringBuilder(id).append("_").append(fieldName).toString();
                esSuggestEntity.setId(newId);
                esSuggestEntity.setDeleteFlag(deleteFlag);
                String suggestValue = jsonObject.getString(fieldName);
                esSuggestEntity.setSuggestValue(suggestValue);
                if (!StringUtils.isEmpty(esSuggestEntity.getSuggestValue())) {
                    esSuggestEntities.add(esSuggestEntity);
                }
            }
        }

        BulkRequest request = new BulkRequest();
        // 保存搜索建议字段
        for (EsSuggestEntity o : esSuggestEntities) {
            JSONObject jsonObject = (JSONObject) JSON.toJSON(o);
            String id = jsonObject.getString(EsConstant.ES_ID);
            String data = JSONObject.toJSONString(o);
            // 如果ID重复则修改数据,若ID不重复则新增数据
            request.add(new IndexRequest(requestParam.getSuggestCode()).id(id).source(data, XContentType.JSON));
        }
        request.routing(requestParam.getSuggestCode());
        // 批量插入搜索建议
        restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
    }
}

搜索建议的实体类

@Data
public class EsSuggestEntity {
    /**
     * 唯一主键,取自搜索数据的唯一主键加上搜索建议字段名称
     */
    private String id;
    /**
     * 字段名称
     */
    private String suggestValue;
    /**
     * 删除标识
     */
    private String deleteFlag;
}

三、数据搜索功能,权重搜索

此处详细搜索功能参考我另外一篇文章ES 7.x (ElasticSearch) 与Java集成使用新增、修改、查询含操作语句,文章已经更新权重搜索等,本文不在占用其他篇幅去说明

四、搜索建议。去重搜索

本文实现搜索建议分词搜索功能,但是没有实现拼音功能,而且还是使用取巧的方式实现的,如果大家有更好的方式实现,欢迎告知一下,此处仅为记录一种实现个人搜索建议功能代码逻辑,不喜勿喷,但欢迎多多提意见#

本文搜索建议使用是基于本文 2.2章节以及2.3的前提下。

@Data
public class EsSuggestReq {

    /**
     * 业务系统编码
     */
    @NotBlank(message = "系统编码不能为空")
    private String systemCode;

    /**
     * 业务编码,类似表名
     */
    @NotBlank(message = "业务编码不能为空")
    private String businessCode;

      /**
     * 第几页
     */
    private Integer page = 1;

    /**
     * 每页展示的梳理
     */
    private Integer size = 10;

    /**
     * 从低几条开始(不传)
     */
    private Integer from;

    /**
     * 搜索值
     */
    private String searchValue;

    /**
     * 数据删除标识状态
     */
    private String deleteFlag;

    /**
     * 此处生成的是ES的索引名称,请注意此处没有检验系统编码和业务编码大小写,只能是小写字母,也可以在此处设置转换为小写字母
     *
     * @return
     */
    public String getSuggestCode() {
        return new StringBuilder("suggest_").append(systemCode).append("_").append(businessCode).toString();
    }
    // 此处挖了个坑,个人认为没有必要查询第二页搜索建议数据,如果各位认为有必要请注意使用注释调的来计算
    public Integer getFrom() {
//        if (from == null){
//            from = (page - 1) * size;
//        }
        from = 0;
        return from;
    }
}

搜索建议核心代码逻辑功能

/**
     * 查询搜索建议
     *
     * @param suggestReq
     * @return
     */
    public EsResponse querySuggest(EsSuggestReq suggestReq) throws IOException {
        // 设置搜索建议索引
        SearchRequest searchRequest = new SearchRequest(suggestReq.getSuggestCode());
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();

        // 设置搜索的值
        MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder(“suggestValue”, suggestReq.getSearchValue());
        boolQueryBuilder.should(matchQueryBuilder);

        // 设置删除功能, 搜索建议不应该把过期的数据给删除
        if (!StringUtils.isEmpty(suggestReq.getDeleteFlag())){
            boolQueryBuilder.must(QueryBuilders.termsQuery("deleteFlag.keyword", suggestReq.getDeleteFlag()));
        }
        searchSourceBuilder.query(boolQueryBuilder).from(suggestReq.getFrom()).size(suggestReq.getSize());
        // 去重功能,此时我们的搜索字段的名称为 suggestValue ,此处需要使用 suggestValue.keyword 来进行查询
        CollapseBuilder collapseBuilder = new CollapseBuilder("suggestValue.keyword");
        searchSourceBuilder.collapse(collapseBuilder);
        searchRequest.source(searchSourceBuilder);

        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        EsResponse suggestPageResponse = EsResponseUtil.getSuggestPageResponse(search, suggestReq.getPage(), suggestReq.getSize());

        return suggestPageResponse;
    }

五、本文其他类代码信息

        EsResponse esResponse;
        Set<String> resultSet = new HashSet<>(size * 2);
        // 如果成功
        if (HttpStatus.SC_OK == response.status().getStatus()) {
            esResponse = new EsResponse();
            SearchHit[] hits = response.getHits().getHits();
            if (hits != null && hits.length != 0) {
                for (SearchHit searchHit : hits) {
                    Map<String, Object> sourceAsMap = searchHit.getSourceAsMap();
                    JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(sourceAsMap));
                    String suggestValue = jsonObject.getString("suggestValue");
                    resultSet.add(suggestValue);
                }
            }
            esResponse.setData(JSONObject.toJSONString(resultSet));
        } else {
            esResponse = EsResponse.failRes(String.valueOf(response.status().getStatus()), response.status().toString());
        }
        return esResponse;
    }
public class EsResponse implements Serializable {
    private static final long serialVersionUID = 14284224234985L;
    /**
     * 返回编码  0代表成功
     */
    private String code = "0";
    /**
     * 返回参数信息
     */
    private String message = "操作成功";
    /**
     * 是否成功
     */
    private Boolean success = true;
    /**
     * 数据信息
     */
    private String data;
    /**
     * 成功的方法
     *
     * @return
     */
    public static EsResponse successRes() {
        return new EsResponse();
    }
    /**
     * 成功的方法
     *
     * @return
     */
    public static EsResponse successRes(String data) {
        EsResponse response = new EsResponse();
        response.setData(data);
        return response;
    }
    /**
     * 失败的方法
     *
     * @return
     */
    public static EsResponse failRes() {
        EsResponse response = new EsResponse();
        response.setSuccess(Boolean.FALSE);
        response.setCode("999999");
        response.setMessage("ES操作错误,请联系管理员");
        return response;
    }
    /**
     * 失败的方法
     *
     * @return
     */
    public static EsResponse failRes(String retMsg) {
        EsResponse response = new EsResponse();
        response.setSuccess(Boolean.FALSE);
        response.setCode("999999");
        response.setMessage(retMsg);
        return response;
    }
    /**
     * 失败的方法
     *
     * @return
     */
    public static EsResponse failRes(String retCode, String retMsg) {
        EsResponse response = new EsResponse();
        response.setSuccess(Boolean.FALSE);
        response.setCode(retCode);
        response.setMessage(retMsg);
        return response;
    }
}

本文结束,感谢大家的浏览,如果觉得本文对大家有帮助,欢迎点个赞,如果文章中有错误的地方,欢迎大家批评指正;
最后关于搜索建议这块,如果有更好的建议欢迎告知,蟹蟹!

上一篇下一篇

猜你喜欢

热点阅读