1、主要使用的技术及开发工具
- Elasticsearch 7.17.3
- REST API
- Elasticsearch java API Client 7.17.3
- Kibana 7.17.3
- Jackson 2.12.3
2、Elasticsearch简介
Elasticsearch 是一个分布式的免费开源搜索和分析引擎,适用于包括文本、数字、地理空间、结构化和非结构化数据等在内的所有类型的数据。可用于应用程序搜索,网站搜索等诸多使用场景。
使用ES,我们可以快速的开发一个自己的搜索引擎,ES会帮我们在庞大的数据中建索
3、mapping的设计思路
有四个数据要存进ES建索,分别是url
、title
、text
、declareTime
,他们的数据类型分别是keyword、text、text、date;其中date的格式为xxxx-xx-xx
其中text类型均采用ik_max_word
保证最大限度的分词,为了实现搜索提示,可以定义title的第二个type为completion,方便自动补全。综上,新建索引的操作如下
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
| PUT /link-repo2 { "mappings": { "properties": { "url":{"type": "keyword"}, "title":{ "type": "text", "analyzer": "ik_max_word", "fields": { "suggest": { "type": "completion", "analyzer": "ik_max_word" } } }, "text":{ "type": "text", "analyzer": "ik_max_word" }, "declareTime": { "type": "date" } } } }
|
4、ES的搜索策略
4.1 Rest操作
在此课程设计中,我主要采用match分词搜索,分别在title和text中搜索,并附带相关的highlight和filler策略以作为高亮显示和按时间范围搜索的基础。
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
| GET link-repo2/_search { "query": { "bool": { "must": [ { "multi_match": { "query": "软银机器人杯”2019中国机器人技能大赛:我院学子获1项季军、1项二等奖", "fields": ["title", "text"], "analyzer": "ik_smart" } } ], "filter": [ { "range": { "declareTime": { "gte": "2022-01-01" } } } ] } }, "highlight": { "pre_tags": "<span class=\"hit-result\">", "post_tags": "</span>", "fields": { "title": {}, "text": {} } }, "from": 0, "size": 10 }
|
4.2 全文检索功能
multi_match表示多字段的分词搜索,query为搜索内容,fields为搜索字段,analyzer表示搜索文本的分词器。
4.3 按时间范围检索功能
在bool块里,通过must和filter的组合,must实现分词搜索,filter实现过滤时间;range块中的field为字段,gte表示大于等于。这样就可实现时间范围检索
4.4 高亮检索
highlight,搜索中关注title和text,当有匹配分词出现,就套上pre_tags和post_tags标签,“高亮”出来。这样就可以实现高亮查询的功能
4.5 分页搜索
from和size的组合,可以实现分页功能
5、使用Elasticsearch java API Client连接ES
团队项目的ES是有使用xpack插件保证安全性的,连接的时候要创建使用许可证才能成功连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static ElasticsearchClient getConnect() { final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials( AuthScope.ANY, new UsernamePasswordCredentials(USERNAME, PASSWORD)); RestClientBuilder builder = RestClient.builder(new HttpHost(URL, PORT)) .setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder .setDefaultCredentialsProvider(credentialsProvider)); restClient = builder.build(); transport = new RestClientTransport( restClient, new JacksonJsonpMapper()); return new ElasticsearchClient(transport); }
|
6、代码实现
Search.java
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
| package es;
import bean.ResultEntry;
import java.io.Reader; import java.util.List;
public interface Search {
long getSearchCount();
boolean newIndex(Reader reader);
boolean deleteIndex();
ResultEntry add(ResultEntry entry);
List<ResultEntry> search(String searchText);
List<ResultEntry> search(String searchText, int page);
List<ResultEntry> search(String searchText, int page, String beginDate);
List<ResultEntry> search(String searchText, int page, String beginDate, String endDate);
List<String> getSearchSuggest(String prefix);
void close(); }
|
新建索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public boolean newIndex(Reader reader) { CreateIndexRequest createIndexRequest = new CreateIndexRequest.Builder() .withJson(reader) .index(EsUtil.index) .build();
CreateIndexResponse response = null; try { response = client.indices().create(createIndexRequest); } catch (Exception e) { } if(response != null) { return Objects.requireNonNullElse(response.acknowledged(), false); } else { return false; } }
|
删除索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public boolean deleteIndex() { DeleteIndexResponse deleteIndexResponse = null; try { deleteIndexResponse = client.indices().delete(d -> d .index(EsUtil.index)); } catch (Exception e) { } if(deleteIndexResponse == null) { return false; } else { return deleteIndexResponse.acknowledged(); } }
|
添加文档
1 2 3 4 5 6 7 8 9
| public ResultEntry add(ResultEntry entry) { try { client.index(i -> i .index(EsUtil.index).document(entry)); } catch (IOException e) { return null; } return entry; }
|
全文检索
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
| public List<ResultEntry> search(String searchText, int page) { int value = (page - 1) * 10; SearchResponse<ResultEntry> search = null;
try { search = client.search(s -> s .index(EsUtil.index) .query(q -> q .multiMatch(m -> m .query(searchText) .fields("title", "text") .analyzer("ik_smart"))) .highlight(h -> h .preTags("<span class=\"hit-result\">") .postTags("</span>") .fields("title", builder -> builder) .fields("text", builder -> builder)) .from(value) .size(10) , ResultEntry.class); } catch (IOException e) { e.printStackTrace(); }
return dealSearchResponse(search); }
|
按时间范围全文检索
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
| public List<ResultEntry> search(String searchText, int page, String beginDate) { int value = (page - 1) * 10; JsonData jsonBeginDate = JsonData.of(beginDate); SearchResponse<ResultEntry> search = null; try { search = client.search(s -> s .index(EsUtil.index) .query(q -> q .bool(b -> b .must(b1 -> b1 .multiMatch(b2 -> b2 .query(searchText) .fields("title", "text") .analyzer("ik_smart"))) .filter(b3 -> b3 .range(b4 -> b4 .field("declareTime") .gte(jsonBeginDate))))) .highlight(h -> h .preTags("<span class=\"hit-result\">") .postTags("</span>") .fields("title", builder -> builder) .fields("text", builder -> builder)) .from(value) .size(10) , ResultEntry.class); } catch (IOException e) { e.printStackTrace(); }
return dealSearchResponse(search); }
|
7、参考链接
官方新版java API文档
新版java api 操作示例
ES英文文档
ES中文文档
ES设置账号密码
Java连接ES有账号密码的情况