ElasticSearch - 「ダーク ホース ツーリズム」の事例に基づいて、検索ボックス、ページング、条件フィルタリング、近くのホテル、広告のトップ機能を実装します。

目次

1. ダークホースツーリズムの事例

1.1. 検索ボックスとページング機能の実装

1.1.1. 需要分析

a) 最初の検索ボックスの要件

b) ページング要件

1.1.2. エンティティクラスの定義

1.1.2. コントローラーの定義

1.1.3. RestHighLevelClient の挿入

1.1.4. IHotelService インターフェースの検索メソッドの実装

1.1.5. 機能表示

1.2. ブランド、都市、星評価、価格などのフィルタリング機能を追加します。

1.2.1. 需要分析

1.2.2. RequestParam にパラメータを追加する

1.2.3. 検索方法の変更

1.3. 近隣のホテ​​ル

1.3.1. 需要分析

1.3.2. RequestParam にパラメータを追加する

1.3.3. 検索方法の変更

1.3.4. 解析方法の変更

1.4. 上位広告(指定ホテルを検索順位上位に表示)

1.4.1. 需要分析

1.4.2. HotelDoc エンティティクラスに属性を追加する

1.4.3. クエリを変更し、並べ替えに関数スコアを使用します。


1. ダークホースツーリズムの事例


1.1. 検索ボックスとページング機能の実装

1.1.1. 需要分析

a) 最初の検索ボックスの要件

検索ボックスに「Home Inn」と入力し、[検索] をクリックすると、次のリクエストが次のパラメータとともに送信されたことがわかります。

  • キー: 検索ボックスの内容
  • ページ: ページ番号。
  • size: 1 ページに表示されるデータの数。
  • sortBy:ソート規則(デフォルトはクエリキーワードの一致度に応じて降順にソート)

リクエスト ヘッダーを調整すると、以下に示すように、リクエストの形式が JSON であることがわかります。

次に、ユーザーが検索ボックスをクリックすると、バックエンドがそれを処理し、検索されたデータの総数とホテル データ リスト (戻り形式は JSON) を返すことが要件となります。

Ps: 応答データと形式は事前に合意されています。

b) ページング要件
ページネーションをクリックすると、同じリクエストがトリガーされ、応答形式も同じになります。

1.1.2. エンティティクラスの定義

フロントエンドリクエストを受信し、レスポンスを返すためのエンティティクラスを定義します。

リクエストエンティティクラスは次のとおりです。

@Data
public class RequestParams {

    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;

}

応答エンティティ クラスは次のとおりです。

@Data
public class PageResult {

    private Long total;
    private List<HotelDoc> hotels;

    public PageResult() {
    }

    public PageResult(Long total, List<HotelDoc> hotels) {
        this.total = total;
        this.hotels = hotels;
    }
    
}

1.1.2. コントローラーの定義

コントローラーは、フロントエンドによって渡された JSON パラメーターを受信し、インターフェイス IHotelService の検索メソッドを呼び出す責任があります。

@RestController
@RequestMapping("/hotel")
public class HotelController {

    @Autowired
    private IHotelService hotelService;

    @RequestMapping("/list")
    public PageResult search(@RequestBody RequestParams params) {
        return hotelService.search(params);
    }

}

IHotelService インターフェイスは次のとおりです。 


public interface IHotelService extends IService<Hotel> {
    PageResult search(RequestParams params);
}

1.1.3. RestHighLevelClient の挿入

ElasticSearch の高度なクライアントを Spring コンテナに挿入して、その後の es による検索機能の実装を容易にします。

@Component
public class ESComponent {

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://140.143.166.138:9200")
        ));
        return client;
    }

}

1.1.4. IHotelService インターフェースの検索メソッドの実装

ここでの実装のアイデアは、前の章で説明した JavaRestClient ドキュメントの操作手順と同じです~

  1. 検索リクエストを作成します。
  2. リクエストパラメータを準備します。具体的なパラメータは次のとおりです。
    1. QueryBuilders を使用して一致クエリを構築します (ユーザーが検索を入力せずに直接クリックした場合は、ここで match_all を構築してすべてをクエリします)。
    2. ページング データを追加します: from (オフセット オフセット) とサイズ (表示されるデータの数)。
  3. リクエストを送信し、レスポンスを受け取ります。
  4. 応答を解析して、クエリの総数とホテル情報のリストを取得します。

