Day07 ES搜索
# Day07 ES 搜索
# 根据关键字查询
changgou_service_search 项目创建 SearchService 接口
package com.changgou.search.service;
import java.util.Map;
public interface SearchService {
//按照查询添加进行数据查询
Map search(Map<String ,String> searchMap);
}
2
3
4
5
6
7
8
9
创建 SearchService 接口实现类 SearchServiceImpl
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
return resultMap;
}
return null;
}
}
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
changgou_service_search 项目创建 SearchController
package com.changgou.search.controller;
import com.changgou.search.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
import java.util.Set;
@RestController
@RequestMapping("/search")
public class SearchController {
@Autowired
private SearchService searchService;
@GetMapping
public Map search(@RequestParam Map<String, String> searchMap) {
//特殊符号处理
this.handleSearchMap(searchMap);
Map searchResult = searchService.search(searchMap);
return searchResult;
}
private void handleSearchMap(Map<String, String> searchMap) {
Set<Map.Entry<String, String>> entries = searchMap.entrySet();
for (Map.Entry<String, String> entry : entries) {
if (entry.getKey().startsWith("spec_")) {
searchMap.put(entry.getKey(), entry.getValue().replace("+", "%2B"));
}
}
}
}
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
删除索引库 重新导入索引 请求指定 localhost:9009/manager/importAll
测试关键字查询 localhost:9009/search?keywords = 手机
# 品牌条件筛选
用户有可能会根据分类搜索、品牌搜索,还有可能根据规格搜索,以及价格搜索和排序操作。根据分类和品牌搜索的时候,可以直接根据指定域搜索,而规格搜索的域数据是不确定的,价格是一个区间搜索,所以我们可以分为三段实现,先实现分类、品牌搜素,再实现规格搜索,然后实现价格区间搜索。

页面每次向后台传入对应的分类和品牌,后台据分类和品牌进行条件过滤即可。
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加品牌过滤
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
}
2
3
4
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName",searchMap.get("brand")));
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
return resultMap;
}
return null;
}
}
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
测试请求 localhost:9009/search?keywords = 手机 & brand = 华为
# 按照品牌进行聚合查询
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?keywords = 电视
添加品牌聚合后 通过关键字查询出结果中包含的品牌列表
# 规格过滤

