微服务实战:数据同步、ES搜索引擎、过滤器

本文介绍了在微服务环境中,如何实现商品业务的CRUD操作,包括分页查询、根据ID查询等。此外,详细阐述了如何集成Elasticsearch搜索引擎,实现搜索业务,包括自动补全和过滤项聚合功能。数据同步通过RabbitMQ进行,确保MySQL与Elasticsearch的数据一致性。同时,还涵盖了登录用户信息获取和下单业务流程。
摘要由CSDN通过智能技术生成

搭建环境

  1. 前端部署到nginx中
   server {
   
        listen 9001;
        server_name localhost;
        location / {
   
            root   html/hm-mall-admin;
        }
    }
    server {
   
        listen 9002;
        server_name localhost;
        location / {
   
            root    html/hm-mall-portal;
        }
    }
  1. 启动nacos,创建gatway模块设置端口为10010的网关,将模块统一部署到nacos台中
  server:
      port: 10010 # 网关端口
    spring:
      application:
        name: gateway # 服务名称
      cloud:
        nacos:
          server-addr: localhost:8848 # nacos地址
        gateway:
          routes: # 网关路由配置
            - id: user-service # 路由id,自定义,只要唯一即可
             #  uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
              uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
              predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
                - Path=/user/**  # 这个是按照路径匹配,只要以/user/开头就符合要求
                - Path=/address/**
            - id: order-service
              uri: lb://orderservice
              predicates:
                - Path=/order/**
                - Path=/pay/**
            - id: item-service
              uri: lb://itemservice
              predicates:
                - Path=/item/**
            - id: search-service
              uri: lb://searchservice
              predicates:
                - Path=/search/**
          default-filters: # 默认过滤项
            - AddRequestHeader=Truth, Itcast is freaking awesome!
          globalcors: # 全局的跨域处理
            add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
            corsConfigurations:
              '[/**]':
                allowedOrigins: # 允许哪些网站的跨域请求
                  - "http://localhost:9001"
                  - "http://localhost:9002"
                  - "http://127.0.0.1:9001"
                  - "http://127.0.0.1:9002"
                allowedMethods: # 允许的跨域ajax的请求方式
                  - "GET"
                  - "POST"
                  - "DELETE"
                  - "PUT"
                  - "OPTIONS"
                allowedHeaders: "*" # 允许在请求中携带的头信息
                allowCredentials: true # 是否允许携带cookie
                maxAge: 360000 # 这次跨域检测的有效期

商品业务(CRUD)

分页查询

根据MP提供的分页进行查询

1)在MP的官网查询分页插件,将最新版的cv到刚创建的config配置类中

2)在pageDTO封装类中定义两个属性:list和total

3)由于mp底层已经自动注入好Mapper和service层之间的关联,无需自动注入@Autowired,但是在每一个方法中都需要重写getBaseMapper(获取Mapper),但我目前使用的是自动注入Mapper

4)习惯分三层去写就没写到一块,所以在controller定义参数和返回参数类型的接口(GET)

5)路径为"/list",参数是前端给出的加注解@RequestParam,从request里面拿取值(http://localhost:8080/list?page=1&size=10),page和size就需要加@RequestParam

6)service层分实现类和接口类(解耦),实现层实现page(MP自带的),然后获取page提供的total(getTotal)和list(getRecords),将获取的结果封装到pageDTO

根据ID查询

1)创建参数为id、返回类型为实体类的接口(GET),路径为"/{id}"

 http://localhost:8080/id,id就需要使用@PathVariable,相当于uri路径的占位符

2)去service层实现,调用Mapper层的方法selectById(id)

基于mp的其他查询(符合分类一致、价格范围的商品)

1)创建参数为category、minPrice、maxPrice(@RequestParam),返回类型为实体类的接口(GET)

2)去service层实现,调用条件查询方法query

  • 根据id查询使用getById(id)
  • 根据id查询多个listByIds(List)
  • 根据复杂条件查询一个或者多个以及分页
    • 一个:query().eq().ge().one()
    • 多个:query().eq().ge().list()
    • 分页:query().eq().ge().page(new Page(page,size))

新增商品

1)创建参数为实体类(@RequestBody)、返回类型为int或boolean的接口(请求头为POST)

@RequestBody:是用于前端传给后端的JSON格式的参数

2)service层调用Mapper层的方法insert(item)

商品的上架和下架

对商品进行修改或者删除,需要先对商品进行下架,修改完毕之后在对其下架

1)对商品上架、下架,只需要改变商品的状态

2)创建参数是id和status、返回类型是int或布尔的接口(PUT),路径:“/status/{id}/{status}”

3)service层调用Mapper层的方法updateById(item)

4)由于接收的是item实体类,需要封装对象先对其创建new,然后将前端改变的id和状态赋予对象

修改商品

1)创建参数为实体类、返回类型为int或boolean的接口(请求头为PUT)

2)service层调用Mapper层的方法updateById(item)

3)修改前需要回显数据,调用根据id查询信息的方法即可

删除商品

此处做的不是逻辑删除(没有真正意义上的删除,而是改变状态为不可用),而是从数据库直接删除一条数据

1)创建参数id、返回类型为int或boolean的接口(请求头为DELETE)

2)service层调用Mapper层的方法deleteById(id)

搜索业务(ES搜索引擎实现)

创建搜索微服务search-service,添加elasticsearch依赖、elasticsearch配置信息、注册elasticsearch的HighLevelRestClient对象,前面已经将search由网关管理

        <!-- elasticsearch依赖 -->
       <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--nacos服务注册发现依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--amqp mq依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

#elasticsearch配置信息
server:
  port: 8084
spring:
  application:
    name: searchservice
  cloud:
    nacos:
      server-addr: localhost:8848
  rabbitmq:
    host: localhost
    port: 5672
    virtual-host: /
    username: itcast
    password: 123321
logging:
  level:
    com.hmall: debug
  pattern:
    dateformat: HH:mm:ss:SSS

/**  注册elasticsearch的HighLevelRestClient对象  **/
@Configuration
public class ElasticsearchConfig {
   
