添加pom
1 <!--elasticsearch--> 2 <dependency> 3 <groupId>org.elasticsearch.client</groupId> 4 <artifactId>elasticsearch-rest-high-level-client</artifactId> 5 <version>7.5.1</version> 6 <exclusions> 7 <exclusion> 8 <groupId>org.elasticsearch</groupId> 9 <artifactId>elasticsearch</artifactId> 10 </exclusion> 11 <exclusion> 12 <groupId>org.elasticsearch.client</groupId> 13 <artifactId>elasticsearch-rest-client</artifactId> 14 </exclusion> 15 </exclusions> 16 </dependency> 17 <dependency> 18 <groupId>org.elasticsearch.client</groupId> 19 <artifactId>elasticsearch-rest-client</artifactId> 20 <version>7.5.1</version> 21 </dependency> 22 <dependency> 23 <groupId>org.elasticsearch</groupId> 24 <artifactId>elasticsearch</artifactId> 25 <version>7.5.1</version> 26 </dependency>
yml添加配置
1es: 2 host: 192.168.1.107 3 port: 9200 4 scheme: http
初始化client
1package com.zh.search.config; 2 3 4import org.apache.http.HttpHost; 5import org.elasticsearch.client.RestClient; 6import org.elasticsearch.client.RestClientBuilder; 7import org.elasticsearch.client.RestHighLevelClient; 8import org.springframework.beans.factory.annotation.Autowired; 9import org.springframework.beans.factory.annotation.Value; 10import org.springframework.context.annotation.Bean; 11import org.springframework.context.annotation.Configuration; 12 13@Configuration 14public class ElasticConfig { 15 16 @Value("${es.host}") 17 public String host; 18 @Value("${es.port}") 19 public int port; 20 @Value("${es.scheme}") 21 public String scheme; 22 23 @Bean 24 public RestClientBuilder restClientBuilder() { 25 return RestClient.builder(makeHttpHost()); 26 } 27 28 @Bean 29 public RestClient restClient(){ 30 return RestClient.builder(new HttpHost(host, port, scheme)).build(); 31 } 32 33 private HttpHost makeHttpHost() { 34 return new HttpHost(host, port, scheme); 35 } 36 37 @Bean 38 public RestHighLevelClient restHighLevelClient(@Autowired RestClientBuilder restClientBuilder){ 39 return new RestHighLevelClient(restClientBuilder); 40 } 41}
在resource下创建索引配置json文件,
settings.json
1{ 2 "number_of_shards": 5, 3 "number_of_replicas": 1, 4 "refresh_interval": "5s", 5 "analysis": { 6 "analyzer": { 7// ik细粒度 8 "ikSearchAnalyzer": { 9 "type": "custom", 10 "tokenizer": "ik_max_word", 11 "char_filter": [ 12 "tsconvert" 13 ] 14 }, 15// ik粗粒度分词 16 "ikSmartSearchAnalyzer": { 17 "type": "custom", 18 "tokenizer": "ik_smart", 19 "char_filter": [ 20 "tsconvert" 21 ] 22 }, 23// 拼音分词 24 "pinyinSimpleAnalyzer": { 25 "tokenizer": "my_pinyin" 26 }, 27// 拼音,大小写,短语分词 28 "pinyinComplexAnalyzer": { 29 "tokenizer": "ik_smart", 30 "filter": [ 31 "lowercase", 32 "pinyin_simple_filter", 33 "edge_ngram_filter" 34 ] 35 }, 36// 大小写转换分词 37 "lowercaseAnalyzer": { 38 "type": "custom", 39 "tokenizer": "keyword", 40 "filter": "lowercase" 41 } 42 }, 43 "tokenizer" : { 44 "my_pinyin" : { 45 "type" : "pinyin", 46 "keep_separate_first_letter" : false, 47 "keep_full_pinyin" : true, 48 "keep_original" : true, 49 "limit_first_letter_length" : 16, 50 "lowercase" : true, 51 "remove_duplicated_term" : true 52 } 53 }, 54 "filter": { 55// 短语过滤 56 "edge_ngram_filter": { 57 "type": "edge_ngram", 58 "min_gram": 1, 59 "max_gram": 50 60 }, 61// 拼音过滤 62 "pinyin_simple_filter": { 63 "type": "pinyin", 64 "first_letter": "prefix", 65 "padding_char": " ", 66 "limit_first_letter_length": 50, //设置first_letter结果的最大长度,默认值:16 67// "keep_separate_first_letter" : false, //启用该选项时,将保留第一个字母分开,例如:刘德华> l,d,h,默认:false,注意:查询结果也许是太模糊,由于长期过频 68// "keep_full_pinyin" : true, //当启用该选项,例如:刘德华> [ liu,de,hua],默认值:true 69// "keep_original" : true, //启用此选项时,也将保留原始输入,默认值:false 70// "remove_duplicated_term" : true, //启用此选项后,将删除重复的术语以保存索引,例如:de的> de,default:false,注意:位置相关的查询可能会受到影响 71 "lowercase": true //小写非中文字母,默认值:true 72 } 73 }, 74 "char_filter": { 75// 简繁体过滤 76 "tsconvert": { 77 "type": "stconvert", 78 "convert_type": "t2s" 79 } 80 } 81 } 82}
创建索引映射文件
commodity-mapping.json
1{ 2 "properties": { 3 "id": { 4 "type": "integer" 5 }, 6 "keyword": { 7 //text和keyword的区别text:存储数据时候,会自动分词,并生成索引,keyword:存储数据时候,不会分词建立索引 8 "type": "text", 9 "analyzer": "ikSearchAnalyzer", 10 "search_analyzer": "ikSmartSearchAnalyzer", 11 "fields": { 12 "pinyin": { 13 "type": "text", 14 "analyzer": "pinyinComplexAnalyzer", 15 "search_analyzer": "pinyinComplexAnalyzer", 16 "store": false, 17 "term_vector": "with_offsets" 18 } 19 } 20 }, 21 "ownerNature": { 22 "type": "keyword" 23 }, 24 "model": { 25 "type": "keyword", 26 //不能通过这个字段搜索 27 "index": false 28 }, 29 "weight": { 30 "type": "integer" 31 }, 32 "createTime": { 33 "type": "date", 34 "format": "yyyy-MM-dd HH:mm:ss" 35 } 36 } 37}
采用json的方式,我觉得直观一点
创建索引(需要注意的是,7.x后,es删除了type,只允许存在一种type,不需要指定type的值,默认是_doc)
1public void init() throws Exception { 2 this.createIndex("commodity"); 3 } 4 5/** 6 * 创建索引 7 * @param index 8 * @throws IOException 9 */ 10 public void createIndex(String index) throws IOException { 11 //如果存在就不创建了 12 if(this.existsIndex(index)) { 13 System.out.println(index+"索引库已经存在!"); 14 return; 15 } 16 // 开始创建库 17 CreateIndexRequest request = new CreateIndexRequest(index); 18 //配置文件 19 ClassPathResource seResource = new ClassPathResource("mapper/setting.json"); 20 InputStream seInputStream = seResource.getInputStream(); 21 String seJson = String.join("\n",IOUtils.readLines(seInputStream,"UTF-8")); 22 seInputStream.close(); 23 //映射文件 24 ClassPathResource mpResource = new ClassPathResource("mapper/"+index+"-mapping.json"); 25 InputStream mpInputStream = mpResource.getInputStream(); 26 String mpJson = String.join("\n",IOUtils.readLines(mpInputStream,"UTF-8")); 27 mpInputStream.close(); 28 29 request.settings(seJson, XContentType.JSON); 30 request.mapping(mpJson, XContentType.JSON); 31 32 //设置别名 33 request.alias(new Alias(index+"_alias")); 34 CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT); 35 boolean falg = createIndexResponse.isAcknowledged(); 36 if(falg){ 37 System.out.println("创建索引库:"+index+"成功!" ); 38 } 39 }
判断索引是否存在
1 /** 2 * 判断索引是否存在 3 * @param index 4 * @return 5 * @throws IOException 6 */ 7 public boolean existsIndex(String index) throws IOException { 8 GetIndexRequest getRequest = new GetIndexRequest(index); 9 getRequest.local(false); 10 getRequest.humanReadable(true); 11 return restHighLevelClient.indices().exists(getRequest, RequestOptions.DEFAULT); 12 }
删除索引
1 /** 2 * 删除索引 3 * @param index 4 * @return 5 * @throws IOException 6 */ 7 public boolean delIndex(String index) throws IOException { 8 DeleteIndexRequest request = new DeleteIndexRequest(index); 9 AcknowledgedResponse deleteIndexResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT); 10 return deleteIndexResponse.isAcknowledged(); 11 }
添加索引数据
1 /** 2 * 保存文档 3 * @param kv 对应json映射里面键值对,index是索引名称 4 * @return 5 * @throws IOException 6 */ 7 public boolean save(Kv kv) throws IOException { 8 IndexRequest request = new IndexRequest(kv.getStr("index")) 9 .id(kv.getStr("id")).source(kv); 10 IndexResponse response = restHighLevelClient.index(request,RequestOptions.DEFAULT); 11 return response.isFragment(); 12 }
删除索引数据
1 /** 2 * 根据id删除文档 3 * @param id 4 * @return 5 * @throws IOException 6 */ 7 public boolean delById(String id) throws IOException { 8 DeleteRequest request = new DeleteRequest(ModuleConstants.COMMODITY.toLowerCase(),id); 9 DeleteResponse response = restHighLevelClient.delete(request,RequestOptions.DEFAULT); 10 return response.isFragment(); 11 }
IK,拼音,短语分词分页搜索
1 @Resource 2 private RestHighLevelClient restHighLevelClient; 3 @Resource 4 private OutputChannel outputChannel; 5 6 /** 7 分页分词关键词查询 8 * 使用QueryBuilder 9 termQuery("key", obj) 完全匹配 10 termsQuery("key", obj1, obj2..) 一次匹配多个值 11 matchQuery("key", Obj) 单个匹配, field不支持通配符, 前缀具高级特性 12 multiMatchQuery("text", "field1", "field2"..); 匹配多个字段, field有通配符忒行 13 matchAllQuery(); 匹配所有文件 14 * 组合查询 15 must(QueryBuilders) : AND 16 mustNot(QueryBuilders): NOT 17 should: : OR 18 percent_terms_to_match:匹配项(term)的百分比,默认是0.3 19 min_term_freq:一篇文档中一个词语至少出现次数,小于这个值的词将被忽略,默认是2 20 max_query_terms:一条查询语句中允许最多查询词语的个数,默认是25 21 stop_words:设置停止词,匹配时会忽略停止词 22 min_doc_freq:一个词语最少在多少篇文档中出现,小于这个值的词会将被忽略,默认是无限制 23 max_doc_freq:一个词语最多在多少篇文档中出现,大于这个值的词会将被忽略,默认是无限制 24 min_word_len:最小的词语长度,默认是0 25 max_word_len:最多的词语长度,默认无限制 26 boost_terms:设置词语权重,默认是1 27 boost:设置查询权重,默认是1 28 analyzer:设置使用的分词器,默认是使用该字段指定的分词器 29 */ 30 @Override 31 public Page<SearchVo> page(SearchVo searchVo){ 32 Page<SearchVo> page = new Page(searchVo.getCurrent(),searchVo.getSize(),0); 33 // 页码 34 try { 35 // 构建查询 36 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 37 // 索引查询 38 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); 39 //boost 设置权重 40 //分词查询 41 boolQueryBuilder.should(QueryBuilders.matchQuery("keyword", searchVo.getKeyword()).boost(2f)); 42 //拼音查询 43 boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("keyword.pinyin", searchVo.getKeyword()).boost(2f)); 44 //模糊查询,不区分大小写 45// boolQueryBuilder.should(QueryBuilders.wildcardQuery("keyword", "*"+searchVo.getKeyword().toLowerCase()+"*").boost(2f)); 46 //指定商家的性质 47 if(StrKit.notBlank(searchVo.getKeyword1())){ 48 boolQueryBuilder.must(QueryBuilders.termQuery("ownerNature",searchVo.getKeyword1())); 49 } 50 //必须满足should其中一个条件 51 boolQueryBuilder.minimumShouldMatch(1); 52 //时间范围查询 53// boolQueryBuilder.must(QueryBuilders.rangeQuery("createTime") 54// .from(DateKit.format(DateKit.getDayBegin(),"yyyy-MM-dd HH:mm:ss")) 55// .to(DateKit.format(DateKit.getDayBegin(),"yyyy-MM-dd HH:mm:ss"))); 56 sourceBuilder.query(boolQueryBuilder); 57 //设置返回的字段 58// String[] includeFields = new String[] {"keyword"}; 59// sourceBuilder.fetchSource(includeFields,null); 60 // 分页设置 61 sourceBuilder.from(searchVo.getFrom()); 62 sourceBuilder.size(searchVo.getSize()); 63 // sourceBuilder.sort("id", SortOrder.ASC); // 设置排序规则 64 sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); 65 66 SearchRequest searchRequest = new SearchRequest(searchVo.getIndex()); 67 searchRequest.source(sourceBuilder); 68 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 69 SearchHits searchHits = response.getHits(); 70 page.setTotal(searchHits.getTotalHits().value); 71 List<SearchVo> list = new ArrayList<>(); 72 for (SearchHit hit : searchHits.getHits()) { 73 SearchVo vo = new SearchVo(); 74 Kv kv = Kv.create().set(hit.getSourceAsMap()); 75 vo.setId(kv.getStr("id")); 76 vo.setKeyword(kv.getStr("keyword")); 77 vo.setKeyword1(kv.getStr("ownerNature")); 78 vo.setModel(kv.getStr("model")); 79 list.add(vo); 80 } 81 page.setRecords(list); 82 } catch (Exception e) { 83 e.printStackTrace(); 84 } 85 //收集关键词搜索记录 86 searchVo.setIndex(ModuleConstants.KEYWORD.toLowerCase()); 87 outputChannel.searchSaveOutput().send(MessageBuilder.withPayload(searchVo).build()); 88 89 return page; 90 }
IK,拼音,短语分词分页并高亮关键词搜索
1 @Resource 2 private RestHighLevelClient restHighLevelClient; 3 4 @Override 5 public Page<SearchVo> pageHigh(SearchVo searchVo){ 6 Page<SearchVo> page = new Page(searchVo.getCurrent(),searchVo.getSize(),0); 7 // 页码 8 try { 9 // 构建查询 10 SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); 11 // 索引查询 12 BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery(); 13 //boost 设置权重 14 //分词查询 15 boolQueryBuilder.should(QueryBuilders.matchQuery("keyword", searchVo.getKeyword()).boost(2f)); 16 //拼音查询 17 boolQueryBuilder.should(QueryBuilders.matchPhraseQuery("keyword.pinyin", searchVo.getKeyword()).boost(2f)); 18 //模糊查询,不区分大小写 19// boolQueryBuilder.should(QueryBuilders.wildcardQuery("keyword", "*"+searchVo.getKeyword().toLowerCase()+"*").boost(2f)); 20 //必须满足should其中一个条件 21 boolQueryBuilder.minimumShouldMatch(1); 22 //时间范围查询 23// boolQueryBuilder.must(QueryBuilders.rangeQuery("createTime") 24// .from(DateKit.format(DateKit.getDayBegin(),"yyyy-MM-dd HH:mm:ss")) 25// .to(DateKit.format(DateKit.getDayBegin(),"yyyy-MM-dd HH:mm:ss"))); 26 sourceBuilder.query(boolQueryBuilder); 27 //设置返回的字段 28 String[] includeFields = new String[] {"keyword"}; 29 sourceBuilder.fetchSource(includeFields,null); 30 // 高亮设置 31 List<String> highlightFieldList = new ArrayList<>(); 32 highlightFieldList.add("keyword"); 33 HighlightBuilder highlightBuilder = new HighlightBuilder(); 34 for (int x = 0; x < highlightFieldList.size(); x++) { 35 HighlightBuilder.Field field = new HighlightBuilder.Field(highlightFieldList.get(x)).preTags("<high>").postTags("</high>"); 36 highlightBuilder.field(field); 37 } 38 sourceBuilder.highlighter(highlightBuilder); 39 // 分页设置 40 sourceBuilder.from(searchVo.getFrom()); 41 sourceBuilder.size(searchVo.getSize()); 42 // sourceBuilder.sort("id", SortOrder.ASC); // 设置排序规则 43 sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS)); 44 //不指定索引,则搜索所有的索引 45 SearchRequest searchRequest = new SearchRequest(searchVo.getIndex()); 46 searchRequest.source(sourceBuilder); 47 SearchResponse response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT); 48 SearchHits searchHits = response.getHits(); 49 page.setTotal(searchHits.getTotalHits().value); 50 List<SearchVo> list = new ArrayList<>(); 51 Pattern pattern = Pattern.compile("(?i)"+searchVo.getKeyword()); 52 for (SearchHit hit : searchHits.getHits()) { 53 SearchVo vo = new SearchVo(); 54 Kv kv = Kv.create().set(hit.getSourceAsMap()); 55 vo.setKeyword(kv.getStr("keyword")); 56 //高亮字段(拼音不做高亮,拼音的高亮有问题,会将整个字符串高亮) 57 if (!StringUtils.isEmpty(hit.getHighlightFields().get("keyword"))) { 58 Text[] text = hit.getHighlightFields().get("keyword").getFragments(); 59 vo.setKeyword(text[0].toString()); 60 } 61 //ngram短语,模糊搜索高亮,不区分大小写直接字符串替换 62 String keyword = vo.getKeyword(); 63 if(!keyword.contains("<high>")){ 64 Matcher matcher = pattern.matcher(keyword); 65 if(matcher.find()){ 66 String s = matcher.group(); 67 vo.setKeyword(keyword.replace(s,"<high>"+s+"</high>")); 68 } 69 } 70 list.add(vo); 71 } 72 page.setRecords(list); 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 return page; 77 }