コードは以下のように表示されます。

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public PageResult search(RequestParams params) {
        try {
            //1.创建请求
            SearchRequest request = new SearchRequest("hotel");
            //2.准备参数
            // 1) 查询
            String searchContent = params.getKey();
            if(!StringUtils.hasLength(searchContent)) {
                request.source().query(QueryBuilders.matchAllQuery());
            } else {
                request.source().query(QueryBuilders.matchQuery("all", searchContent));
            }
            // 2) 分页
            Integer page = params.getPage();
            Integer size = params.getSize();
            if(page == null || size == null) {
                throw new IOException("分页数据不能为空!");
            }
            request.source().from((page - 1) * size).size(size);
            //3.发送请求,接收响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //4.解析响应
            return handlerResponse(response);
        } catch (IOException e) {
            System.out.println("[HotelService] 搜索失败!");
            e.printStackTrace();
            return null;
        }
    }

    private PageResult handlerResponse(SearchResponse response) throws JsonProcessingException {
        //1.解析结果
        SearchHits hits = response.getHits();
        long total = hits.getTotalHits().value;
        SearchHit[] hits1 = hits.getHits();
        List<HotelDoc> hotelDocList = new ArrayList<>();
        for(SearchHit searchHit : hits1) {
            //获取source
            String json = searchHit.getSourceAsString();
            hotelDocList.add(objectMapper.readValue(json, HotelDoc.class));
        }
        return new PageResult(total, hotelDocList);
    }

}

1.1.5. 機能表示

検索ボックスに「ホーム イン」というキーワードを入力し、「検索」をクリックすると、「ホーム イン」キーワードに関連するホテル データが下に表示されます。

1.2. ブランド、都市、星評価、価格などのフィルタリング機能を追加します。

1.2.1. 需要分析

以下に示すフィルタ条件を検索に追加した後、「検索」をクリックすると、フィルタリングされたデータが表示されます。

したがって、リクエストに 5 つのパラメータを追加する必要があります。

  1. 都市: 都市
  2. スター名前: スター
  3. ブランド:ブランド。
  4. minPrice: 下限価格。
  5. maxPrice: 価格制限。

バックエンドは、リクエストに基づいて応答データ、つまり検索されたデータの総数とホテル データ リストを計算します。

1.2.2. RequestParam にパラメータを追加する

@Data
public class RequestParams {

    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String starName;
    private String city;
    private Integer minPrice;
    private Integer maxPrice;

}

1.2.3. 検索方法の変更

要件に応じて、ここでは複合クエリ (BoolQuery) を使用できます。

フィルタリングする必要があるフィールドは、city、brand、starName、price です。最初の 3 つのフィールドには、分離できない単語という 1 つの特徴があり、すべてキーワード タイプであるため、term を使用して正確にクエリできます。また、価格は次を使用してクエリできます。範囲。

Ps: コードの読みやすさを向上させるために、クエリ フィルタリング ロジックをメソッドにカプセル化しました。

    private BoolQueryBuilder getHandlerBoolQueryBuilder(RequestParams params) {
        //使用 boolean 查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 1) 查询
        String searchContent = params.getKey();
        if(!StringUtils.hasLength(searchContent)) {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchContent));
        }
        // 2) 城市过滤
        String city = params.getCity();
        if(StringUtils.hasLength(city)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", city));
        }
        // 3) 品牌过滤
        String brand = params.getBrand();
        if(StringUtils.hasLength(brand)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", brand));
        }
        // 4) 星级过滤
        String star = params.getStarName();
        if(StringUtils.hasLength(star)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", star));
        }
        // 5) 价格范围过滤
        if(params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders
                    .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        return boolQueryBuilder;
    }

    @Override
    public PageResult search(RequestParams params) {
        try {
            //1.创建请求
            SearchRequest request = new SearchRequest("hotel");
            //2.准备参数
            BoolQueryBuilder boolQuery = getHandlerBoolQueryBuilder(params);
            request.source().query(boolQuery);
            //3.分页
            Integer page = params.getPage();
            Integer size = params.getSize();
            if(page == null || size == null) {
                throw new IOException("分页数据不能为空!");
            }
            request.source().from((page - 1) * size).size(size);
            //4.发送请求,接收响应
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //5.解析响应
            return handlerResponse(response);
        } catch (IOException e) {
            System.out.println("[HotelService] 搜索失败!");
            e.printStackTrace();
            return null;
        }
    }

1.3. 近隣のホテ​​ル

1.3.1. 需要分析

ユーザーがミニマップ上の位置ポイントをクリックすると、自動的に位置が特定され、近くのホテルのリストが表示され、距離が表示されます。

測位ボタンをクリックすると、フロントエンドは次のように現在地の緯度と経度の情報を返します。

次に、緯度と経度のパラメーターをリクエストに追加する必要があります。

1.3.2. RequestParam にパラメータを追加する

ここでは、緯度と経度の情報を受け取るための文字列パラメータを追加するだけです。

@Data
public class RequestParams {

    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String brand;
    private String starName;
    private String city;
    private Integer minPrice;
    private Integer maxPrice;
    private String location;

}

1.3.3. 検索方法の変更

距離による並べ替えのロジックを検索メソッドに追加する必要があります。

            //4.根据距离排序
            request.source().sort(SortBuilders.geoDistanceSort("location",
                    new GeoPoint(params.getLocation()))
                    .order(SortOrder.ASC)
                    .unit(DistanceUnit.KILOMETERS));

よくわからない場合は、DSL ステートメントを比較してください。

