当前位置:   article > 正文

HM-SpringCloud微服务系列6.4【黑马旅游案例】

springcloud微服务helm

imageimage


1 酒店搜索和分页

1.1 课件

imageimageimageimage

1.2 需求分析

  1. 在项目的首页,有一个大大的搜索框,还有分页按钮
    点击搜索按钮,可以看到浏览器控制台发出了请求:image
    请求参数如下:image
  2. 由此可以知道,我们这个请求的信息如下:
    • 请求方式:POST
    • 请求路径:/hotel/list
    • 请求参数:JSON对象,包含4个字段:
      • key:搜索关键字
      • page:页码
      • size:每页大小
      • sortBy:排序,目前暂不实现
    • 返回值:分页查询,需要返回分页结果PageResult,包含两个属性:
      • total:总条数
      • List<HotelDoc>:当前页的数据
  3. 因此,我们实现业务的流程如下:
    • 步骤一:定义实体类,接收请求参数的JSON对象
    • 步骤二:编写controller,接收页面的请求
    • 步骤三:编写业务实现,利用RestHighLevelClient实现搜索、分页

1.3 定义实体类

实体类有两个,一个是前端的请求参数实体,一个是服务端应该返回的响应结果实体。

1.3.1 请求参数

  1. 前端请求的json结构如下:
    1. {
    2. "key": "搜索关键字",
    3. "page": 1,
    4. "size": 3,
    5. "sortBy": "default"
    6. }
  2. 因此,我们在com.yppah.hoteldemo.pojo包下定义一个实体类:
    1. package com.yppah.hoteldemo.pojo;
    2. import lombok.Data;
    3. @Data
    4. public class RequestParams {
    5. private String key;
    6. private Integer page;
    7. private Integer size;
    8. private String sortBy;
    9. }
    image

1.3.2 返回值

  1. 分页查询,需要返回分页结果PageResult,包含两个属性:
    • total:总条数
    • List<HotelDoc>:当前页的数据
  2. 因此,我们在com.yppah.hoteldemo.pojo中定义返回结果:
    1. package com.yppah.hoteldemo.pojo;
    2. import lombok.Data;
    3. import java.util.List;
    4. @Data
    5. public class PageResult {
    6. private Long total;
    7. private List<HotelDoc> hotels;
    8. }
    image

1.4 定义Controller

  1. 定义一个HotelController,声明查询接口,满足下列要求:

    • 请求方式:Post
    • 请求路径:/hotel/list
    • 请求参数:对象,类型为RequestParam
    • 返回值:PageResult,包含两个属性
      • Long total:总条数
      • List<HotelDoc> hotels:酒店数据
  2. 因此,我们在com.yppah.hoteldemo.web中定义HotelController:

    点击查看代码
    1. package com.yppah.hoteldemo.web;
    2. import com.yppah.hoteldemo.pojo.PageResult;
    3. import com.yppah.hoteldemo.pojo.RequestParams;
    4. import com.yppah.hoteldemo.service.IHotelService;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.web.bind.annotation.PostMapping;
    7. import org.springframework.web.bind.annotation.RequestBody;
    8. import org.springframework.web.bind.annotation.RequestMapping;
    9. import org.springframework.web.bind.annotation.RestController;
    10. @RestController
    11. @RequestMapping("/hotel")
    12. public class HotelController {
    13. @Autowired
    14. private IHotelService hotelService;
    15. @PostMapping("/list")
    16. public PageResult search(@RequestBody RequestParams params) {
    17. return hotelService.search(params);
    18. }
    19. }

