学习笔记:SpringCloud 微服务技术栈_实用篇②_黑马旅游案例

2023-10-27


  • 若文章内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系博主删除。

前言



  • 写这篇博客旨在制作笔记,巩固知识。同时方便个人在线阅览,回顾知识。
  • 博客的内容主要来自视频内容和资料中提供的学习笔记。


系列目录


SpringCloud 微服务技术栈_实用篇①_基础知识

SpringCloud 微服务技术栈_实用篇②_黑马旅游案例


SpringCloud 微服务技术栈_高级篇①_微服务保护

SpringCloud 微服务技术栈_高级篇②_分布式事务

SpringCloud 微服务技术栈_高级篇③_分布式缓存

SpringCloud 微服务技术栈_高级篇④_多级缓存

SpringCloud 微服务技术栈_高级篇⑤_可靠消息服务


0.微服务技术栈导学



在这里插入图片描述


在这里插入图片描述


# SpringCloudDay06


1.项目简述


通过该案例来实战演练下之前所学知识。

实现四部分功能:

  • 酒店搜索和分页
  • 酒店结果过滤
  • 周边的酒店
  • 酒店竞价排名

启动资料中提供的 hotel-demo 项目,其默认端口是 8089,访问 http://localhost:8090,就能看到项目页面了。

在这里插入图片描述


  • 课前资料链接https://pan.baidu.com/s/169SFtYEvel44hRJhmFTRTQ提取码:1234
  • 1.微服务开发框架 SpringCloud + RabbitMQ + Docker + Redis + 搜索 + 分布式史上最全面的微服务全技术栈课程>
    • 实用篇>学习资料>day06-Elasticsearch02>代码

在这里插入图片描述


2.酒店搜索和分页


需求:实现黑马旅游的酒店搜索功能,完成关键字搜索和分页


2.1.需求分析


在项目的首页,有一个大大的搜索框,还有分页按钮

在这里插入图片描述

点击搜索按钮,可以看到浏览器控制台发出了请求

在这里插入图片描述

请求参数如下

在这里插入图片描述

由此可以知道,我们这个请求的信息如下

  • 请求方式:POST
  • 请求路径:/hotel/list
  • 请求参数:JSON 对象,包含 4 个字段:
    • key:搜索关键字
    • page:页码
    • size:每页大小
    • sortBy:排序,目前暂不实现
  • 返回值:分页查询,需要返回分页结果 PageResult,包含两个属性:
    • total:总条数
    • List<HotelDoc>:当前页的数据

因此,我们实现业务的流程如下

  • 步骤一:定义实体类,接收前端请求:请求参数的 JSON 对象
  • 步骤二:编写 controller,接收页面的请求,调用 IHotelServicesearch 方法
  • 步骤三:编写业务实现,定义 IHotelService 中的 search 方法,利用 RestHighLevelClient 中的 match 查询实现搜索、分页

2.2.定义实体类


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


2.2.1.请求参数


前端请求的 json 结构如下

{
    "key": "搜索关键字",
    "page": 1,
    "size": 3,
    "sortBy": "default"
}

因此,我们在 cn.itcast.hotel.pojo 包下定义一个实体类

src/main/java/cn/itcast/hotel/pojo/RequestParams.java

package cn.itcast.hotel.pojo;

import lombok.Data;

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
}

2.2.2.返回值


分页查询,需要返回分页结果 PageResult,包含两个属性

  • total:总条数
  • List<HotelDoc>:当前页的数据

因此,我们在 cn.itcast.hotel.pojo 中定义返回结果

src/main/java/cn/itcast/hotel/pojo/PageResult.java

package cn.itcast.hotel.pojo;

import lombok.Data;

import java.util.List;

@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;
    }
}

2.3.定义 controller


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

  • 请求方式:Post
  • 请求路径:/hotel/list
  • 请求参数:对象,类型为 RequestParam
  • 返回值:PageResult,包含两个属性
    • Long total:总条数
    • List<HotelDoc> hotels:酒店数据

因此,我们在 cn.itcast.hotel.web 中定义 HotelController

src/main/java/cn/itcast/hotel/web/HotelController.java

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

    @Autowired
    private IHotelService hotelService;
	// 搜索酒店数据
    @PostMapping("/list")
    public PageResult search(@RequestBody RequestParams params){
        return hotelService.search(params);
    }
}

2.4.实现搜索业务


我们在 controller 调用了 IHotelService,并没有实现该方法。

因此下面我们就在 IHotelService 中定义方法,并且去实现业务逻辑。


2.4.1.在接口中定义方法


cn.itcast.hotel.service 中的 IHotelService 接口中定义一个方法

src/main/java/cn/itcast/hotel/service/IHotelService.java

/**
 * 根据关键字搜索酒店信息
 * 
 * @param params 请求参数对象,包含用户输入的关键字 
 * @return 酒店文档列表
 */
PageResult search(RequestParams params);

2.4.2.注入 es 客户端组件


实现搜索业务,肯定离不开 RestHighLevelClient,我们需要把它注册到 Spring 中作为一个 Bean

cn.itcast.hotel中的HotelDemoApplication 中声明这个 Bean

src/main/java/cn/itcast/hotel/HotelDemoApplication.java

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

2.4.3.实现业务逻辑


cn.itcast.hotel.service.impl 中的 HotelService 中实现 search 方法

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

