当前位置:   article > 正文

二、springboot整合elasticsearch+hanlp(7.16.2)----附完整测试用例_co.elastic.clients.elasticsearch._types.elasticsea

co.elastic.clients.elasticsearch._types.elasticsearchexception: [es/indices.

一、实现效果图:

请添加图片描述

二、主要实现以下几点:

1、springboot整合elasticsearch+hanlp分词器 (7.16.2) 。

2、实现基本的增删改查功能、关键字分页搜索、时间排序、关键字高亮等操作 。

建议elastic用7.16.2版本,(即使是用了其他比7.16更高的版本,基本语法是不会改变的,所以降到7.16并不会有非常大的影响)而且插件也只支持到7.16.2。)

三、gitee测试用例(推荐):

链接: https://gitee.com/muyangrenOvo/elastic-boot

四、部分实现代码

1、添加pom依赖

<!--elasticsearch7.16.2依赖包-->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.16.2</version>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>7.16.2</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>
<dependency>
    <groupId>jakarta.json</groupId>
    <artifactId>jakarta.json-api</artifactId>
    <version>2.0.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.12.3</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.12.3</version>
</dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

2、配置appllication.yml

#elasticsearch-ip-端口-索引-分词器(如下)-是否需要账号密码
#hanlp: hanlp默认分词
#hanlp_standard: 标准分词
#hanlp_index: 索引分词
#hanlp_n_short: N-最短路分词
#hanlp_dijkstra: 最短路分词
#hanlp_speed: 极速词典分词
elasticsearch:
  ip: 192.168.xxx.xxx:9200       
  index: transfer 
  analyzer: hanlp_index
  isNeedAccount: false	
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3、配置ES信息

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.json.jackson.JacksonJsonpMapper;
import co.elastic.clients.transport.ElasticsearchTransport;
import co.elastic.clients.transport.rest_client.RestClientTransport;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author muyangren
 * @Auther: muyangren
 * @Date: 2022/6/30
 * @Description: @Configuration 类似 xml-bean
 * @Version: 1.0
 */
  @Configuration
  public class ElasticSearchClientConfig {
      @Value("${elasticsearch.ip}")
      private String ip;
      @Value("${elasticsearch.isNeedAccount}")
      private boolean isNeedAccount;
      @Bean
      public ElasticsearchClient elasticsearchClient() {
          ElasticsearchClient client;
          //是否需要账号密码
          if (isNeedAccount){
              CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
              credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "admin@123456"));
              RestClient restClient = RestClient.builder(this.getElasticSearchHttpHosts()).setHttpClientConfigCallback(httpAsyncClientBuilder -> {
                  httpAsyncClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
                  return httpAsyncClientBuilder;
              }).build();
              ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
              client = new ElasticsearchClient(transport);
          }else {
              // Create the low-level client
              RestClient restClient = RestClient.builder(this.getElasticSearchHttpHosts()).build();
              // Create the transport with a Jackson mapper
              ElasticsearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
              // And create the API client
              client = new ElasticsearchClient(transport);
          }
          return client;
      }
      /**
       * ElasticSearch 连接地址
       * 多个逗号分隔
       * 示例:127.0.0.1:9201,127.0.0.1:9202,127.0.0.1:9203
       */
      private HttpHost[] getElasticSearchHttpHosts() {
          String[] hosts = ip.split(",");
          HttpHost[] httpHosts = new HttpHost[hosts.length];
          for (int i = 0; i < httpHosts.length; i++) {
              String host = hosts[i];
              httpHosts[i] = new HttpHost(host.split(":")[0], Integer.parseInt(host.split(":")[1]));
          }
          return httpHosts;
      }
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

4、集成工具类ElasticSearchUtils

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ElasticsearchException;
import co.elastic.clients.elasticsearch._types.Result;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.mapping.TextProperty;
import co.elastic.clients.elasticsearch._types.mapping.TypeMapping;
import co.elastic.clients.elasticsearch.core.*;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.core.bulk.BulkResponseItem;
import co.elastic.clients.elasticsearch.indices.CreateIndexRequest;
import co.elastic.clients.elasticsearch.indices.CreateIndexResponse;
import co.elastic.clients.elasticsearch.indices.IndexSettings;
import co.elastic.clients.transport.endpoints.BooleanResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springblade.core.tool.utils.Func;
import org.springblade.modules.talk.entity.TransEsContent;
import org.springframework.stereotype.Component;

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