1.5 实现搜索业务

  1. 我们在controller调用了IHotelService,并没有实现该方法,因此下面我们就在IHotelService中定义方法,并且去实现业务逻辑。

  2. com.yppah.hoteldemo.service中的IHotelService接口中定义一个方法:

    点击查看代码
    1. package com.yppah.hoteldemo.service;
    2. import com.yppah.hoteldemo.pojo.Hotel;
    3. import com.baomidou.mybatisplus.extension.service.IService;
    4. import com.yppah.hoteldemo.pojo.PageResult;
    5. import com.yppah.hoteldemo.pojo.RequestParams;
    6. public interface IHotelService extends IService<Hotel> {
    7. /**
    8. * 根据关键字搜索酒店信息
    9. * @param params 请求参数对象,包含用户输入的关键字
    10. * @return 酒店文档列表
    11. */
    12. PageResult search(RequestParams params);
    13. }
  3. com.yppah.hoteldemo.service.impl中的HotelService中实现search方法:

    点击查看代码
    1. package com.yppah.hoteldemo.service.impl;
    2. import com.alibaba.fastjson.JSON;
    3. import com.yppah.hoteldemo.mapper.HotelMapper;
    4. import com.yppah.hoteldemo.pojo.Hotel;
    5. import com.yppah.hoteldemo.pojo.HotelDoc;
    6. import com.yppah.hoteldemo.pojo.PageResult;
    7. import com.yppah.hoteldemo.pojo.RequestParams;
    8. import com.yppah.hoteldemo.service.IHotelService;
    9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    10. import org.elasticsearch.action.search.SearchRequest;
    11. import org.elasticsearch.action.search.SearchResponse;
    12. import org.elasticsearch.client.RequestOptions;
    13. import org.elasticsearch.client.RestHighLevelClient;
    14. import org.elasticsearch.index.query.QueryBuilders;
    15. import org.elasticsearch.search.SearchHit;
    16. import org.elasticsearch.search.SearchHits;
    17. import org.springframework.beans.factory.annotation.Autowired;
    18. import org.springframework.stereotype.Service;
    19. import java.io.IOException;
    20. import java.util.ArrayList;
    21. import java.util.List;
    22. @Service
    23. public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    24. @Autowired
    25. private RestHighLevelClient client; // 使用之前需要在项目启动类HoteldemoApplication中用@Bean将其注入到spring中
    26. @Override
    27. public PageResult search(RequestParams params) {
    28. try {
    29. // 1. 准备Request
    30. SearchRequest request = new SearchRequest("hotel");
    31. // 2. 准备DSL
    32. // 2.1 关键字搜索query
    33. String key = params.getKey();
    34. if (key==null || "".equals(key)) {
    35. request.source().query(QueryBuilders.matchAllQuery());
    36. } else {
    37. request.source().query(QueryBuilders.matchQuery("all", key));
    38. }
    39. // 2.2 查询结果分页处理
    40. int page = params.getPage(); // 自动拆箱
    41. int size = params.getSize();
    42. request.source().from((page-1)*size).size(size);
    43. // 3. 发送Request,得到Response
    44. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    45. // 4. 解析Response
    46. return handleResponse(response);
    47. } catch (IOException e) {
    48. throw new RuntimeException(e);
    49. }
    50. }
    51. private PageResult handleResponse(SearchResponse response) { // 需要在PageResult中添加构造函数两个
    52. //4 解析响应
    53. SearchHits searchHits = response.getHits();
    54. //4.1 获取总条数
    55. long total = searchHits.getTotalHits().value;
    56. //4.2 获取文档数组
    57. SearchHit[] hits = searchHits.getHits();
    58. //4.3 遍历数组
    59. List<HotelDoc> hotels = new ArrayList<>(); //用于存放PageResult所要求格式的数据
    60. for (SearchHit hit : hits) {
    61. //4.3.1 获取文档source
    62. String json = hit.getSourceAsString();
    63. //4.3.2 反序列化解析json
    64. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
    65. hotels.add(hotelDoc);
    66. }
    67. //4.4 封装返回
    68. return new PageResult(total, hotels);
    69. }
    70. }
  4. 实现搜索业务,肯定离不开RestHighLevelClient,我们需要把它注册到Spring中作为一个Bean。在com.yppah.hoteldemo中的HotelDemoApplication中声明这个Bean:

    1. @Bean
    2. public RestHighLevelClient client() {
    3. return new RestHighLevelClient(RestClient.builder(
    4. HttpHost.create("http://10.193.193.141:9200")
    5. ));
    6. }

    imageimage

1.6 测试

imageimageimage

2 酒店结果过滤

2.1 课件

imageimageimage