@Autowired
private RestHighLevelClient client;
@Override
public PageResult search(RequestParams params) {
    try {
        // 1.准备 Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备 DSL
        // 2.1.query
        String key = params.getKey();
        if (key == null || "".equals(key)) {
            request.source().query(QueryBuilders.matchAllQuery());
        } else {
            request.source().query(QueryBuilders.matchQuery("all", key));
        }

        // 2.2.分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page - 1) * size).size(size);

        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        
        // 4.解析响应
        return handleResponse(response);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
// 结果解析
private PageResult handleResponse(SearchResponse response) {
    // 4.解析响应
    SearchHits searchHits = response.getHits();
    // 4.1.获取总条数
    long total = searchHits.getTotalHits().value;
    // 4.2.文档数组
    SearchHit[] hits = searchHits.getHits();
    // 4.3.遍历
    List<HotelDoc> hotels = new ArrayList<>();
    for (SearchHit hit : hits) {
        // 获取文档 source
        String json = hit.getSourceAsString();
        // 反序列化
        HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);
		// 放入集合
        hotels.add(hotelDoc);
    }
    // 4.4.封装返回
    return new PageResult(total, hotels);
}

3.酒店结果过滤


需求:添加品牌、城市、星级、价格等过滤功能


3.1.需求分析


在页面搜索框下面,会有一些过滤项

在这里插入图片描述

传递的参数如图

在这里插入图片描述

包含的过滤条件有

  • brand:品牌值
  • city:城市
  • minPrice~maxPrice:价格范围
  • starName:星级

我们需要做两件事情

  • 修改请求参数的对象 RequestParams,接收上述参数
  • 修改业务逻辑,在搜索条件之外,添加一些过滤条件

3.2.修改实体类


修改在 cn.itcast.hotel.pojo 包下的实体类 RequestParams

src/main/java/cn/itcast/hotel/pojo/RequestParams.java

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    /* 下面是新增的过滤条件参数 */
    private String city;
    private String brand;
    private String starName;
    private Integer minPrice;
    private Integer maxPrice;
}

3.3.修改搜索业务


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

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

  • 品牌过滤:是 keyword 类型,用 term 查询
  • 星级过滤:是 keyword 类型,用 term 查询
  • 价格过滤:是数值类型,用 range 查询
  • 城市过滤:是 keyword 类型,用 term 查询

多个查询条件组合,肯定是 boolean 查询来组合:

  • 关键字搜索放到 must 中,参与算分
  • 其它过滤条件放到 filter 中,不参与算分

因为条件构建的逻辑比较复杂,这里先封装为一个函数

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

buildBasicQuery(params, request);

在这里插入图片描述


补充:封装方法快捷键:Ctrl + Alt + M

在这里插入图片描述


方法 buildBasicQuery 的代码如下

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

private void buildBasicQuery(RequestParams params, SearchRequest request) {
    // 1.构建 BooleanQuery
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 2.关键字搜索
    String key = params.getKey();
    if (key == null || "".equals(key)) {
        boolQuery.must(QueryBuilders.matchAllQuery());
    } else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }
    // 3.城市条件
    if (params.getCity() != null && !params.getCity().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // 4.品牌条件
    if (params.getBrand() != null && !params.getBrand().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // 5.星级条件
    if (params.getStarName() != null && !params.getStarName().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
	// 6.价格
    if (params.getMinPrice() != null && params.getMaxPrice() != null) {
        boolQuery.filter(QueryBuilders
                         .rangeQuery("price")
                         .gte(params.getMinPrice())
                         .lte(params.getMaxPrice()));
    }
	// 7.放入 source
    request.source().query(boolQuery);
}

4.周边的酒店


4.1.需求分析


在酒店列表页的右侧,有一个小地图,点击地图的定位按钮,地图会找到你所在的位置

在这里插入图片描述

并且,在前端会发起查询请求,将你的坐标发送到服务端

在这里插入图片描述

我们要做的事情就是基于这个 location 坐标,然后按照距离对周围酒店排序。实现思路如下

  • 修改 RequestParams 参数,接收 location 字段
  • 修改 search 方法业务逻辑,如果 location 有值,添加根据 geo_distance 排序的功能

4.2.修改实体类


修改在cn.itcast.hotel.pojo包下的实体类RequestParams:

src/main/java/cn/itcast/hotel/pojo/RequestParams.java

package cn.itcast.hotel.pojo;

import lombok.Data;

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    private String city;
    private String brand;
    private String starName;
    private Integer minPrice;
    private Integer maxPrice;
    //当前的地理坐标
    private String location;
}

4.3.距离排序 API


我们以前学习过排序功能,包括两种:

  • 普通字段排序
  • 地理坐标排序

我们只讲了普通字段排序对应的 java 写法。地理坐标排序只学过 DSL 语法。

距离排序与普通字段的排序有所差异,具体情况如下:

GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": "asc"  
    },
    {
      "_geo_distance" : {
          "FIELD" : "纬度,经度",
          "order" : "asc",
          "unit" : "km"
      }
    }
  ]
}

对应的 java 代码示例

在这里插入图片描述


4.4.添加距离排序


cn.itcast.hotel.service.implHotelServicesearch 方法中,添加一个排序功能

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

在这里插入图片描述


完整代码

@Override
public PageResult search(RequestParams params) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1.query
        buildBasicQuery(params, request);

        // 2.2.分页
        int page = params.getPage();
        int size = params.getSize();
        request.source().from((page - 1) * size).size(size);

        // 2.3.排序
        String location = params.getLocation();
        if (location != null && !location.equals("")) {
            request.source().sort(SortBuilders
                                  .geoDistanceSort("location", new GeoPoint(location))
                                  .order(SortOrder.ASC)
                                  .unit(DistanceUnit.KILOMETERS)
                                 );
        }

        // 3.发送请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析响应
        return handleResponse(response);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

4.5.排序距离显示


4.5.1.解析


重启服务后,测试功能

在这里插入图片描述

发现确实可以实现对我附近酒店的排序,不过并没有看到酒店到底距离我多远,这该怎么办?

