赞
踩
导包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置es连接
spring:
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 127.0.0.1:9300
准备query接收查询条件
@Data public class CarSearchQuery extends BaseQuery { private Long carType; private Double maxPrice; private Double minPrice; //0 以下 1 以上 private Integer carAgeType; private Integer carAge; //是否超值 private Integer costEffective; //急售 private Integer rushSale; //准新车 private Integer quasiNewCar; //可迁全国 private Integer transitiveCountry; //排序字段 private String sortField; //排序类型 desc降序 asc升序号 private String sortType; private Double longitude; private Double latitude; private Double distance; private Long shopId; }
准备Controller
@RestController
@RequestMapping("/car/search")
public class CarSearchController {
@Autowired
private ICarSearchService carSearchService;
@PostMapping
public AjaxResult search(@RequestBody CarSearchQuery query) {
PageList<CarDoc> search = carSearchService.search(query);
return AjaxResult.me().setData(search);
}
}
定义Document文档类型
@Data @AllArgsConstructor @NoArgsConstructor @Document(indexName = "example-car", type = "car") public class CarDoc { @Id private Long id; @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart") private String title; @Field(type = FieldType.Keyword) private String cover; @Field(type = FieldType.Double) private BigDecimal salePrice; @Field(type = FieldType.Double) private BigDecimal costPrice; @Field(type = FieldType.Integer) private Integer isNew; @Field(type = FieldType.Date) private Date registerTime; @Field(type = FieldType.Double) private Double mileAge; @Field(type = FieldType.Long) private Long shopId; @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart"), otherFields = {@InnerField(type = FieldType.Keyword, suffix = "keyword")}) private String shopName; // @MultiField对同一字段应用不同的分析器或存储策略,这里既可以将该字段按照text进行拆分,也可以作为keyword进行查询 @Field(type = FieldType.Keyword) private String shopAddress; @Field(type = FieldType.Date) private Date onSaleTime; @Field(type = FieldType.Integer) private Integer costEffective; @Field(type = FieldType.Integer) private Integer rushSale; @Field(type = FieldType.Integer) private Integer quasiNewCar; @Field(type = FieldType.Integer) private Integer transitiveCountry; @Field(type = FieldType.Long) private Long typeId; @MultiField(mainField = @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart"), otherFields = {@InnerField(type = FieldType.Keyword, suffix = "keyword")}) private String typeName; @Field(type = FieldType.Text, analyzer = "ik_smart", searchAnalyzer = "ik_smart") private String carInfo; @GeoPointField // 表示坐标类型 private GeoPoint shopPoint; }
准备一个Repository接口
@Repository
public interface CarDocRepository extends ElasticsearchRepository<CarDoc, Long> {
}// 接口中的泛型,一个是文档对象的类型,一个是文档对象id的类型
使用spring-boot-starter-data-elasticsearch对es进行操作时也要按照es请求的格式进行操作
{ "query": { "bool": { "must": { "match_all": { } }, "filter": { "term": { "username": "Steven King" } } } } }
查询
@Service public class CarSearchServiceImpl implements ICarSearchService { @Autowired private CarDocRepository repository; @Autowired private HighlightResultMapper highlightResultMapper; @Autowired private ElasticsearchTemplate elasticsearchTemplate; @Override public PageList<CarDoc> search(CarSearchQuery query) { NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); BoolQueryBuilder boole = QueryBuilders.boolQuery(); // 外层的bool List<QueryBuilder> filter = boole.filter(); // 内层的fitler List<QueryBuilder> must = boole.must(); // 内层的must // 查询"title", "typeName", "shopName", "carInfo"中有查询关键字的结果 if (StringUtils.isNotBlank(query.getSearch())) { must.add(QueryBuilders.multiMatchQuery(query.getSearch(), "title", "typeName", "shopName", "carInfo")); } if (Objects.nonNull(query.getShopId())) { filter.add(QueryBuilders.termQuery("shopId", query.getShopId())); } // 进行范围查询 if (Objects.nonNull(query.getMinPrice())) { filter.add(QueryBuilders.rangeQuery("costPrice").gte(query.getMinPrice())); } if (Objects.nonNull(query.getMaxPrice())) { filter.add(QueryBuilders.rangeQuery("costPrice").lte(query.getMaxPrice())); } // 按时间进行查找 if (Objects.nonNull(query.getCarAge())) { Date date = DateUtils.addYears(new Date(), (-query.getCarAge())); if (query.getCarAgeType() == 1) { filter.add(QueryBuilders.rangeQuery("registerTime").lt(date.getTime())); } if (query.getCarAgeType() == 0) { filter.add(QueryBuilders.rangeQuery("registerTime").gte(date.getTime())); } } // 经纬度 Double lon = query.getLongitude(); Double lat = query.getLatitude(); // 按照经纬度计算距离并按照距离进行查询 if (Objects.nonNull(lon) && Objects.nonNull(lat) && Objects.nonNull(query.getDistance())) { GeoDistanceQueryBuilder point = QueryBuilders.geoDistanceQuery("shopPoint").point(lat, lon) .distance(query.getDistance(), DistanceUnit.KILOMETERS); filter.add(point); } // 将bool加入到最外层query的构造器 builder.withQuery(boole); SortOrder order = SortOrder.ASC; if (!"asc".equals(query.getSortType())) { order = SortOrder.DESC; } // 通过指定字段进行排序 if (Objects.nonNull(query.getSortField())) { FieldSortBuilder sort = SortBuilders.fieldSort(query.getSortField()).order(order); builder.withSort(sort); } else { // 没传则默认按照距离排序 if (Objects.nonNull(lat) && Objects.nonNull(lon)) { GeoDistanceSortBuilder point = new GeoDistanceSortBuilder("shopPoint", lat, lon); GeoDistanceSortBuilder sort = point.order(order); builder.withSort(sort); } } // 高亮展示,通过给字段前后加html标签的方式实现高亮等效果,需要一个高亮的工具类 HighlightBuilder.Field title = new HighlightBuilder.Field("title") .preTags("<span style='color:red'>").postTags("</span>"); builder.withHighlightFields(title); // 进行分页展示 builder.withPageable(PageRequest.of(query.getCurrentPage() - 1, query.getPageSize())); TermsAggregationBuilder aggBuilders1 = AggregationBuilders.terms("typeIdGroup").field("typeId").order(BucketOrder.count(true)) .subAggregation(AggregationBuilders.terms("typeNameGroup").field("typeName.keyword").order(BucketOrder.count(true)));// 聚合查询 builder.addAggregation(aggBuilders1); AggregatedPage<CarDoc> carDocs = elasticsearchTemplate.queryForPage(builder.build(), CarDoc.class, highlightResultMapper); Map<String, Object> map = SearchUtil.handleTermsAggsData(carDocs.getAggregations()); Long counts = carDocs.getTotalElements(); List<CarDoc> content = carDocs.getContent(); for (CarDoc doc : content) { String s = doc.getShopAddress(); if (StringUtils.isNotBlank(s)) { String string = s.split("市")[0] + "市"; } doc.setShopAddress(s); } return new PageList<>(counts, content, map); } }
PageList
@Data public class PageList<T> { private Long count; private List<T> data; // 用于存放聚合查询的数据 private Map<String,Object> map; public PageList() { } public PageList(Long count, List<T> data, Map<String, Object> map) { this.count = count; this.data = data; this.map = map; } public PageList(Long count, List<T> data) { this.count = count; this.data = data; } }
高亮结果映射器
@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 = new ArrayList<>(); // 获取搜索结果(真正的的记录) 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 = JSONObject.parseObject(JSONObject.toJSONString(map),aClass); list.add(item); } // 返回的是带分页的结果 return new AggregatedPageImpl<>(list, pageable, totalHits,response.getAggregations()); //获取聚合结果 } @Override public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) { return null; } }
搜索工具类,用于处理聚合查询结果
public class SearchUtil { /** * 处理terms聚合 id name * @param aggregations * @return */ public static Map<String, Object> handleTermsAggsData(Aggregations aggregations) { // 获取聚合查询结果 Map<String, Aggregation> aggregationsMap = aggregations.getAsMap(); Set<Map.Entry<String, Aggregation>> entries = aggregationsMap.entrySet(); Iterator<Map.Entry<String, Aggregation>> iterator = entries.iterator(); //6.1有多少聚合就要返回多少个key-List<IdName> Map<String, Object> aggsData = new HashMap<>(); while (iterator.hasNext()) { Map.Entry<String, Aggregation> entry = iterator.next(); String key = entry.getKey(); System.out.println(key); Aggregation aggsId = entry.getValue(); if (aggsId instanceof LongTerms) { //6.2 拿到id聚合,并且必须是LongTerms LongTerms aggsIdLong = (LongTerms) aggsId; List<LongTerms.Bucket> buckets = aggsIdLong.getBuckets(); //6.3 List<IdName<Long,String> List<IdName> list = new ArrayList<>(); buckets.forEach(bucket -> { String idStr = bucket.getKeyAsString(); //6.4 通过子聚合获取name Map<String, Aggregation> subAggs = bucket.getAggregations().getAsMap(); Set<Map.Entry<String, Aggregation>> entries1 = subAggs.entrySet(); //直接获取第一个 Map.Entry<String, Aggregation> nameAggEntry = entries1.iterator().next(); Aggregation nameAgg = nameAggEntry.getValue(); if (nameAgg instanceof StringTerms) { StringTerms nameAggStringTerms = (StringTerms) nameAgg; String nameStr = nameAggStringTerms.getBuckets().get(0).getKeyAsString(); IdName idName = new IdName(); idName.setId(Long.valueOf(idStr)); idName.setName(nameStr); list.add(idName); } }); aggsData.put(key, list); } } return aggsData; } }
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。