门户网站提供一个搜索框给用户,用户可以使用这个搜索框对商品的某些字段进行一个搜索(列如,商品名,商品的简介),ElasticSearch对输入搜索的字符串在搜索出来的数据中进行一个特殊展示称之为高亮。当需要对某些特定的商品进行一些基础计算时可以使用ElasticSearch聚合进行(列如,获取到某些类型商品的平均价格,最大价格,最小价格,总数量,等等…)
1.高亮展示
2.聚合展示
3.导入jar包,创建启动类,配置application文件
<dependencies>
<!--微服务基础依赖-->
<dependency>
<groupId>com.zengjx</groupId>
<artifactId>hrm-service-dependencies</artifactId>
<version>${hrm.version}</version>
</dependency>
<!--导入elasticsearch的依赖包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!--导入测试包-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>
</dependencies>
//启动类 忽略Mysqlplus的一个配置类
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaClient //eureka客户端
public class SearchApp {
public static void main(String[] args) {
SpringApplication.run(SearchApp.class, args);
}
}
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1010/eureka/
registry-fetch-interval-seconds: 5 #拉取注册表的时间间隔
instance:
instance-id: www.search.com:1070 #实例的ID
prefer-ip-address: true #使用IP注册到注册中心
server:
port: 1070
spring:
application:
name: service-search
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1:9300 #9200是图形界面端,9300代码端
4.创建一个映射类,该类会把查询出来的结果中的高亮字段取出来并进行一个封装然后返回给SpringBootDataES,该类还添加了聚合结果
//就是把原生的ES的结果中的高亮字段取出来,封装给SpringBootDataES的结果
@Component
public class HighlightResultMapper implements SearchResultMapper {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> aClass, Pageable pageable) {
// 记录总条数
long totalHits = response.getHits().getTotalHits();
// 记录列表(泛型) - 构建Aggregate使用
List<T> list = Lists.newArrayList();
// 获取搜索结果(真正的的记录)
SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
if(hits.getHits().length <= 0){
return null;
}
// 将原本的JSON对象转换成Map对象
Map<String, Object> map = hit.getSourceAsMap();
// 获取高亮的字段Map
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
for (Map.Entry<String, HighlightField> highlightField : highlightFields.entrySet()) {
// 获取高亮的Key
String key = highlightField.getKey();
// 获取高亮的Value
HighlightField value = highlightField.getValue();
// 实际fragments[0]就是高亮的结果,无需遍历拼接
Text[] fragments = value.getFragments();
StringBuilder sb = new StringBuilder();
for (Text text : fragments) {
sb.append(text);
}
// 因为高亮的字段必然存在于Map中,就是key值
// 可能有一种情况,就是高亮的字段是嵌套Map,也就是说在Map里面还有Map的这种情况,这里没有考虑
map.put(key, sb.toString());
}
// 把Map转换成对象
T item = JSON.parseObject(JSONObject.toJSONString(map),aClass);
list.add(item);
}
// 返回的是带分页的结果
//第三个参数response.getAggregations() 添加聚合结果
return new AggregatedPageImpl<>(list, pageable, totalHits,response.getAggregations());
}
}
5.创建一个dto类用来接收聚合查询到的数据
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BucketDto {
//用来封装聚合查询查询出来的数据
//key值
private String key;
//count值
private Long count;
}
6.创建数据返回的类,该类对所有的数据进行了一个封装
//分页对象:easyui只需两个属性,total(总数),datas(分页数据)就能实现分页
@Data
public class PageList<T> {
private long total;
private List<T> rows = new ArrayList<>();
//创建新的字段用来封装聚合查询查询到的数据
private List<BucketDto> tenantNameAggs = new ArrayList<>();
//创建新的字段用来封装聚合查询查询到的数据
private List<BucketDto> gradeNameAggs = new ArrayList<>();
//private List<Map<String,Object>> tenantNameAggs = new ArrayList<>();
//private List<Map<String,Object>> gradeNameAggs = new ArrayList<>();
//提供一个构造方法用来设置数据
public PageList(long total, List<T> rows,List<BucketDto> tenantNameAggs,List<BucketDto> gradeNameAggs) {
this.total = total;
this.rows = rows;
this.tenantNameAggs = tenantNameAggs;
this.gradeNameAggs = gradeNameAggs;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public List<T> getRows() {
return rows;
}
public void setRows(List<T> rows) {
this.rows = rows;
}
@Override
public String toString() {
return "PageList{" +
"total=" + total +
", rows=" + rows +
'}';
}
//提供有参构造方法,方便测试
public PageList(long total, List<T> rows) {
this.total = total;
this.rows = rows;
}
//除了有参构造方法,还需要提供一个无参构造方法
public PageList() {
}
}
7.创建controller
@RestController
public class CourseSearchController {
//注入service层中处理业务的接口
@Autowired
private ICourseElasticsearchService courseElasticsearchService;
//使用ES课程查询并返回给前端一个查询后的数据集合
@RequestMapping(value = "/es/searchCourse",method = RequestMethod.POST)
public AjaxResult searchCourse(@RequestBody CourseQuery courseQuery ){
PageList pageList = courseElasticsearchService.searchCourse(courseQuery);
return AjaxResult.me().setResultObj(pageList);
}
}
8.创建service
@Service
public class CourseElasticsearchServiceImpl implements ICourseElasticsearchService{
//注入ElasticsearchTemplate 因为需要使用高亮
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
//注入HighlightResultMapper用于处理高亮和聚合的封装结果
@Autowired
private HighlightResultMapper highlightResultMapper;
//使用ES课程查询并返回给前端一个查询后的数据集合
@Override
public PageList searchCourse(CourseQuery courseQuery) {
//创建一个本地的查询器
NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
//自定义方法:根据参数创建分页方式和根据那个字段排序
createSortAndPage(courseQuery, builder);
//设置高亮
//创建HighlightBuilder.Field对象设置指定高亮字段 第一个参数为哪一个字段 第二个参数为设置高亮方式 用户在name字段中查询,所以在该字段中展示高亮
HighlightBuilder.Field field = new HighlightBuilder.Field("name").preTags("<font style='color:red'>").postTags("</font>");
builder.withHighlightFields(field);
//使用聚合查询
//查询tenantName的聚合 第一个参数为name(数据封装在这个name的数据结构中) 第二个参数为对哪一个字段进行聚合查询
TermsAggregationBuilder tenantAgg = AggregationBuilders.terms("tenantAgg").field("tenantName");
//查询gradeName的聚合 第一个参数为name(数据封装在这个name的数据结构中) 第二个参数为对哪一个字段进行聚合查询
TermsAggregationBuilder gradeAgg = AggregationBuilders.terms("gradeAgg").field("gradeName");
//将聚合设置给查询器
builder.addAggregation(tenantAgg).addAggregation(gradeAgg);
//自定义方法:使用组合查询
getBoolQueryBuilder(courseQuery, builder);
//改用ElasticsearchTemplate的查询方法获取到查询后的结果值 因为使用了高亮和聚合查询 只有注入的ElasticsearchTemplate支持
AggregatedPage<CourseDoc> courseDocs = elasticsearchTemplate.queryForPage(builder.build(), CourseDoc.class, highlightResultMapper);
//在查询到的数据中获取到指定name的聚合查询的数据
//courseDocs.getAggregation获取到的对象为Aggregation(这是个接口) 我们使用StringTerms来接收数据(StringTerms继承了AggregatedPage某个抽象类)
StringTerms tenantAgg1 = (StringTerms)courseDocs.getAggregation("tenantAgg");
StringTerms gradeAgg1 = (StringTerms)courseDocs.getAggregation("gradeAgg");
//获取到封装在Aggregation中我们需要的数据 tenantAgg1
List<StringTerms.Bucket> tenantAggBuckets = tenantAgg1.getBuckets();
//创建一个数组用来接收遍历后产生的数据集合
//List<Map<String,Object>> tenantNameAggs = new ArrayList<>();
//创建一个数组用来接收遍历后产生的数据集合
List<BucketDto> resultTenant = new ArrayList<>();
//遍历拿到的数据使用一个对象来接收数据
tenantAggBuckets.forEach(bucket -> {
//bucket.getKey()相当于获取到名字 bucket.getDocCount()获取到数量
BucketDto bucketDto = new BucketDto(bucket.getKey().toString(), bucket.getDocCount());
resultTenant.add(bucketDto);
//使用Map的方式接收数据
/*Map<String,Object> bucketMap = new HashMap<>();
bucketMap.put("key",bucket.getKey());
bucketMap.put("count",bucket.getDocCount());
tenantNameAggs.add(bucketMap);*/
});
//获取到封装在Aggregation中我们需要的数据 gradeAgg1
List<StringTerms.Bucket> gradeAggBuckets = gradeAgg1.getBuckets();
//创建一个数组用来接收遍历后产生的数据集合
//ArrayList<Map<String,Object>> gradeNameAggs = new ArrayList<>();
//创建一个数组用来接收遍历后产生的数据集合
ArrayList<BucketDto> resultGrade = new ArrayList<>();
//遍历拿到的数据使用一个对象来接收数据
gradeAggBuckets.forEach(bucket -> {
//bucket.getKey()相当于获取到名字 bucket.getDocCount()获取到数量
BucketDto bucketDto = new BucketDto(bucket.getKey().toString(), bucket.getDocCount());
resultGrade.add(bucketDto);
//使用Map的方式接收数据
/*Map<String,Object> bucketMap = new HashMap<>();
bucketMap.put("key",bucket.getKey());
bucketMap.put("count",bucket.getDocCount());
gradeNameAggs.add(bucketMap);*/
//gradeNameAggs.add(bucketDto);
});
//=====================================================================================
//最后封装数据到PageList中并返回给前端
return new PageList(courseDocs.getTotalElements(),courseDocs.getContent(),resultTenant,resultGrade);
}
9.个人总结
9.1构建高亮查询对象的时候需要指定查询的字段和对高亮字符串采用什么样样式展示
9.2将高亮查询对象交给查询器
9.3构建聚合查询对象,给聚合查询到的数据集合取一个名字,指定哪一个字段需要进行聚合查询
9.4将聚合查询对象交给查询器
9.5使用ElasticsearchTemplate中的查询方法进行查询,因为它支持高亮和聚合,在使用它时需要指定查询器、查询到的数据的封装类、高亮和聚合的映射类(该类对高亮字段进行了封装,添加了聚合结果)
9.6对聚合数据进行处理。当查询完之后聚合查询到数据需要取出来并封装到我们自己定义的对象中,使用查询结果对象中的方法getAggregation去获取指定的聚合查询的数据,然后在遍历数据并将它们封装到我们指定的类中
9.7对整个查询的数据进行封装。在我们的返回对象的类中需要提供满足需求的字段,这样前端拿到的数据才是完整的