elasticsearch 分组查询(ElasticSearch地理位置查询与分组)
elasticsearch 分组查询(ElasticSearch地理位置查询与分组)@Document(indexName = "goods_index_" EsSettings.GOODS_INDEX_NAME type = EsSettings.GOODS_TYPE_NAME) public class GoodsIndex { @Id private Integer goodsId; @Field(type = FieldType.text analyzer = "ik_max_word") private String goodsName; @Field(type = FieldType.Integer) private Integer sellerId; @GeoPointField private GeoPoint location; //其它字段、
- 使用场景
根据用户当前所在的地理位置坐标,按商品关键字查询出附近店铺的相关商品,并按店铺位置远近将搜索结果排序。
- 场景说明按商品关键字搜索,比如关键字为“牛奶”,那么需要搜索出附近店铺发布的带有“牛奶”关键字的商品。商品不会带有位置信息,但是商品所属的店铺是有位置信息的,因此要将店铺的位置信息存放进商品的ES索引中。
- 具体实现ES索引和Mapping的创建
地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型为geo-point,如下:
创建索引和mapping具体代码如下(以下代码为单元测试类,可根据业务需要自定义修改):
public class CreateIndex {
/** 商品索引名称 */
private static final String INDEX_NAME = "goods_index";
/** ES cluster-name*/
private static final String CLUSTER_NAME = "es_cluster";
/** ES cluster-nodes*/
private static final String CLUSTER_NODES = "127.0.0.1:9300";
/** ES 用户密码*/
private static final String USER_PASSWORD = "";
@Test
public void create() {
EsTemplateBuilder esTemplateBuilder;
//如果用户验证
if(!StringUtil.isEmpty(USER_PASSWORD)){
esTemplateBuilder = new DefaultEsTemplateBuilder().setClusterName(CLUSTER_NAME).setClusterNodes(CLUSTER_NODES).setUserPass(USER_PASSWORD);
}else{
esTemplateBuilder = new DefaultEsTemplateBuilder().setClusterName(CLUSTER_NAME).setClusterNodes(CLUSTER_NODES);
}
ElasticsearchTemplate esTemplate = esTemplateBuilder.build();
//商品索引名称
String goodsIndexName = INDEX_NAME "_" EsSettings.GOODS_INDEX_NAME;
//先删除商品索引,再创建
esTemplate.deleteIndex(goodsIndexName);
esTemplate.createIndex(goodsIndexName);
//获取商品索引mapping
Map goodsMapping = createGoodsMapping();
//创建商品索引mapping
esTemplate.putMapping(goodsIndexName EsSettings.GOODS_TYPE_NAME goodsMapping);
}
/**
* 创建商品索引mapping
* @return
*/
private Map createGoodsMapping() {
Map goodsMap = new HashMap();
goodsMap.put("goodsId" new MyMap().put("type" "long").getMap());
goodsMap.put("goodsName" new MyMap().put("type" "text").put("analyzer" "ik_max_word").getMap());
goodsMap.put("sellerId" new MyMap().put("type" "integer").getMap());
goodsMap.put("location" new MyMap().put("type" "geo_point").getMap());
//其它字段略...
return new MyMap().put("properties" goodsMap).getMap();
}
}
- 创建商品索引内容
首先可将要放入ES中的商品信息进行封装,如下:
@Document(indexName = "goods_index_" EsSettings.GOODS_INDEX_NAME type = EsSettings.GOODS_TYPE_NAME)
public class GoodsIndex {
@Id
private Integer goodsId;
@Field(type = FieldType.text analyzer = "ik_max_word")
private String goodsName;
@Field(type = FieldType.Integer)
private Integer sellerId;
@GeoPointField
private GeoPoint location;
//其它字段、get set等内容略...
初始化商品索引内容并放入ES中,具体代码如下:
@Service
public class GoodsIndexManagerImpl implements GoodsIndexManager {
@Autowired
protected ElasticsearchTemplate elasticsearchOperations;
@Override
public void addIndex(Map goods) {
GoodsIndex goodsIndex = new GoodsIndex();
goodsIndex.setGoodsId(StringUtil.toInt(goods.get("goods_id").toString() 0));
goodsIndex.setGoodsName(goods.get("goods_name").toString());
goodsIndex.setSellerId(StringUtil.toInt(goods.get("seller_id").toString() 0));
//获取店铺所在位置纬度
double latitude = goods.get("latitude") == null ? 0d : StringUtil.toDouble(goods.get("latitude").toString() 0d);
//获取店铺所在位置经度
double longitude = goods.get("longitude") == null ? 0d : StringUtil.toDouble(goods.get("longitude").toString() 0d);
//设置店铺所在位置经纬度
GeoPoint location = new GeoPoint(latitude longitude);
goodsIndex.setLocation(location);
//索引名字
String indexName = "goods_index_" EsSettings.GOODS_INDEX_NAME;
IndexQuery indexQuery = new IndexQuery();
indexQuery.setIndexName(indexName);
indexQuery.setType(EsSettings.GOODS_TYPE_NAME);
indexQuery.setId(goodsIndex.getGoodsId().toString());
indexQuery.setObject(goodsIndex);
elasticsearchOperations.index(indexQuery);
}
}
- 搜索商品
@Service
public class GoodsSearchManagerImpl implements GoodsSearchManager {
/** 查询范围默认20千米内 */
private static final double SEARCH_DISTANCE = 20.00;
@Override
public SearchResult searchAllResult(GoodsSearchParams goodsSearch) {
//获取搜索页数
Integer pageNo = goodsSearch.getPageNo();
//获取搜索每页数量
Integer pageSize = goodsSearch.getPageSize();
//返回结果
SearchResult searchResult = new SearchResult();
SearchRequestBuilder searchRequestBuilder;
try {
//创建查询条件
searchRequestBuilder = this.createGoodsQuery(goodsSearch);
//设置分页信息
searchRequestBuilder.setFrom((pageNo - 1) * pageSize).setSize(pageSize);
//设置是否按查询匹配度排序
searchRequestBuilder.setExplain(true);
//获取查询结果
SearchResponse response = searchRequestBuilder.execute().actionGet();
SearchHits searchHits = response.getHits();
//新建搜索到的商品结果集合
List<GoodsSearchResult> resultList = new ArrayList<>();
//店铺到当前坐标位置的距离
Map<Integer Double> shopDistanceMap = new HashMap<>();
for (SearchHit hit : searchHits) {
Map<String Object> map = hit.getSource();
GoodsSearchResult goodsSearchLine = new GoodsSearchResult();
//设置商品名称
goodsSearchLine.setName(map.get("goodsName").toString());
//设置商品ID
goodsSearchLine.setGoodsId(map.get("goodsId") == null ? 0 : StringUtil.toInt(map.get("goodsId").toString() 0));
//获取商家店铺ID
Integer sellerId = map.get("sellerId") == null ? 0 : StringUtil.toInt(map.get("sellerId").toString() 0);
//设置商家ID
goodsSearchLine.setSellerId(sellerId);
//将商品信息放入结果集合中
resultList.add(goodsSearchLine);
//获取当前坐标与店铺的距离
Double distance = StringUtil.toDouble(hit.getSortValues()[0] false);
shopDistanceMap.put(sellerId distance);
}
Page webPage = new Page<>(pageNo searchHits.getTotalHits() pageSize resultList);
searchResult.setWebPage(webPage);
//初始化搜索结果
this.initSearchResult(searchResult response shopDistanceMap);
return searchResult;
} catch (Exception e) {
e.printStackTrace();
}
return searchResult;
}
/**
* 构建索引查询条件
* @param goodsSearch 查询条件参数
* @return
*/
protected SearchRequestBuilder createGoodsQuery(GoodsSearchParams goodsSearch) {
//获取查询关键字
String keyword = goodsSearch.getKeyword();
//获取店铺ID
Integer sellerId = goodsSearch.getSellerId();
//获取用户当前所在位置经度
double userLng = goodsSearch.getLongitude();
//获取用户当前所在位置纬度
double userLat = goodsSearch.getLatitude();
SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient().prepareSearch("goods_index_" EsSettings.GOODS_INDEX_NAME);
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//关键字检索
if (!StringUtil.isEmpty(keyword)) {
QueryStringQueryBuilder queryString = new QueryStringQueryBuilder(keyword).field("goodsName");
queryString.defaultOperator(Operator.AND);
queryString.analyzer("ik_smart");
queryString.useDisMax(false);
boolQueryBuilder.must(queryString);
}
//卖家搜索
if (sellerId != null) {
boolQueryBuilder.must(QueryBuilders.termQuery("sellerId" sellerId));
}
//设置查询条件
searchRequestBuilder.setQuery(boolQueryBuilder);
// 以某点为中心,搜索指定范围
GeoDistanceQueryBuilder distanceQueryBuilder = new GeoDistanceQueryBuilder("location");
distanceQueryBuilder.point(userLat userLng);
// 定义查询单位:公里
distanceQueryBuilder.distance(SEARCH_DISTANCE DistanceUnit.KILOMETERS);
boolQueryBuilder.filter(distanceQueryBuilder);
//添加聚合
this.addAggregation(searchRequestBuilder);
//添加排序
this.addSort(searchRequestBuilder userLng userLat);
return searchRequestBuilder;
}
/**
* 添加聚合条件
* @param searchRequestBuilder
* @return
*/
protected void addAggregation(SearchRequestBuilder searchRequestBuilder) {
//sellerId聚合的子聚合:是一个topHits,只显示一条记录,目的是查出这个聚合的店铺信息
AggregationBuilder shopDetailAgg = AggregationBuilders.topHits("shopDetail").size(1).fetchSource(new String[]{"sellerName" "sellerId" "shopLogo" "shopPraiseRate" "deliveryScope"} new String[]{});
//构建按sellerId聚合
AggregationBuilder shopAgg = AggregationBuilders.terms("shop").field("sellerId").subAggregation(shopDetailAgg);
searchRequestBuilder.addAggregation(shopAgg);
}
/**
* 添加排序条件
* @param searchRequestBuilder
* @param sortField 排序字段
* @param userLng 用户当前所在位置经度
* @param userLat 用户当前所在位置纬度
*/
protected void addSort(SearchRequestBuilder searchRequestBuilder double userLng double userLat) {
//以当前的区为基准排序
SortBuilder locationOrder = SortBuilders.geoDistanceSort("location" userLat userLng).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC);
searchRequestBuilder.addSort(locationOrder);
}
/**
* 初始化搜索结果
* @param searchResult 搜索结果数据
* @param response 查询结果
* @param shopDistanceMap 店铺距离数据
*/
protected void initSearchResult(SearchResult searchResult SearchResponse response Map<Integer Double> shopDistanceMap) {
//获取聚合结果数据
Map<String Aggregation> aggMap = response.getAggregations().asMap();
//新建店铺信息集合
List<Map> shopList = new ArrayList<>();
//获取店铺聚合结果数据
LongTerms shopTerms = (LongTerms) aggMap.get("shop");
List<LongTerms.Bucket> bucketList = shopTerms.getBuckets();
for (LongTerms.Bucket bucket : bucketList) {
Aggregations shopDetailAggResult = bucket.getAggregations();
Map<String Aggregation> detailAggMap = shopDetailAggResult.asMap();
InternalTopHits hits =(InternalTopHits) detailAggMap.get("shopDetail");
SearchHit[] detailHit = hits.getHits().getHits();
if (detailHit != null && detailHit.length >= 1) {
SearchHit hit = detailHit[0];
Map shopDetail = hit.getSource();
double dis = shopDistanceMap.get(shopDetail.get("sellerId"));
String disStr = "";
if (dis >= 1) {
disStr = CurrencyUtil.round(dis 2) "km";
} else {
disStr = CurrencyUtil.round(CurrencyUtil.mul(dis 1000.00) 0) "m";
}
shopDetail.put("distance" disStr);
shopList.add(shopDetail);
}
}
//设置店铺信息集合
searchResult.setShopList(shopList);
}
}