规格这一部分,需要向后台发送规格名字以及规格值,我们可以按照一定要求来发送数据,例如规格名字以特殊前缀提交到后台: spec_网络制式:电信4G、spec_显示屏尺寸:4.0-4.9英寸
后台接到数据后,可以根据前缀 spec_来区分是否是规格,如果以 spec_xxx 开始的数据则为规格数据,需要根据指定规格找信息。
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")){
String value = searchMap.get(key).replace("%2B","+");
boolQuery.filter(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword",value));
}
}
2
3
4
5
6
7
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")){
String value = searchMap.get(key).replace("%2B","+");
boolQuery.filter(QueryBuilders.termQuery("specMap."+key.substring(5)+".keyword",value));
}
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?keywords = 电视 & spec_尺寸 = 金色
# 按照规格进行聚合查询
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加
//按照规格进行聚合查询
String skuSpec = "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
2
3
//封装规格分组结果
StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("specList", specList);
2
3
4
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")) {
String value = searchMap.get(key).replace("%2B", "+");
boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
}
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//按照规格进行聚合查询
String skuSpec = "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
//封装规格分组结果
StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("specList", specList);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?keywords = 电视
# 价格区间查询
价格区间查询,每次需要将价格传入到后台,前端传入后台的价格大概是 price=0-500 或者 price=500-1000 依次类推,最后一个是 price=3000 , 后台可以根据 - 分割,如果分割得到的结果最多有 2 个,第 1 个表示 x<price ,第 2 个表示 price<=y 。
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加
//按照价格进行区间过滤查询
if (StringUtils.isNotEmpty(searchMap.get("price"))) {
String[] prices = searchMap.get("price").split("-");
if (prices.length == 2) {
//是xx - xx 价格区间的条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
}
2
3
4
5
6
7
8
9
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")) {
String value = searchMap.get(key).replace("%2B", "+");
boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
}
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照价格进行区间过滤查询
if (StringUtils.isNotEmpty(searchMap.get("price"))) {
String[] prices = searchMap.get("price").split("-");
if (prices.length == 2) {
//是xx - xx 价格区间的条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
}
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//按照规格进行聚合查询
String skuSpec = "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
//封装规格分组结果
StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("specList", specList);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?price=0-500
localhost:9009/search?price=4000
# 分页查询
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加
//开启分页查询
String pageNum = searchMap.get("pageNum"); //当前页
String pageSize = searchMap.get("pageSize"); //每页显示多少条
if (StringUtils.isEmpty(pageNum)) {
pageNum = "1";
}
if (StringUtils.isEmpty(pageSize)) {
pageSize = "30";
}
//设置分页
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
2
3
4
5
6
7
8
9
10
11
封装当前页码到返回结果中
//当前页
resultMap.put("pageNum", pageNum);
2
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")) {
String value = searchMap.get(key).replace("%2B", "+");
boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
}
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照价格进行区间过滤查询
if (StringUtils.isNotEmpty(searchMap.get("price"))) {
String[] prices = searchMap.get("price").split("-");
if (prices.length == 2) {
//是xx - xx 价格区间的条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
}
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//按照规格进行聚合查询
String skuSpec = "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
//开启分页查询
String pageNum = searchMap.get("pageNum"); //当前页
String pageSize = searchMap.get("pageSize"); //每页显示多少条
if (StringUtils.isEmpty(pageNum)) {
pageNum = "1";
}
if (StringUtils.isEmpty(pageSize)) {
pageSize = "30";
}
//设置分页
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
//封装规格分组结果
StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("specList", specList);
//当前页
resultMap.put("pageNum", pageNum);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?price=4000&pageNum=10&pageSize=20
# 搜索排序

排序这里总共有根据价格排序、根据评价排序、根据新品排序、根据销量排序,排序要想实现非常简单,只需要告知排序的域以及排序方式即可实现。
价格排序:只需要根据价格高低排序即可,降序价格高 -> 低,升序价格低 -> 高
评价排序:评价分为好评、中评、差评,可以在数据库中设计 3 个列,用来记录好评、中评、差评的量,每次排序的时候,好评的比例来排序,当然还要有条数限制,评价条数需要超过 N 条。
新品排序:直接根据商品的发布时间或者更新时间排序。
销量排序:销量排序除了销售数量外,还应该要有时间段限制。
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加
//按照相关字段进行排序查询
//1.当前域 2.当前的排序操作(升序ASC,降序DESC)
if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))) {
if ("ASC".equals(searchMap.get("sortRule"))) {
//升序操作
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
}else {
//降序
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
}
}
2
3
4
5
6
7
8
9
10
11
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")) {
String value = searchMap.get(key).replace("%2B", "+");
boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
}
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照价格进行区间过滤查询
if (StringUtils.isNotEmpty(searchMap.get("price"))) {
String[] prices = searchMap.get("price").split("-");
if (prices.length == 2) {
//是xx - xx 价格区间的条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
}
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//按照规格进行聚合查询
String skuSpec = "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
//开启分页查询
String pageNum = searchMap.get("pageNum"); //当前页
String pageSize = searchMap.get("pageSize"); //每页显示多少条
if (StringUtils.isEmpty(pageNum)) {
pageNum = "1";
}
if (StringUtils.isEmpty(pageSize)) {
pageSize = "30";
}
//设置分页
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
//按照相关字段进行排序查询
//1.当前域 2.当前的排序操作(升序ASC,降序DESC)
if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))) {
if ("ASC".equals(searchMap.get("sortRule"))) {
//升序操作
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
}else {
//降序
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
}
}
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
//封装规格分组结果
StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("specList", specList);
//当前页
resultMap.put("pageNum", pageNum);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?pageSize=1000&sortField=price&sortRule=DESC
# 高亮显示

高亮显示是指根据商品关键字搜索商品的时候,显示的页面对关键字给定了特殊样式,让它显示更加突出,如上图商品搜索中,关键字编程了红色,其实就是给定了红色样式。