排序完成后,页面还要获取我附近每个酒店的具体距离值,这个值在响应结果中是独立的

在这里插入图片描述

因此,我们在结果解析阶段,除了解析source部分以外,还要得到sort部分,也就是排序的距离,然后放到响应结果中。

我们要做两件事:

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

4.5.2.实体类添加距离字段


修改 HotelDoc 类,添加距离字段

src/main/java/cn/itcast/hotel/pojo/HotelDoc.java

package cn.itcast.hotel.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

@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();
    }
}

4.5.3.添加排序业务


修改 HotelService 中的 handleResponse 方法

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

// 获取排序值
Object[] sortValues = hit.getSortValues();
if (sortValues.length > 0) {
    Object sortValue = sortValues[0];
    hotelDoc.setDistance(sortValue);
}

在这里插入图片描述


重启后测试,发现页面能成功显示距离了

在这里插入图片描述


5.酒店竞价排名


需求:让指定的酒店在搜索结果中排名置顶


5.1.需求分析


要让指定酒店在搜索结果中排名置顶,效果如图

在这里插入图片描述

页面会给指定的酒店添加广告标记。


那怎样才能让指定的酒店排名置顶呢?

我们之前学习过的 function_score 查询可以影响算分,算分高了,自然排名也就高了。

function_score 包含 3 个要素:

  • 过滤条件:哪些文档要加分
  • 算分函数:如何计算 function score
  • 加权方式:function scorequery score 如何运算

这里的需求是:让指定酒店排名靠前。

因此我们需要给这些酒店添加一个标记,这样在过滤条件中就可以根据这个标记来判断,是否要提高算分

比如,我们给酒店添加一个字段:isADBoolean 类型:

  • true:是广告
  • false:不是广告

这样 function_score 包含 3 个要素就很好确定了:

  • 过滤条件:判断 isAD 是否为 true
  • 算分函数:我们可以用最简单暴力的 weight,固定加权值
  • 加权方式:可以用默认的相乘,大大提高算分

因此,业务的实现步骤包括

  1. HotelDoc 类添加 isAD 字段,Boolean 类型
  2. 挑选几个你喜欢的酒店,给它的文档数据添加 isAD 字段,值为 true
  3. 修改 search 方法,添加 function score 功能,给 isAD 值为 true 的酒店增加权重

5.2.修改 HotelDoc 实体


cn.itcast.hotel.pojo 包下的 HotelDoc 类添加 isAD 字段

src/main/java/cn/itcast/hotel/pojo/HotelDoc.java

private Boolean isAD;

在这里插入图片描述


5.3.添加广告标记


接下来,我们挑几个酒店,添加 isAD 字段,设置为 true

# 事实上这个值(1902197537 )是没有的
POST /hotel/_update/1902197537 
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/2056126831
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/1989806195
{
    "doc": {
        "isAD": true
    }
}
POST /hotel/_update/2056105938
{
    "doc": {
        "isAD": true
    }
}

然后就报错了。

究其原因是视频里创建索引库的时候,并没有创建 isAD 这个字段。

参考博客https://blog.csdn.net/weixin_44757863/article/details/120959505

只需在 kibana 控制台执行(追加该字段)代码即可

# 给索引库新增一个叫 isAD 的字段,类型是布尔类型
PUT /hotel/_mapping
{
  "properties":{
    "isAD":{
      "type": "boolean"
    }
  }
}
# 给索引库 id 为 45845 的记录赋值,让其 isAD 字段为 true(用于测试广告竞价排名,该记录会靠前)
POST /hotel/_update/45845
{
  "doc": {  
    "isAD":true
  }
}
GET hotel/_doc/45845

5.4.添加算法函数查询


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


function_score 查询结构如下

在这里插入图片描述


对应的 JavaAPI 如下

在这里插入图片描述


我们可以将之前写的 boolean 查询作为原始查询条件放到 query 中,

接下来就是添加过滤条件算分函数加权模式了。所以原来的代码依然可以沿用。

修改 cn.itcast.hotel.service.impl 包下的 HotelService 类中的 buildBasicQuery 方法,添加算分函数查询:

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