/**
 * @author guangsheng
 * @Date: 2022/7/14
 * @Description: 重构ES方法,解决代码重复问题 方法大致如此,细节需要自己去打磨
 * @Version: 1.0
 */
@Component
public class ElasticSearchUtils {

    private static final Logger logger = LoggerFactory.getLogger(ElasticSearchUtils.class);


    /**
     * 判断索引(index)是否存在
     */
    public static Boolean whetherIndex(ElasticsearchClient esClient,String index){
        BooleanResponse getIndexResponse;
        boolean whetherIndex = false;
        try {
            getIndexResponse = esClient.indices().exists(e -> e.index(index));
            whetherIndex = getIndexResponse.value();
        } catch (IOException e) {
            logger.info("es服务器超时,【{}】索引查询失败,请检查es服务器是否启动,该功能不影响后续代码执行。",index);
        }
        return whetherIndex;
    }

    /**
     * 添加索引
     * 分析器主要有两种情况会被使用:
     * 第一种是插入文档时,将text类型的字段做分词然后插入倒排索引,
     * 第二种就是在查询时,先对要查询的text类型的输入做分词,再去倒排索引搜索
     * analyzer: 分词器
     * searchAnalyzer: 查询分词器
     */
    public static Boolean addIndex(ElasticsearchClient esClient,String index,String analyzer){
        boolean whetherTrue =false;
        try {
            // 配置索引
            Map<String, Property> property = new HashMap<>(1);
            property.put("onlyOneBest", new Property(new TextProperty.Builder()
                    .analyzer(analyzer)
                    .searchAnalyzer(analyzer)
                    .index(true)
                    .store(true)
                    .build()));
            TypeMapping typeMapping = new TypeMapping.Builder()
                    .properties(property)
                    .build();
            IndexSettings indexSettings  = new IndexSettings.Builder()
                    .numberOfShards("5")
                    .build();
            CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder()
                    .index(index)
                    .mappings(typeMapping)
                    .settings(indexSettings)
                    .build();
            CreateIndexResponse createIndexResponse = esClient.indices().create(createIndexRequest);
            whetherTrue = Boolean.TRUE.equals(createIndexResponse.acknowledged());
        } catch (IOException e) {
            logger.info("es服务器超时,请检查es服务器是否启动,该功能不影响后续代码执行");
        } catch (ElasticsearchException e) {
            logger.info("同步服务器--es服务器未启用,或者版本错误导致");
        }
        return whetherTrue;
    }

    /**
     * ES添加方法
     */
    public static void addEsTransfer(ElasticsearchClient esClient, TransEsContent transEsContent , String index) {
        //存储到ES中
        try {
            CreateResponse createResponse = esClient.create(e -> e
                    .index(index)
                    .id(String.valueOf(transEsContent.getId()))
                    .document(transEsContent));
            Result result = createResponse.result();
            if (Func.isNotEmpty(result)) {
                logger.info("【id】:{},es添加数据:{}", transEsContent.getId(), result);
            }
        } catch (IOException e) {
            logger.info("【id】:{}未添加到es服务器,es服务器超时,请检查es服务器是否启动,该功能不影响后续代码执行", transEsContent.getId());
        } catch (ElasticsearchException e) {
            logger.info("添加---es服务器未启用,或者版本错误导致");
        }
    }

