最近比较忙,好久没更新博客了,今天抽个空记录一下使用springboot接入阿里云ES并带模糊高亮查询功能,闲话不多说,上干活。
引入POM依赖:
<!-- ES config 接入阿里云ES --> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-high-level-client</artifactId> <version>7.4.0</version><!--$NO-MVN-MAN-VER$--> <exclusions> <exclusion> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </exclusion> <exclusion> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>elasticsearch-rest-client</artifactId> <version>7.4.0</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>7.4.0</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.7</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.7</version><!--$NO-MVN-MAN-VER$--> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.1</version><!--$NO-MVN-MAN-VER$--> </dependency>
properties文件内容添加如下:
## ES config spring.elasticsearch.username= spring.elasticsearch.password= spring.elasticsearch.cluster_host=IP地址 或 阿里云内网域名 或 公网域名 spring.elasticsearch.cluster_port=9200 spring.es.env.flag=sit
创建实体类:
package com.xx.xx.es.entity; import org.springframework.data.annotation.Id; import lombok.Data; /** * @author Jimmy Shan * @date 2020-11-26 * @desc 示例DEMO 数据存储ES */ @Data public class CusDemoInfoDocument { @Id private String id;// 主键ID private String demoName;// 名称 private String demoCode;// 编码 private String demoValue;// 值 public CusDemoInfoDocument() { } public CusDemoInfoDocument(String id, String demoName, String demoCode, String demoValue) { this.id = id; this.demoName = demoName; this.demoCode = demoCode; this.demoValue = demoValue; } public CusDemoInfoDocument(String id) { this.id = id; } }
创建工具类:
package com.xx.xx.common.util; import java.io.IOException; import java.util.List; import java.util.Map; 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.apache.http.impl.nio.client.HttpAsyncClientBuilder; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.HttpAsyncResponseConsumerFactory; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestClient; import org.elasticsearch.client.RestClientBuilder; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.indices.CreateIndexRequest; import org.elasticsearch.client.indices.CreateIndexResponse; import org.elasticsearch.client.indices.GetIndexRequest; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import com.xx.xx.common.Constant; import com.xx.xx.es.entity.CusDemoInfoDocument; /** * @author Jimmy Shan * @date 2020-11-30 * @desc aliyun ES tools */ @Component public class ElasticsearchUtil { private static final Logger LOGGER = LoggerFactory.getLogger(ElasticsearchUtil.class); private static final RequestOptions COMMON_OPTIONS; @Value("${spring.elasticsearch.cluster_host}") private String clusterHost; @Value("${spring.elasticsearch.cluster_port}") private Integer clusterPort; @Value("${spring.elasticsearch.username}") private String username; @Value("${spring.elasticsearch.password}") private String password; static { RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder(); // 默认缓存限制为100MB,此处修改为30MB。 builder.setHttpAsyncResponseConsumerFactory( new HttpAsyncResponseConsumerFactory .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024)); COMMON_OPTIONS = builder.build(); } /** * @desc 获取ES client */ public RestHighLevelClient getEsClient() { Map<String, RestHighLevelClient> esClientMap = Constant.esClientMap; if (esClientMap == null || esClientMap.isEmpty()) { // 阿里云Elasticsearch集群需要basic auth验证。 final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); //访问用户名和密码,创建阿里云Elasticsearch实例时设置的用户名和密码,也是Kibana控制台的登录用户名和密码。 credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(this.username, this.password)); // 通过builder创建rest client,配置http client的HttpClientConfigCallback。 // 单击所创建的Elasticsearch实例ID,在基本信息页面获取公网地址,即为ES集群地址。 RestClientBuilder builder = RestClient.builder(new HttpHost(this.clusterHost, this.clusterPort)) .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() { @Override public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) { return httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider); } }); // RestHighLevelClient实例通过REST low-level client builder进行构造。 RestHighLevelClient highClient = new RestHighLevelClient(builder); esClientMap.put(Constant.ES_CLIENT, highClient); } return esClientMap.get(Constant.ES_CLIENT); } /** * @desc 创建索引 */ public void createIndex(String index) throws IOException { if(!existsIndex(index)) { CreateIndexRequest request = new CreateIndexRequest(index); CreateIndexResponse createIndexResponse = getEsClient().indices().create(request, COMMON_OPTIONS); LOGGER.info("createIndex: {}", JsonWare.beanToJson(createIndexResponse)); } } /** * @desc 判断索引是否存在 */ public boolean existsIndex(String index) throws IOException { GetIndexRequest request = new GetIndexRequest(index); boolean exists = getEsClient().indices().exists(request, COMMON_OPTIONS); LOGGER.info("existsIndex: {}", exists); return exists; } /** * @desc 判断记录是否存在 */ public boolean exists(String index, String type, CusDemoInfoDocument document) throws IOException { GetRequest getRequest = new GetRequest(index, type, document.getId()); getRequest.fetchSourceContext(new FetchSourceContext(false)); getRequest.storedFields("_none_"); boolean exists = getEsClient().exists(getRequest, COMMON_OPTIONS); LOGGER.info("exists: {}", exists); return exists; } /** * @desc 删除索引 */ public boolean deleteIndex(String index) { try { DeleteIndexRequest request = new DeleteIndexRequest(index); request.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN); AcknowledgedResponse deleteIndexResponse = getEsClient().indices().delete(request, RequestOptions.DEFAULT); return deleteIndexResponse.isAcknowledged(); } catch (Exception e) { LOGGER.error("删除索引失败, index:{}", index); return false; } } /** * @desc 添加数据 */ public String addData(CusDemoInfoDocument document, String index, String type, String docId) { String id = null; try { //index_name为索引名称;type_name为类型名称,7.0及以上版本必须为_doc;doc_id为文档的id。 // 同步执行,并使用自定义RequestOptions(COMMON_OPTIONS)。 RestHighLevelClient highClient = getEsClient(); IndexRequest request = new IndexRequest(index, type) .id(docId).source(JsonWare.beanToJson(document), XContentType.JSON); request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);// 立即刷新 IndexResponse response = highClient.index(request, COMMON_OPTIONS); id = response.getId(); //highClient.close(); LOGGER.info("索引:{}, 数据添加, 返回码:{}, id:{}", index, response.status().getStatus(), id); } catch (IOException e) { LOGGER.error("添加数据失败, index:{}, id:{}", index, id); } return id; } /** * @desc 修改数据 */ public String updateData(CusDemoInfoDocument document, String index, String type, String docId) { String id = null; try { RestHighLevelClient highClient = getEsClient(); UpdateRequest request = new UpdateRequest(index, type, docId) .doc(JsonWare.beanToJson(document), XContentType.JSON); request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); UpdateResponse response = highClient.update(request, COMMON_OPTIONS); id = response.getId(); //highClient.close(); LOGGER.info("数据更新, 返回码:{}, id:{}", response.status().getStatus(), id); } catch (IOException e) { LOGGER.error("数据更新失败, index:{}, id:{}", index, id); } return id; } /** * @desc 批量插入数据 */ public boolean insertBatch(String index, String type, List<CusDemoInfoDocument> list) { BulkRequest request = new BulkRequest(); request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); list.forEach(item -> request.add(new IndexRequest(index, type) .id(item.getId()).source(JsonWare.beanToJson(item), XContentType.JSON))); try { RestHighLevelClient highClient = getEsClient(); BulkResponse bulk = highClient.bulk(request, COMMON_OPTIONS); bulk.status().getStatus(); //highClient.close(); LOGGER.info("索引:{}, 批量插入 {} 条数据成功!", index, list.size()); } catch (IOException e) { LOGGER.error(e.getMessage()); LOGGER.error("索引:{}, 批量插入数据失败", index); return false; } return true; } /** * @desc 根据id删除数据 */ public boolean deleteById(String index, String type, String docId) { DeleteRequest request = new DeleteRequest(index, type, docId); request.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); try { DeleteResponse response = getEsClient().delete(request, COMMON_OPTIONS); response.status().getStatus(); LOGGER.info("索引:{}, 根据id {} 删除数据:{}", index, docId, JsonWare.beanToJson(response)); } catch (Exception e) { LOGGER.error("根据id删除数据失败, index:{}, id:{}", index, docId); return false; } return true; } }
看下Constant类内容:
/** * @author Jimmy Shan * @date 2020-08-07 * @desc 常量类 */ public class Constant { public static final Map<String, RestHighLevelClient> esClientMap = new HashMap<>(); public static final String ES_CLIENT = "esClient"; // ES查询限制数量 public static final Integer ES_QUERY_PAGE_START = 0; public static final Integer ES_QUERY_COUNT = 20; }
以上只是一些基本操作方式,下面 重点记录下 模糊高亮查询方法
本人这里自定义一个service服务,用来处理高亮查询
Service
package com.xx.xx.service; import java.util.List; import com.xx.xx.es.entity.CusDemoInfoDocument;/** * @author Jimmy Shan * @date 2020-09-28 * @desc ES服务 */ public interface EsService { /** * @desc 高连显示查询-DEMO */ List<CusDemoInfoDocument> searchHighLightDocForDemo(String contents, String index, String type); }
Service实现
/** * @desc 高连显示查询-DEMO */ @Override public List<CusDemoInfoDocument> searchHighLightDocForDemo(String contents, String index, String type) { try { // 获取链接RestHighLevelClient对象 RestHighLevelClient highClient = esUtil.getEsClient(); // 查询维度设置 BoolQueryBuilder queryBuilder = new BoolQueryBuilder(); // 查询列,此处支持多列查询 String[] field = {"id", "demoName", "demoCode", "demoValue"}; // 多列匹配规则 queryBuilder.must(QueryBuilders.multiMatchQuery(contents, field)); SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); sourceBuilder.query(queryBuilder); sourceBuilder.from(Constant.ES_QUERY_PAGE_START);// 分页起始位,下标从0开始 sourceBuilder.size(Constant.ES_QUERY_COUNT);// 每次20条记录 //设置高亮显示 HighlightBuilder highlightBuilder = new HighlightBuilder() .field("*").requireFieldMatch(false); highlightBuilder.preTags("<font color='#dd4b39'>"); highlightBuilder.postTags("</font>"); sourceBuilder.highlighter(highlightBuilder); SearchRequest searchRequest = new SearchRequest(index); searchRequest.types(type); searchRequest.source(sourceBuilder); SearchResponse response = highClient.search(searchRequest, RequestOptions.DEFAULT); LOGGER.info("highClient response : {}", JsonWare.beanToJson(response)); // 遍历结果 List<CusDemoInfoDocument> list = new ArrayList<>(); for(SearchHit hit : response.getHits()) { if (response.getHits().getHits().length <= 0) { return null; } CusDemoInfoDocument cusDoc = JsonWare.jsonToBean(hit.getSourceAsString(), CusDemoInfoDocument.class); // 处理高亮片段 Map<String, HighlightField> highlightFields = hit.getHighlightFields(); HighlightField nameField = highlightFields.get("demoValue"); if(nameField != null){ Text[] fragments = nameField.fragments(); String fragmentString = fragments[0].string(); cusDoc.setDemoValue(fragmentString); } list.add(cusDoc); } if (!CollectionUtils.isEmpty(list)) { return list; } return null; } catch (Exception e) { LOGGER.error("searchHighLightDocForDemo happen exception: {}", e.getMessage(), e); return null; } }
好了,主要方法都已完成,现在让我们写个测试类试一试。
ControllerTest
@Controller @RequestMapping("/api") public class TestController extends AbstractController{ @Autowired private EsService esService; @Autowired private ElasticsearchUtil esUtil; /** * @author Jimmy Shan * @date 2020-11-26 * @desc 测试ES保存 */ @ResponseBody @RequestMapping(value = "/demoEsSaveTest", method = RequestMethod.POST) public String demoEsSaveTest(HttpServletRequest request, HttpServletResponse response) { String id = request.getParameter("id"); String demoName = request.getParameter("demoName"); String demoCode = request.getParameter("demoCode"); String demoValue = request.getParameter("demoValue");
String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; esUtil.addData(new CusDemoInfoDocument( id, demoName, demoCode, demoValue), index, type, id); LOGGER.info("TestController --> demoEsSaveTest"); return "The data of ES was save success."; } /** * @author Jimmy Shan * @date 2020-11-26 * @desc 测试ES读取 */ @ResponseBody @RequestMapping(value = "/demoEsGetTest", method = RequestMethod.POST) public List<CusDemoInfoDocument> demoEsGetTest(HttpServletRequest request, HttpServletResponse response) { String demoValue = request.getParameter("demoValue"); String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; List<CusDemoInfoDocument> demoList = esService.searchHighLightDocForDemo(demoValue, index, type); LOGGER.info("TestController --> demoEsGetTest result ==> {}", JsonWare.beanToJson(demoList)); return demoList; } /** * @author Jimmy Shan * @date 2020-11-26 * @desc 测试ES 根据ID删除 */ @ResponseBody @RequestMapping(value = "/demoEsDelTest", method = RequestMethod.POST) public String demoEsDelTest(HttpServletRequest request, HttpServletResponse response) { String id = request.getParameter("id"); String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; esUtil.deleteById(index, type, id); LOGGER.info("TestController --> demoEsDelTest"); return "Delete Success."; } /** * @author Jimmy Shan * @date 2020-11-30 * @desc 测试ES 根据ID更新 */ @ResponseBody @RequestMapping(value = "/demoEsUpdateByIdTest", method = RequestMethod.POST) public String demoEsUpdateByIdTest(HttpServletRequest request, HttpServletResponse response) { String id = request.getParameter("id"); String index = "cusdemoinfoidx"; String type = "cusDemoInfo"; esUtil.updateData(new CusDemoInfoDocument(id, "这里只是测试更新name", "这里只是测试更新code", "这里只是测试更新val"), index, type, id); LOGGER.info("TestController --> demoEsUpdateByIdTest"); return "Update Success."; } /** * @author Jimmy Shan * @date 2020-11-30 * @desc 测试ES 删除索引 */ @ResponseBody @RequestMapping(value = "/demoEsDelIdxTest", method = RequestMethod.POST) public String demoEsDelIdxTest(HttpServletRequest request, HttpServletResponse response) { String index = "cusdemoinfoidx"; esUtil.deleteIndex(index); return "delete index Success."; } }
好了,我们将程序部署到阿里云ECS环境中,用postman可以调试一下,以上方法,本人是经过验证的,可以和阿里云ES互通及各项操作。
PS:本人在阿里云ES管理端,开启了自动创建及删除索引功能,这个是个好东西,可以不用每次单独去创建索引,直接使用addData添加数据,自动
会创建对应的索引。
还是那句老话,发出来供大家一起学习及讨论,如觉得好可转载,但请注明原创地址,万分感谢。