1.3.4. 解析方法の変更

需要分析からわかるように、ホテル情報には「現在地からxx km」という情報も表示する必要があるため、ここでも応答内のソートされた「目的地と現在地の間の距離」属性を解析する必要があります。 . 次のように:

したがって、距離を表すために HotelDoc に Object プロパティを追加する必要もあります。

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}

解析コードは次のとおりです。 

    private PageResult handlerResponse(SearchResponse response) throws JsonProcessingException {
        //1.解析结果
        SearchHits hits = response.getHits();
        long total = hits.getTotalHits().value;
        SearchHit[] hits1 = hits.getHits();
        List<HotelDoc> hotelDocList = new ArrayList<>();
        for(SearchHit searchHit : hits1) {
            //获取source
            String json = searchHit.getSourceAsString();
            HotelDoc hotelDoc = objectMapper.readValue(json, HotelDoc.class);
            Object[] sortValues = searchHit.getSortValues();
            //通过 getSortValues 得到的是一个 Object 类型数组,因为有可能是根据多个条件排序(价格、评价、距离...)
            //获取的下标,就要看你先给谁排序,谁就是 0 下标(以此类推)
            if(sortValues != null && sortValues.length > 0) {
                hotelDoc.setDistance(sortValues[0]);
            }
            hotelDocList.add(hotelDoc);
        }
        return new PageResult(total, hotelDocList);
    }

追伸: getSortValues で取得するものは、複数の条件 (価格、評価、距離など) に従って並べ替えられるため、オブジェクト型の配列です。取得される添え字は、最初に並べ替えた人、および添え字を持つ人によって異なります。 0 (など)。

1.3.5. 実証効果

アンカーポイントをクリックすると、あなたからの距離に応じて昇順に並べ替えられ、ページングを通じて該当するホテルデータが表示されます。

追伸: 下の写真の距離は遠すぎます。これは、現在の場所に近いホテルをデータベース (すべての州にわたって) に追加していないためです。 

1.4. 上位広告(指定ホテルを検索順位上位に表示)

1.4.1. 需要分析

ユーザーが検索をクリックすると、広告データ(特別にマークされたホテル情報)が優先的に最上位に表示されます。

したがって、現在のホテル情報が広告として存在するかどうかをマークするフィールドを HotelDoc に追加する必要があります。その後、バックエンドは広告情報により多くの重みを割り当て、広告データを最上位に維持します。

1.4.2. HotelDoc エンティティクラスに属性を追加する

ブール型フィールドを HotelDoc に追加して、現在のホテル情報が広告として存在するかどうかをマークします。

@Data
@NoArgsConstructor
public class HotelDoc {
    private Long id;
    private String name;
    private String address;
    private Integer price;
    private Integer score;
    private String brand;
    private String city;
    private String starName;
    private String business;
    private String location;
    private String pic;
    private Object distance;
    private Boolean isAD;

    public HotelDoc(Hotel hotel) {
        this.id = hotel.getId();
        this.name = hotel.getName();
        this.address = hotel.getAddress();
        this.price = hotel.getPrice();
        this.score = hotel.getScore();
        this.brand = hotel.getBrand();
        this.city = hotel.getCity();
        this.starName = hotel.getStarName();
        this.business = hotel.getBusiness();
        this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
        this.pic = hotel.getPic();
    }
}

1.4.3. クエリを変更し、並べ替えに関数スコアを使用します。

関数スコアは、クエリのフィルタリングに使用されます。term を使用して、isAD の値が true であるかどうかを正確にクエリできます。そうである場合は、weight によって重みを変更します (ここでは重み付けモードが指定されていないため、デフォルトは乗算です)。

    private void HandlerBoolQueryBuilder(SearchRequest request, RequestParams params) {
        //1.使用 boolean 查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 1) 查询
        String searchContent = params.getKey();
        if(!StringUtils.hasLength(searchContent)) {
            boolQueryBuilder.must(QueryBuilders.matchAllQuery());
        } else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("all", searchContent));
        }
        // 2) 城市过滤
        String city = params.getCity();
        if(StringUtils.hasLength(city)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("city", city));
        }
        // 3) 品牌过滤
        String brand = params.getBrand();
        if(StringUtils.hasLength(brand)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", brand));
        }
        // 4) 星级过滤
        String star = params.getStarName();
        if(StringUtils.hasLength(star)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("starName", star));
        }
        // 5) 价格范围过滤
        if(params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQueryBuilder.filter(QueryBuilders
                    .rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }

        //2.算分控制
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                //原始查询,相关性算分查询
                boolQueryBuilder,
                //function score 的数组
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        //其中的一个 function score 元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                //过滤条件
                                QueryBuilders.termQuery("isAD", true),
                                //算分函数
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );
        request.source().query(functionScoreQuery);
    }

ここでは、DSL ステートメントに対応していることがわかります。

おすすめ

転載: blog.csdn.net/CYK_byte/article/details/133314299