    /**
     * 同步所有数据
     * 批量超过一万条会超时,这里使用分页解决,每次插入五百条
     */
    public static void addAllEsTransfer(ElasticsearchClient esClient,List<TransEsContent> transList,String index) {
        BulkRequest.Builder builder = new BulkRequest.Builder();
        for (TransEsContent trans : transList) {
            builder.operations(op -> op
                    .index(idx -> idx
                            .index(index)
                            .id(String.valueOf(trans.getId()))
                            .document(trans)));
        }
        BulkResponse result;
        try {
            result = esClient.bulk(builder.build());
            if (Func.isNotEmpty(result)) {
                logger.info("数据同步到es服务器,数量为:" + result.took());
            }
        } catch (IOException e) {
            logger.info("es服务器超时,请检查es服务器是否启动,该功能不影响后续代码执行");
        } catch (ElasticsearchException e) {
            logger.info("同步服务器--es服务器未启用,或者版本错误导致");
        }
    }
    /**
     * ES更新方法
     */
    public static void updateEsTransfer(TransEsContent transEsContent, ElasticsearchClient esClient, String index) {
        try {
            //先查看是否存在ES服务中
            GetRequest.Builder builder = new GetRequest.Builder();
            GetRequest transfer = builder.index(index).id(String.valueOf(transEsContent.getId())).build();
            GetResponse<TransEsContent> getResponse = esClient.get(transfer, TransEsContent.class);
            TransEsContent source = null;
            if (Func.isNotEmpty(getResponse)) {
                source = getResponse.source();
            }
            //先根据id查询是否存在该数据
            if (Func.isEmpty(source)) {
                CreateResponse createResponse = esClient.create(e -> e
                        .index(index)
                        .id(String.valueOf(transEsContent.getId()))
                        .document(transEsContent));
                Result result = createResponse.result();
                if (Func.isNotEmpty(result)) {
                    logger.info("【id】:{},es添加数据:{}", transEsContent.getId(), result);
                }
            } else {
                //更新到es服务器中
                UpdateResponse<TransEsContent> updateResponse;
                updateResponse = esClient.update(e -> e
                                .index(index)
                                .id(String.valueOf(transEsContent.getId()))
                                .doc(transEsContent)
                        , TransEsContent.class);
                Result result = updateResponse.result();
                if (Func.isNotEmpty(result)) {
                    logger.info("【id】:{},es更新数据:{}", transEsContent.getId(), result);
                }
            }
        } catch (IOException e) {
            logger.info("【id】:{}未更新到es服务器,es服务器超时,请检查es服务器是否启动,该功能不影响后续代码执行", transEsContent.getId());
        } catch (ElasticsearchException e) {
            logger.info("更新---es服务器未启用,或者版本错误导致");
        }
    }

