- 使用场景
根据用户当前所在的地理位置坐标,按商品关键字查询出附近店铺的相关商品,并按店铺位置远近将搜索结果排序。
- 场景说明按商品关键字搜索,比如关键字为“牛奶”,那么需要搜索出附近店铺发布的带有“牛奶”关键字的商品。商品不会带有位置信息,但是商品所属的店铺是有位置信息的,因此要将店铺的位置信息存放进商品的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中,具体代码如下:
@Servicepublic 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); }}
- 搜索商品
@Servicepublic 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 resultList = new ArrayList<>(); //店铺到当前坐标位置的距离 Map shopDistanceMap = new HashMap<>(); for (SearchHit hit : searchHits) { Map 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 shopDistanceMap) { //获取聚合结果数据 Map aggMap = response.getAggregations().asMap(); //新建店铺信息集合 List shopList = new ArrayList<>(); //获取店铺聚合结果数据 LongTerms shopTerms = (LongTerms) aggMap.get("shop"); List bucketList = shopTerms.getBuckets(); for (LongTerms.Bucket bucket : bucketList) { Aggregations shopDetailAggResult = bucket.getAggregations(); Map 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); }}