private void buildBasicQuery(RequestParams params, SearchRequest request) {
    // 1.构建BooleanQuery
    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
    // 关键字搜索
    String key = params.getKey();
    if (key == null || "".equals(key)) {
        boolQuery.must(QueryBuilders.matchAllQuery());
    } else {
        boolQuery.must(QueryBuilders.matchQuery("all", key));
    }
    // 城市条件
    if (params.getCity() != null && !params.getCity().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
    }
    // 品牌条件
    if (params.getBrand() != null && !params.getBrand().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
    }
    // 星级条件
    if (params.getStarName() != null && !params.getStarName().equals("")) {
        boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
    }
    // 价格
    if (params.getMinPrice() != null && params.getMaxPrice() != null) {
        boolQuery.filter(QueryBuilders
                         .rangeQuery("price")
                         .gte(params.getMinPrice())
                         .lte(params.getMaxPrice())
                        );
    }

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

# SpringCloudDay07


6.多条件的数据聚合


6.1.业务需求


需求:搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的

在这里插入图片描述

分析

目前,页面的城市列表、星级列表、品牌列表都是写死的,并不会随着搜索结果的变化而变化。

但是用户搜索条件改变时,搜索结果会跟着变化。

例如

用户搜索 “东方明珠”,那搜索的酒店肯定是在上海东方明珠附近。

因此,城市只能是上海,此时城市列表中就不应该显示北京、深圳、杭州这些信息了。

也就是说,搜索结果中包含哪些城市,页面就应该列出哪些城市;搜索结果中包含哪些品牌,页面就应该列出哪些品牌。

那么如何得知搜索结果中包含哪些品牌?如何得知搜索结果中包含哪些城市?

使用聚合功能,利用 Bucket 聚合,对搜索结果中的文档基于品牌分组、基于城市分组,就能得知包含哪些品牌、哪些城市了。

因为是对搜索结果聚合,因此聚合是限定范围的聚合,也就是说聚合的限定条件跟搜索文档的条件一致。


查看浏览器可以发现,请求参数与之前 search 时的 RequestParam 完全一致,即请求参数与搜索文档的参数完全一致

这是在限定聚合时的文档范围。

在这里插入图片描述

返回值类型就是页面要展示的最终结果

在这里插入图片描述

结果是一个 Map 结构:

  • key 是字符串,城市、星级、品牌、价格
  • value 是集合,例如多个城市的名称

6.2.业务实现


cn.itcast.hotel.web 包的 HotelController 中添加一个方法,遵循下面的要求:

  • 请求方式:POST
  • 请求路径:/hotel/filters
  • 请求参数:RequestParams,与搜索文档的参数一致
  • 返回值类型:Map<String, List<String>>

这里调用了 IHotelService 中的 getFilters 方法,但尚未实现。

src/main/java/cn/itcast/hotel/web/HotelController.java

@PostMapping("filters")
public Map<String, List<String>> getFilters(@RequestBody RequestParams params){
    return hotelService.getFilters(params);
}

cn.itcast.hotel.service.IHotelService 中定义新方法

src/main/java/cn/itcast/hotel/service/IHotelService.java

/**
 * 查询城市、星级、品牌的聚合结果
 *
 * @return 聚合结果,格式:{“城市”:[“上海”],“品牌”:[“如家”,“希尔顿”]}
 */
Map<String, List<String>> filters(RequestParams params);

cn.itcast.hotel.service.impl.HotelService 中实现该方法

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

@Override
public Map<String, List<String>> filters(RequestParams params) {
    try {
        // 1.准备Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备DSL
        // 2.1.query
        buildBasicQuery(params, request);
        // 2.2.设置size
        request.source().size(0);
        // 2.3.聚合
        buildAggregation(request);
        // 3.发出请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Map<String, List<String>> result = new HashMap<>();
        Aggregations aggregations = response.getAggregations();
        // 4.1.根据品牌名称,获取品牌结果
        List<String> brandList = getAggByName(aggregations, "brandAgg");
        result.put("品牌", brandList);
        // 4.2.根据品牌名称,获取品牌结果
        List<String> cityList = getAggByName(aggregations, "cityAgg");
        result.put("城市", cityList);
        // 4.3.根据品牌名称,获取品牌结果
        List<String> starList = getAggByName(aggregations, "starAgg");
        result.put("星级", starList);

        return result;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

获取聚合名称

private void buildAggregation(SearchRequest request) {
    request.source().aggregation(AggregationBuilders
                                 .terms("brandAgg")
                                 .field("brand")
                                 .size(100)
                                );
    request.source().aggregation(AggregationBuilders
                                 .terms("cityAgg")
                                 .field("city")
                                 .size(100)
                                );
    request.source().aggregation(AggregationBuilders
                                 .terms("starAgg")
                                 .field("starName")
                                 .size(100)
                                );
}

封装聚合条件

private List<String> getAggByName(Aggregations aggregations, String aggName) {
    // 4.1.根据聚合名称获取聚合结果
    Terms brandTerms = aggregations.get(aggName);
    // 4.2.获取buckets
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    // 4.3.遍历
    List<String> brandList = new ArrayList<>();
    for (Terms.Bucket bucket : buckets) {
        // 4.4.获取key
        String key = bucket.getKeyAsString();
        brandList.add(key);
    }
    return brandList;
}

7.实现酒店搜索框自动补全


此时我们的 hotel 索引库还没有设置拼音分词器,需要修改索引库中的配置。

但是我们知道索引库是无法修改的,只能删除然后重新创建。

另外,我们需要添加一个字段,用来做自动补全,将 brandsuggestioncity 等都放进去,作为自动补全的提示。


因此,总结一下,我们需要做的事情包括:

  1. 修改 hotel 索引库结构,设置自定义拼音分词器
  2. 修改索引库的 nameall 字段,使用自定义分词器
  3. 索引库添加一个新字段 suggestion,类型为 completion 类型,使用自定义的分词器
  4. HotelDoc 类添加 suggestion 字段,内容包含 brandbusiness
  5. 重新导入数据到 hotel

7.1.修改酒店映射结构


先删除之前创建的索引库

DELETE /hotel

再创建新的索引库(映射结构发生变化)

// 酒店数据索引库
PUT /hotel
{
  "settings": {
    "analysis": {
      "analyzer": {
        "text_anlyzer": {
          "tokenizer": "ik_max_word",
          "filter": "py"
        },
        "completion_analyzer": {
          "tokenizer": "keyword",
          "filter": "py"
        }
      },
      "filter": {
        "py": {
          "type": "pinyin",
          "keep_full_pinyin": false,
          "keep_joined_full_pinyin": true,
          "keep_original": true,
          "limit_first_letter_length": 16,
          "remove_duplicated_term": true,
          "none_chinese_pinyin_tokenize": false
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "id":{
        "type": "keyword"
      },
      "name":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart",
        "copy_to": "all"
      },
      "address":{
        "type": "keyword",
        "index": false
      },
      "price":{
        "type": "integer"
      },
      "score":{
        "type": "integer"
      },
      "brand":{
        "type": "keyword",
        "copy_to": "all"
      },
      "city":{
        "type": "keyword"
      },
      "starName":{
        "type": "keyword"
      },
      "business":{
        "type": "keyword",
        "copy_to": "all"
      },
      "location":{
        "type": "geo_point"
      },
      "pic":{
        "type": "keyword",
        "index": false
      },
      "all":{
        "type": "text",
        "analyzer": "text_anlyzer",
        "search_analyzer": "ik_smart"
      },
      "suggestion":{
          "type": "completion",
          "analyzer": "completion_analyzer"
      }
    }
  }
}

7.2.修改 HotelDoc 实体


HotelDoc 中要添加一个字段,用来做自动补全,内容可以是酒店品牌、城市、商圈等信息。

按照自动补全字段的要求,最好是这些字段的数组。

因此我们在 HotelDoc 中添加一个 suggestion 字段,类型为 List<String>,然后将 brandcitybusiness 等信息放到里面。

代码如下:

src/main/java/cn/itcast/hotel/pojo/HotelDoc.java

package cn.itcast.hotel.pojo;

import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@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;
    private List<String> suggestion;

    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();
        // 组装 suggestion
        if(this.business.contains("/")){
            // business 有多个值,需要切割
            String[] arr = this.business.split("/");
            // 添加元素
            this.suggestion = new ArrayList<>();
            this.suggestion.add(this.brand);
            Collections.addAll(this.suggestion, arr);
        }else {
            this.suggestion = Arrays.asList(this.brand, this.business);
        }
    }
}

7.3.重新导入


重新执行之前编写的导入数据功能,可以看到新的酒店数据中包含了 suggestion

相关的导入功能在 src/test/java/cn/itcast/hotel/HotelDocumentTest.java 中的 testBulkRequest() 方法中实现了。

在这里插入图片描述

GET /hotel/_search
{
  "query": {
    "match_all": {}
  }
}

在这里插入图片描述

GET /hotel/_search
{
  "suggest": {
    "suggestions": {
      "text": "s",
      "completion": {
        "field": "suggestion",
        "skip_duplicates": true,
        "size": 10
      }
    }
  }
}

7.4.自动补全查询的 JavaAPI


之前我们学习了自动补全查询的 DSL,而没有学习对应的 JavaAPI,这里给出一个示例

在这里插入图片描述

而自动补全的结果也比较特殊,解析的代码如下

在这里插入图片描述


src/test/java/cn/itcast/hotel/HotelSearchTest.java

/**
 * 自动补全查询
 *
 * @throws IOException
 */
@Test
void testSuggest() throws IOException {
    //1.准备 Request
    SearchRequest request = new SearchRequest("hotel");
    //2.准备 DSL
    request.source().suggest(new SuggestBuilder().addSuggestion(
            "suggestions",
            SuggestBuilders.completionSuggestion("suggestion")
                    .prefix("h")
                    .skipDuplicates(true)
                    .size(10)
    ));

    //3.发送请求
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    //4.解析结果
    //System.out.println(response);
    handleCompletionResponse(response);
}
/**
 * 处理补全结果
 *
 * @param response
 */
private void handleCompletionResponse(SearchResponse response) {
    //4.处理结果
    Suggest suggest = response.getSuggest();
    //4.1.根据名称获取补全结果
    CompletionSuggestion suggestion = suggest.getSuggestion("suggestions");
    //4.2.获取 options 并遍历
    for (CompletionSuggestion.Entry.Option option : suggestion.getOptions()) {
        //4.3.获取一个 option 的 text ,也就是补全的词条
        String text = option.getText().string();
        System.out.println(text);
    }
}

在这里插入图片描述


7.5.实现搜索框自动补全


查看前端页面,可以发现当我们在输入框键入时,前端会发起 ajax 请求

在这里插入图片描述

返回值是补全词条的集合,类型为 List<String>


  1. cn.itcast.hotel.web 包下的 HotelController 中添加新接口,接收新的请求

src/main/java/cn/itcast/hotel/web/HotelController.java

@GetMapping("suggestion")
public List<String> getSuggestions(@RequestParam("key") String prefix) {
    return hotelService.getSuggestions(prefix);
}

  1. cn.itcast.hotel.service 包下的 IhotelService 中添加方法

src/main/java/cn/itcast/hotel/service/IHotelService.java

List<String> getSuggestions(String prefix);

  1. cn.itcast.hotel.service.impl.HotelService 中实现该方法

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

@Override
public List<String> getSuggestions(String prefix) {
    try {
        // 1.准备 Request
        SearchRequest request = new SearchRequest("hotel");
        // 2.准备 DSL
        request.source().suggest(new SuggestBuilder().addSuggestion(
            "suggestions",
            SuggestBuilders.completionSuggestion("suggestion")
            .prefix(prefix)
            .skipDuplicates(true)
            .size(10)
        ));
        // 3.发起请求
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        // 4.解析结果
        Suggest suggest = response.getSuggest();
        // 4.1.根据补全查询名称,获取补全结果
        CompletionSuggestion suggestions = suggest.getSuggestion("suggestions");
        // 4.2.获取 options
        List<CompletionSuggestion.Entry.Option> options = suggestions.getOptions();
        // 4.3.遍历
        List<String> list = new ArrayList<>(options.size());
        for (CompletionSuggestion.Entry.Option option : options) {
            String text = option.getText().toString();
            list.add(text);
        }
        return list;
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

8.实现数据同步


elasticsearch 中的酒店数据来自于 mysql 数据库,因此 mysql 数据发生改变时,elasticsearch 也必须跟着改变。

这个就是 elasticsearchmysql 之间的数据同步


在微服务中,负责酒店管理(操作 MySQL)的业务与负责酒店搜索(操作 ElasticSearch)的业务可能在两台不同的微服务上。

那么此时的数据同步应该如何实现呢?

常见的数据同步方案有三种:同步调用、异步通知、监听 binlog


案例基于 MQ 来实现 MySQLElsaticSearch 数据同步


8.1.思路


利用课前资料提供的 hotel-admin 项目作为酒店管理的微服务。

当酒店数据发生增、删、改时,要求对 elasticsearch 中数据也要完成相同操作。


步骤

  • 导入 课前资料 提供的 hotel-admin 项目,启动并测试酒店数据的 CRUD
  • 声明 exchangequeueRoutingKey
  • hotel-admin 中的增、删、改业务中完成消息发送
  • hotel-demo 中完成消息监听,并更新 elasticsearch 中数据
  • 启动并测试数据同步功能

8.2.导入 demo


导入 课前资料 提供的 hotel-admin 项目

在这里插入图片描述


运行后,访问 http://localhost:8099

在这里插入图片描述


其中包含了酒店的 CRUD 功能

hotel-admin 项目下的 src/main/java/cn/itcast/hotel/web/HotelController.java

@PostMapping
public void saveHotel(@RequestBody Hotel hotel){
    hotelService.save(hotel);
}

@PutMapping()
public void updateById(@RequestBody Hotel hotel){
    if (hotel.getId() == null) {
        throw new InvalidParameterException("id 不能为空");
    }
    hotelService.updateById(hotel);
}

@DeleteMapping("/{id}")
public void deleteById(@PathVariable("id") Long id) {
    hotelService.removeById(id);
}

8.3.声明交换机、队列


MQ 结构如图

在这里插入图片描述


8.3.1.配置文件


引入依赖

hotel-adminhotel-demo 中引入 RabbitMQ 的依赖

pom.xml

<!-- AMQP -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

配置 RabbitMQ 地址

hotel-adminhotel-demo 项目中的 src/main/resources/application.yaml 下配置 RabbitMQ 的地址

spring:
	rabbitmq:
	  host: 192.168.150.101
	  port: 5672
	  username: itcast
	  password: 123456
	  virtual-host: /

这里补充一句,记得开启 RabbitMQ 服务

使用 docker start [你使用 Docker 创建的 RabbitMQ 容器的名称] 启动即可

docker start mq

8.3.2.声明队列交换机的名称


hotel-adminhotel-demo 中的 cn.itcast.hotel.constatnts 包下新建一个类 MqConstants

src/main/java/cn/itcast/hotel/constants/MqConstants.java

package cn.itcast.hotel.constatnts;

public class MqConstants {
/**
 * 交换机
 */
public final static String HOTEL_EXCHANGE = "hotel.topic";
/**
 * 监听新增和修改的队列
 */
public final static String HOTEL_INSERT_QUEUE = "hotel.insert.queue";
/**
 * 监听删除的队列
 */
public final static String HOTEL_DELETE_QUEUE = "hotel.delete.queue";
/**
 * 新增或修改的 RoutingKey
 */
public final static String HOTEL_INSERT_KEY = "hotel.insert";
/**
 * 删除的 RoutingKey
 */
public final static String HOTEL_DELETE_KEY = "hotel.delete";
}

8.3.3.声明队列交换机


一般都是在消费者中声明交换机、队列的。

故选择在 hotel-demo 项目中定义配置类(声明队列、交换机)

一般来说,有两种方式来声明交换机队列绑定关系、以及队列交换机对象

两种方式:1.基于注解的方式;2.基于 Bean 的方式

这里是基于 Bean 的方式来声明队列交换机。资料中提供的最终代码则是基于注解来声明队列交换机的。

src/main/java/cn/itcast/hotel/config/MqConfig.java

package cn.itcast.hotel.config;

import cn.itcast.hotel.constants.MqConstants;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MqConfig {
    @Bean
    public TopicExchange topicExchange(){
        return new TopicExchange(MqConstants.HOTEL_EXCHANGE, true, false);
    }

    @Bean
    public Queue insertQueue(){
        return new Queue(MqConstants.HOTEL_INSERT_QUEUE, true);
    }

    @Bean
    public Queue deleteQueue(){
        return new Queue(MqConstants.HOTEL_DELETE_QUEUE, true);
    }

    @Bean
    public Binding insertQueueBinding(){
        return BindingBuilder.bind(insertQueue()).to(topicExchange()).with(MqConstants.HOTEL_INSERT_KEY);
    }

    @Bean
    public Binding deleteQueueBinding(){
        return BindingBuilder.bind(deleteQueue()).to(topicExchange()).with(MqConstants.HOTEL_DELETE_KEY);
    }
}

8.4.发送 MQ 消息


hotel-admin 中的增、删、改业务中分别发送 MQ 消息

src/main/java/cn/itcast/hotel/web/HotelController.java

在这里插入图片描述

完整代码

package cn.itcast.hotel.web;

import cn.itcast.hotel.constants.MqConstants;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.service.IHotelService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.security.InvalidParameterException;

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

    @Autowired
    private IHotelService hotelService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/{id}")
    public Hotel queryById(@PathVariable("id") Long id) {
        return hotelService.getById(id);
    }

    @GetMapping("/list")
    public PageResult hotelList(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "size", defaultValue = "1") Integer size
    ) {
        Page<Hotel> result = hotelService.page(new Page<>(page, size));

        return new PageResult(result.getTotal(), result.getRecords());
    }

    @PostMapping
    public void saveHotel(@RequestBody Hotel hotel) {
        hotelService.save(hotel);

        //交换机、RoutingKey、要发送的内容
        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

    @PutMapping()
    public void updateById(@RequestBody Hotel hotel) {
        if (hotel.getId() == null) {
            throw new InvalidParameterException("id 不能为空");
        }
        hotelService.updateById(hotel);

        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_INSERT_KEY, hotel.getId());
    }

    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        hotelService.removeById(id);

        rabbitTemplate.convertAndSend(MqConstants.HOTEL_EXCHANGE, MqConstants.HOTEL_DELETE_KEY, id);
    }
}

8.5.接收 MQ 消息


hotel-demo 接收到 MQ 消息要做的事情包括

  • 新增消息:根据传递的 hotelid 查询 hotel 信息,然后新增一条数据到索引库
  • 删除消息:根据传递的 hotelid 删除索引库中的一条数据

8.5.1.新增和删除的业务


首先在 hotel-democn.itcast.hotel.service 包下的 IHotelService 中编写新增、删除业务的方法

src/main/java/cn/itcast/hotel/service/IHotelService.java

void deleteById(Long id);

void insertById(Long id);

8.5.2.业务实现


hotel-demo 中的 cn.itcast.hotel.service.impl 包下的 HotelService 中实现业务

src/main/java/cn/itcast/hotel/service/impl/HotelService.java

@Override
public void deleteById(Long id) {
    try {
        // 1.准备 Request
        DeleteRequest request = new DeleteRequest("hotel", id.toString());
        // 2.发送请求
        client.delete(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}
@Override
public void insertById(Long id) {
    try {
        // 0.根据 id 查询酒店数据
        Hotel hotel = getById(id);
        // 转换为文档类型
        HotelDoc hotelDoc = new HotelDoc(hotel);

        // 1.准备 Request 对象
        IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
        // 2.准备 Json 文档
        request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
        // 3.发送请求
        client.index(request, RequestOptions.DEFAULT);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

8.5.3.编写监听器


hotel-demo 中的 cn.itcast.hotel.mq 包新增一个类

src/main/java/cn/itcast/hotel/mq/HotelListener.java

package cn.itcast.hotel.mq;

import cn.itcast.hotel.constants.MqConstants;
import cn.itcast.hotel.service.IHotelService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class HotelListener {

    @Autowired
    private IHotelService hotelService;

    /**
     * 监听酒店新增或修改的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_INSERT_QUEUE)
    public void listenHotelInsertOrUpdate(Long id){
        hotelService.insertById(id);
    }

    /**
     * 监听酒店删除的业务
     * @param id 酒店id
     */
    @RabbitListener(queues = MqConstants.HOTEL_DELETE_QUEUE)
    public void listenHotelDelete(Long id){
        hotelService.deleteById(id);
    }
}

8.6.测试同步功能


直接访问 虚拟机的IP地址:15672


队列

在这里插入图片描述


交换机

在这里插入图片描述


点击上方的 hotel.topic,查看交换机和队列绑定关系

在这里插入图片描述


将酒店价格由 2688 改为 2888

在这里插入图片描述

查询此条数据的 id

在这里插入图片描述

将价格修改为 2888

在这里插入图片描述

查看交换机的 hotel.topic 情况

在这里插入图片描述

回到 http://localhost:8089/ 页面,发现数据修改的数据同步成功

在这里插入图片描述


删除数据、修改的数据同步同理,此处不作演示。 (主要是懒得截图)


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

学习笔记:SpringCloud 微服务技术栈_实用篇②_黑马旅游案例 的相关文章

  • Jetbrains系列开发工具日常配置与使用

    文章目录 IntelliJ IDEA 各种常用配置 常见问题 PyCharm 常见问题 IntelliJ IDEA IDEA的确是Java开发利器 之前一直用Eclipse 后来实习单位都用IDEA 就慢慢转了 开始还不习惯 后来就不禁被其
  • 3.1 ZK客户端创建节点源码解析上(基于NIO)

    ZK客户端创建节点源码解析上 前言 1 为什么能用来做注册中心 2 创建节点 2 1 ZooKeeper create 方法 2 2 ClientCnxn queuePacket 方法 2 3 ClientCnxnSocketNIO doT
  • 1003 我要通过 (20 分)

    题目 题目链接 题解 实现题 理解题目表达的意思 字符串中必须仅有 P A T 这三种字符 不可以包含其它字符 可以在 PAT 左右两侧加任意个相同数量的 A 比如 PAT APATA AAPATAA 均是正确的 但 APAT PATA A
  • 比较删除某个变量后的模型与原模型的差异(使用F检验,R语言)

    比较删除某个变量后的模型与原模型的差异 使用F检验 R语言 在统计建模中 我们经常需要评估不同变量对模型的影响程度 其中一个常见的问题是 当我们删除某个变量后 模型的性能是否会发生显著变化 为了回答这个问题 我们可以使用F检验来比较删除某个
  • C++实现根据类名动态生成类对象

    在开发后台服务的过程中 我们常常需要从数据库中取数据 并将数据缓存在本地中 另外 我们的服务还需要有更新数据的能力 包括定时的主动更新以及数据库数据更新时服务收到通知的被动更新 之前在需要用到以上功能的时候 模仿着组内通用的数据Cache部
  • Keil写STM32程序.axf: Error: L6218E: Undefined symbol HAL_RTC_Init (referred from rtc.o)报错解决办法

    在写RTC的程序时 报如下的错误 Output atk f103 axf Error L6218E Undefined symbol HAL RTC Init referred from rtc o 显示没有定义 网上有很多解决办法 这里提

随机推荐

  • 【Docker 1(2),分享一波阿里、字节、腾讯、美团等精选大厂面试题

    8 卸载 yum remove docker ce docker ce cli containerd io rm rf var lib docker rm rf var lib containerd 三 run的流程和docker原理 1
  • 还在愁csdn进不去吗,看这里

    浏览器其他页面都可以发送请求进入 唯独www csdn net进不去 一直处于访问不了状态 目录 1 DNS的问题 2 关闭防火墙 3 清除浏览器数据 4 关闭代理服务器 5 VPN 6 切换其他网络 相信很多的网页是这样的 然后打开Win
  • 定时器每隔n秒请求n条数据,setInterval分批请求数据

  • 前端测试——端对端测试框架 Playwright 总结

    在进行前端测试前 我们需要明确我们需要怎样的前端测试 前端测试类型总结 前端应用测试分为几种常见类型 端到端 e2e 一个辅助机器人 表现得像一个用户 在应用程序周围点击 并验证其功能是否正确 常见的测试框架是 Playwright 单元
  • 引导过程以及服务控制

    目录 服务器开机过程 开机自检 BIOS MBR主引导程序 grub菜单 加载内核 init初始化 步骤说明 初步检测 mbr引导 加载linux内核 驱动系统 系统初始化 命令 控制类型 运行级别相关命令 运行级命令 服务器开机过程 开机
  • 管理科学与工程 国内核心期刊 国外a刊及SCI

    国内 管理科学与工程 管理科学学报 A 匿名审稿 绝对牛刊 不比一般的SCi期刊的质量差 系统工程理论与实践 A 实名审稿 关系稿很多 尤其是挂编委的文章很多 但质量尚可 系统工程学报 A 匿名审稿 侧重方法论多写 编辑部的老师特好 中国管
  • unity 判断是否点击在某个面板身上

    using System Collections using System Collections Generic using UnityEngine public class TestImage MonoBehaviour Use thi
  • 随机变量序列的收敛性质分类

    分类 X n 趋向某个固定的数 X n 趋向某个确定函数的输出值 X n 的概率分布越来越接近某个特定的随机变量的概率分布 X n 和某个特定随机变量的差别的平均值 数学期望值 趋向于0 X n 和某个特定随机变量的差别的方差趋向于0 约束
  • 面试题:String 和 StringBuilder、StringBuffer 的区别?

    Java 平台提供两种类型的字符串 String 和 StringBuffer StringBuilder 它们可以存储和操作字符串 其中String是只读字符串 也就意味着String 引用的字符串内容是不能被改变的 而StringBuf
  • 多益网络校招笔试题

    马上要参加多益的笔试了 所以在网上找了一下多益的笔试题 原文 我感觉我想出了一个更简单的方法 时间复杂度O 1 如果有问题希望大家及时指正 题目如下 给定一个数x x gt 5 找到该数与3 4之间的关系 关系如下 x 3 n 4 m 然后
  • 最近我在忙什么之【毕业设计大纲】

    毕业设计工作日志 误差校正仿真 理论部分 Stewart平台位姿误差分析与标定研究 仿真部分 基于Matlab的全局搜索 单通道控制算法设计 滑模论文 根据论文仿真 填入参数 获取具体的传递函数 改进滑模的论文 扰动及对照实验设计 稳定平台
  • Ubuntu下使用MySQL(C++,Cmake)

    安装需要使用的库 sudo apt get install libmysqlclient dev 头文件 usr include mysql mysql的头文件在这里 引入头文件 include mysql h 如果找不到就 include
  • python web.py+requests 视频接收与发送

    web py是python中一个相对容易上手的web服务器搭建工具 1 安装方式 web py可以直接通过pip install 的方式安装即可 即 pip install web py 2 服务器 2 1 完整程序 import web
  • 迷宫问题—回溯法

    文章目录 一 项目分析的一般步骤 二 迷宫问题的具体解决 1 需求分析 2 问题分析 2 1 问题分析 2 2 数据结构设计的分析 3 设计 流程图设计 代码设计 3 1流程图设计 3 2代码设计 4 代码测试 5 完成交付 一 项目分析的
  • Springboot+Mybatis,dao加上@Repository注解无法注入

    在springboot 中 给mapper的接口上加上 Repository 无法生成相应的bean 从而无法 Autowired 这是因为spring扫描注解时 自动过滤掉了接口和抽象类 这种情况下可以在启动的类前加 上 MapperSc
  • 如何在 Python 中创建元组字典

    本演练是关于在 Python 中创建元组字典的全部内容 此数据结构存储键值对 通过组合字典和元组 可以创建元组字典 好处是以结构化格式组织且可访问的数据 可以轻松表示每个键的多个值 例如学生成绩或联系信息 让我们看看它如何有效地存储和检索复
  • 抖音生活小妙招类短视频创作技巧分享,几个方面带你了解整个流程

    想做抖音 又不想真人出镜 该选择什么项目做呢 更多精彩干货请关注共众号 萤火宠 免费领取108个抖音小项目 我们的学员中有宝妈 有大学生 也有不少职场人员 他们大多数都非常普通 没有什么很强的职业技能 也没有什么丰富的专业知识 但是他们有人
  • 找实习、工作的一点浅见

    一 实习的必要性 为什么需要去实习 1 实习能帮助自己增进对于具体职场的认识 包括具体工作的职责 内容 工作氛围 是否有较大压力等等 2 通过一段时间的实习经历 能帮助自己作出未来是否能胜任类似的工作的判断 如果有留用 是否考虑留下 如果没
  • 阿里的iOS协程库 coobjc 源码解析(一)——元组和协程

    Coobjc中的元组 底层主要依赖NSPointerArray进行实现 因为NSPointerArray支持插入nil指针 能配合元组中有对象为nil的特性 比较引人入胜的设计 主要是co tuple 这个宏定义 co tuple COTu
  • 学习笔记:SpringCloud 微服务技术栈_实用篇②_黑马旅游案例

    若文章内容或图片失效 请留言反馈 部分素材来自网络 若不小心影响到您的利益 请联系博主删除 前言 学习视频链接 SpringCloud RabbitMQ Docker Redis 搜索 分布式 史上最全面的 SpringCloud 微服务技