2.2 需求分析

  1. 在页面搜索框下面,会有一些过滤项:image
  2. 传递的参数如图:image
  3. 包含的过滤条件有:
    • brand:品牌值
    • city:城市
    • minPrice~maxPrice:价格范围
    • starName:星级
  4. 我们需要做两件事情:
    • 修改请求参数的对象RequestParams,接收上述参数
    • 修改业务逻辑,在搜索条件之外,添加一些过滤条件

2.3 修改实体类RequestParams

  1. @Data
  2. public class RequestParams {
  3. private String key;
  4. private Integer page;
  5. private Integer size;
  6. private String sortBy;
  7. // 下面是新增的过滤条件参数
  8. private String city;
  9. private String brand;
  10. private String starName;
  11. private Integer minPrice;
  12. private Integer maxPrice;
  13. }

image

2.4 修改搜索业务

  1. 在HotelService的search方法中,只有一个地方需要修改:requet.source().query( ... )其中的查询条件。

  2. 在之前的业务中,只有match查询,根据关键字搜索,现在要添加条件过滤,包括:

    • 品牌过滤:是keyword类型,用term查询
    • 星级过滤:是keyword类型,用term查询
    • 价格过滤:是数值类型,用range查询
    • 城市过滤:是keyword类型,用term查询
  3. 多个查询条件组合,肯定是boolean查询来组合:

    • 关键字搜索放到must中,参与算分
    • 其它过滤条件放到filter中,不参与算分
  4. 因为条件构建的逻辑比较复杂,这里先封装为一个函数:imageimage

    点击查看代码
    1. private void buildBasicQuery(RequestParams params, SearchRequest request) {
    2. // 1. 构建booleanQuery
    3. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    4. // 2. 关键字搜索
    5. String key = params.getKey();
    6. if (key==null || "".equals(key)) {
    7. request.source().query(QueryBuilders.matchAllQuery());
    8. } else {
    9. request.source().query(QueryBuilders.matchQuery("all", key));
    10. }
    11. // 3. 条件过滤
    12. // 3.1 城市
    13. if (params.getCity()!=null && !params.getCity().equals("")) {
    14. boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    15. }
    16. // 3.2 品牌
    17. if (params.getBrand()!=null && !params.getBrand().equals("")) {
    18. boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    19. }
    20. // 3.3 星级
    21. if (params.getStarName()!=null && !params.getStarName().equals("")) {
    22. boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    23. }
    24. // 3.4 价格
    25. if (params.getMinPrice()!=null && params.getMaxPrice()!=null) {
    26. boolQuery.filter(QueryBuilders.rangeQuery("price")
    27. .gte(params.getMinPrice())
    28. .lte(params.getMaxPrice())
    29. );
    30. }
    31. // 4. 放入resource
    32. request.source().query(boolQuery);
    33. }

2.5 测试

imageimageimageimageimage

3 我周边的酒店

3.1 课件

imageimageimage

3.2 需求分析

  1. 在酒店列表页的右侧,有一个小地图,点击地图的定位按钮,地图会找到你所在的位置:image
  2. 并且,在前端会发起查询请求,将你的坐标发送到服务端:image
  3. 我们要做的事情就是基于这个location坐标,然后按照距离对周围酒店排序。实现思路如下:
    • 修改RequestParams参数,接收location字段
    • 修改search方法业务逻辑,如果location有值,添加根据geo_distance排序的功能

3.3 修改实体类

image

3.4 距离排序API

image

3.5 添加距离排序

image

点击查看代码
  1. @Override
  2. public PageResult search(RequestParams params) {
  3. try {
  4. // 1. 准备Request
  5. SearchRequest request = new SearchRequest("hotel");
  6. // 2. 准备DSL
  7. // 2.1 封装自定义query函数
  8. buildBasicQuery(params, request);
  9. // 2.2 查询结果分页处理
  10. int page = params.getPage(); // 自动拆箱
  11. int size = params.getSize();
  12. request.source().from((page-1)*size).size(size);
  13. // 2.3 查询结果排序处理
  14. String location = params.getLocation();
  15. if (location!=null && !location.equals("")) {
  16. request.source().sort(SortBuilders.
  17. geoDistanceSort("location", new GeoPoint(location))
  18. .order(SortOrder.ASC)
  19. .unit(DistanceUnit.KILOMETERS)
  20. );
  21. }
  22. // 3. 发送Request,得到Response
  23. SearchResponse response = client.search(request, RequestOptions.DEFAULT);
  24. // 4. 解析Response
  25. return handleResponse(response);
  26. } catch (IOException e) {
  27. throw new RuntimeException(e);
  28. }
  29. }

