1. 使用场景

根据用户当前所在的地理位置坐标,按商品关键字查询出附近店铺的相关商品,并按店铺位置远近将搜索结果排序。

  1. 场景说明按商品关键字搜索,比如关键字为“牛奶”,那么需要搜索出附近店铺发布的带有“牛奶”关键字的商品。商品不会带有位置信息,但是商品所属的店铺是有位置信息的,因此要将店铺的位置信息存放进商品的ES索引中。
  2. 具体实现ES索引和Mapping的创建

地理坐标点不能被动态映射(dynamic mapping)自动检测,而是需要显式声明对应字段类型为geo-point,如下:




es分组结果过滤 es 分组查询_es分组结果过滤


创建索引和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();    }}
  1. 创建商品索引内容

首先可将要放入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);    }}
  1. 搜索商品
@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);    }}