第 1 点,例如在百度中搜索数据的时候,会有 2 个地方高亮显示,分别是标题和描述,商城搜索的时候,只是商品名称高亮显示了。而高亮显示其实就是添加了样式,例如 <span style="color:red;">笔记本</span> , 而其中 span 开始标签可以称为前缀,span 结束标签可以称为后缀。
第 2 点,高亮搜索使用 ElasticsearchTemplate 实现。
第 3 点,高亮搜索后,会搜出非高亮数据和高亮数据,高亮数据会加上第 1 点中的高亮样式,此时我们需要将非高亮数据换成高亮数据即可。例如非高亮: 华为笔记本性能超强悍 高亮数据: 华为<span style="color:red;"笔记本</span>性能超强悍 ,将非高亮的换成高亮的,到页面就能显示样式了。
修改搜索微服务 com.changgou.service.SearchServiceImpl 的搜索方法 在关键字查询 boolQuery 构建下添加
//设置高亮域以及高亮的样式
HighlightBuilder.Field field =new HighlightBuilder.Field("name")//高亮域
//高亮前缀
.preTags("<span style='color:red'>")
//高亮后缀
.postTags("</span>");
nativeSearchQueryBuilder.withHighlightFields(field);
2
3
4
5
6
7
在 SearchHit 转换为 skuinfo 后 降 skuinfo 中的 name 属性 替换为添加高亮样式后的 name
Map<String, HighlightField> highlightFields = hit.getHighlightFields(); //获取所有高亮域
if (highlightFields !=null && highlightFields.size() > 0){
//替换数据
skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
}
2
3
4
5
完整代码
package com.changgou.search.service.impl;
import com.alibaba.fastjson.JSON;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SearchService;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.get.MultiGetResponse;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.AbstractResultMapper;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class SearchServiceImpl implements SearchService {
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
/**
* 根据前端传过来的字段进行查询
*
* @param searchMap
* @return
*/
@Override
public Map search(Map<String, String> searchMap) {
Map<String, Object> resultMap = new HashMap<>();
//构建查询
if (searchMap != null) {
//构建查询条件封装对象
NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//按照关键字查询
if (StringUtils.isNotEmpty(searchMap.get("keywords"))) {
boolQuery.must(QueryBuilders.matchQuery("name", searchMap.get("keywords")).operator(Operator.AND));
}
//按照品牌进行过滤查询
if (StringUtils.isNotEmpty(searchMap.get("brand"))) {
boolQuery.filter(QueryBuilders.termQuery("brandName", searchMap.get("brand")));
}
//按照规格进行过滤查询
for (String key : searchMap.keySet()) {
if (key.startsWith("spec_")) {
String value = searchMap.get(key).replace("%2B", "+");
boolQuery.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", value));
}
}
nativeSearchQueryBuilder.withQuery(boolQuery);
//按照价格进行区间过滤查询
if (StringUtils.isNotEmpty(searchMap.get("price"))) {
String[] prices = searchMap.get("price").split("-");
if (prices.length == 2) {
//是xx - xx 价格区间的条件
boolQuery.filter(QueryBuilders.rangeQuery("price").lte(prices[1]));
}
boolQuery.filter(QueryBuilders.rangeQuery("price").gte(prices[0]));
}
//按照品牌进行分组(聚合)查询
String skuBrand = "skuBrand";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuBrand).field("brandName"));
//按照规格进行聚合查询
String skuSpec = "skuSpec";
nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms(skuSpec).field("spec.keyword"));
//开启分页查询
String pageNum = searchMap.get("pageNum"); //当前页
String pageSize = searchMap.get("pageSize"); //每页显示多少条
if (StringUtils.isEmpty(pageNum)) {
pageNum = "1";
}
if (StringUtils.isEmpty(pageSize)) {
pageSize = "30";
}
//设置分页
nativeSearchQueryBuilder.withPageable(PageRequest.of(Integer.parseInt(pageNum) - 1, Integer.parseInt(pageSize)));
//按照相关字段进行排序查询
//1.当前域 2.当前的排序操作(升序ASC,降序DESC)
if (StringUtils.isNotEmpty(searchMap.get("sortField")) && StringUtils.isNotEmpty(searchMap.get("sortRule"))) {
if ("ASC".equals(searchMap.get("sortRule"))) {
//升序操作
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.ASC));
}else {
//降序
nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(searchMap.get("sortField")).order(SortOrder.DESC));
}
}
//设置高亮域以及高亮的样式
HighlightBuilder.Field field =new HighlightBuilder.Field("name")//高亮域
//高亮前缀
.preTags("<span style='color:red'>")
//高亮后缀
.postTags("</span>");
nativeSearchQueryBuilder.withHighlightFields(field);
//开启查询
/**
* 第一个参数: 条件的构建对象
* 第二个参数: 查询操作实体类
* 第三个参数: 查询结果操作对象
*/
AggregatedPage<SkuInfo> resultInfo = elasticsearchTemplate.queryForPage(nativeSearchQueryBuilder.build(), SkuInfo.class,
new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
//查询结果操作
List<T> list = new ArrayList<>();
//获取查询命中的数据
SearchHits hits = searchResponse.getHits();
if (hits != null) {
//非空
for (SearchHit hit : hits) {
//SearchHit转换为skuinfo
SkuInfo skuInfo = JSON.parseObject(hit.getSourceAsString(), SkuInfo.class);
Map<String, HighlightField> highlightFields = hit.getHighlightFields(); //获取所有高亮域
if (highlightFields !=null && highlightFields.size() > 0){
//替换数据
skuInfo.setName(highlightFields.get("name").getFragments()[0].toString());
}
list.add((T) skuInfo);
}
}
return new AggregatedPageImpl<T>(list, pageable, hits.getTotalHits(), searchResponse.getAggregations());
}
});
//封装最终返回结果
//总记录数
resultMap.put("total", resultInfo.getTotalElements());
//总页数
resultMap.put("totalPages", resultInfo.getTotalPages());
//数据集合
resultMap.put("rows", resultInfo.getContent());
//封装品牌的分组结果
StringTerms brandTerms = (StringTerms) resultInfo.getAggregation(skuBrand);
List<String> brandList = brandTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("brandList", brandList);
//封装规格分组结果
StringTerms specTerms = (StringTerms) resultInfo.getAggregation(skuSpec);
List<String> specList = specTerms.getBuckets().stream().map(bucket -> bucket.getKeyAsString()).collect(Collectors.toList());
resultMap.put("specList", specList);
//当前页
resultMap.put("pageNum", pageNum);
return resultMap;
}
return null;
}
}
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
请求地址 localhost:9009/search?keywords = 电脑 查看关键字是否被添加高亮样式