image

3.6 距离排序显示

image
发现确实可以实现对我附近酒店的排序,不过并没有看到酒店到底距离我多远,这该怎么办?
排序完成后,页面还要获取我附近每个酒店的具体距离值,这个值在响应结果中是独立的:image
因此,我们在结果解析阶段,除了解析source部分以外,还要得到sort部分,也就是排序的距离,然后放到响应结果中。
我们要做两件事:

  • 修改HotelDoc,添加排序距离字段,用于页面显示
  • 修改HotelService类中的handleResponse方法,添加对sort值的获取

3.6.1 修改HotelDoc类,添加距离字段

image

点击查看代码
  1. package com.yppah.hoteldemo.pojo;
  2. import lombok.Data;
  3. import lombok.NoArgsConstructor;
  4. @Data
  5. @NoArgsConstructor
  6. public class HotelDoc {
  7. private Long id;
  8. private String name;
  9. private String address;
  10. private Integer price;
  11. private Integer score;
  12. private String brand;
  13. private String city;
  14. private String starName;
  15. private String business;
  16. private String location;
  17. private String pic;
  18. private Object distance;
  19. public HotelDoc(Hotel hotel) {
  20. this.id = hotel.getId();
  21. this.name = hotel.getName();
  22. this.address = hotel.getAddress();
  23. this.price = hotel.getPrice();
  24. this.score = hotel.getScore();
  25. this.brand = hotel.getBrand();
  26. this.city = hotel.getCity();
  27. this.starName = hotel.getStarName();
  28. this.business = hotel.getBusiness();
  29. this.location = hotel.getLatitude() + ", " + hotel.getLongitude();
  30. this.pic = hotel.getPic();
  31. }
  32. /*public HotelDoc() {
  33. }*/
  34. }

3.6.2 修改HotelService中的handleResponse方法

image

点击查看代码
  1. private PageResult handleResponse(SearchResponse response) {
  2. //4 解析响应
  3. SearchHits searchHits = response.getHits();
  4. //4.1 获取总条数
  5. long total = searchHits.getTotalHits().value;
  6. //4.2 获取文档数组
  7. SearchHit[] hits = searchHits.getHits();
  8. //4.3 遍历数组
  9. List<HotelDoc> hotels = new ArrayList<>(); //用于存放PageResult所要求格式的数据
  10. for (SearchHit hit : hits) {
  11. //4.3.1 获取文档source
  12. String json = hit.getSourceAsString();
  13. //4.3.2 反序列化解析json
  14. HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
  15. // 我附近的酒店功能-获取排序值
  16. Object[] sortValues = hit.getSortValues();
  17. if (sortValues.length > 0) {
  18. Object sortValue = sortValues[0];
  19. hotelDoc.setDistance(sortValue);
  20. }
  21. hotels.add(hotelDoc);
  22. }
  23. //4.4 封装返回
  24. return new PageResult(total, hotels);
  25. }

3.6.2 重启服务测试

image

4 酒店竞价排名

4.1 课件

imageimage