    @Bean
    public RestHighLevelClient restHighLevelClient() {
   
        return new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://localhost:9200")
        ));
    }
}

设计索引库数据结构

基本字段包括:

  • 用于关键字全文检索的字段,比如All,里面包含name、brand、category信息
  • 用于自动补全(suggestion)的字段,包括brand、category信息
  • 分类、品牌、价格、销量、id、name、评价数量、图片
  #创建数据索引库
  PUT /hmall
  {
   
    "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"
        },
        "image":{
   
          "type": "keyword",
          "index": false
        },
        "price":{
   
          "type": "integer"
        },
        "category":{
   
          "type": "keyword",
          "copy_to": "{all}"
        },
        "brand":{
   
          "type": "keyword",
          "copy_to": "all"
        },
        "sold":{
   
          "type": "integer"
        },
        "commentCount":{
   
          "type": "integer"
        },
        "isAD":{
   
          "type": "boolean"
        },
        "all":{
   
          "type": "text",
          "analyzer": "text_anlyzer",
          "search_analyzer": "ik_smart"
        },
        "suggestion":{
   
            "type": "completion",
            "analyzer": "completion_analyzer"
        }
      }
    }
  }

完成数据批量导入

1)将商品微服务中的分页查询商品接口定义为一个FeignClient,放到feign-api模块中,此项目只暴露了分页查询接口和查询根据id查询(数据同步中的上架监听)

2)搜索服务编写一个业务,实现下面功能:

  • 调用item-service提供的FeignClient(itemClient),分页查询商品 PageDTO,itemClient.list
  • 将查询到的商品封装为一个ItemDoc对象,放入ItemDoc集合
  • 将ItemDoc集合批量导入elasticsearch中

多线程实现批量导入,由于数据库中的数据过多,采用分页批量导入

@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class InjectData {
   
    @Autowired
    private RestHighLevelClient esClient;
    @Autowired
    private ItemClient itemClient;
 
    //多线程导入es
    @Test
    public void injectDataByThread() throws IOException {
   
        //创建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 60,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(3),
                new ThreadPoolExecutor.DiscardOldestPolicy());
        //查询总条数

        Integer total = itemClient.list(1,20).getTotal().intValue();
        //计算总页数
        int pages = (int) Math.ceil((double) total / 5500);
        //分页导入数据
        for (int page = 1; page <= pages; page++) {
   
            PageDTO<Item> itemPageDTO = itemClient.list(page, 3000);
            if (itemPageDTO.getList() != null && itemPageDTO.getList().size() > 0) {
   
                threadPool.execute(new Runnable() {
   
                    @Override
                    public void run() {
    //加入到线程池任务
                        try {
   
//                            List<User> list = userService.list();

                            // 1.创建Request
                            BulkRequest request = new BulkRequest();
                            // 2.准备参数,添加多个新增的Request
                            for (Item user : itemPageDTO.getList()) {
   
                                // 2.1.转换为文档类型HotelDoc
                                ItemDoc userDoc = new ItemDoc(user);
                                // 2.2.创建新增文档的Request对象
                                request.add(new IndexRequest("hmall")
                                        .id(userDoc.getId().toString())
                                        .source(JSON.toJSONString(userDoc), XContentType.JSON));
                            }
                            // 3.发送请求
                            esClient.bulk(request, RequestOptions.DEFAULT);
                            log.info("【es】thread:{
   },msg:本次同步 {
   } 条数据",
                                    Thread.currentThread().getName(), itemPageDTO.getList().size());
                        } catch (IOException e) {
   
                            e.printStackTrace();
                        }
                    }
                });
            }
            try {
   
                //延迟3秒等elasticsearch完成写入数据
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
   
                throw new RuntimeException(e);
            }
        }
    }
}

实现搜索栏自动补全功能(suggestion)

1)创建参数为 key: 用户输入的词条前缀、返回值List:自动补全的词条集合的接口(GET),路径:/search/suggestion

2)service层实现自动补全功能,suggestion是在实体类中拼接brand和category,source是查询商品的信息

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值