ES实战 | 黑马旅游案例

关键词搜索

ES实战 | 黑马旅游案例_第1张图片

需求:根据文字搜索,也可以选择标签搜索

思路:用bool查询,先根据关键词查询全部,再根据标签过滤。

public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;
    @Override
    public PageResult search(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
//        关键字搜索
        QueryBuilder query = buildBasicQuery(params);
        request.source().query(query);
        
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return extracted(response);
    }

    private static QueryBuilder buildBasicQuery(RequestParams params) {
        String key  = params.getKey();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        query
//        关键字搜索
        if ("".equals(key) || key==null){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else {
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }
//        过滤条件
        if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        if (!("".equals(params.getCity()) || params.getCity()==null)){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (!("".equals(params.getBrand()) || params.getBrand()==null)){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (!("".equals(params.getStarName()) || params.getStarName()==null)){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
        return boolQuery;
    }

    private static PageResult extracted(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> list = Arrays.stream(hits).map(item -> {
            String jsonStr = item.getSourceAsString();
            Object[] sortValues = item.getSortValues();
            HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
            return hotelDoc;
        }).collect(Collectors.toList());
        return new PageResult(total, list);
    }
}

分页排序

在这里插入图片描述

需求:实现分页排序

思路:分页跟排序是单独的功能,可以根据选项排好序再分页

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;
    private static Boolean isLocation = false;
    @Override
    public PageResult search(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
//        关键字搜索
        QueryBuilder query = buildBasicQuery(params);
        request.source().query(query);
//        分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page-1)*size).size(size);
//        排序
        if (!("default".equals(params.getSortBy()))){
            FieldSortBuilder sortBy = SortBuilders.fieldSort(params.getSortBy());
            if ("price".equals(params.getSortBy())){
                sortBy.order(SortOrder.ASC);
            }else {
                sortBy.order(SortOrder.DESC);
            }
            request.source().sort(sortBy);
        }
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return extracted(response);
    }

    private static QueryBuilder buildBasicQuery(RequestParams params) {
        String key  = params.getKey();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        query
//        关键字搜索
        if ("".equals(key) || key==null){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else {
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }
//        过滤条件
        if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        if (!("".equals(params.getCity()) || params.getCity()==null)){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (!("".equals(params.getBrand()) || params.getBrand()==null)){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (!("".equals(params.getStarName()) || params.getStarName()==null)){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
		return boolQuery;
    }

    private static PageResult extracted(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> list = Arrays.stream(hits).map(item -> {
            String jsonStr = item.getSourceAsString();
            Object[] sortValues = item.getSortValues();
            HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
            return hotelDoc;
        }).collect(Collectors.toList());
        return new PageResult(total, list);
    }
}

距离显示

ES实战 | 黑马旅游案例_第2张图片

要求:点击获取位置后,根据距离显示酒店,且要显示距离

思路:先判断有没有点击地图,如果携带了位置的请求就代表点击了地图,就需要根据坐标查询,且规定坐标先查找,可以保证在sort里value[0]就是坐标值

ES实战 | 黑马旅游案例_第3张图片

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;
    private static Boolean isLocation = false;
    @Override
    public PageResult search(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
//        关键字搜索
        QueryBuilder query = buildBasicQuery(params);
        request.source().query(query);
//        分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page-1)*size).size(size);
//        排序
        String location = params.getLocation();
//        坐标排序
        if (!("".equals(location) || location==null)){
            GeoDistanceSortBuilder locationSort = SortBuilders.geoDistanceSort("location",new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS);
            request.source().sort(locationSort);
            FieldSortBuilder priceSort = SortBuilders.fieldSort("price").order(SortOrder.ASC);
            request.source().sort(priceSort);
            isLocation =true;
        }
//		  price/defult/socre排序        
        if (!("default".equals(params.getSortBy()))){
            FieldSortBuilder sortBy = SortBuilders.fieldSort(params.getSortBy());
            if ("price".equals(params.getSortBy())){
                sortBy.order(SortOrder.ASC);
            }else {
                sortBy.order(SortOrder.DESC);
            }
            request.source().sort(sortBy);
        }
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return extracted(response);
    }

    private static QueryBuilder buildBasicQuery(RequestParams params) {
        String key  = params.getKey();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        query
//        关键字搜索
        if ("".equals(key) || key==null){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else {
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }
//        过滤条件
        if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        if (!("".equals(params.getCity()) || params.getCity()==null)){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (!("".equals(params.getBrand()) || params.getBrand()==null)){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (!("".equals(params.getStarName()) || params.getStarName()==null)){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }
		return boolQuery;
    }

    private static PageResult extracted(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> list = Arrays.stream(hits).map(item -> {
            String jsonStr = item.getSourceAsString();
            Object[] sortValues = item.getSortValues();
            HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
            if (sortValues.length>0 && isLocation){
                Object sortValue = sortValues[0];
                hotelDoc.setDistance(sortValue);
            }
            return hotelDoc;
        }).collect(Collectors.toList());
        return new PageResult(total, list);
    }
}

广告置顶

ES实战 | 黑马旅游案例_第4张图片

要求:广告置顶,有些广告需要在展示搜索结果的时候排在前面

思路:利用functionScore来做,但是functionScore是用不到bool的只能用普通的查询,在文档中维护一个字段叫isAD,在functions中过滤出isAD的文档,重新分配权重

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient client;
    private static Boolean isLocation = false;
    @Override
    public PageResult search(RequestParams params) throws IOException {
        SearchRequest request = new SearchRequest("hotel");
//        关键字搜索
        QueryBuilder query = buildBasicQuery(params);
        request.source().query(query);
//        分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page-1)*size).size(size);
//        排序
        String location = params.getLocation();
        if (!("".equals(location) || location==null)){
            GeoDistanceSortBuilder locationSort = SortBuilders.geoDistanceSort("location",new GeoPoint(location)).order(SortOrder.ASC).unit(DistanceUnit.KILOMETERS);
            request.source().sort(locationSort);
            FieldSortBuilder priceSort = SortBuilders.fieldSort("price").order(SortOrder.ASC);
            request.source().sort(priceSort);
            isLocation =true;
        }
        if (!("default".equals(params.getSortBy()))){
            FieldSortBuilder sortBy = SortBuilders.fieldSort(params.getSortBy());
            if ("price".equals(params.getSortBy())){
                sortBy.order(SortOrder.ASC);
            }else {
                sortBy.order(SortOrder.DESC);
            }
            request.source().sort(sortBy);
        }
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        return extracted(response);
    }

    private static QueryBuilder buildBasicQuery(RequestParams params) {
        String key  = params.getKey();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        query
//        关键字搜索
        if ("".equals(key) || key==null){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else {
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }
//        过滤条件
        if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        if (!("".equals(params.getCity()) || params.getCity()==null)){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (!("".equals(params.getBrand()) || params.getBrand()==null)){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (!("".equals(params.getStarName()) || params.getStarName()==null)){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }

//        广告置顶
        return QueryBuilders.functionScoreQuery(boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.termQuery("isAD",true),
                        ScoreFunctionBuilders.weightFactorFunction(10)
                )
        }).boostMode(CombineFunction.MULTIPLY);
    }

    private static PageResult extracted(SearchResponse response) {
        SearchHits searchHits = response.getHits();
        long total = searchHits.getTotalHits().value;
        SearchHit[] hits = searchHits.getHits();
        List<HotelDoc> list = Arrays.stream(hits).map(item -> {
            String jsonStr = item.getSourceAsString();
            Object[] sortValues = item.getSortValues();
            HotelDoc hotelDoc = JSONObject.parseObject(jsonStr, HotelDoc.class);
            if (sortValues.length>0 && isLocation){
                Object sortValue = sortValues[0];
                hotelDoc.setDistance(sortValue);
            }
            return hotelDoc;
        }).collect(Collectors.toList());
        return new PageResult(total, list);
    }
}

标签聚合

ES实战 | 黑马旅游案例_第5张图片

需求:从后台文档中获取实际数据,传入前端页面显示,且会动态变化,比如:选择了上海,就展示在上海的品牌,而不在上海的品牌就不显示

思路:先根据用户条件搜索文档,按照类别聚合

    @Override
    public Map<String, List<String>> filters(RequestParams requestParams){
        Map<String, List<String>> listMap = null;
        try {
            SearchRequest request = new SearchRequest("hotel");
//        查询条件构建
            searchAggs(request,requestParams);
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            Aggregations aggregations = response.getAggregations();
            listMap = new HashMap<>();
            listMap.put("city",getList(aggregations, "getCity"));
            listMap.put("brand",getList(aggregations, "getBrand"));
            listMap.put("starName",getList(aggregations, "getStarName"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return listMap;
    }

    private void searchAggs(SearchRequest request,RequestParams requestParams) {
        QueryBuilder queryBuilder = buildBasicQuery(requestParams);
        request.source().query(queryBuilder);
//        查询品牌
        request.source().aggregation(
                AggregationBuilders
                        .terms("getBrand")
                        .field("brand")
                        .size(20));
//        查询城市
        request.source().aggregation(
                AggregationBuilders
                        .terms("getCity")
                        .field("city")
                        .size(20));
//        查询星级
        request.source().aggregation(
                AggregationBuilders
                        .terms("getStarName")
                        .field("starName")
                        .size(20));
    }

    private List<String> getList(Aggregations aggregations, String name) {
        Terms getBrand = aggregations.get(name);
        List<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : getBrand.getBuckets()) {
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }

    private QueryBuilder buildBasicQuery(RequestParams params) {
        String key  = params.getKey();
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
//        query
//        关键字搜索
        if ("".equals(key) || key==null){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else {
            boolQuery.must(QueryBuilders.matchQuery("all",key));
        }
//        过滤条件
        if (params.getMinPrice()!=null && params.getMaxPrice()!=null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        if (!("".equals(params.getCity()) || params.getCity()==null)){
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (!("".equals(params.getBrand()) || params.getBrand()==null)){
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (!("".equals(params.getStarName()) || params.getStarName()==null)){
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }

//        广告置顶
        return QueryBuilders.functionScoreQuery(boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.termQuery("isAD",true),
                        ScoreFunctionBuilders.weightFactorFunction(10)
                )
        }).boostMode(CombineFunction.MULTIPLY);
    }

自动补全

ES实战 | 黑马旅游案例_第6张图片

需求:要求用户输入拼音,可以根据输入的拼音自动补全,例如:输入s自动补全 三,四,蛇

思路:利用分词器,在新增文档的时候,根据拼音分词器把品牌,地址做拆分,用户输入受字母的时候,后台根据首字符检索字段的拼音,要设计好拼音分词器的拆分条件

/**
 * 自动补全
 * @param key 补全前缀
 * @return 补全数组
 */
@Override
public List<String> suggestion(String key) {
    List<String> collect;
    try {
        SearchRequest request = new SearchRequest("hotel");
        request.source().suggest(new SuggestBuilder()
                .addSuggestion("mySuggestion",
                        SuggestBuilders
                                .completionSuggestion("suggestion")
                                .prefix(key)
                                .skipDuplicates(true)
                                .size(10)));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        CompletionSuggestion mySuggestion = response.getSuggest().getSuggestion("mySuggestion");
        List<CompletionSuggestion.Entry.Option> options = mySuggestion.getOptions();
        collect = options.stream().map(item -> item.getText().string()).collect(Collectors.toList());
        System.err.println(collect);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    return collect;
}

构建索引库比较关键,这里把用不到的字段省略了,不然太冗余了

#hotel
PUT /hotel
{
  "mappings":{
    "properties":{
      "address":{
        "type": "keyword",
        "copy_to": "all"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },    
      "city":{
        "type": "keyword",
        "copy_to": "all"
      },
      "name":{
        "type": "text",
        "analyzer": "text_analyzere",
        "search_analyzer": "ik_smart",
        "copy_to": "all"
      },
      # all字段,用户确认搜索的时候用到的。analyzer代表新增文档的时候,按照text_analyzere这个分词器,根据最大粒度拆分,且用到了py拼音分词器。
      # search_analyzer,用户确认搜索的时候用这个分词器去拆分用户的输入信息
      "all":{
        "type": "text",
        "analyzer": "text_analyzere",
        "search_analyzer": "ik_max_word"
      },
      #suggestion专门为自动补全做的字段,类型只能是completion,completion是一个数组,数组内的字段只能是keyword不能拆分,因为如果拆分的话就有很多个值出来,keyword保证不可拆分,那么在同一个文档内这个suggestion是固定的,比如completion由品牌跟地址组成,品牌=如家,地址=北京,那这个分词器拆出来只能是:rujia/rj,beijing/bj,
      "suggestion":{
        "type": "completion",
        "analyzer": "completion_analyzere"
      }
    }
  },
  "settings": {
    "analysis": {
      "analyzer": {
      #text的分词器,新增文档的时候有些text字段可能搜索的时候需要用到。
        "text_analyzere":{
          "tokenizer":"ik_max_word",
          "filter":"py"
        },
      #completion的分词器
        "completion_analyzere":{
          "tokenizer":"keyword",
          "filter":"py"
        }
      },
      "filter": {
          "type": "pinyin", //类型
          "keep_full_pinyin": false,//当启用这个选项,如: 刘德华 >[ liu , de , hua ),默认值:真的
          "keep_joined_full_pinyin": true,//当启用此选项时,例如: 刘德华 >[ liudehua ],默认:false
          "keep_original": true,//当启用此选项时,将保留原始输入,默认值:false
          "limit_first_letter_length": 16,//set first_letter结果的最大长度,默认值:16
          "remove_duplicated_term": true,//当此选项启用时,重复项将被删除以保存索引,例如: de的 > de ,默认:false,注:职位相关查询可能会受到影响
          "none_chinese_pinyin_tokenize" :false //非中国字母分解成单独的拼音词如果拼音,默认值:true,如:liu , de , hua , a , li , ba , ba , 13 , zhuang , han ,注意: keep_none_chinese 和 keep_none_chinese_together 应该启用
      }
    }
  }
}

数据同步

利用mq实现数据同步

生产者

@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
    Long i = 1L;
    hotel.setId(i);
    hotelService.save(hotel);
    String exchangeName = "topic.hotel";
    String key = "hotel.insert";
    rabbitTemplate.convertAndSend(exchangeName,key,hotel.getId());
    System.err.println("发送成功--->新增");
}

消费者

@Bean
public Queue queueInsert(){
    return new Queue("topic.insert.queue",true);
}
@Bean
public TopicExchange topicExchange(){
    return new TopicExchange("topic.hotel");
}
@Bean
public Queue queueDelete(){
    return new Queue("topic.delete.queue",true);
}
@Bean
public Binding bindingTopicBuilder(TopicExchange topicExchange,Queue queueInsert){
    return BindingBuilder.bind(queueInsert).to(topicExchange).with("hotel.insert");
}
@Bean
public Binding bindingTopicBuilder2(TopicExchange topicExchange,Queue queueDelete){
    return BindingBuilder.bind(queueDelete).to(topicExchange).with("hotel.delete");
}
@Component
public class ListenMq {
    @Autowired
    private IHotelService hotelService;
    @RabbitListener(queues = "topic.insert.queue")
    public void listenInsert(Long id){
        hotelService.updateById(id);
    }

    @RabbitListener(queues = "topic.delete.queue")
    public void listenDelete(Long id){
        hotelService.deleteById(id);
    }
}
@Override
public void updateById(Long id) {
    try {
        IndexRequest request = new IndexRequest("hotel").id(id.toString());
        Hotel hotel = this.getById(id);
        HotelDoc hotelDoc = new HotelDoc(hotel);
        String jsonString = JSON.toJSONString(hotelDoc);
        request.source(jsonString, XContentType.JSON);
        client.index(request, RequestOptions.DEFAULT);
        System.err.println("新增一条数据");
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

你可能感兴趣的:(elasticsearch,旅游,java)