美文网首页
用JAVA代码实现ES7搜索功能 elasticsearch数据

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

作者: 一个忙来无聊的人 | 来源:发表于2021-03-20 15:44 被阅读0次

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

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

    • 1、使用泛型保存搜索数据信息--通用性
    • 2、保存搜索数据同时,进行搜索数据建议存储
    • 3、搜索建议数据去重(也就是搜索数据)

    一 、基础配置信息

    • 1.1、pom文件中引入
     <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>
    
    • 1.2、配置文件
      本项目配置文件类型为 properties
    spring.elasticsearch.rest.uris=http://127.0.0.1:9200
    spring.elasticsearch.rest.username=es
    spring.elasticsearch.rest.password=es
    

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

    • 2.1、保存搜索数据泛型
    @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();
        }
    
    
    • 2.2、保存数据核心代码
     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);
        }
    
    
    • 2.3、搜索建议数据保存
      在保存搜索数据的controller 新增一个 sevice 异步保存搜索建议
     @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;
        }
    }
    

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

    相关文章

      网友评论

          本文标题:用JAVA代码实现ES7搜索功能 elasticsearch数据

          本文链接:https://www.haomeiwen.com/subject/falwcltx.html