最近比較忙,好久沒更新博客了,今天抽個空記錄一下使用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添加數據,自動
會創建對應的索引。
還是那句老話,發出來供大家一起學習及討論,如覺得好可轉載,但請注明原創地址,萬分感謝。