4.2 需求分析

  1. 要让指定酒店在搜索结果中排名置顶,效果如图:image
  2. 页面会给指定的酒店添加广告标记。那怎样才能让指定的酒店排名置顶呢?
  3. 我们之前学习过的function_score查询可以影响算分,算分高了,自然排名也就高了。而function_score包含3个要素:
    • 过滤条件:哪些文档要加分
    • 算分函数:如何计算function score
    • 加权方式:function score 与 query score如何运算
  4. 这里的需求是:让指定酒店排名靠前。因此我们需要给这些酒店添加一个标记,这样在过滤条件中就可以根据这个标记来判断,是否要提高算分
  5. 比如,我们给酒店添加一个字段:isAD,Boolean类型:
    • true:是广告
    • false:不是广告
  6. 这样function_score包含3个要素就很好确定了:
    • 过滤条件:判断isAD 是否为true
    • 算分函数:我们可以用最简单暴力的weight,固定加权值
    • 加权方式:可以用默认的相乘,大大提高算分
  7. 因此,业务的实现步骤包括:
    1. 给HotelDoc类添加isAD字段,Boolean类型
    2. 挑选几个你喜欢的酒店,给它的文档数据添加isAD字段,值为true
    3. 修改search方法,添加function score功能,给isAD值为true的酒店增加权重

4.3 修改HotelDoc实体类

image

4.4 添加广告标记

挑几个酒店,利用ES控制台手动添加isAD字段,设置为true:

  1. # 添加isAD字段
  2. POST /hotel/_update/609372
  3. {
  4. "doc": {
  5. "isAD": true
  6. }
  7. }
  8. POST /hotel/_update/5873072
  9. {
  10. "doc": {
  11. "isAD": true
  12. }
  13. }
  14. POST /hotel/_update/2056298828
  15. {
  16. "doc": {
  17. "isAD": true
  18. }
  19. }
  20. POST /hotel/_update/2062643512
  21. {
  22. "doc": {
  23. "isAD": true
  24. }
  25. }

image

4.5 添加算分函数查询

  1. 接下来我们就要修改查询条件了。之前是用的boolean 查询,现在要改成function_socre查询。

  2. function_score查询结构如下:image

  3. 对应的JavaAPI如下:image

  4. 我们可以将之前写的boolean查询作为原始查询条件放到query中,接下来就是添加过滤条件算分函数加权模式了。所以原来的代码依然可以沿用。

  5. 修改com.yppah.hoteldemo.service.impl包下的HotelService类中的buildBasicQuery方法,添加算分函数查询:image

    点击查看代码
    1. private void buildBasicQuery(RequestParams params, SearchRequest request) {
    2. // 1. 构建booleanQuery
    3. BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    4. // 2. 关键字搜索
    5. String key = params.getKey();
    6. if (key==null || "".equals(key)) {
    7. request.source().query(QueryBuilders.matchAllQuery());
    8. } else {
    9. request.source().query(QueryBuilders.matchQuery("all", key));
    10. }
    11. // 3. 条件过滤
    12. // 3.1 城市
    13. if (params.getCity()!=null && !params.getCity().equals("")) {
    14. boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    15. }
    16. // 3.2 品牌
    17. if (params.getBrand()!=null && !params.getBrand().equals("")) {
    18. boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    19. }
    20. // 3.3 星级
    21. if (params.getStarName()!=null && !params.getStarName().equals("")) {
    22. boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    23. }
    24. // 3.4 价格
    25. if (params.getMinPrice()!=null && params.getMaxPrice()!=null) {
    26. boolQuery.filter(QueryBuilders.rangeQuery("price")
    27. .gte(params.getMinPrice())
    28. .lte(params.getMaxPrice())
    29. );
    30. }
    31. // 酒店竞价排名功能-算分控制
    32. FunctionScoreQueryBuilder functionScoreQuery =
    33. QueryBuilders.functionScoreQuery(
    34. // 原始查询,相关性算分的查询
    35. boolQuery,
    36. // function score的数组
    37. new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
    38. // 其中的一个function score 元素
    39. new FunctionScoreQueryBuilder.FilterFunctionBuilder(
    40. // 过滤条件
    41. QueryBuilders.termQuery("isAD", true),
    42. // 算分函数
    43. ScoreFunctionBuilders.weightFactorFunction(10) //*10
    44. )
    45. });
    46. // 4. 放入resource
    47. // request.source().query(boolQuery);
    48. request.source().query(functionScoreQuery);
    49. }

4.6 测试

注意:要把浏览器中的广告拦截插件暂时关掉image
有点问题广告标识未显示QAQ

4.7 扩展思考

image

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/718096
推荐阅读
相关标签
  

闽ICP备14008679号