    /**
     * 异步删除ES服务器中的数据
     */
    public static void asyncDeleteEsTrans(List<TransEsContent> list, ElasticsearchClient esClient, String index) {
        new Thread(() -> {
            try {
                ArrayList<BulkOperation> bulkOperations = new ArrayList<>();
                for (TransEsContent trans : list) {
                    bulkOperations.add(new BulkOperation.Builder()
                            .delete(d -> d
                                    .id(String.valueOf(trans.getId()))
                                    .index(index))
                            .build());
                }
                BulkResponse bulkResponse = esClient.bulk(e -> e.index(index).operations(bulkOperations));
                List<BulkResponseItem> items = bulkResponse.items();
                if (Func.isNotEmpty(items)) {
                    logger.info("【id】:{},调用ES批量删除返回数据:{}", list.get(0).getId(), items);
                }
            } catch (IOException e) {
                logger.info("es服务器超时,【id】:{}未同步到es服务器,请检查es服务器是否启动,该功能不影响后续代码执行。", list.get(0).getId());
            } catch (ElasticsearchException e) {
                logger.info("删除---es服务器未启用,或者版本错误导致");
            }
        }).start();
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209

5、测试用例

在这里插入图片描述

	@Value("${elasticsearch.analyzer}")
	private String analyzer;
	@Value("${elasticsearch.index}")
	private String index;
	@Autowired
	private ElasticsearchClient esClient;


  /**
     * 初始化数据
     * @return
  */
	@Override
	public Boolean initEsData() {
		//1、查询索引是否存在
		Boolean whetherTrue = ElasticSearchUtils.whetherIndex(esClient, index);

		if (!whetherTrue) {
			//2、不存在先添加索引在同步数据
			whetherTrue = ElasticSearchUtils.addIndex(esClient, index, analyzer);
		}
		//3、获取需要添加的总数(select count(1) from table where ... )
		Integer transCount =500;

  	//4、用框架自带的分页方法
		Query query = new Query();
  	//4.1、每次查询500
		query.setSize(500);
		long pages = transCount / query.getSize();
		if (transCount % query.getSize() != 0L) {
			++pages;
		}
		for (int i = 1; i <= pages; i++) {
			query.setCurrent(i);
			List<xxxx> transList = baseMapper.selectPage(Condition.getPage(query));
			if (Func.isNotEmpty(transList)) {
				ElasticSearchUtils.addAllEsTransfer(esClient, esTransList, index);
			}
		}
		return whetherTrue;
	}


    /**
     * 分页(细节方面需要自己打磨,这里只是展示如何使用到hanlp)
     * @param transEsSearchVo
     * @return
     */
	@Override
	public EsTransMap selectEsTransPage(TransEsSearchVo transEsSearchVo) {
		StopWatch sw = new StopWatch();
		sw.start();
		int current;
		if (transEsSearchVo.getPageNo() < 1) {
			transEsSearchVo.setPageNo(1);
		}
		if (transEsSearchVo.getPageSize() < 1) {
			transEsSearchVo.setPageSize(10);
		}
		if (transEsSearchVo.getPageNo() == 1) {
			current = transEsSearchVo.getPageNo() - 1;
		} else {
			current = (transEsSearchVo.getPageNo() - 1) * transEsSearchVo.getPageSize();
		}

		//1、过滤关键字的特殊符号
		String regEx="[\n`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。, 、?]";
		Pattern p = Pattern.compile(regEx);
		Matcher m = p.matcher(transEsSearchVo.getKeyword());
		transEsSearchVo.setKeyword(m.replaceAll("").trim());

    //2、构造过滤请求
		SearchRequest.Builder builder = new SearchRequest.Builder();
		SearchRequest specificContent;

		specificContent = builder
			.index(index)
			.query(q -> q
					.bool(b -> {
						//3、查询关键字(对应字段)
						b.must( t-> t.match(v -> v.field("onlyOneBest").query(FieldValue.of(transEsSearchVo.getKeyword()))));
						return b;
					})
			)
       //4、命中关键字高亮
			.highlight(h ->h.fields("onlyOneBest",f->f.preTags("<font color='red'>").postTags("</font>")))
       //5、排序
			.sort(sort -> sort.field(f->f.field(EsConstant.SORT_FILED_TALK_CREATE_TIME).order(SortOrder.Desc)))
			//从第from开始每次查询size个(第一条数据下标为0)
			.from(current)
			.size(transEsSearchVo.getPageSize())
			.build();

		SearchResponse<TransEsContent> searchResponse;
		EsTransMap esTransMap = new EsTransMap();
		try {
			searchResponse = esClient.search(specificContent, TransEsContent.class);
			List<TransEsContent> transEsList = new ArrayList<>();
            //6、获取高亮部分
			List<Hit<TransEsContent>> hits = searchResponse.hits().hits();

			if (Func.isNotEmpty(hits)) {
				hits.forEach( h -> {
					assert h.source() != null;
      	 //7、处理高亮部分
					transEsList.add(handleContentAndTitle(h.source(),h.highlight()));
				});
			}
			sw.stop();
			esTransMap.setRecords(transEsList);
			esTransMap.setQueryTime(sw.getTotalTimeMillis());
			esTransMap.setTotal(searchResponse.hits().total().value());
		} catch (IOException e) {
			throw new ServiceException("ES服务器未启用,无法使用搜索功能");
		}
		return esTransMap;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117

以上就是部分代码,如果想更好看实现效果、可以拉下代码到本地跑。

五、拓展内容(部署es服务器)

没有部署es服务器的可以参考下该篇文章 链接: docker-compose部署elasticsearch+hanlp分词器

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/172898
推荐阅读
相关标签
  

闽ICP备14008679号